/*
Copyright Michigan State University Board of Trustees
This file is part of the LearningOnline Network with CAPA (LON-CAPA).
LON-CAPA is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
LON-CAPA is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with LON-CAPA; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
/home/httpd/html/adm/gpl.txt
http://www.lon-capa.org/
*/
'use strict';
// initialize namespaces if necessary
var LC = LC || {};
LC.HW = LC.HW || {};
/**
* This is implementing the reaction response real-time preview.
* Some of the code comes from the old reaction editor at
* loncom/html/res/adm/pages/reactionresponse/reaction_editor.html
*
* @constructor
*
* @param {string} field_id - the input field id
*/
LC.HW.ReactionPreview = function(field_id) {
/** @type {string} */
this.field_id = field_id;
/** @type {number} */
this.level = null;
/** @type {Array.<string>} */
this.reactants = null;
/** @type {Array.<string>} */
this.products = null;
/** @type {string} */
this.old_string = null;
}
/**
* Parses the reaction string
* @param {string} s - the user input
*/
LC.HW.ReactionPreview.prototype.parse_reaction = function(s) {
var reaction_array = s.split(/\s*->\s*/);
this.reactants = new Array(0);
this.products = new Array(0);
if (reaction_array.length > 0)
this.reactants = reaction_array[0].split(/\s+\+\s*/);
if (reaction_array.length > 1)
this.products = reaction_array[1].split(/\s+\+\s*/);
}
/**
* Converts the reaction string to the CAPA syntax.
* @param {string} s - the user input
* @returns {string}
*/
LC.HW.ReactionPreview.prototype.to_capa = function(s) {
var reaction = '';
var i;
this.parse_reaction(s);
for (i = 0; i < this.reactants.length; i++)
this.reactants[i] = this.capa_component(this.reactants[i]);
for (i = 0; i < this.products.length; i++)
this.products[i] = this.capa_component(this.products[i]);
// this.reactants.sort();
// this.products.sort();
for (i = 0; i < this.reactants.length-1; i++) {
reaction += this.reactants[i];
reaction += ' + ';
}
if (i < this.reactants.length)
reaction += this.reactants[i];
if (this.products.length > 0) {
reaction += ' -> ';
for (i = 0; i < this.products.length-1; i++) {
reaction += this.products[i];
reaction += ' + ';
}
if (i < this.products.length)
reaction += this.products[i];
}
return reaction;
}
/**
* Converts a component string to the CAPA syntax.
* @param {string} s - the component string
*/
LC.HW.ReactionPreview.prototype.capa_component = function(s) {
var reactant = '';
var i = 0;
this.level = 0;
for ( ; s.substring(i, i+1) == ' '; i++)
;
for ( ; LC.HW.ReactionPreview.isDigit(s.substring(i, i+1)); i++)
reactant += s.substring(i, i+1);
for ( ; i < s.length; i++)
reactant += this.capa_char(s.substring(i, i+1));
return reactant;
}
/**
* Processes a character for the CAPA syntax.
* @param {string} chr - a character
* @returns {string}
*/
LC.HW.ReactionPreview.prototype.capa_char = function(chr) {
if (this.level == 0) { // baseline
if (chr == '^')
this.level = 1;
if (chr == ' ')
return '';
return chr;
}
if (this.level == 1) { // superscript
if (LC.HW.ReactionPreview.isDigit(chr))
return chr;
this.level = 0;
return chr;
}
}
/**
* Converts a reaction string to HTML for preview.
* @param {string} string - the user input
* @returns {string}
*/
LC.HW.ReactionPreview.prototype.to_html = function(string) {
var reaction = '';
var i;
this.parse_reaction(string);
for (i = 0; i < this.reactants.length-1; i++) {
reaction += this.html_component(this.reactants[i]);
reaction += ' + ';
}
reaction += this.html_component(this.reactants[i]);
if (this.products.length > 0) {
reaction += ' → ';
for (i = 0; i < this.products.length-1; i++) {
reaction += this.html_component(this.products[i]);
reaction += ' + ';
}
reaction += this.html_component(this.products[i]);
}
return reaction;
}
/**
* Converts a component to HTML.
* @param {string} string - the user string for the component
* @returns {string}
*/
LC.HW.ReactionPreview.prototype.html_component = function(string) {
var reactant = '';
var i = 0;
this.level = -1;
var hydrate = string.split('.');
if (hydrate.length > 1)
return this.html_component(hydrate[0]) + '·' + this.html_component(hydrate[1]);
for ( ; i < string.length; i++)
reactant += this.html_char(string.substring(i, i+1));
return reactant;
}
/**
* Converts a character to HTML, updating the level.
* @param {string} string - the user input character
* @returns {string}
*/
LC.HW.ReactionPreview.prototype.html_char = function(chr) {
if (this.level == -1) { // stoichiometric coefficient
if (LC.HW.ReactionPreview.isDigit(chr) || chr == '/')
return chr;
if (chr == ' ')
return '';
else
this.level = 0;
}
if (this.level == 0) { // baseline
if (LC.HW.ReactionPreview.isDigit(chr))
return chr.sub();
if (chr == '^') {
this.level = 1;
return '';
}
if (chr == '+') // baseline or superscript?
return '?';
if (chr == ' ')
return '';
if (chr == '(' || chr == '|') // coefficient
this.level = -1;
return chr;
}
if (this.level == 1) { // superscript
if (LC.HW.ReactionPreview.isDigit(chr))
return chr.sup();
if (chr == '+') {
this.level = 0;
return chr.sup();
}
if (chr == '-') {
this.level = 0;
return '<sup>−</sup>';
}
if (chr == ' ') {
this.level = 0;
return '';
}
this.level = 0;
return chr;
}
}
/**
* Starts listening to input changes to display the preview.
*/
LC.HW.ReactionPreview.prototype.start_preview = function() {
var ta = document.getElementById(this.field_id);
// The old reaction editor was suggesting using a readonly textline.
// To let users edit the field directly with problems using readonly=yes,
// the input's readOnly has to be set to false.
ta.readOnly = false;
var output_node = document.createElement('span');
output_node.id = this.field_id + '_preview';
output_node.style.display = 'none';
output_node.style.position = 'absolute';
output_node.style.backgroundColor = 'rgba(255,255,224,0.9)';
output_node.style.color = 'black';
output_node.style.border = '1px solid #A0A0A0';
output_node.style.padding = '5px';
output_node.style.zIndex = '1';
if (ta.nextSibling)
ta.parentNode.insertBefore(output_node, ta.nextSibling);
else
ta.parentNode.appendChild(output_node);
var hide_node = function(e) {
output_node.style.display = 'none';
}
output_node.addEventListener('mouseenter', hide_node, false);
var that = this;
var blur = function(e) {
output_node.style.display = 'none';
ta.value = that.to_capa(ta.value);
};
ta.addEventListener('blur', blur, false);
var keydown = function(e) {
if (e.keyCode == 13) // ENTER
ta.value = that.to_capa(ta.value);
};
ta.addEventListener('keydown', keydown, false);
var focus = function(e) {
if (ta.value != '') {
if (ta.value != this.old_string) {
that.handleChange();
} else {
output_node.style.display = 'block';
LC.HW.ReactionPreview.place(ta, output_node);
}
}
};
ta.addEventListener('focus', focus, false);
this.old_string = '';
var startChange = function(e) {
that.handleChange();
};
ta.addEventListener('change', startChange, false);
ta.addEventListener('keyup', startChange, false);
}
/**
* Handle a change in the user input
*/
LC.HW.ReactionPreview.prototype.handleChange = function() {
var ta = document.getElementById(this.field_id);
if (document.activeElement == ta) {
var output_node = document.getElementById(this.field_id + '_preview');
var s = ta.value;
this.old_string = s;
if (s != '') {
output_node.innerHTML = this.to_html(s);
output_node.style.display = 'block';
LC.HW.ReactionPreview.place(ta, output_node);
} else {
output_node.style.display = 'none';
}
}
}
/**
* Returns an element absolute position.
* @static
* @param {Element} el
* @returns {Object} - keys: top, left
*/
LC.HW.ReactionPreview.getCSSAbsolutePosition = function(el) {
var x = 0;
var y = 0;
while (el && !isNaN(el.offsetLeft) && !isNaN(el.offsetTop)) {
var scrollLeft;
if (el.nodeName == 'INPUT')
scrollLeft = 0;
else
scrollLeft = el.scrollLeft;
x += el.offsetLeft - scrollLeft;
y += el.offsetTop - el.scrollTop;
el = el.offsetParent;
if (el) {
var style = window.getComputedStyle(el);
if (style.position == 'absolute' || style.position == 'relative')
break;
}
}
return {top: y, left: x};
}
/**
* Positions the output_node below or on top of the input field
* @static
* @param {Element} ta - the input field
* @param {Element} output_node - the preview node
*/
LC.HW.ReactionPreview.place = function(ta, output_node) {
var ta_rect = ta.getBoundingClientRect();
var root = document.documentElement;
var docTop = (window.pageYOffset || root.scrollTop) - (root.clientTop || 0);
var docLeft = (window.pageXOffset || root.scrollLeft) - (root.clientLeft || 0);
var ta_pos = LC.HW.ReactionPreview.getCSSAbsolutePosition(ta);
output_node.style.left = ta_pos.left + 'px';
if (window.innerHeight > ta_rect.bottom + output_node.offsetHeight)
output_node.style.top = (ta_pos.top + ta.offsetHeight) + 'px';
else
output_node.style.top = (ta_pos.top - output_node.offsetHeight) + 'px';
}
/**
* Returns true if the given character is a digit.
* @static
* @param {string} chr - a character
* @returns {boolean}
*/
LC.HW.ReactionPreview.isDigit = function(chr) {
return (chr >= '0' && chr <= '9');
}
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>