Friday 3 February 2012

InnerHTML : The biggest source of Ugly & Unreadable JS

If tomorrow browsers stopped supporting the innerHTML property , most of the worlds Javascript code would stop working except possible the "hello world" programs :P, But this can't hide the fact the worst JS written is mostly because of the overuse of innerHTML .
Problems :
 -> HTML inside JS file , extremely bad for maintainability
 -> Can overwrite existing data by mistake
 -> Can introduce invalid html into the page which would break the DOM tree practically ensuring that most of the JS in the page has bugs or does not work
 Example
    Given we need to add a person to list from a combobox similar to how you add people on facebook message or in email sender in yahoomail


    It could be acheived in many ways :
    a) When an option is selected in the combobox , we could get the user details construct the div element inside the javascript and append it to the box
    Something similar to this :
        element.onselect=function(){
// getData() gets a JSON object containing data of selected user 

            var data =getData(element.selectedIndex);
            var innerHtml="<div class='containBox'><span class='user'>"+ data.Name+"</span></div>";
            this.innerHTML=this.innerHTML+innerHtml;
        }
    Disadvantages :
    -To change DOM structure we need to access the javascript function
    -String gets bigger and harder to maintain as the widget functionality increases    
    -Population of data is cumbersome with contant appending
    -Easy to introduce bugs since html cannot be validated inside JS strings
   
    Alternative Approach  1 :
    b) In this approach we first create a template HTML copy the DOM element and then append it to the node, then we populate the data as required .
    Example :
        Append the template container as such in an HTML file (include the HTML file wherever required using server side script or dojo xhrGet )
        <div id="containBoxTemplate" class="templateContainer" style="display:none;">
            <div class='containBox'>
                <span class='user'></span>
            </div>
        </div>
       
        The above code can be now rewritten as :
        element.onselect=function(){
// getData() gets a JSON object containing data of selected user 

            var data =getData(element.selectedIndex);
            var innerHtml=dojo.byId("containBoxTemplate").innerHTML;           
            this.innerHTML=this.innerHTML+innerHtml;
            this.lastChild.innerHTML=data.name;       
        }
    Advantages :    
    -HTML template is easier to maintain
    -Probability of bugs are significantly reduced
   
    Disadvantages :
    -Data population is still cumbersome
   
    Alternative Approach 2 :
    c) In this approach we will programattically create all the elements and then populate data as required then append it
        element.onselect=function(){
// getData() gets a JSON object containing data of selected user 

            var data =getData(element.selectedIndex);
            var div = document.createElement("div");
            var span= document.createElement("span");
            span.className="user";
            span.innerHTML=data.name;
            div.appendChild(span);
            this.appendChild(div);           
        }
    Advantages :
    -Chance of accidental deletion of previous content is removed since we are not playing around with innerHTML of parent element
    -No Strings used , clear programmatic structure
    -Efficient
   
    Disadvantages :
    -Complex code
    -Hard to change the HTML template (DOM structure)
   
    While approach c) seems programatically correct , it makes for some really complex and unmaintainable code. It is a pure JS solution where it is not required.
   
    Alternative Approach 3 :
    d) In this approach we will clone the template ,populate the data and then append it
    Now Given the same HTML template ,the code can be rewritten as follows  :
    element.onselect=function(){
// getData() gets a JSON object containing data of selected user 

            var data =getData(element.selectedIndex);
            var cloneNode=dojo.clone(dojo.byId("containBoxTemplate"));
           
            //Query the cloned node only and populate data
            dojo.query(cloneNode).query("span.user")[0].innerHTML=data.name;
           
            this.appendChild(cloneNode);
        }
    Advantages :
    -Code is safe since it does not append to the DOM tree until it is fully populated 
    -Cannot affect existing data since we do not use innerHTML
    -Depends on template and does not modify it ( otherwise that would introduce bugs :) )
   
    Disadvantages :
    Can't think of any - well that would be too patronizing, i think this should be it
    -Slightly complex code 

End of the day none of the techniques are defacto standards to create new DOM elements (or widgets as we like to call them),but a combination of any of the alternative 3 techniques should provide a good combination of advantages and simple code.I would personally use option d) ( no wonder i couldn't think of disadvantages) but any suggestions and better practice would be duly appreciated.Please do share what you practice and would prefer in the above mentioned scenario.
   

1 comment: