E4X for templating

  • Revision slug: E4X_for_templating
  • Revision title: E4X for templating
  • Revision id: 75018
  • Created:
  • Creator: Brettz9
  • Is current revision? No
  • Comment 6 words added, 1 words removed

Revision Content

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

// Security
function e (str) { // escape E4X; needed for security when using untrusted user input
    if (typeof str === 'xml') {str = str.toString();}
    return str.replace(/\{/g, '{').replace(/\}/g, '}');
}
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, ''');
    }
    else 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);
    }
    else {
      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>

</>)}

Revision Source

<p>E4X can be used for creating templates for dynamic content.</p>
<p>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 <a class="external" href="http://github.com/bard/seethrough_js/wikis" title="http://github.com/bard/seethrough_js/wikis">Seethrough templating</a>).</p>
<h3>Security and escaping</h3>
<pre class="brush: js">// Security
function e (str) { // escape E4X; needed for security when using untrusted user input
    if (typeof str === 'xml') {str = str.toString();}
    return str.replace(/\{/g, '&amp;#123;').replace(/\}/g, '&amp;#125;');
}
</pre>
<pre class="brush: js">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, '&amp;quot;').replace(/'/g, '&amp;apos;');
    }
    else if (typeof s === 'xml') {
        return s.toString().replace(/"/g, '&amp;quot;').replace(/'/g, '&amp;apos;');
    }
    return String(s).replace(/"/g, '&amp;quot;').replace(/'/g, '&amp;apos;');
}
</pre>
<p> </p>
<h3>Localization</h3>
<p>E4X works nicely with a simple utility for localizing strings of a properties file:</p>
<pre class="brush: js">// 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);
    }
    else {
      return STRS.GetStringFromName(msg);
    }
}
</pre>
<p>For example,</p>
<pre class="brush: xml">&lt;toolbarbutton label={$S('myToolbar.label')}/&gt;
</pre>
<h3>Conditionals</h3>
<pre class="brush: js">function _if (cond, h, _else) {
    if (cond &amp;&amp; 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
}</pre>
<p>For example:</p>
<pre class="brush: xml">{_if(elems.length(), function ()

    &lt;description&gt;{elems[0]}&lt;/description&gt;,

function _else () 

    &lt;label&gt;No data&lt;/label&gt;

)}
</pre>
<p>Note that the simple XMLList() constructor (&lt;&gt;&lt;/&gt;) may be useful to still be able to use an expression closure (i.e., without needing return statements and braces):</p>
<pre class="brush: xml">{_if(elems.length(), function () &lt;&gt;
        &lt;markup/&gt;
        &lt;markup/&gt;
&lt;/&gt;)}</pre>
<p>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:</p>
<pre class="brush: js">var list = &lt;&gt;{_if(elems.length(), function () &lt;&gt;
        &lt;markup/&gt;
        &lt;markup/&gt;
&lt;/&gt;)}&lt;/&gt;.toXMLString();
</pre>
<h3>Iterating</h3>
<p>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.</p>
<p> </p>
<pre class="brush: js">/* 
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=&lt;&gt;&lt;/&gt;, 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 &lt; min) {
                ++it;
                continue;
            }
            if (it &gt; 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 &lt; min) {
                ++it;
                continue;
            }
            if (it &gt; max) {
                break;
            }
            ret+=h(k, arr[k], it, lev);
            ++it;
        }
    }
    return ret;
}</pre>
<p>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:</p>
<pre class="brush: xml">&lt;vbox&gt;

{foreach(e(someEl.someChild[0]).split('\n'), function (line)
    &lt;description&gt;{line}&lt;/description&gt;
)}

&lt;/vbox&gt;</pre>
<p>The following example shows iteration over an E4X object itself:</p>
<pre class="brush: xml">{foreach(elems, function (k, elem, iter) &lt;&gt;

    &lt;row&gt;{k}: {elem}&lt;/row&gt;

    &lt;row&gt;&lt;image src="chrome://myExt/skin/images/FillerRow.jpg" /&gt;&lt;/row&gt;

&lt;/&gt;)}
</pre>
<p>or if the E4X child element had its own children and text:</p>
<pre class="brush: xml">{foreach(elems, function (k, elem, iter) &lt;&gt;

    &lt;row&gt;{k}: {elem.text()} {elem.someChild}&lt;/row&gt;

    &lt;row&gt;&lt;image src="chrome://myExt/skin/images/FillerRow.jpg" /&gt;&lt;/row&gt;

&lt;/&gt;)}</pre>
Revert to this revision