Thursday, December 9, 2010

Creating HTML with JavaScript

When I first started creating HTML elements with JavaScript, I used the standard JavaScript DOM methods (e.g. createElement, createAttribute, etc.). Although building HTML this way provides a natural way to store references to all the important elements, for all but trivial scenarios, you'll end up with a convoluted mess.

Next I started writing out the HTML as a big string, and then using the innerHTML property to build the actual elements. That was easier to write, but I found it difficult to reference the various elements in my HTML. Sure I could provide IDs and use getElementById to get the actual references, but let me try to explain some of the issues with this approach.

Consider this scenario: I want to create a PictureButton class. When building the HTML for the control, I might do something like this:
     // Define the markup for the control  
     var html = '';  
     html += '<div id="PictureButton">';  
     html += ' <img id="Picture" src="Picture.png" />';  
     html += '</div>';  
   
     // Add control to the DOM  
     Parent.innerHTML = html;  
   
     // Get a reference to our elements (perhaps for wiring up events, etc)  
     var PictureButton = document.getElementById('PictureButton');  
     var Picture = document.getElementById('Picture');  

There are three issues here:
1. IDs are global to the entire document, so this will quickly fall apart as subsequent buttons are created.
2. I might want to reference the elements of my control before I'm ready to add them to the document.
3. I might want to append my control to the parent, without having to clear the inner HTML.

Frustrated by the limitations of both approaches, I put together a routine that, I think, addresses all of the limitations discussed.

Here it is:
 function CreateElementsFromHtml(html, elements){  
   // Create a temporary container so we can build  
   // our HTML structure  
   var container = document.createElement("div");  
   container.innerHTML = html;  
   
   // Traverse through the nodes looking for elements  
   // with a "eid" and store references to those elements  
   Traverse(container.childNodes, elements);  
   
   function Traverse(elementsToTraverse, elements){  
     for (var i = 0; i < elementsToTraverse.length; i++) {  
       var element = elementsToTraverse[i];  
   
       // Check to see if the "eid" attribute was set  
       if(element.getAttribute){  
         var id = element.getAttribute('eid');  
         if(id){  
           // If found, remove the attribute (since it's not a  
           // valid attribute) and store the reference to this  
           // element using the "eid" as the property name.  
           element.removeAttribute('eid');  
           elements[id] = element;  
         }  
       }  
   
       // Recursively repeat this operation for the child  
       // nodes.  
       if(element.childNodes.length != 0){  
         Traverse(element.childNodes, elements);  
       }  
     }  
   }  
 }  

Here's how it can be used:
     // Will be used to house our element references. Should be  
     // global to the class.  
     var E = {};  
   
     // Define the markup for the control  
     var html = '';  
     html += '<div eid="PictureButton">';  
     html += ' <img eid="Picture" src="Picture.png" />';  
     html += '</div>';  
   
     // Create the elements and add all references we  
     // defined with "eid" to the object E.  
     CreateElementsFromHtml(html, E);  
   
     // Now we can easily reference our elements using E  
     // (E.PictureButton, E.Picture, etc.).  
   
     // Add control to the DOM  
     Parent.appendChild(E.PictureButton);  

The idea is so simple, yet it can really clean up your DHTML. Basically, we're abandoning element IDs all together. Instead, we just have JavaScript references to all the elements we care about (all grouped together with that "E" object). Since E is unique to the instance of the control object, you can define as many of these controls as you want, and you won't have any conflicts. We identify which elements we care about by simply adding the "eid" attribute in the HTML (which will get removed during creation). Further, you can add the elements to the document whenever you please.

I'd be really curious to know if anyone has any other solutions for creating HTML with JavaScript. For now, though, I'm going to experiment further with this approach.

4 comments:

  1. Good stuff. I usually prefer the DOM method to build elements but this is a neat alternative. For cross-browser compatibility you may want to check out the .childNodes property references. I don't think IE fully supports that until IE8. Before that I think you have to use the .children property.

    -David.

    ReplyDelete
  2. Hah... between me and you (and anyone else reading this), I only intend to support the latest versions of the main browsers (and for IE that means IE9) for my projects. It's probably not smart business, but it's a matter of principle for me. :-)

    ReplyDelete
  3. I like your style! Out of curiosity, have you used any jQuery? The downside to it is you have to include a script reference, but you gain a LOT of easy rich functionality and abstraction. Not sure if it could help in this case however...

    ReplyDelete
  4. I have, but I'm actually phasing it out. I feel like jQuery is fantastic for abstracting all the cross-browser compatibility garbage and adding or simulating new functionality that older browsers don't have. HOWEVER, since I'm all about using the latest and greatest browsers, I've found that I don't need it as much, and am probably better off without the overhead (jQuery does a lot!). But I don't know, I'm always experimenting. :-)

    Oh, and also, jQuery is all about finding nodes using that "query" syntax it has. My approach is different because I just store all my references in memory rather than querying for them in the DOM. I've been doing it this way for a while now and so far I still like it. :-)

    ReplyDelete