envjs / env-js

A pure-JavaScript browser environment.
http://www.envjs.com/
87 stars 19 forks source link

getElementsByTagName out of synch with innerHTML #11

Open orslumen opened 13 years ago

orslumen commented 13 years ago

One of my jQuery plugins uses the $.wrap() function. After that method is called, the getElementsByTagName function returns incorrect results. It completely ignores the new element and moves the wrapped element to the end of the list.

To reproduce (without jQuery):

<html lang='en-US' xml:lang='en-US' xmlns='http://www.w3.org/1999/xhtml'>
  <head/>
  <body>
    #container
      #before
      #to_be_wrapped
      #after
    <script type="text/javascript">
      //<![CDATA[
        // Print the current state to the console, this is OK
        var container = document.getElementById('container');
        console.log('\r\nBefore:\r\n'+container.innerHTML);
        var all = container.getElementsByTagName('*'); var ids = ''; for(var i=0; i < all.length; i++) { ids = ids + (String.isBlank(ids) ? '' : ', ') + all[i].getAttribute('ID'); } console.log('getElementsByTagName("*") returns%selements:%s', all.length, ids);

        // Wrap the #to_be_wrapped node in a new #wrapper node
        var wrapper = document.createElement('div');
        wrapper.id = "wrapper";
        var to_be_wrapped = document.getElementById('to_be_wrapped');
        container.insertBefore(wrapper, to_be_wrapped);
        wrapper.appendChild(to_be_wrapped);

        // Print the results of the incorrect state of getElementsByTagName
        console.log('\r\nAfter:\r\n'+container.innerHTML);
        all = container.getElementsByTagName('*'); ids = ''; for(var i=0; i < all.length; i++) { ids = ids + (String.isBlank(ids) ? '' : ', ') + all[i].getAttribute('ID'); } console.log('getElementsByTagName("*") returns%selements:%s', all.length, ids); 

        // Show that childNodes still reflects the correct state
        all = container.childNodes; ids = ''; var id_count = 0; for(var i=0; i < all.length; i++) { if (all[i].nodeType == Node.ELEMENT_NODE) { id_count++; ids = ids + (String.isBlank(ids) ? '' : ', ') + all[i].getAttribute('ID'); } } console.log('childNodes returns%selements:%s', id_count, ids); 
      //]]>
    </script>
  </body>
</html>

This produces the following results: Before:

<div id="before"/>
<div id="to_be_wrapped"/>
<div id="after"/>
getElementsByTagName("*") returns 3 elements: before, to_be_wrapped, after 

After:

<div id="before"/>
<div id="wrapper"><div id="to_be_wrapped"/></div>
<div id="after"/>
getElementsByTagName("*") returns 3 elements: before, after, to_be_wrapped 
childNodes returns 3 elements: before, wrapper, after 

As you can see the innerHTML is correct, but the container.getElementsByTagName("*") is missing the #wrapper div and has incorrectly moved the #to_be_wrapped div to the end.

Fortunately the childNodes property does contain the correct state, so as a workaround I switched back to the old recursive implementation of getElementsByTagName using childNodes, which seems to work fine for the moment.

mauricioszabo commented 13 years ago

I'm having a different problem: I have a HTML file like the following:

 <script language="javascript">
     document.getElementById('ex').innerHTML = '\
 <table class="table">\
   <tr>\
       <th class="sorting"><a href="#">login</a></th>\
   </tr>\
 </table>';
 </script>

It just replaces an innerHTML of a div with some data. After loading this page:

console.log("Link elements: " + document.getElementsByTagName("a").length);

This shows me "Link elements: 0", which is wrong - expected to be 1. However, if I try:

var elements = [];
var all = document.getElementsByTagName("*");
for(var i = 0; i < all.length; i++) {
    if(all[i].tagName === 'A') {
        elements.push(all[i]);
    }
}
console.log("Link elements filtered: " + elements.length);

It gives me the correct number of elements (in this case, 1).