Annotation of loncom/html/res/adm/pages/reactionresponse/reaction_preview.js, revision 1.2

1.1       damieng     1: /*
                      2: 
                      3: Copyright Michigan State University Board of Trustees
                      4: 
                      5: This file is part of the LearningOnline Network with CAPA (LON-CAPA).
                      6: 
                      7: LON-CAPA is free software; you can redistribute it and/or modify
                      8: it under the terms of the GNU General Public License as published by
                      9: the Free Software Foundation; either version 2 of the License, or
                     10: (at your option) any later version.
                     11: 
                     12: LON-CAPA is distributed in the hope that it will be useful,
                     13: but WITHOUT ANY WARRANTY; without even the implied warranty of
                     14: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
                     15: GNU General Public License for more details.
                     16: 
                     17: You should have received a copy of the GNU General Public License
                     18: along with LON-CAPA; if not, write to the Free Software
                     19: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
                     20: 
                     21: /home/httpd/html/adm/gpl.txt
                     22: 
                     23: http://www.lon-capa.org/
                     24: 
                     25: */
                     26: 
                     27: 'use strict';
                     28: 
                     29: // initialize namespaces if necessary
                     30: var LC = LC || {};
                     31: LC.HW = LC.HW || {};
                     32: 
                     33: 
                     34: /**
                     35:  * This is implementing the reaction response real-time preview.
                     36:  * Some of the code comes from the old reaction editor at
                     37:  * loncom/html/res/adm/pages/reactionresponse/reaction_editor.html
                     38:  * 
                     39:  * @constructor
                     40:  * 
                     41:  * @param {string} field_id - the input field id
                     42:  */
                     43: LC.HW.ReactionPreview = function(field_id) {
                     44:     /** @type {string} */
                     45:     this.field_id = field_id;
                     46:     /** @type {number} */
                     47:     this.level = null;
                     48:     /** @type {Array.<string>} */
                     49:     this.reactants = null;
                     50:     /** @type {Array.<string>} */
                     51:     this.products = null;
                     52:     /** @type {string} */
                     53:     this.old_string = null;
                     54: }
                     55: 
                     56: /**
                     57:  * Parses the reaction string
                     58:  * @param {string} s - the user input
                     59:  */
                     60: LC.HW.ReactionPreview.prototype.parse_reaction = function(s) {
                     61:     var reaction_array = s.split(/\s*->\s*/);
                     62:     this.reactants = new Array(0);
                     63:     this.products = new Array(0);
                     64: 
                     65:     if (reaction_array.length > 0) 
                     66:         this.reactants = reaction_array[0].split(/\s+\+\s*/);
                     67:     if (reaction_array.length > 1) 
                     68:         this.products = reaction_array[1].split(/\s+\+\s*/);
                     69: }
                     70: 
                     71: /**
                     72:  * Converts the reaction string to the CAPA syntax.
                     73:  * @param {string} s - the user input
                     74:  * @returns {string}
                     75:  */
                     76: LC.HW.ReactionPreview.prototype.to_capa = function(s) {
                     77:     var reaction = '';
                     78:     var i;
                     79:     
                     80:     this.parse_reaction(s);
                     81:     
                     82:     for (i = 0; i < this.reactants.length; i++) 
                     83:         this.reactants[i] = this.capa_component(this.reactants[i]);
                     84:     for (i = 0; i < this.products.length; i++) 
                     85:         this.products[i] = this.capa_component(this.products[i]);
                     86:     
                     87:     // this.reactants.sort();
                     88:     // this.products.sort();
                     89:     
                     90:     for (i = 0; i < this.reactants.length-1; i++) {
                     91:         reaction += this.reactants[i];
                     92:         reaction += ' + ';
                     93:     }
                     94:     if (i < this.reactants.length)
                     95:         reaction += this.reactants[i];
                     96:     if (this.products.length > 0) {
                     97:         reaction += ' -> ';
                     98:         for (i = 0; i < this.products.length-1; i++) {
                     99:             reaction += this.products[i];
                    100:             reaction += ' + ';
                    101:         }
                    102:         if (i < this.products.length)
                    103:             reaction += this.products[i];
                    104:     }
                    105:     
                    106:     return reaction;
                    107: }
                    108: 
                    109: /**
                    110:  * Converts a component string to the CAPA syntax.
                    111:  * @param {string} s - the component string
                    112:  */
                    113: LC.HW.ReactionPreview.prototype.capa_component = function(s) {
                    114:     var reactant = '';
                    115:     var i = 0;
                    116:     this.level = 0;
                    117:     
                    118:     for ( ; s.substring(i, i+1) == ' '; i++)
                    119:         ;
                    120:     for ( ; LC.HW.ReactionPreview.isDigit(s.substring(i, i+1)); i++)
                    121:         reactant += s.substring(i, i+1);
                    122:     for ( ; i < s.length; i++)
                    123:         reactant += this.capa_char(s.substring(i, i+1));
                    124:     
                    125:     return reactant;
                    126: }
                    127: 
                    128: /**
                    129:  * Processes a character for the CAPA syntax.
                    130:  * @param {string} chr - a character
                    131:  * @returns {string}
                    132:  */
                    133: LC.HW.ReactionPreview.prototype.capa_char = function(chr) {
                    134:     if (this.level == 0) { // baseline
                    135:         if (chr == '^') 
                    136:             this.level = 1;
                    137:         if (chr == ' ')
                    138:             return '';
                    139:         return chr;
                    140:     }
                    141:     if (this.level == 1) { // superscript
                    142:         if (LC.HW.ReactionPreview.isDigit(chr))
                    143:             return chr;
                    144:         this.level = 0;
                    145:         return chr;
                    146:     }
                    147: }
                    148: 
                    149: /**
                    150:  * Converts a reaction string to HTML for preview.
                    151:  * @param {string} string - the user input
                    152:  * @returns {string}
                    153:  */
                    154: LC.HW.ReactionPreview.prototype.to_html = function(string) {
                    155:     var reaction = '';
                    156:     var i;
                    157: 
                    158:     this.parse_reaction(string);
                    159:     for (i = 0; i < this.reactants.length-1; i++) {
                    160:         reaction += this.html_component(this.reactants[i]);
                    161:         reaction += ' + ';
                    162:     }
                    163:     reaction += this.html_component(this.reactants[i]);
                    164: 
                    165:     if (this.products.length > 0) {
                    166:         reaction += ' &rarr; ';
                    167:         for (i = 0; i < this.products.length-1; i++) {
                    168:             reaction += this.html_component(this.products[i]);
                    169:             reaction += ' + ';
                    170:         }
                    171:         reaction += this.html_component(this.products[i]);
                    172:     }
                    173: 
                    174:     return reaction;
                    175: }
                    176: 
                    177: /**
                    178:  * Converts a component to HTML.
                    179:  * @param {string} string - the user string for the component
                    180:  * @returns {string}
                    181:  */
                    182: LC.HW.ReactionPreview.prototype.html_component = function(string) {
                    183:     var reactant = '';
                    184:     var i = 0;
                    185:     this.level = -1;
                    186: 
                    187:     var hydrate = string.split('.');
                    188:     if (hydrate.length > 1) 
                    189:         return this.html_component(hydrate[0]) + '&middot;' + this.html_component(hydrate[1]);
                    190:         
                    191:     for ( ; i < string.length; i++)
                    192:         reactant += this.html_char(string.substring(i, i+1));
                    193: 
                    194:     return reactant;
                    195: }
                    196: 
                    197: /**
                    198:  * Converts a character to HTML, updating the level.
                    199:  * @param {string} string - the user input character
                    200:  * @returns {string}
                    201:  */
                    202: LC.HW.ReactionPreview.prototype.html_char = function(chr) {
                    203:     if (this.level == -1) { // stoichiometric coefficient
                    204:         if (LC.HW.ReactionPreview.isDigit(chr) || chr == '/')
                    205:             return chr;
                    206:         if (chr == ' ')
                    207:             return '';
                    208:         else
                    209:             this.level = 0;
                    210:     }
                    211:     if (this.level == 0) { // baseline
                    212:         if (LC.HW.ReactionPreview.isDigit(chr))
                    213:             return chr.sub();
                    214:         if (chr == '^') {
                    215:             this.level = 1;
                    216:             return '';
                    217:         }
                    218:         if (chr == '+') // baseline or superscript?
                    219:             return '?';
                    220:         if (chr == ' ')
                    221:             return '';
                    222:         if (chr == '(' || chr == '|') // coefficient
                    223:             this.level = -1;
                    224:         return chr;
                    225:     }
                    226:     if (this.level == 1) { // superscript
                    227:         if (LC.HW.ReactionPreview.isDigit(chr))
                    228:             return chr.sup();
                    229:         if (chr == '+') {
                    230:             this.level = 0;
                    231:             return chr.sup();
                    232:         }
                    233:         if (chr == '-') {
                    234:             this.level = 0;
                    235:             return '<sup>&minus;</sup>';
                    236:         } 
                    237:         if (chr == ' ') {
                    238:             this.level = 0;
                    239:             return '';
                    240:         }
                    241:         this.level = 0;
                    242:         return chr;
                    243:     }
                    244: }
                    245: 
                    246: /**
                    247:  * Starts listening to input changes to display the preview.
                    248:  */
                    249: LC.HW.ReactionPreview.prototype.start_preview = function() {
                    250:     var ta = document.getElementById(this.field_id);
                    251:     // The old reaction editor was suggesting using a readonly textline.
                    252:     // To let users edit the field directly with problems using readonly=yes,
                    253:     // the input's readOnly has to be set to false.
                    254:     ta.readOnly = false;
                    255:     var output_node = document.createElement('span');
                    256:     output_node.id = this.field_id + '_preview';
                    257:     output_node.style.display = 'none';
                    258:     output_node.style.position = 'absolute';
                    259:     output_node.style.backgroundColor = 'rgba(255,255,224,0.9)';
                    260:     output_node.style.color = 'black';
                    261:     output_node.style.border = '1px solid #A0A0A0';
                    262:     output_node.style.padding = '5px';
                    263:     output_node.style.zIndex = '1';
                    264:     if (ta.nextSibling)
                    265:         ta.parentNode.insertBefore(output_node, ta.nextSibling);
                    266:     else
                    267:         ta.parentNode.appendChild(output_node);
                    268:     var hide_node = function(e) {
                    269:         output_node.style.display = 'none';
                    270:     }
                    271:     output_node.addEventListener('mouseenter', hide_node, false);
                    272:     var that = this;
                    273:     var blur = function(e) {
                    274:         output_node.style.display = 'none';
                    275:         ta.value = that.to_capa(ta.value);
                    276:     };
                    277:     ta.addEventListener('blur', blur, false);
1.2     ! damieng   278:     var keydown = function(e) {
        !           279:         if (e.keyCode == 13) // ENTER
        !           280:             ta.value = that.to_capa(ta.value);
        !           281:     };
        !           282:     ta.addEventListener('keydown', keydown, false);
1.1       damieng   283:     var focus = function(e) {
                    284:         if (ta.value != '') {
                    285:             if (ta.value != this.old_string) {
                    286:                 that.handleChange();
                    287:             } else {
                    288:                 output_node.style.display = 'block';
                    289:                 LC.HW.ReactionPreview.place(ta, output_node);
                    290:             }
                    291:         }
                    292:     };
                    293:     ta.addEventListener('focus', focus, false);
                    294:     this.old_string = '';
                    295:     var startChange = function(e) {
                    296:         that.handleChange();
                    297:     };
                    298:     ta.addEventListener('change', startChange, false);
                    299:     ta.addEventListener('keyup', startChange, false);
                    300: }
                    301: 
                    302: /**
                    303:  * Handle a change in the user input
                    304:  */
                    305: LC.HW.ReactionPreview.prototype.handleChange = function() {
                    306:     var ta = document.getElementById(this.field_id);
                    307:     if (document.activeElement == ta) {
                    308:         var output_node = document.getElementById(this.field_id + '_preview');
                    309:         var s = ta.value;
                    310:         this.old_string = s;
                    311:         if (s != '') {
                    312:             output_node.innerHTML = this.to_html(s);
                    313:             output_node.style.display = 'block';
                    314:             LC.HW.ReactionPreview.place(ta, output_node);
                    315:         } else {
                    316:             output_node.style.display = 'none';
                    317:         }
                    318:     }
                    319: }
                    320: 
                    321: /**
                    322:  * Returns an element absolute position.
                    323:  * @static
                    324:  * @param {Element} el
                    325:  * @returns {Object} - keys: top, left
                    326:  */
                    327: LC.HW.ReactionPreview.getCSSAbsolutePosition = function(el) {
                    328:     var x = 0;
                    329:     var y = 0;
                    330:     while (el && !isNaN(el.offsetLeft) && !isNaN(el.offsetTop)) {
                    331:         var scrollLeft;
                    332:         if (el.nodeName == 'INPUT')
                    333:             scrollLeft = 0;
                    334:         else
                    335:             scrollLeft = el.scrollLeft;
                    336:         x += el.offsetLeft - scrollLeft;
                    337:         y += el.offsetTop - el.scrollTop;
                    338:         el = el.offsetParent;
                    339:         if (el) {
                    340:             var style = window.getComputedStyle(el);
                    341:             if (style.position == 'absolute' || style.position == 'relative')
                    342:                 break;
                    343:         }
                    344:     }
                    345:     return {top: y, left: x};
                    346: }
                    347: 
                    348: /**
                    349:  * Positions the output_node below or on top of the input field
                    350:  * @static
                    351:  * @param {Element} ta - the input field
                    352:  * @param {Element} output_node - the preview node
                    353:  */
                    354: LC.HW.ReactionPreview.place = function(ta, output_node) {
                    355:     var ta_rect = ta.getBoundingClientRect();
                    356:     var root = document.documentElement;
                    357:     var docTop = (window.pageYOffset || root.scrollTop)  - (root.clientTop || 0);
                    358:     var docLeft = (window.pageXOffset || root.scrollLeft) - (root.clientLeft || 0);
                    359:     var ta_pos = LC.HW.ReactionPreview.getCSSAbsolutePosition(ta);
                    360:     output_node.style.left = ta_pos.left + 'px';
                    361:     if (window.innerHeight > ta_rect.bottom + output_node.offsetHeight)
                    362:         output_node.style.top = (ta_pos.top + ta.offsetHeight) + 'px';
                    363:     else
                    364:         output_node.style.top = (ta_pos.top - output_node.offsetHeight) + 'px';
                    365: }
                    366: 
                    367: /**
                    368:  * Returns true if the given character is a digit.
                    369:  * @static
                    370:  * @param {string} chr - a character
                    371:  * @returns {boolean}
                    372:  */
                    373: LC.HW.ReactionPreview.isDigit = function(chr) {
                    374:     return (chr >= '0' && chr <= '9');
                    375: }
                    376: 

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>