Section 21.6. Expanding HTML Templates with XML Data


21.6. Expanding HTML Templates with XML Data

One key feature of IE's XML data islands is that they can be used with an automatic templating facility in which data from a data island is automatically inserted into HTML elements. These HTML templates are defined in IE by adding datasrc and datafld ("fld" is short for "field") attributes to the elements.

This section applies the XML techniques seen earlier in the chapter and uses XPath and the DOM to create an improved templating facility that works in IE and Firefox. A template is any HTML element with a datasource attribute. The value of this attribute should be the ID of an XML data island or the URL of an external XML document. The template element should also have a foreach attribute. The value of this attribute is an XPath expression that evaluates to the list of nodes from which XML data will be extracted. For each XML node returned by the foreach expression, an expanded copy of the template is inserted into the HTML document. The template is expanded by finding all elements within it that have a data attribute. This attribute is another XPath expression to be evaluated in the context of a node returned by the foreach expression. This data expression is evaluated with XML.getNode( ), and the text contained by the returned node is used as the content of the HTML element on which the data attribute was defined.

This description becomes much clearer with a concrete example. Example 21-12 is a simple HTML document that includes an XML data island and a template that uses it. It has an onload( ) event handler that expands the template.

Example 21-12. An XML data island and HTML template

 <html> <!-- Load our XML utilities for data islands and templates --> <head><script src="/books/2/427/1/html/2/xml.js"><script></head> <!-- Expand all templates on document load --> <body onload="XML.expandTemplates( )"> <!-- This is an XML data island with our data --> <xml  style="display:none"> <!-- hidden with CSS -->   <contacts>     <contact name="Able Baker"><email>able@example.com</email></contact>     <contact name="Careful Dodger"><email>dodger@example.com</email></contact>     <contact name="Eager Framer"><email>framer@example.com</email></contact>   </contacts> </xml> <!-- These are just regular HTML elements --> <table> <tr><th>Name</th><th>Address</th></tr> <!-- This is a template. Data comes from the data island with id "data". --> <!-- The template will be expanded and copied once for each <contact> tag --> <tr datasource="#data" foreach="//contact"> <!-- The "name" attribute of the <contact> is inserted into this element --> <td data="@name"></td> <!-- The content of the <email> child of the <contact> goes in here --> <td data="email"></td> </tr> <!-- end of the template --> </table> </html> 

A critical piece of Example 21-12 is the onload event handler, which calls a function named XML.expandTemplates( ). Example 21-13 shows the implementation of this function. The code is fairly platform-independent, relying on basic Level 1 DOM functionality and on the XPath utility functions XML.getNode( ) and XML.getNodes( ) defined in Example 21-10.

Example 21-13. Expanding HTML templates

 /*  * Expand any templates at or beneath element e.  * If any of the templates use XPath expressions with namespaces, pass  * a prefix-to-URL mapping as the second argument as with XML.XPathExpression( )  *  * If e is not supplied, document.body is used instead. A common  * use case is to call this function with no arguments in response to an  * onload event handler. This automatically expands all templates.  */ XML.expandTemplates = function(e, namespaces) {     // Fix up arguments a bit.     if (!e) e = document.body;     else if (typeof e == "string") e = document.getElementById(e);     if (!namespaces) namespaces = null; // undefined does not work     // An HTML element is a template if it has a "datasource" attribute.     // Recursively find and expand all templates. Note that we don't     // allow templates within templates.     if (e.getAttribute("datasource")) {         // If it is a template, expand it.         XML.expandTemplate(e, namespaces);     }     else {         // Otherwise, recurse on each of the children. We make a static         // copy of the children first so that expanding a template doesn't         // mess up our iteration.         var kids = []; // To hold copy of child elements         for(var i = 0; i < e.childNodes.length; i++) {             var c = e.childNodes[i];             if (c.nodeType == 1) kids.push(e.childNodes[i]);         }         // Now recurse on each child element         for(var i = 0; i < kids.length; i++)             XML.expandTemplates(kids[i], namespaces);     } }; /**  * Expand a single specified template.  * If the XPath expressions in the template use namespaces, the second  * argument must specify a prefix-to-URL mapping  */ XML.expandTemplate = function(template, namespaces) {     if (typeof template=="string") template=document.getElementById(template);     if (!namespaces) namespaces = null; // Undefined does not work     // The first thing we need to know about a template is where the     // data comes from.     var datasource = template.getAttribute("datasource");     // If the datasource attribute begins with '#', it is the name of     // an XML data island. Otherwise, it is the URL of an external XML file.     var datadoc;     if (datasource.charAt(0) == '#')   // Get data island         datadoc = XML.getDataIsland(datasource.substring(1));     else                               // Or load external document         datadoc = XML.load(datasource);     // Now figure out which nodes in the datasource will be used to     // provide the data. If the template has a foreach attribute,     // we use it as an XPath expression to get a list of nodes. Otherwise,     // we use all child elements of the document element.     var datanodes;     var foreach = template.getAttribute("foreach");     if (foreach) datanodes = XML.getNodes(datadoc, foreach, namespaces);     else {         // If there is no "foreach" attribute, use the element         // children of the documentElement         datanodes = [];         for(var c=datadoc.documentElement.firstChild; c!=null; c=c.nextSibling)             if (c.nodeType == 1) datanodes.push(c);     }     // Remove the template element from its parent,     // but remember the parent, and also the nextSibling of the template.     var container = template.parentNode;     var insertionPoint = template.nextSibling;     template = container.removeChild(template);     // For each element of the datanodes array, we'll insert a copy of     // the template back into the container. Before doing this, though, we     // expand any child in the copy that has a "data" attribute.     for(var i = 0; i < datanodes.length; i++) {         var copy = template.cloneNode(true);           // Copy template         expand(copy, datanodes[i], namespaces);        // Expand copy         container.insertBefore(copy, insertionPoint);  // Insert copy     }     // This nested function finds any child elements of e that have a data     // attribute. It treats that attribute as an XPath expression and     // evaluates it in the context of datanode. It takes the text value of     // the XPath result and makes it the content of the HTML node being     // expanded. All other content is deleted.     function expand(e, datanode, namespaces) {         for(var c = e.firstChild; c != null; c = c.nextSibling) {             if (c.nodeType != 1) continue;  // elements only             var dataexpr = c.getAttribute("data");             if (dataexpr) {                 // Evaluate XPath expression in context.                 var n = XML.getNode(datanode, dataexpr, namespaces);                 // Delete any content of the element                 c.innerHTML = "";                 // And insert the text content of the XPath result                 c.appendChild(document.createTextNode(getText(n)));             }             // If we don't expand the element, recurse on it.             else expand(c, datanode, namespaces);         }     }     // This nested function extracts the text from a DOM node, recursing     // if necessary.     function getText(n) {         switch(n.nodeType) {         case 1: /* element */             var s = "";             for(var c = n.firstChild; c != null; c = c.nextSibling)                 s += getText(c);             return s;         case 2: /* attribute*/         case 3: /* text */         case 4: /* cdata */             return n.nodeValue;         default:             return "";         }     } }; 




JavaScript. The Definitive Guide
JavaScript: The Definitive Guide
ISBN: 0596101996
EAN: 2147483647
Year: 2004
Pages: 767

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net