E4X for templating

Warning: E4X is deprecated. It will be disabled by default for content in Firefox 16, disabled by default for chrome in Firefox 17, and removed in Firefox 18. Use DOMParser/DOMSerializer or a non-native JXON algorithm instead.

E4X can be used for creating templates for dynamic content.

While it may be obvious after a study of the basics of E4X that it can be used for this purpose, if one adds a few common purpose functions (especially along with the convenience of JavaScript 1.8 expression closures), the templates can function more dynamically, offering the power and readability of templating languages such as Smarty for PHP (though admittedly without the currently wider cross-browser support of XSLT or the strictly-XML approach of PHPTAL or Seethrough templating).

Security and escaping

function e (str) {
    if (typeof str === 'xml') {str = str.toString();}
    return str;
}
function quot (s) { // useful for placing user input within inline JavaScript; may be combined with escape function above as well
    if (typeof s === 'string') {
        return s.replace(/"/g, '"').replace(/'/g, ''');
    }
    if (typeof s === 'xml') {
        return s.toString().replace(/"/g, '"').replace(/'/g, ''');
    }
    return String(s).replace(/"/g, '"').replace(/'/g, ''');
}

Localization

E4X works nicely with a simple utility for localizing strings of a properties file:

// Localization
function $S(msg, args){ //get localized message
    var STRS = Cc['@mozilla.org/intl/stringbundle;1'].getService(Ci.nsIStringBundleService).
                                    createBundle('chrome://myeExt/locale/myExt.properties');
    if (args){
        args = Array.prototype.slice.call(arguments, 1);
        return STRS.formatStringFromName(msg,args,args.length);
    }
    return STRS.GetStringFromName(msg);
}

For example,

<toolbarbutton label={$S('myToolbar.label')}/>

Conditionals

function _if (cond, h, _else) {
    if (cond && cond != undefined) { // We need undefined condition for E4X
        return h(cond);
    }
    else if (_else) {
        return _else(cond);
    }
    return ''; // Empty string allows conditions in attribute as well as element content
}

For example:

{_if(elems.length(), function ()
    <description>{elems[0]}</description>,
function _else () 
    <label>No data</label>
)}

Note that the simple XMLList() constructor (<></>) may be useful to still be able to use an expression closure (i.e., without needing return statements and braces):

{_if(elems.length(), function () <>
        <markup/>
        <markup/>
</>)}

Note that, while it is convenient to store such E4X in separate file templates (to be eval()d at a later time, taking into account security considerations, such as escaping with the above), E4X content using such functions can also be easily serialized inline (and then perhaps converted to the DOM) as needed:

var list = <>{_if(elems.length(), function () <>
        <markup/>
        <markup/>
</>)}</>.toXMLString();

Iterating

Functions such as the following foreach (which can work with arrays, objects, or E4X objects) are quite convenient in iterating over complex structures such as E4X would not normally allow. 

/* 
The first two arguments are optional: (h is a handler with an explicit argument v only, or beginning with k, v)
lev is optional argument to note recursive depth (if part of recursion)
*/
function foreach (min, max, arr, h, lev) {
    var k, ret=<></>, it = 1;
    lev = lev || 0;
    if (typeof min === 'number') {
        if (typeof max !== 'number') {
            lev = h;
            h = arr;
            arr = max;
            max = min;
            min = 1;
        }
    }
    else {
        lev = arr;
        h = max;
        arr = min;
        max = Number.POSITIVE_INFINITY;
        min = 1;
    }
    if (h.length === 1) {
        for (k in arr) {
            if (it < min) {
                ++it;
                continue;
            }
            if (it > max) {
                break;
            }
            ret+=h(arr[k], it, lev); // Need to get it or lev via arguments[] since our length detection implies no explicit additional params; otherwise define with more than one param (see below)
            ++it;
        }
    }
    else {
        for (k in arr) {
            if (it < min) {
                ++it;
                continue;
            }
            if (it > max) {
                break;
            }
            ret+=h(k, arr[k], it, lev);
            ++it;
        }
    }
    return ret;
}

The following real case example iterates over an array of the lines in an E4X child element to produce an XMLList of multiple vbox's representing each line:

<vbox>
{foreach(e(someEl.someChild[0]).split('\n'), function (line)
    <description>{line}</description>
)}
</vbox>

The following example shows iteration over an E4X object itself:

{foreach(elems, function (k, elem, iter) <>
    <row>{k}: {elem}</row>
    <row><image src="chrome://myExt/skin/images/FillerRow.jpg" /></row>
</>)}

or if the E4X child element had its own children and text:

{foreach(elems, function (k, elem, iter) <>
    <row>{k}: {elem.text()} {elem.someChild}</row>
    <row><image src="chrome://myExt/skin/images/FillerRow.jpg" /></row>
</>)}

 

Sorting

/*
@param {XMLList} xmllist The XMLList to sort
@param {Function} h The sorting handler
*/
function sort (xmllist, h) {
    var k, arr=[], ret = <></>;
    for (k in xmllist) {
        if (xmllist.hasOwnProperty(k)) {
            arr.push(xmllist[k]);
        }
    }
    arr.sort(h).forEach(function (item) {
        if (typeof item === 'xml') {
            ret +=  item;
        }
        else if (typeof item === 'string') {
            ret += new XML(item);
        }
        else {
            var ser = (new XMLSerializer()).serializeToString(item);
            ret += new XML(ser);
        }
    });
    return ret;
}

Example:

var fruits = <fruits>
    <item>Pear</item>
    <item>Banana</item>
    <item>Grapes</item>
</fruits>;
alert( // Using a JavaScript 1.8 expression closure
<output>
    {sort(fruits.*, function (a, b) a.text() > b.text() /* text() call may not be necessary */  )}
</output>.toXMLString()
);
/*
<output>
  <item>Banana</item>
  <item>Grapes</item>
  <item>Pear</item>
</output>
*/

The above utility also works if the input is an HTMLCollection, an array of strings, an array of DOM objects, or an array of E4X objects (assuming the comparison function is changed or adapted accordingly).

Inline functions

As explained in the tutorial, it is possible to use anonymous functions inline (returning the desired content, including potentially XMLList's) in order to execute more than a single related statement, keeping this logic together with the resulting XML.

Although a big advantage of E4X is being able to separate presentation from business logic, and the above-mentioned technique may fly in the face of this, if formatted well, it can also allow inline shaping of XML somewhat akin to the W3C standard XQuery language, allowing the scripting to mix in context with the surrounding declarative XML:

var a = <a><b/><c/><d/></a>;
var b =
<bar>{function () {
    var content = <></>;
    for each (var el in a) {
        el.@att = 'val';
        content += el;
    }
    return content;
}()}</bar>;

giving:

<bar>
  <b att="val"/>
  <c att="val"/>
  <d att="val"/>
</bar>

One may still wish to remove complex business logic and supply as variables to the E4X, but the above allows the shaping of resulting content to be made more clear (and sometimes design logic also calls for extra processing).

Document Tags and Contributors

Tags: 
 Contributors to this page: teoli, Sheppy, kmaglione, Brendan, Brettz9
 Last updated by: teoli,