Eli Grey

Another E4X DOM library

I just made another E4X DOM library, but this one is intended to implement the optional features in the ECMA-357 standard that are not implemented by Mozilla in JavaScript. The library is named e4x.js due to, for the most part, it only implementing stuff already specified in E4X. The only difference between the standard and my implementation is that the XML.xpath(xpathExpression) method also supports numeric, string, and boolean result types.

E4X DOM toolkit

I have created a small JavaScript toolkit named e4x-dom.js for making it easy to manipulate the DOM with E4X.

The following methods are implemented on XML objects by e4x-dom.js:

node()
Returns the HTML node representation of the XML.
over(element or selector)
Either overwrites element or every element matched by the CSS selector with node().
overId(id)
Same as over(document.getElementById(id)) but also preserves the the id of the element being overwritten in the element replacing it.
fill(element or selector)
Removes every child node of element or every element matched by the CSS selector. Then node() is appended to all of the emptied elements.
fillId(id)
Same as fill(document.getElementById(id))
appendTo(element or selector)
Appends node() to element or every element matched by the CSS selector.
appendToId(id)
Same as appendTo(document.getElementById(id)).
insertBefore(element or selector)
Inserts node() before element or every element matched by the CSS selector.
insertBeforeId(id)
Same as insertBefore(document.getElementById(id)).
insertAfter(element or selector)
Inserts node() after element or every element matched by the CSS selector.
insertAfterId(id)
Same as insertAfter(document.getElementById(id)).

The following are examples of using the toolkit:

var img = <img
 src={prompt("Enter an image URI")}
 alt={prompt("Describe the image")}
 id="foobar"
/>;
img.appendTo(document.body);
 
<h1>The image was <em>removed</em>.</em>.overId("foobar");
<![CDATA[And then this text node filled the header]]>.fill("#foobar");
 
// the CDATA isn't itself put into the document but the data inside it is escaped so it
// works in HTML and XHTML
<h1><![CDATA[<html> in here is <escaped>]]></h1>.insertBefore(document.body.firstChild);
 
// if you just want to create an element quickly, do xml.node()
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"/>.node(); // SVG node
<div/>.node(); // div node
 
// DOM methods are forwarded to .node()[method]()
<canvas/>.getContext("2d");
<foo bar="baz"/>.getAttribute("bar") === "baz"; // same as getting .@bar
 
// mass-modifications with CSS selectors
<strong>Absolute links are <em>not</em> allowed</strong>
 .over('a[href^="http://"], a[href^="https://"], a[href^="//"]');
 
if (user.hasEditPrivileges) {
 // wikipedia-style "put an edit link at the start of each section"
 <a href={"/edit?page=" + page.id}>[edit]</a>.insertAfter('h2');
}
 
// <![CDATA[]]> nodes directly accessed are converted to text nodes:
 
<![CDATA[
 This is a text node.
 <html> is escaped inside it.
]]>.appendTo("#some-element");

Array methods for XML lists

I have created an open source library that implements every array method for E4X XML lists in JavaScript named e4x-array-methods.js. The methods output XML as opposed to arrays to make the output directly usable with other XML. To get the array representation of an XML list, use slice. This returns XML when used to slice out ranges from XML but if no arguments are passed or a third argument is specified which is equivalent to true, it will convert an XML list to an array instead. For example, xmllist.slice(3, 5) returns an XML list and xmllist.slice(3, 5, true) returns an array.

Download

You can download e4x-array-methods.js at it’s github repository. If you wish to minify the library yourself, make sure that your minification tool supports the E4X used in this library. Rhino-based minifiers fail, throwing syntax errors, and /packer/ errantly minifies all of the public methods names preceded by .function::.

Example

The following example demonstrate the usefulness of having array methods for XML lists. If you want to try out the example in a JavaScript shell, make sure XML.prettyPrinting is set to false, which removes any unnecessary whitespace from xml.toString() and xml.toXMLString().

XML.prettyPrinting = false; // for simplifying the comparisons below
 
var foo = <></>; // XMLList literal
foo.push(<n>0</n>, 1, 5, 3, 2, 6, 4);
foo.toXMLString() === "<n>0</n><n>1</n><n>5</n><n>3</n><n>2</n><n>6</n><n>4</n>";
foo.sort(function(a, b) {
    return a - b;
}).toXMLString() === "<n>0</n><n>1</n><n>2</n><n>3</n><n>4</n><n>5</n><n>6</n>";
foo.filter(function(x) { // filter out integers less then 3
    if (!(x < 3))
        return true;
}).toXMLString() === "<n>3</n><n>4</n><n>5</n><n>6</n>";
foo.slice(2, 5).toXMLString() === "<n>2</n><n>3</n><n>4</n>";
foo.splice(2, 1, <n>9</n>, <n>8</n>).toXMLString() === "<n>2</n>";
foo.slice(2, 5).toXMLString() === "<n>3</n><n>9</n><n>8</n>";
foo.pop().toXMLString() === "<n>6</n>";
foo.shift().toXMLString() === "<n>0</n>";
foo.shift().toXMLString() === "<n>1</n>";
foo.unshift(<bar/>);
foo.toXMLString() === "<bar/><n>3</n><n>9</n><n>8</n><n>4</n><n>5</n>";
foo.map(parseFloat).forEach(function(n){ print(n) }) // prints NaN, 3, 9, 8, 4, 5
foo.some(function(x) { // some are greater than 5
    if (x > 5) return true;
}) === true;
foo.every(function(x) { // all are numbers
    if (typeof x == "number") return true
}) === false;

Namespacing properties in JavaScript

Namespacing properties is a great way to make a JavaScript library produce extendible objects. Namespacing in JavaScript can be done by prefixing namespace:: before object and property names. Unfortunately, namespacing is only supported in JavaScript 1.6 and higher, which is currently only implemented in SpiderMonkey (Firefox’s JavaScript engine) and Rhino with E4X enabled. Namespaces should have a toString method that returns a string of the same name (Foo.toString() == "Foo"). In Firefox, the @mozilla.org/js/function namespace (“function”) seems to be the default at which everything is under. For example, function::document == this.document and x = "@mozilla.org/js/function"; x::document == function::document. Also, anything with a blank string representation behaves the same way as long as it’s being used in a property, for example:

var foo = "@mozilla.org/js/function", bar = "";
try { // using try..catch because the error still fires in a typeof statement
    foo::document; // works fine
    bar::document; // error
} catch(err if err instanceof ReferenceError) {
    // err.message == "reference to undefined XML name document";
}
this.bar::document == this.document;

The following example shows a naming conflict that can be solved using namespaced properties. In the examples, the fictional libraries, libSpaceTime and libDimensions are loaded. libDimensions adds a Size global and creates a ‘dimensions’ setter on Object.prototype that accepts various inputs to change the width/height/depth/ect. of something. libSpaceTime introduces the SpaceTime global. When a universe is created, the dimensions setter changes the number of dimensions the universe has. The width/height/ect. of a universe are automatically set (but can be changed) from the matter property.
(more…)