4

I have an object like the one below:

var obj = {
   a : {
      x : 1,
      y : 2,
      z : 3
   },
   b : {
      x : 1,
      y : 2,
      z : 3
   }
}

With it, I will like to generate the following table. Format is showed bellow

http://jsfiddle.net/gD87t/

I am trying to get the elements from object and and trying to append but getting confused with the rowSpan value

var tr = document.createElement('tr');
for(var i in obj){
   var td = document.createElement('td');
   td.rowSpan = ? // Here I am getting confused.
}

Can a template engine solve my problem? What is the best way to do this?

6
  • 2
    FYI, this is not JSON, this is simply an object literal. JSON is a data-exchange format, like XML or CSV. Commented Jun 19, 2013 at 10:09
  • do you have to use table. Just use inline-block divs and set proper css. Commented Jun 19, 2013 at 10:27
  • @OnurTOPAL My current focus is on getting the thing done, then I will focus on making it without tables Commented Jun 19, 2013 at 10:29
  • @OnurTOPAL why showing table data without tables? you missing whole point of not using tables in design. Commented Jun 22, 2013 at 13:05
  • 1
    @OnurTOPAL SEO has nothing to do with that. That is table data, it should be rendered using table html element. when you use html elements for design purposes, that is where you should avoid using tables, mainly because of the way how tables are rendered. Commented Jun 22, 2013 at 19:41

7 Answers 7

4
+100

Here's one way of doing it with a recursive function in pure js:

function addObjectToTable(table, obj, tr) {
  var rows = 0;
  for (key in obj) {
    if (tr == null) {
      tr = document.createElement('tr');
      table.appendChild(tr);
    }  

    var td = document.createElement('td');
    td.textContent = key;
    tr.appendChild(td);

    var value = obj[key];
    if (typeof value != 'object') {
      var td = document.createElement('td');
      td.textContent = value;
      tr.appendChild(td);
      rows += 1;
    }
    else {
      var subrows = addObjectToTable(table, value, tr);
      td.setAttribute('rowspan',subrows);
      rows += subrows;
    }

    tr = null;
  }
  return rows;
}

Which would be called like this:

var table = document.createElement('table');
addObjectToTable(table,obj);
document.body.appendChild(table);

Note that when first called, the tr parameter is null, since we always have to create a new row at the top level. When the function is called recursively though, the tr parameter is passed in from the upper level since lower levels will intially be adding to the the row of their parent object.

The function returns the number of rows added, so when it is called recusively the caller will know what to set the rowspan value to.

Fiddle link

Sign up to request clarification or add additional context in comments.

Comments

3

I couldn't find an answer which handled circulars, and I also decided to do this without any unnecessary DOM writes. Also, I didn't follow the exact mark-up the OP requested because I felt nesting tables was more convenient for a recursive operation such as this- and serves close to the same visual purpose.

So, here's the function I've created:

function dataToTable (data) {
    var storage = [];
    return (function buildTable (data) {
        var table = '<table><tbody>';
        var name, value;
        // Add the object/array to storage for cirular detection.
        storage.push(data);
        for (name in data) {
            value = data[name];
            table += '<tr><td>' + name + '</td><td>';
            // If the value is an object we've put in storage (circular)
            if (storage.indexOf(value) !== -1) {
                table += '<em>Circular</em>';
            } else if (typeof value === 'object') {
                table += buildTable(value);
            } else {
                table += value;
            }
            table += '</td></tr>';
        }
        return table + '</tbody></table>';
    }(data));
}

Here is the object I used to test:

var obj = {
   a : {
      x : 1,
      y : 2,
      z : 3
   },
   b : {
      x : 1,
      y : 2,
       z : {
           test1: 0,
           test2: {
               test3: 1,
               test4: ['a','b','c']
           }
       }
   }
};
obj.c = obj;
obj.b.z.test2.test4.push(obj.a);

The function will turn this object into an HTML table. What you do with the table is up to you. On my fiddle, I used the DOM to add the table to a DIV (document.getElementById).

http://jsfiddle.net/5RhXF/1/

I hope you'll find my implementation clear.

UPDATE::

I decided to test this on the jQuery library, and it worked! Except, the functions were printing as their toString value with no good format for text.. Which makes sense, but not very helpful. So, I'm thinking this is a nice and easy way to look through APIs for frameworks/libraries and what-not. Therefore, I added prettify for syntax-highlighting of functions, and also added a type-check for functions in the table generator, and a quick class to get rid of borders around the prettify box (as there's already a border on the table cell). If anyone is interested in the version designed for source-reading/debugging, here's the fiddle:

http://jsfiddle.net/5RhXF/7/

Comments

2

UPDATED: if you don't need empty cells solution could be (check fiddle http://jsfiddle.net/gD87t/11/)

Example object :

var obj = {
   a : {
      x : 1,
      y : 2,
       z : { 
           c : 4,
           d : 5
       }
   },
   b : {
      x : 1,
      y : 2,
      z : 3
   }
}

And routine to build table:

function merge(rows , inner) {
    inner.reduce(function (i, p) {
      rows.push(i)  
    })
}

function getRows(o) {
    var rows = []
    if (typeof o == 'object') {
       for (var k in o) {                        
           var innerRows = getRows(o[k])
           , firstCell = $('<td />')
                     .text(k)
                     .attr('rowspan',innerRows.length)           
           innerRows[0].prepend(firstCell)            
           rows = rows.concat(innerRows)        
    }
    } else {
       var tr = $('<tr />')
       , td = $('<td />').text(o)
       tr.append(td)
       rows.push(tr)
    }
    return rows
}

function buildTable(o, $t) {    
    var rows = getRows(o)    
    $t.append(rows)    
}

buildTable(obj, $('#table2'))

4 Comments

Thanks for the answer. But it fails if the hierarchy is more than two levels like here jsfiddle.net/gD87t/7
Please have a look at this jsfiddle.net/gD87t/8. I have coded example part of how I am expecting from the new data
@Exception see updated answer, it is not entirely the same, but could meet your needs.
@Exception just explains what is going on in recursive function calls.
1

The value for the rowspan is the number of properties in the inner object. You can use the Object.keys function to get a list of the keys on the object, then use its length property to determine how many properties there are:

for(var i in obj){
   var td = document.createElement('td');
   td.rowSpan = Object.keys(obj[i]).length;
}

2 Comments

What if the object has nested object..?
@Exception Then you handle that in your code in whatever manner you want it to be handled? Without a more explicit example of what that would look like, and how it would modify the desired table output, I'm not sure how you expect me to answer that.
1
var table = document.createElement('table');
var i, j;
var row, cell;
for(i in obj) {
    if(obj.hasOwnProperty(i)) {
        var row = document.createElement('tr');
        var cell = document.createElement('td');
        cell.rowSpan = Object.keys(obj[i]).length;
        cell.innerText = i;
        row.appendChild(cell);
        for(j in obj[i]) {
            if(obj[i].hasOwnProperty(j)) {
                cell = document.createElement('td');
                cell.innerText = j;
                row.appendChild(cell);
                cell = document.createElement('td');
                cell.innerText = obj[i][j];
                row.appendChild(cell);
                table.appendChild(row);
                row = document.createElement('tr');
            }
        }
    }
}
document.body.appendChild(table);

Of course, it would look less verbose in jQuery. But it looked like you wanted to do it in plain DOM.

See it woking

1 Comment

Thanks for the answer but it will fail if the hierarchy is more than two levels here jsfiddle.net/9dyFL/1
0

well it was really tricky but I think it is done. By the way I still suggest table-less solution. you can check the working code here

var obj = {
   a : {
      x : 1,
      y : 2,
       z : {c:1, d:3}
   },
   b : {
      x : 1,
      y : 2,
      z : 3
   }
}
var table = document.createElement('table');
function createTable (o, parentCells) {
    for (var key in o) {

        var row = document.createElement('tr');

        var cell = document.createElement('td');
        cell.rowSpan = 1;
        cell.innerText = key;
        if (typeof o[key] !== "object") {
           var cellv = document.createElement('td');
           cellv.innerText = o[key];
           row.appendChild(cell);        
           row.appendChild(cellv);
           table.appendChild(row);
        }
        else {

           for (var i = 0; i < parentCells.length; i++) {
              parentCells[i].rowSpan += Object.keys(o[key]).length;
           }

           cell.rowSpan += Object.keys(o[key]).length;

            var newParentCells = new Array(parentCells);
            newParentCells.push(cell);
            row.appendChild(cell);
            table.appendChild(row);
           createTable(o[key], newParentCells);
        }


    }
}
createTable(obj, []);
document.body.appendChild(table);

Comments

0

If you wish to use document.createElement as you do in your question, the easiest way is probably like this:

function generateTable(o) {
    var table, tr, td, i, j, l, keys;

    table = document.createElement('table')

    for(i in o){
        tr = document.createElement('tr');
        table.appendChild(tr);
        td = document.createElement('td');
        keys = Object.keys(o[i]);
        td.rowSpan = keys.length;
        td.textContent = i;
        tr.appendChild(td);
        x=0;

        for(j=0;j<keys.length;j++) {
            if(j) {
                tr = document.createElement('tr');
                table.appendChild(tr);            
            }
            td = document.createElement('td');
            td.textContent = keys[j];
            tr.appendChild(td);
            td = document.createElement('td');
            td.textContent =o[i][keys[j]];
            tr.appendChild(td);
        }
    }
    return table;
}

CAVEAT: Object.keys isn't available in older browsers so you'll need a polyfill like this:

(taken from MOZILLA DEVELOPER NETWORK)

if (!Object.keys) {
  Object.keys = (function () {
    var hasOwnProperty = Object.prototype.hasOwnProperty,
        hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'),
        dontEnums = [
          'toString',
          'toLocaleString',
          'valueOf',
          'hasOwnProperty',
          'isPrototypeOf',
          'propertyIsEnumerable',
          'constructor'
        ],
        dontEnumsLength = dontEnums.length;

    return function (obj) {
      if (typeof obj !== 'object' && typeof obj !== 'function' || obj === null) throw new TypeError('Object.keys called on non-object');

      var result = [];

      for (var prop in obj) {
        if (hasOwnProperty.call(obj, prop)) result.push(prop);
      }

      if (hasDontEnumBug) {
        for (var i=0; i < dontEnumsLength; i++) {
          if (hasOwnProperty.call(obj, dontEnums[i])) result.push(dontEnums[i]);
        }
      }
      return result;
    };
  })();
}

Alternatively there is a much simpler polyfill that will cover most cases quite well here:

(Taken from Token Posts)

if (!Object.keys) Object.keys = function(o) {
  if (o !== Object(o))
    throw new TypeError('Object.keys called on a non-object');
  var k=[],p;
  for (p in o) if (Object.prototype.hasOwnProperty.call(o,p)) k.push(p);
  return k;
}

You can see a fiddle of it in action here: http://jsfiddle.net/uyJv2/

1 Comment

Thanks for the answer. But the code you have provided works only on two levels of data. If two levels of data I can do that. But my requirement is n times, then it will fail. :(

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.