Can't set innerHTML on tbody in IE

I have a table like this:

<table>
<thead>
    <tr>
        <th colspan="1">a</th>
        <th colspan="3">b</th>
    </tr>
</thead>
<tbody id="replaceMe">
    <tr>
        <td>data 1</td>
        <td>data 2</td>
        <td>data 3</td>
        <td>data 4</td>
    </tr>
</tbody>
</table>

and a method returns me the following after an ajax request:

<tr>
    <td>data 1 new</td>
    <td>data 2 new</td>
    <td>data 3 new</td>
    <td>data 4 new</td>
</tr>

I want to change the innerHTML like

document.getElementById('replaceMe').innerHTML = data.responseText;

However, it seems that IE can't set innerHTML on <tbody>. Can anyone help me with a simple workaround for this issue?


That is true, innerHTML on tbody elements is readOnly in IE

The property is read/write for all objects except the following, for which it is read-only: COL, COLGROUP, FRAMESET, HEAD, HTML, STYLE, TABLE, TBODY, TFOOT, THEAD, TITLE, TR.

source: http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx

You can do something like this to work around it:

function setTBodyInnerHTML(tbody, html) {
  var temp = tbody.ownerDocument.createElement('div');
  temp.innerHTML = '<table>' + html + '</table>';

  tbody.parentNode.replaceChild(temp.firstChild.firstChild, tbody);
}

Basically it creates a temporary node into which you inject a full table. Then it replaces the tbody with the tbody from the injected table. If it proves to be slow, you could make it faster by caching temp instead of creating it each time.


Create a temp node to store a table in, then copy them to the tbody

  var tempNode = document.createElement('div');
  tempNode.innerHTML = "<table>" + responseText+ "</table>";

  var tempTable = tempNode.firstChild;
  var tbody = // get a reference to the tbody
  for (var i=0, tr; tr = tempTable.rows[i]; i++) {
    tbody.appendChild(tr);
  } 

Both the answers above seem a bit unclear. Plus, the created div is never removed, so calling those functions repeatedly eats memory. Try this:


// this function must come before calling it to properly set “temp” 
function MSIEsetTBodyInnerHTML(tbody, html) { //fix MS Internet Exploder’s lameness
  var temp = MSIEsetTBodyInnerHTML.temp;
  temp.innerHTML = '<table><tbody>' + html + '</tbody></table>';
  tbody.parentNode.replaceChild(temp.firstChild.firstChild, tbody);  }
MSIEsetTBodyInnerHTML.temp = document.createElement('div');

if (navigator  &&  navigator.userAgent.match( /MSIE/i ))  
  MSIEsetTBodyInnerHTML(tbody, html);
else  //by specs, you can not use “innerHTML” until after the page is fully loaded  
  tbody.innerHTML=html;    

Even with this code, though, MSIE does not seem to properly re-size the table cells in my application, but I'm filling an empty tbody tag with variable generated content, while the thead cells' colspan values are set to a fixed value: the maximum number of cells that may be in the generated tbody. While the table tbody is 50 cells wide, only two columns show. Perhaps if the table was originally filled, and the cells were replaced with the same internal structure, this method would work. Google's Chrome does an excellent job of rebuilding the table, while Opera's desktop browser can resize to more columns just fine, but if you remove columns, the remaining column widths remain as narrow as they were; however with Opera, by hiding the table (display=none) then re-showing it (display=table), the generated table tbody cells then size properly. I've given up with Firefox. It's the MSIE-6 of 2012 - a nightmare to develop for which must have additional markup added just to make HTML-CSS layouts work because it does not conform to standards that even MSIE now does. So I haven't tested the tbody.innerHTML workings in Firefox.


This can be fixed by creating a shim/polyfill for .innerHTML. This could get you (you, dear reader) started:

if (/(msie|trident)/i.test(navigator.userAgent)) {
 var innerhtml_get = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "innerHTML").get
 var innerhtml_set = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "innerHTML").set
 Object.defineProperty(HTMLElement.prototype, "innerHTML", {
  get: function () {return innerhtml_get.call (this)},
  set: function(new_html) {
   var childNodes = this.childNodes
   for (var curlen = childNodes.length, i = curlen; i > 0; i--) {
    this.removeChild (childNodes[0])
   }
   innerhtml_set.call (this, new_html)
  }
 })
}

var mydiv = document.createElement ('div')
mydiv.innerHTML = "test"
document.body.appendChild (mydiv)

document.body.innerHTML = ""
console.log (mydiv.innerHTML)

http://jsfiddle.net/DLLbc/9/