Javascript Effectively Build Table From JSON and Add It to DOM

17,330

Solution 1

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;
}

Solution 2

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.

Solution 3

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);

Solution 4

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);
Share:
17,330
Eliezer
Author by

Eliezer

Software engineer pursuing a MS in computer science. Currently focused on Android, but I've been known to get involved with .NET, Python (App Engine), and C++.

Updated on June 21, 2022

Comments

  • Eliezer
    Eliezer almost 2 years

    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 :)