5

I have perfomance issues when inserting data into the dom.

Insertions + jQuery mobile enhancement done on pagecontainerbeforeshow() event, jQuery mobile version 1.4.2.

I have tried to see which would be the fastest approach by comparing three simplified versions of what I want to do:

jQuery approach :

for(var i=0;i<2000;++i){
    $('<div>').attr({'data-role':'collapsible','id':'asdf'+i+''}).html('<h2>asdf</h2>').appendTo("#manage_content");
    $('<ul>').attr({'data-role':'listview'}).html('<li>bit</li>').appendTo('#asdf'+i+'');
    }
$('#manage').trigger('create');

Pure js, creating all nodes :

var d=document.createDocumentFragment();
var title,listitem,list;
var coll=new Array();
for(var i=0;i<2000;++i){
coll[i]=document.createElement('div');
coll[i].setAttribute("data-role", "collapsible");
title = document.createElement('h2');
title.innerHTML='asdf';
coll[i].appendChild(title);
list=document.createElement('ul');
list.setAttribute("data-role","listview");
listitem = document.createElement('li');
listitem.innerHTML='bit';
list.appendChild(listitem);
coll[i].appendChild(list);
d.appendChild(coll[i]);
}
document.getElementById("manage_content").appendChild(d);
$('#manage').trigger('create');

jQuery with big string :

var html='';
for(var i=0;i<2000;++i){
    html+='<div data-role="collapsible"><h2>asdf<h2><ul data-role="listview"><li>bit</li></ul></div>';
}
$('#manage_content').append(html);
$('#manage').trigger('create');

To my surprise, the three ways of doing this give same result (around 7seconds execution time...)

Am I doing any of the ways wrong? Is there a better way?

I have seen lots of questions related to this topic but mostly outdated or stating that the pure javascript should be faster, which is not the case for me.

Without jQuery enhancement :

  • jQuery standard : ~400ms
  • Pure JS : ~40ms
  • jQuery big string : ~80ms
    So pure javascript with document fragments is the best even though it is horrible to read/write x_x

    With jQuery enhancement :

    Test here (credits to @Omar)

  • vzwick
    • 11,008
    • 5
    • 43
    • 63
    IazertyuiopI
    • 488
    • 2
    • 12
    • How many nodes are inserted in total? 7 seconds seems a lot. Have you tried profiling it? – marekful Jun 17 '14 at 11:32
    • About 8000 to my belief. However alot of reflow happening with all three examples I'd imagine. – Hless Jun 17 '14 at 11:33
    • Run the process while profiling to learn where most time is spent. – marekful Jun 17 '14 at 11:36
    • I tried profiling, the results are extremely hard to read to me... it seems the jquerymobile.js functions are a huge % of the execution time. I will try adding more timers to have better information. – IazertyuiopI Jun 17 '14 at 11:51
    • In most profilers (Firebug, Chrome dev. tools), you can order results by different columns displayed like number of calls, time spent, etc. That should give you an good overview of what's going on. – marekful Jun 17 '14 at 11:54
    • you forgot to mention when do you append those elements (on which page event) and jQM version you're using. – Omar Jun 17 '14 at 12:02
    • back in the day, I've used something similar to your first method, and replacing it with your 3rd method cut the running time by at least 80%. – Yossi Shasho Jun 17 '14 at 12:02
    • Updated the question with jqm version and event. @Marcell Fülöp : The jquery mobile function _createWidget() is the one consuming 95% of the execution time. – IazertyuiopI Jun 17 '14 at 12:08
    • 1
      As per this [test](http://jsperf.com/collapsible), 2nd and 3rd code of yours are the fastest. However, note that `.trigger("create")` is deprecated and replaced with `.ehanceWithin()`. Edit: `.enhanceWithin()` is faster than `.trigger("create")`. – Omar Jun 17 '14 at 12:27
    • @Omar thanks for the detailed test - .enhanceWithin is slower for me though (Chrome 35). Anyway the difference is not huge; I am looking for at least a 2fold perf increase. Could I achieve this by preventing the enhancement process except for the visual part? – IazertyuiopI Jun 17 '14 at 12:44
    • 1
      What you can do is to add classes statically and them bind events manually to collapse/expand collapsibles. If you want I can write an answer for you. – Omar Jun 17 '14 at 13:03
    • Yeah was thinking of that also, but seems not very appealing... Going to give it a shot, I'll come back to you if I have issues =P Also, is it possible to make that the generated content is still here when the user leaves the page then comes back? – IazertyuiopI Jun 17 '14 at 13:13
    • Genarally document.createDocumentFragment() gives better performance as appending nodes to it does not cause page reflow. So 2nd one is better even though its not very readable. If nodes are less than i use 1st approach as its cleaner and readable. – Sanjeev Jun 18 '14 at 15:00
    • possible duplicate of [jquery: fastest DOM insertion?](http://stackoverflow.com/questions/117665/jquery-fastest-dom-insertion) – Paul Sweatte Jun 20 '14 at 02:11
    • @PaulSweatte I am also considering the widget enhancement time, even if it is not very clear in the question title. Cf. comments – IazertyuiopI Jun 20 '14 at 04:54
    • @Omar I did what you suggested and it seems to be a feasible approach; however, it breaks some of my custom collapsible formatting. Maybe I should ask this as another question? I will answer this question with the comparison between automatic and 'manual' enhancement when it fully works. – IazertyuiopI Jun 20 '14 at 06:38
    • Your answer will benefit others. For your other issue, post a new question. – Omar Jun 20 '14 at 07:10

    1 Answers1

    1

    Here is what one can do to speed up dom insertion + enhancement, assuming one only uses jquery mobile for css formatting.

  • For the insertion part : as many comments suggest, method 3 seems to be the best option, because the small perfomance gain of method 2 is not enough to make up for its lack of readability, which would turn the enhancement part into hell...

  • For the enhancement part :

  • -- Add all the jquery mobile classes manually to html elements
    -- Do not add any data-% attribute.
    -- Do not call $('your_page').trigger('create') or $('.ui-content').enhanceWithin()
    -- Add desired widget events manually instead.

    Performance gain :

    With regular jquery mobile enhancement, the page creation took 2s in my worst case of use.
    With this method, it is around 50ms... so 40* perf increase...

    The code below is for a sample case (mine obviously) of two nested collapsiblesets and a listview as last child. If someone is interested I could add jsperf for x times this element insertion + enhancement.

    fiddle : http://jsfiddle.net/3pyRX/1/

    HTML:

    <div data-role='content'>
    
    <div class="ui-collapsible ui-collapsible-themed-content">
                            <h2 class="ui-collapsible-heading">
                            <a href="#" class="ui-collapsible-heading-toggle ui-btn ui-icon-plus ui-btn-icon-left ui-btn-b">Employee collapsible</a>
                            </h2>
                            <div class="ui-collapsible-content ui-body-inherit" style="display:none;">
                                <div class="ui-collapsible ui-collapsible-themed-content">
                                    <h2 class="ui-collapsible-heading">
                                    <a class="ui-collapsible-heading-toggle ui-btn ui-icon-plus ui-btn-icon-left ui-btn-b">
                                        <p class="inline">Child collapsible</p>
                                        <p class="inline coll_head_butt timecard ui-link ui-btn ui-btn-b ui-icon-check ui-btn-icon-notext ui-shadow ui-corner-all"></p>
                                    </a>
                                    </h2>
                                    <div class="ui-collapsible-content ui-body-inherit" style="display:none;">
                                        <ul class="ui-listview">
                                        <li class="projectxtask ui-li-has-count"><a class="ui-btn">li 1<span class="ui-li-count ui-body-inherit">42</span></a></li>
                                        <li><a class="ui-btn">li 2</a></li><li><a class="ui-btn">li 3</a></li>
                                        </ul>
                                    </div>
                                </div>
                            </div>
                        </div>
    </div>
    


    CSS:

    .projectxtask a{
    
        background-color:#33ccff !important;
        padding-right:2.6em !important;
        border-top:1px solid black !important;
        border-bottom:1px solid black !important;
    }
    .inline{display:inline !important;}
    
    
    .coll_head_butt{
        margin-left : 20px !important;
        border-style:none !important;
        box-shadow:none !important;
    }
    


    JS:

    $('.ui-collapsible-heading').on('click',function(event,ui){
    
        var coll_content = $(this).siblings();
        if(coll_content.is(":visible"))
            {$('a',this).removeClass( "ui-icon-minus" ).addClass( "ui-icon-plus" );
            coll_content.hide();}
        else
            {$('a',this).removeClass( "ui-icon-plus" ).addClass( "ui-icon-minus" );
            coll_content.show();}   
    });
    

    The code is a bit annoying to write but if you have similar performance issues as mine jquery mobile it is well worth it.

    I added the icon inside the collapsible header in this sample because with jquery mobile, you would use <.a data-icon=check data-iconpos=notext data-role=button> to get an icon button, which is not possible to do without jquery mobile enhancement because nested <.a> tags are not allowed in html and will be corrected by the browser.
    When manually adding classes, replacing the <.a> with a <.p> like I did here seemingly works.

    IazertyuiopI
    • 488
    • 2
    • 12
    • It's no problem to answer your own question. But if what you posted as an answer is only an addition to the question, then you should edit the question and remove the answer. – Yoshi Jun 17 '14 at 12:30