Annotation of loncom/html/adm/LC_math_editor/src/ui.js, revision 1.3
1.1 damieng 1: /*
2:
3: Copyright (C) 2014 Michigan State University Board of Trustees
4:
5: The JavaScript code in this page is free software: you can
6: redistribute it and/or modify it under the terms of the GNU
7: General Public License (GNU GPL) as published by the Free Software
8: Foundation, either version 3 of the License, or (at your option)
9: any later version. The code is distributed WITHOUT ANY WARRANTY;
10: without even the implied warranty of MERCHANTABILITY or FITNESS
11: FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
12:
13: As additional permission under GNU GPL version 3 section 7, you
14: may distribute non-source (e.g., minimized or compacted) forms of
15: that code without the copy of the GNU GPL normally required by
16: section 4, provided you include this license notice and a URL
17: through which recipients can access the Corresponding Source.
18:
19: */
20:
21: "use strict";
22:
23: var handleChange = function(math_object) {
24: // math_object has 3 fields: ta, output_node, oldtxt
25: // we need to pass this object instead of the values because oldtxt will change
26: var ta, output_node, txt, parser, output, root, test1, test2;
27: ta = math_object.ta;
28: output_node = math_object.output_node;
29: txt = ta.value;
30:
31: // automatically add brackets to something like "1;2;3", for LON-CAPA:
32: // NOTE: this is ugly and sometimes adds brackets to error messages
33: test1 = '';
34: test2 = txt;
35: while (test2 != test1) {
36: test1 = test2;
37: test2 = test1.replace(/\[[^\[\]]*\]/g, '');
38: }
39: if (test2.split("[").length == test2.split("]").length) {
40: test1 = '';
41: while (test2 != test1) {
42: test1 = test2;
43: test2 = test1.replace(/\([^\(\)]*\)/g, '');
44: }
45: if (test2.split("(").length == test2.split(")").length) {
1.2 damieng 46: test1 = '';
47: while (test2 != test1) {
48: test1 = test2;
49: test2 = test1.replace(/\{[^\{\}]*\}/g, '');
50: }
51: if (test2.split("{").length == test2.split("}").length) {
52: if (test2.indexOf(Definitions.ARG_SEPARATOR) != -1) {
53: txt = '['+txt+']';
54: }
1.1 damieng 55: }
56: }
57: }
58:
59: if (txt != math_object.oldtxt) {
60: math_object.oldtxt = txt;
61: while (output_node.firstChild != null)
62: output_node.removeChild(output_node.firstChild);
63: output_node.removeAttribute("title");
64: if (txt != "") {
65: parser = math_object.parser;
66: try {
67: root = parser.parse(txt);
68: if (root != null) {
69: var math = document.createElement("math");
70: math.setAttribute("display", "block");
71: math.appendChild(root.toMathML());
72: output_node.appendChild(math);
73: MathJax.Hub.Queue(["Typeset", MathJax.Hub, output_node]);
74: }
75: } catch (e) {
76: output = "error: " + e;
77: output_node.setAttribute("title", output);
78: if (e instanceof ParseException) {
79: output_node.appendChild(document.createTextNode(txt.substring(0, e.from)));
80: var span = document.createElement('span');
81: span.appendChild(document.createTextNode(txt.substring(e.from, e.to + 1)));
1.3 ! damieng 82: span.style.border = 'solid 1px red';// this used to be CSS "math-error", but using CSS caused too many problems
! 83: span.style.minWidth = '1px';
1.1 damieng 84: output_node.appendChild(span);
85: if (e.to < txt.length - 1) {
86: output_node.appendChild(document.createTextNode(txt.substring(e.to + 1)));
87: }
88: } else {
89: var tn = document.createTextNode(output);
90: output_node.appendChild(tn);
91: }
92: }
93: }
94: }
95: }
96:
1.2 damieng 97: var math_objects = [];
1.1 damieng 98:
99: /*
100: Looks for elements with the "math" class, and
101: adds a preview div afterward which is updated automatically.
1.2 damieng 102: Can be called again after math fields have been added, removed, or when options have changed.
1.1 damieng 103: */
104: var initEditors = function() {
1.2 damieng 105: // to hide the MathJax messages (note: this could be done elsewhere)
106: MathJax.Hub.Config({
107: messageStyle: "none"
108: });
1.1 damieng 109: var math_inputs = document.getElementsByClassName('math');
1.2 damieng 110: // first remove the nodes and objects for the inputs that are gone
111: for (var i=0; i<math_objects.length; i++) {
112: var ta = math_objects[i].ta;
113: var found = false;
114: for (var j=0; j<math_inputs.length; j++) {
115: if (math_inputs[j] == ta) {
116: found = true;
117: break;
118: }
119: }
120: if (!found) {
121: var output_node = math_objects[i].output_node;
122: if (output_node.parentNode) {
123: output_node.parentNode.removeChild(output_node);
124: }
125: math_objects.splice(i, 1);
126: i--;
127: }
128: }
129: // then create or update nodes and objects for the new inputs
1.1 damieng 130: for (var i=0; i<math_inputs.length; i++) {
131: var ta = math_inputs[i];
132: if (ta.nodeName == "TEXTAREA" || ta.nodeName == "INPUT") {
1.2 damieng 133: var ind_math = -1;
134: for (var j=0; j<math_objects.length; j++) {
135: if (math_objects[j].ta == ta) {
136: ind_math = j;
137: break;
138: }
139: }
1.1 damieng 140: var implicit_operators = (ta.getAttribute("data-implicit_operators") === "true");
141: var unit_mode = (ta.getAttribute("data-unit_mode") === "true");
142: var constants = ta.getAttribute("data-constants");
143: if (constants)
144: constants = constants.split(/[\s,]+/);
1.2 damieng 145: var output_node;
146: if (ind_math == -1) {
147: output_node = document.createElement("span");
148: output_node.style.display = "none";
149: output_node.style.position = "absolute";
150: output_node.style.backgroundColor = "rgba(255,255,224,0.9)";
151: output_node.style.color = "black";
152: output_node.style.border = "1px solid #A0A0A0";
153: output_node.style.padding = "5px";
154: output_node.style.zIndex = "1";
155: var getCSSAbsolutePosition = function getCSSAbsolutePosition(el) {
156: var x = 0;
157: var y = 0;
158: while (el && !isNaN(el.offsetLeft) && !isNaN(el.offsetTop)) {
159: x += el.offsetLeft - el.scrollLeft;
160: y += el.offsetTop - el.scrollTop;
161: el = el.offsetParent;
162: if (el) {
163: var style = window.getComputedStyle(el);
164: if (style.position == 'absolute' || style.position == 'relative')
165: break;
166: }
167: }
168: return {top: y, left: x};
169: }
170: var place = function(ta, output_node) {
171: // position the output_node below or on top of ta
172: var ta_rect = ta.getBoundingClientRect();
173: var root = document.documentElement;
174: var docTop = (window.pageYOffset || root.scrollTop) - (root.clientTop || 0);
175: var docLeft = (window.pageXOffset || root.scrollLeft) - (root.clientLeft || 0);
176: var ta_pos = getCSSAbsolutePosition(ta);
177: output_node.style.left = ta_pos.left + "px";
178: if (window.innerHeight > ta_rect.bottom + output_node.offsetHeight)
179: output_node.style.top = (ta_pos.top + ta.offsetHeight) + "px";
180: else
181: output_node.style.top = (ta_pos.top - output_node.offsetHeight) + "px";
182: }
183: if (ta.nextSibling)
184: ta.parentNode.insertBefore(output_node, ta.nextSibling);
185: else
186: ta.parentNode.appendChild(output_node);
187: var hide_node = function(an_output_node) {
188: // returns a function that will hide the node on any event
189: // (we can't use the node directly because it changes in the loop)
190: return function(e) {
191: an_output_node.style.display = "none";
192: };
193: }
1.3 ! damieng 194: var hide_node_if_no_error = function(an_output_node) {
! 195: return function(e) {
! 196: if (!an_output_node.hasAttribute('title'))
! 197: an_output_node.style.display = "none";
! 198: };
! 199: }
1.2 damieng 200: var focus = function(a_ta, an_output_node) {
201: return function(e) {
202: if (a_ta.value != '') {
203: an_output_node.style.display = "block";
204: place(a_ta, an_output_node);
205: }
206: };
1.1 damieng 207: };
1.2 damieng 208: ta.addEventListener("blur", hide_node(output_node), false);
209: ta.addEventListener("focus", focus(ta, output_node), false);
1.3 ! damieng 210: output_node.addEventListener("mouseenter", hide_node_if_no_error(output_node), false);
1.2 damieng 211: ind_math = math_objects.length;
212: var oldtxt = "";
213: math_objects[ind_math] = {
214: "ta": ta,
215: "output_node": output_node,
216: "oldtxt": oldtxt,
217: "parser": new Parser(implicit_operators, unit_mode, constants)
218: };
219: var changeObjectN = function(n) {
220: return function(e) {
221: var obj = math_objects[n];
222: handleChange(obj);
223: if (document.activeElement == obj.ta) {
224: if (obj.ta.value != '') {
225: obj.output_node.style.display = "block";
226: MathJax.Hub.Queue(function () {
227: // position the element only when MathJax is done, because the output_node height might change
228: place(obj.ta, obj.output_node);
229: });
230: } else {
231: obj.output_node.style.display = "none";
232: }
233: }
234: };
235: };
236: var startChange = changeObjectN(ind_math);
237: if (ta.value != oldtxt)
238: startChange(); // process non-empty fields even though they are not visible yet
239: ta.addEventListener('change', startChange, false);
240: ta.addEventListener('keyup', startChange, false);
241: } else {
242: // only create a new parser and update the result if the options have changed
243: var same_constants;
244: var parser = math_objects[ind_math].parser;
245: if (!constants && parser.constants.length == 0) {
246: same_constants = true;
247: } else {
248: if (constants) {
249: same_constants = parser.constants.length == constants.length;
250: if (same_constants) {
251: for (var j=0; j<constants.length; j++) {
252: if (parser.constants[j] != constants[j]) {
253: same_constants = false;
254: break;
255: }
256: }
257: }
258: } else {
259: same_constants = false;
260: }
261: }
262: if (parser.implicit_operators != implicit_operators || parser.unit_mode != unit_mode || !same_constants) {
263: math_objects[ind_math].parser = new Parser(implicit_operators, unit_mode, constants);
264: if (ta.value != '') {
265: math_objects[ind_math].oldtxt = '';
266: handleChange(math_objects[ind_math]);
267: }
268: }
269: }
1.1 damieng 270: }
271: }
272: }
273:
1.2 damieng 274: /**
275: * Updates display for <span class="math"> and <div class="math"> (LON-CAPA ln and dlm tags)
276: */
277: var updateMathSpanAndDiv = function() {
278: var nl = document.getElementsByClassName('math');
279: // convert to an array because the nodelist would change as we are removing nodes from the document
280: var math_nodes = [];
281: for (var i = 0, ref = math_nodes.length = nl.length; i < ref; i++) {
282: math_nodes[i] = nl[i];
283: }
284: for (var i=0; i<math_nodes.length; i++) {
285: var el = math_nodes[i];
286: if (el.nodeName == "SPAN" || el.nodeName == "DIV") {
287: if (el.firstChild == null || el.firstChild.nodeType != 3)
288: continue;
289: var bspan = (el.nodeName == "SPAN");
290: var txt = el.firstChild.nodeValue;
291: var implicit_operators = (el.getAttribute("data-implicit_operators") === "true");
292: var unit_mode = (el.getAttribute("data-unit_mode") === "true");
293: var constants = el.getAttribute("data-constants");
294: if (constants)
295: constants = constants.split(/[\s,]+/);
296: var parser = new Parser(implicit_operators, unit_mode, constants);
297: try {
298: var root = parser.parse(txt);
299: if (root != null) {
300: var math = document.createElement("math");
301: math.setAttribute("display", bspan ? "inline" : "block");
302: math.appendChild(root.toMathML(['#000000']));
303: // at this point it would be nice to replace el by math, but MathJax does not
304: // always typeset math elements when given directly, so we need to typeset the parent...
305: el.classList.remove('math');
306: el.removeChild(el.firstChild);
307: el.appendChild(math);
308: MathJax.Hub.Queue(["Typeset", MathJax.Hub, el]);
309: }
310: } catch (e) {
311: el.firstChild.nodeValue = "[syntax error in math:" + e + "]";
312: el.classList.remove('math');
313: }
314: }
315: }
316: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>