blue       earthtones
Formatting Chemical Formulas in DHTML Pages with a jQuery Widget

Discussion: Chemical formulas in dynamic html pages, whether the data source be an XML file, JSON string, a relational database, or an AJAX response, present an interesting formatting challenge for the web developer. Let's consider the case of XML:  when creating an XML file, it is possible to embed markup in a given element by enclosing it in CDATA (i.e., unparsed character data) markers, but in nearly all cases it is equally pointless to do so since the element content is treated, as specified by CDATA, as unparsed character data. Consequently it is rendered as simple text rather than as the developer might have intended.

For example, consider an XML element which contains embedded markup:

<ChemicalFormula><![CDATA[H<sub>2</sub>O]]></ChemicalFormula>

While the developer might wish this to be rendered on the web page as "H2O", in reality it will be escaped and rendered as "H<sub>2</sub>O" since CDATA is by definition "unparsed".

Solution: A simpler, and for a host of reasons far preferable, approach is illustrated below. Using client-side JavaScript, it's very straightforward to parse a "flat" chemical formula from an XML element, JSON array, or database column and replace the id.innerHTML with a reformatted version of the formula which produces the intended rendering.

I originally wrote this as a JavaScript function in 2008 for a geology website's Mineral Database, then later refactored it as a simple jQuery Widget both to add functionality for HTML< form> elements and improve the code while I was there. Both versions are visible in the accordion tabs below.

Click a formula below to see canonical rendering by the jQuery Widget

<MineralName>the <ChemicalFormula> element:the canonical rendering:


Cancrinite Na6Ca(CO3)(AlSiO4)6·2H2O 

Horneblende (Ca,Na)2-3(Mg,Fe,Al)5Si8Al2O22(OH)2 

Pyrrhotite Fe0.8-1.0S 


Click the formula below for the more common example of in-place reformatting:

(Na,Ca)(Li,Mg,Al)(Al,Fe,Mn)6(BO3)3(Si6O18)(OH)

Finally, automatic reformatting on the completion of an AJAX response is perhaps the most useful example of the widget:

(Na,Ca)(Li,Mg,Al)(Al,Fe,Mn)6(BO3)3(Si6O18)(OH)


Refactored as a jQuery UI Widget

This is the source code for the jQuery 'chemForm' Widget. Rather than minimizimg/obfuscating the code, I've included it as a script at the bottom of this page source so that you can View->Source and re-use it if you like.

$(function(){
  var formatFormula = { // ----> the widget object definition
    pattern: 0,  // 0 = invalid pattern
          // 1 = source is not a form element && target is same as source; 
          // 2 = source is not a form element && target ID is passed as parm 'elem' 
          // 3 = source is a form element && a target element ID is passed as parm 'elem'
    _init: function() { this.element[0].style.cursor = 'pointer'; },
    reformat: function(elem){
          var chemForm = chemResult = target = '';
          var inSub = inHydro = false;
          var receiver = (elem) ? document.getElementById(elem) : false; // absent parameter is OK
          
            this.pattern =  (this.element[0].nodeName.match(/(input|textarea)/i)) ?
                     (receiver.innerHTML !== undefined) ? 3 : 0 : (receiver.innerHTML !== undefined) ? 2 : 1;
            if (!this.pattern) {return false}; // unimplemented pattern
            chemForm = (this.pattern !== 3) ? $(this.element[0]).text() : $(this.element[0]).val();
            for (var cursor=0;cursor < chemForm.length;cursor++)
              {
              if (!isNaN(target = chemForm.charAt(cursor))) {target = '#';}
              switch (target) {
                case  ('#'):
                  if (inSub == false && inHydro == false) {
                  chemResult = chemResult + '<sub>';
                  inSub = true;
                  }
                  chemResult += chemForm.charAt(cursor);
                  break;
                case ' ':
                case '.':
                case '-':
                  chemResult += target;
                  break;
                case '·':              // character 183 = %B7
                  inHydro = true;
                  if (inSub) {
                    chemResult += '</sub>';
                    inSub = false;
                    }
                  chemResult += target;
                  break;
                default:
                  if (inHydro) {inHydro = false;}   // first non-numeric resets inHydro
                  if (inSub) {
                    chemResult += '</sub>';      // terminate the subscript
                    inSub = false;
                    } 
                  chemResult += target;
                }
              }
              if (inSub) { chemResult += '</sub>'; }
              (this.pattern < 2) ? this.element[0].innerHTML = chemResult : receiver.innerHTML = chemResult;
              this.element[0].style.cursor = 'auto';
              return true;
          } // end reformat method
    }; // END widget prototype
	
// -----> create the widget and associate the instances
	$.widget("ui.chemWidget",formatFormula);
	$(".chemForm").chemWidget();
	

The Original JavaScript Function

This is the original JavaScript written circa 2008. It's transported here as a text file via the XMLhttpRequest object.