3

I found two ways to create a table in JS:

  1. Using insertRow and insertCell:

    var table = document.getElementById ("table");
    var row = table.insertRow (1);
    var cell = row.insertCell (0);
    cell.innerHTML = "New row";
    
  2. Using plain DOM operations:

    var tbl     = document.createElement("table");
    var tblBody = document.createElement("tbody");
    
    // creating all cells
    for (var j = 0; j < 2; j++) {
      // creates a table row
      var row = document.createElement("tr");
    
      for (var i = 0; i < 2; i++) {
        // Create a <td> element and a text node, make the text
        // node the contents of the <td>, and put the <td> at
        // the end of the table row
        var cell = document.createElement("td");
        var cellText = document.createTextNode("cell is row "+j+", column "+i);
        cell.appendChild(cellText);
        row.appendChild(cell);
      }
    
      // add the row to the end of the table body
      tblBody.appendChild(row);
    }
    

The first as I see is specially created for the table, but the second is mentioned on MDN, so I'm a bit confused what methods to use. What are the advantages and disadvantages of each? When is one used over another?

icktoofay
  • 126,289
  • 21
  • 250
  • 231
user1692333
  • 2,461
  • 5
  • 32
  • 64

3 Answers3

4

If you can avoid using the elements, and just put a string into an innerHTML you'll get the best performance. Here are some different ways to create a table.

Functional Code demo

We can create some functions to generate our HTML. This code will be very fast (but could be faster). We'll assume this data for all of these examples.

var heading = ['Name', 'Points'];

var data = [
  ["Joe", 50],
  ["Jack", 80],
  ["Doug <b>'the hammer'</b> Jones", 76]
];

We can then generate our table like so,

document.body.innerHTML = table(heading, data);
function wrapTag(tag, html) {
  return "<" + tag + ">" + html + "</" + tag + ">";
}


function table(head, body) {
    return wrapTag("table", thead(head)
    + tbody(body));
}

function thead(head) {
  var _th = head.map(wrapTag.bind(null, "th"));
  var _tr = wrapTag("tr", _th.join("\n"));
  return wrapTag("thead", _tr);
}

function tbody(body) {
  return body.map(tr).join("\n");
}

function tr(row) {
  var _td = row.map(wrapTag.bind(null, "td"));
  return wrapTag("tr", _td.join("\n"));
}

KnockoutJS demo

In Knockout we can give it an object, and have it map directly to our HTML. With the heading and data variables defined above, we map this like so.

ko.applyBindings({heading: heading, data: data});

Our HTML makes use of the foreach binding, which iterates over an array. $data refers to the current array item.

<table>
  <thead>
    <tr data-bind="foreach: heading">
      <th data-bind="text: $data"></th>
    </tr>
  </thead>
  <tbody data-bind="foreach: data">
    <tr data-bind="foreach: $data">
      <td data-bind="html: $data"></td>
    </tr>
  </tbody>
</table>

AngularJS demo

Using the same data from above, we can create an AngularJS controller.

function MyTableCtrl($scope) {
  $scope.heading = heading;
  $scope.data = data;
}

Our HTML is similar to KnockoutJS. One difference is the looping syntax, which lets us name our elements, e.g., row in data, instead of referring to elements as $data.

  <table ng-controller="MyTableCtrl">
    <thead>
      <tr>
        <th ng-repeat="head in heading">{{head}}</th>
      </tr>
    </thead>
    <tbody>
      <tr ng-repeat="row in data">
        <td ng-repeat="content in row" ng-bind-html-unsafe="content"></td>
      </tr>
    </tbody>
  </table>

documentFragment demo

credit to @Ian

This is faster than regular DOM manipulation, and provides a nicer syntax than combining strings.

newTable = createTable(heading, data);
document.body.appendChild(newTable);
function createTable(h, c) {
    var frag, table, head, body;

    frag = document.createDocumentFragment();
    table = document.createElement("table");
    head = createHeader(h);
    body = createContent(c);

    table.appendChild(head);
    table.appendChild(body);

    frag.appendChild(table);

    return frag.cloneNode(true);
}

function createHeader(data) {
    var thead, rowEl, col, colEl, text, i, j;

    thead = document.createElement("thead")
    rowEl = document.createElement("tr");

    for (i = 0, j = data.length; i < j; i++) {
        col = data[i];
        colEl = document.createElement("td");
        text = document.createTextNode(col);
        colEl.appendChild(text);
        rowEl.appendChild(colEl);
    }

    thead.appendChild(rowEl);

    return thead;
}

function createContent(data) {
    var content, row, rowEl, col, colEl, text, i, j, k, l;

    content = document.createElement("tbody");

    for (i = 0, j = data.length; i < j; i++) {
        row = data[i];
        rowEl = document.createElement("tr");
        for (k = 0, l = row.length; k < l; k++) {
            col = row[k];
            colEl = document.createElement("td");
            text = document.createTextNode(col);
            colEl.appendChild(text);
            rowEl.appendChild(colEl);
        }
        content.appendChild(rowEl);
    }

    return content;
}
Brigand
  • 84,529
  • 20
  • 165
  • 173
  • @vol7ron, nice catch, it's fixed. I also made the tables less ugly in the demos (yay bootstrap!) – Brigand Aug 09 '13 at 04:42
  • If you have actual proof for `just put a string into an innerHTML you'll get the best performance.`, I'd love to see it. Any jsPerf (which I understand doesn't always *best* exemplify the situation) I've seen in the past begs to differ (with some inconsistencies across browsers). Technically, using a `documentFragment` is most likely fastest, with the use of `document.createElement` and `.appendChild()` not far behind, but the gap between these methods is closing anyways. And I don't see how the use of slower methods like `map` is going to make it "fast" – Ian Aug 09 '13 at 06:53
  • @Ian, quirksmode did a very [extensive benchmark](http://www.quirksmode.org/dom/innerhtml.html) of DOM vs innerHTML. The gap has been closing in recent years, but it's still not quite there. I wrote my example to be simple, but of course, functions add overhead. DOM manipulation is functions plus additional DOM overhead. The ideal situation for performance is to precompile a JavaScript template for each element you'd like to render. – Brigand Aug 09 '13 at 06:58
  • You're joking, right? You're actually referencing that link? That uses IE 5-8 and FF 2-3? That's hardly something to reference. Like I said, jsPerfs tend to show a good consensus, which is that `innerHTML` is slowest. There's a reason why jQuery uses `documentFragment`s – Ian Aug 09 '13 at 07:00
  • I would love if you would add a documentFragment example to this answer, or create one of your own. – Brigand Aug 09 '13 at 07:03
  • Hmm it wouldn't be much different from the OP's `appendChild` example, except it would be appended to the fragment instead of the document. I'll see what I can come up with and let you do what you want with it :) – Ian Aug 09 '13 at 07:06
  • Here's more or less an example: http://jsfiddle.net/EsRXf/ . Obviously it would have to be changed to use `.innerHTML` to set the ``'s contents if HTML is expected, but I assumed text – Ian Aug 09 '13 at 07:28
1

taken from this post: insertRow vs. appendChild

insertRow would be the much better. It is supported by grade A browsers and it's less verbose and a cleaner API.

insertRow might be argued as more reliable since it's DOM1.

The appendChild method is consistently faster (albeit marginally) across all tested browsers (IE6/7, FF3, Chrome2, Opera9) when operating outside of the DOM, but when trying to modify tables within the document (a more common endeavour) it's significantly slower.

In other words: definitely use insertRow.

My personal opinion is: 1 method more clear and uses native methods

Community
  • 1
  • 1
Dart
  • 787
  • 1
  • 7
  • 16
0

I actually use Mustache in day-to-day development, so you can do something like this:

Bare HTML table:

<table id="myTable"></table>

Then you can store a template in a non-JS <script> tag. You can assign an id or whatever to retrieve the element.

<script type="text/template" id="rowTemplate">
  {{#rows}}
  <tr>
    {{#items}}
    <td>
      {{.}}
    </td>
    {{/items}}
  </tr>
  {{/items}}
</script>

Retrieving the template is something like:

var template = document.getElementById('rowTemplate').innerHTML;

Then you need a data source. Mustache needs an object to do this:

var data = {
  rows : [
    {
      items : ['hello1','world1']
    },{
      items : ['hello2','world2']
    }
  ]
}

Rendering:

var usableHTML = Mustache.render(template,data);

You can then append usableHTML to your table

document.getElementById('myTable').innerHTML = usableHTML;
Joseph
  • 117,725
  • 30
  • 181
  • 234