10

I have a JSON array coming in from the server with an array of 200 objects each containing another 10 objects that I want to display in a table format. At first I was creating a <tr> for each iteration and using jQuery to append a <td> built from the array values to the <tr>. This was taking around 30 seconds in Chrome and 19 seconds in IE 8. This was taking too long so I tried switching to the Array.join() method, where I would store each string that would make up the entire table in an array, and at the end do $('#myTable').append(textToAppend). This actually performed worse than my first version by around 5 seconds.

I would like to get this to around 10 seconds. Do I have any chance at that? If not, I'm just gonna add one row at a time, but I'd rather not do that.

for(allIndex = 0; allIndex < entries.alumnus.length; allIndex++){

  var entry = '<tr id="entry' + allIndex + '" class="entry"></tr>';
  $('#content_table').append(entry);

  $('#entry' + allIndex).append(($.trim(entries.alumnus[allIndex].title) != '' ?
        '<td id="title' + allIndex + '" class="cell"><span class="content">' +
         entries.alumnus[allIndex].title + '</span></td>' : '<td width="5%">' + 
         filler + '</td>'));    
  .
  .
  .
  .//REST OF ELEMENTS
  .
  .
  .
}   

UPDATE: I must have messed something up yesterday, because I went back to trying appending elements out of the DOM and then attaching them later, without using jQuery and I've gotten my time down to 85 ms in Chrome and 450 ms in IE7!!! You guys are awesome!!! I gave user1 the answer because that one was more comprehensive, and using fragments didn't really change my times much in Chrome and added around 20ms in IE7. But I still appreciate @Emre Erkan's answer, and will utilize more often :)

4
  • Do you mean you have an JSON array with 200 elements in it, each of which is an array of 10 elements? Commented Jan 4, 2012 at 22:15
  • Yeah, mixed my terminology there by mistake :) Commented Jan 4, 2012 at 22:17
  • 1
    Can you post some code? Are you just using strings, or are you building elements outside the DOM, then adding it in? Commented Jan 4, 2012 at 22:25
  • 200x10 table taking 15 seconds to generate in Chrome ? nice.. Commented Jan 5, 2012 at 0:42

5 Answers 5

13

The fastest will look something like this:

var oldTable = document.getElementById('example'),
    newTable = oldTable.cloneNode(true);
for(var i = 0; i < json_example.length; i++){
    var tr = document.createElement('tr');
    for(var j = 0; j < json_example[i].length; j++){
        var td = document.createElement('td');
        td.appendChild(document.createTextNode(json_example[i][j]));
        tr.appendChild(td);
    }
    newTable.appendChild(tr);
}

oldTable.parentNode.replaceChild(newTable, oldTable);

And should run in milliseconds. Here is an example: http://jsfiddle.net/Paulpro/YhQEC/ It creates 200 table rows each containing 10 td's.

You want to append using elements, not strings, but you don't want to append anything to the DOM until you're done creating the entire structure (to avoid reflow in your loop). So you can clone the original table and append to the clone, then insert the clone after your loop completes.

You will also gain a fair bit of speed by avoiding jQuery and interacting with the DOM directly.

Your code may look like:

var oldTable = document.getElementById('content_table'),
    newTable = oldTable.cloneNode(true),
    tr, td;
for(var i = 0; i < entries.alumnus.length.length; i++){
    tr = document.createElement('tr');
    tr.id = 'entry' + i;
    tr.className = 'entry';

    if(entries.alumnus[i].title){
        td = document.createElement('td');
        td.id = 'title' + i;
        td.className = 'cell';
        var span = document.createElement('span');
        span.className = 'content';
        span.appendChild(document.createTextNode(entries.alumnus[i].title);
        td.appendChild(span);
        tr.appendChild(td);
        tr.appendChild(createFiller(filler));
    }

    // REST OF ELEMENTS

    newTable.appendChild(tr);

}

oldTable.parentNode.replaceChild(newTable, oldTable);

function createFiller(filler){
    var td = document.createElement('td');
    td.style.width = '5%';
    td.appendChild(document.createTextNode(filler);
    return td;
}
Sign up to request clarification or add additional context in comments.

7 Comments

Edited to include a timer: jsfiddle.net/Paulpro/tPrcK Takes about 15 - 25 milliseconds in Chrome version 16 on my laptop.
Thanks for fixing those, Emre!
It would be better if you use DocumentFragment. It gets even faster. DocumentFragments are lightweight document objects which has no parent and not attached to DOM. You can manipulate it as you wish and when you're done with it you can just append it do DOM. This way you don't have to clone table element.
@EmreErkan Thanks! I did not know about DocumentFragment. I learn more everyday :). I will leave my answer as is for now, and hopefully the OP will see both, as well as these comments. PS. Why do you use fragment.cloneNode(true) instead of just passing in fragment when appending the fragment to the table?
I updated your fiddle but it doesn't change that much. I guess it's because you're also using unattached DOM element.
|
9

I suggest you to use DocumentFragment and use native javascript for this kind of massive DOM manipulation. I prepared an example, you can check it here.

var fragment = document.createDocumentFragment(),
    tr, td, i, il, key;
for(i=0,il=data.length;i<il;i++) {
    tr = document.createElement('tr');
    for(key in data[i]) {
        td = document.createElement('td');
        td.appendChild( document.createTextNode( data[i][key] ) );
        tr.appendChild( td );
    }
    fragment.appendChild( tr );
}
$('#mytable tbody').append( fragment );

I think it's the fastest way to do such a job.

3 Comments

Are there any sort of cross-browser issues to contend with using straight JavaScript over jQuery?
None that I know. But if you use it right, native JavaScript will work faster then jQuery, don't you think?
Oh, I am not arguing about JavaScript being faster than jQuery. I've just run across several issues between Chrome, Firefox, and especially IE8 that for the most part makes me prefer to stay with jQuery since that's all tested to remove that minutia that can be a pain. But with how simple this pure JavaScript is (not trying to do anything ludicrously fancy with it), I'd rather do this all day long.
4

Most important thing is to create whole table content out of dom and then insert in to table. This one in chrome ends after about 3 to 5ms:

function createTableFromData(data) {
    var tableHtml = '';
    var currentRowHtml;
    for (var i = 0, length = data.length; i < length; i++) {
        currentRowHtml = '<tr><td>' + data[i].join('</td><td>') + '</td></tr>';
        tableHtml += currentRowHtml;        
    }  
    return tableHtml;    
}

var textToAppend= createTableFromData(yourData);
$('#myTable').append(textToAppend);

6 Comments

Up-voting because this is, by far, going to be the best performing solution. Since the OP isn't doing anything with the individual rows (e.g. adding event listeners), innerHTML will always be faster than DOM per node. Though your currentRowHtml var is just going to slow things down slightly.
I actually am adding listeners. I just didn't use the elements that I do that with in my example above.
@Eliezer use event delegation.
I'm currently using jQuery to set up handlers for my events
@Eliezer - c69 is right. If you have such big table it would be better from performance point of view to use event delegaion e.g. by delegate() from jQuery.
|
1

If you have a validating string of JSON from the server and the structure is reliably an array of arrays of strings, the below will allow you to avoid a JSON parse, and instead replace the HTML generation with a constant series of regular expression operations which tend to be implemented in native code that uses native buffers. This should avoid one parse altogether and replace any buffer copies that cost O(n**2) with k, O(n) buffer copies for a constant k.

var jsonContent
    = ' [ [ "foo", "bar", "[baz\\"boo\\n]" ], ["1","2" , "3"] ] ';

var repls = {  // Equivalent inside a JSON string.
  ',': "\\u002b",
  '[': "\\u005b",
  ']': "\\u005d"
};
var inStr = false;  // True if the char matched below is in a string.
// Make sure that all '[', ']', and ',' chars in JSON content are
// actual JSON punctuation by re-encoding those that appear in strings.
jsonContent = jsonContent.replace(/[\",\[\]]|\\./g, function (m) {
  if (m.length === 1) {
    if (m === '"') {
      inStr = !inStr;
    } else if (inStr) {
      return repls[m];
    }
  }
  return m;
});

// Prevent XSS.
jsonContent = jsonContent.replace(/&/g, "&amp;")
   .replace(/</g, "&lt;");
// Assumes that the JSON generator does not encode '<' as '\u003c'.

// Remove all string delimiters and space outside of strings.
var html = jsonContent
    .replace(/\"\s*([,\]])\s*\"?|\s*([\[,])\s*\"/g, "$1$2");
// Introduce the table header and footer.
html = html.replace(/^\s*\[/g, "<table>")
html = html.replace(/]\s*$/g, "</table>")
// Introduce row boundaries.
html = html.replace(/\],?/g, "</tr>")
html = html.replace(/\[/g, "<tr><td>")
// Introduce cell boundaries.
html = html.replace(/,/g, "<td>")

// Decode escape sequences.
var jsEscs = {
  '\\n': '\n',
  '\\f': '\f',
  '\\r': '\r',
  '\\t': '\t',
  '\\v': '\x0c',
  '\\b': '\b'
};
html = html.replace(/\\(?:[^u]|u[0-9A-Fa-f]{4})/g, function (m) {
      if (m.length == 2) {
        // Second branch handles '\\"' -> '"'
        return jsEscs[m] || m.substring(1);
      }
      return String.fromCharCode(parseInt(m.substring(2), 16));
    });

// Copy and paste with the below to see the literal content and the table.
var pre = document.createElement('pre');
pre.appendChild(document.createTextNode(html));
document.body.appendChild(pre);
var div = document.createElement('div');
div.innerHTML = html;
document.body.appendChild(div);

Comments

0

This is definitely do-able. I don't think string concatenation is the way to go. In general it seems to be faster to create elements and manipulate them when they aren't attached to the main DOM; therefore create the entire table first then add it in. In plain javascript I think the code below is basically what you want to achieve..

    //mock up the data (this will come from you AJAX call)..
    var data = [];
    for(var i = 0; i < 200; i++){
        var rowData = [];
        for(var j = 0; j < 10; j++){
            rowData.push("This is a string value");
        }
        data.push(rowData);
    }

    //create table..
    var table = document.createElement("table");
    for(var i = 0; i < data.length; i++){
        var rowData = data[i];
        var row = document.createElement("tr");
        for(var j = 0; j < rowData.length; j++){
            var cell = document.createElement("td");
            cell.innerHTML = rowData[j];
            row.appendChild(cell);
        }
        table.appendChild(row);
    }
    //finally append the whole thing..
    document.body.appendChild(table);

When I stick this into the console in Safari, it runs in < 1 second so you should be ok.

5 Comments

@EmreErkan -- there is nothing wrong with innerHTML, though there is with the way it's done in this example :)
As an alternative - cell.appendChild(document.createTextNode(rowData[j]))
@cwolves just wondering - what's you problem with the use of innerHTML in the above code? I wrote it pretty quickly so I'm not bothered but I don't see the problem - it's a new table so I'm not losing references to old elements and since the code is going to be used to show table data from an Ajax call there shouldn't be a security risk.
@EmreErkan -- innerHTML is not evil, it's mis-used. For generating HTML it's not only perfectly valid but is significantly faster in many older browsers than DOM methods. jQuery and many other libraries use it internally.
@MarkRhodes -- Just the fact that you're using createElement everywhere else, it's messy to use innerHtml in one spot, you should be using createTextNode instead. It's more a stylistic thing than anything else, and I tend to only use innerHTML when building large DOM nodes out of strings.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.