Simeon
Simeon

Reputation: 836

Strange FOR loop behaviour in Javascript

I'm trying to generate a table with 3 rows and 3 cells e.g.

<table>
<tr>
   <td> 00 </td>    <td> 01 </td>    <td> 02 </td>
</tr>
<tr>
   <td> 10 </td>    <td> 11 </td>    <td> 12 </td>
</tr>
<tr>
   <td> 20 </td>    <td> 21 </td>    <td> 22 </td>
</tr>
</table>

The Javascript I'm using to do this is:

tab = document.getElementById('tab');
for(i=0; i<3; i++)
{ 
 tab.innerHTML += '<tr>';

  for(j=0; j<3; j++) 
  {
   tab.innerHTML += '<td id="'+i+''+j+'">'+i+''+j+'</td>'; 
  }

 tab.innerHTML += '</tr>';
}

The HTML being:

<table id="tab">
</table>

However, when I run the code all the table elements end up in the first column, so instead of a 3x3 table I get a 1x9 table.

Anyone know what I'm doing wrong?

Upvotes: 5

Views: 231

Answers (6)

J. Holmes
J. Holmes

Reputation: 18546

In the same vein as MamaWalter's answer, here is an alternative functional approach that uses DOM nodes rather than string concatenation:

var tab = document.getElementById('tab');

var bind = Function.prototype.bind,
    $tr = bind.call(Document.prototype.createElement, document, "tr"),
    $td = bind.call(Document.prototype.createElement, document, "td"),
    $text = bind.call(Document.prototype.createTextNode, document),
    appender = function(parent, child) {
      parent.appendChild(child);
      return parent;
    },
    wrapWith = function(fn, child) { return appender(fn(), child); },
    wrapWithTd = wrapWith.bind(undefined, $td);

var outer = [1,2,3], inner = [1,2,3];

outer.map(function(row) {
  function createCellContents(col) {
    return $text(row+ '' + col);
  }

  return inner
    .map(createCellContents)
    .map(wrapWithTd)
    .reduce(appender, $tr());
}).reduce(appender, tab);

jsbin for reference.

Upvotes: 1

dfsq
dfsq

Reputation: 193261

Option #1

You should use helper string variable for this task:

var tab = document.getElementById('tab'),
    str = '';

for (var i = 0; i < 3; i++) {
    str += '<tr>';

    for (var j = 0; j < 3; j++) {
        str += '<td id="' + i + '' + j + '">' + i + '' + j + '</td>';
    }

    str += '</tr>';
}

tab.innerHTML = str;

Demo: http://jsfiddle.net/hrrKg/

Option #2

I would also recommend to make use of table API

var tab = document.getElementById('tab');

for (var i = 0; i < 3; i++) {
    var row = tab.insertRow(i);
    for (var j = 0; j < 3; j++) {
        var cell = row.insertCell(j);
        cell.id = i + '' + j;
        cell.innerHTML = i + '' + j;
    }
}

Demo: http://jsfiddle.net/hrrKg/1/


Basically why it happens. Whenever you set something like:

tab.innerHTML += '<tr>'

table content becomes not valid HTML so browser fixes it automatically making it look like something:

<tbody><tr></tr></tbody>

Further loops makes it even more confused hence a result.

Also take a look at cookie monster's answer, for it is more comprehensive to my mind.

Upvotes: 5

MamaWalter
MamaWalter

Reputation: 2113

Alternative to string:

tab = document.getElementById('tab');
for(i=0; i<3; i++)
{ 
 var tr = document.createElement("tr");

  for(j=0; j<3; j++) 
  {
    var td = document.createElement("td");
    td.id = i+''+j;
    td.innerHTML= i+''+j
    tr.appendChild(td); 
  }

 tab.appendChild(tr);
}

FIDDLE

Upvotes: 3

cookie monster
cookie monster

Reputation: 10972

You're treating the DOM like it's the HTML you write.

When you do

.innerHTML += "<tr>"

..you're creating an entire <tr></tr> element. There's no such thing as "half an element" in the DOM.


Then after that, when you += the <td> elements, they're now going after the <tr> you just created, which is invalid, so the browser corrects it as best it can.

// This is going after the <tr></tr>
tab.innerHTML += '<td id="'+i+''+j+'">'+i+''+j+'</td>'; 

And finally, when you do

.innerHTML += "</tr>"

you're simply adding invalid HTML, so it's probably ignored.


Furthermore, the use of .innerHTML += "...content..." is pretty bad. It first serializes the previous DOM nodes to HTML, destroys those DOM nodes, adds the new HTML to what it just serialized, and then parses that HTML to create (and recreate) the nodes.

So every iteration, you're destroying and recreating all that you created in all the iterations up to that point. This destruction is terribly inefficient and is unnecessary. Instead, build the entire string, and then use .insertAdjacentHTML().

tab = document.getElementById('tab');

var html = ""
for(i=0; i<3; i++)
{ 
 html += '<tr>';

  for(j=0; j<3; j++) 
  {
   html += '<td id="'+i+''+j+'">'+i+''+j+'</td>'; 
  }

 html += '</tr>';
}

tab.insertAdjacentHTML("beforeend", html);

Upvotes: 5

Newse
Newse

Reputation: 2338

I made some small adjustments and got it working in fiddler:

tab = document.getElementById('tab');

var result = "<tbody>";
for(i=0; i<3; i++)
{ 
 result += '<tr>';

  for(j=0; j<3; j++) 
  {
   result += '<td id="'+i+''+j+'">'+i+''+j+'</td>'; 
  }

 result += '</tr>'; 
}
result += "</tbody>";

console.log(result)
tab.innerHTML = result;

Upvotes: 1

Petar Vasilev
Petar Vasilev

Reputation: 4735

Problems you are adding <tr> once which is invalid HTML by itself and gets removed automatically. You can do:

tab = document.getElementById('tab');
for(i=0; i<3; i++)
{ 
 var html = '<tr>';

  for(j=0; j<3; j++) 
  {
   html += '<td id="'+i+''+j+'">'+i+''+j+'</td>'; 
  }

 tab.innerHTML += html + '</tr>';
}

Upvotes: 1

Related Questions