Death to bad DOM Implementations

Posted by Aaron Gustafson on | Permalink

I just encountered a DOM implementation issue in IE which took about three hours to solve (and like a year off my life). The story goes like this:

I could not, for the life of me, figure out why a form submitted in Firefox was coming through perfectly while it was missing fields in IE. The form in question has some normal fields and some dynamically generated ones (if JavaScript is enabled). The normal stuff was coming through fine, but I was getting no values for the dynamically generated fields when the form was submitted in IE. I checked the $_REQUEST variable (I am using PHP) to see what was coming through, just to be sure.

I immediately figured it was missing name attributes, but I was using the proper syntax to create the input elements via the DOM (note: the actual JS is more generic than this)

var inpt = document.createElement('input');
inpt.setAttribute('name', 'company');
view raw not-in-IE.js This Gist brought to you by GitHub.

Indeed, when I looked at the page through the Web Accessibility Toolbar’s View Generated Source, it was indeed missing the name attribute:

<INPUT id=company maxLength=255>

After about another hour or two of fruitless Google-ing, I finally typed in the magic phrase (setting the name attribute in Internet Explorer) and ended up on Bennett McElwee’s blog post of the same name. Suddenly it was all clear and (as I expected) IE’s botched implementation of the DOM’s createElement function was to blame.

According to the MSDN page on the name attribute (linked and quoted in the blog entry):

The NAME attribute cannot be set at run time on elements dynamically created with the createElement method. To create an element with a name attribute, include the attribute and value when using the createElement method.

It continued with the following example:

var oAnchor = document.createElement("<A NAME='AnchorName'></A>");

The script “solution” Bennett posted was somewhat of a red herring, however, as Firefox would actually execute the createElement intended for IE and end up with an element named “<input name=”company” />” which would be rendered on the page as

<<input name="company" /> id="company" maxlength="255" />

Perhaps you can see why this would be problematic.

I augmented Bennett’s script slightly and renamed the function createElementWithName so I wouldn’t have to use it on every element I created in the script:

function createElementWithName(type, name) {
  var element;
  // First try the IE way; if this fails then use the standard way
  if (document.all) {
    element =
      document.createElement('< '+type+' name="'+name+'" />');
  } else {
    element = document.createElement(type);
    element.setAttribute('name', name);
  }
  return element;
}

I am not a super fan of the reference to document.all as it feels so much like browser sniffing. I am up for suggestions to improve the function if you have any ideas.

Anyway, I am posting this to hopefully save someone else from the major headache I had today.

Comments

  1. Kind of redundant but since I ran into this too I figured I’d post the slight variation I used to get this to work using the Prototype.js Try.these() function:

    function createElementWithName( type, name ) {
       return Try.these(
         function(){
           return document.createElement('<'+type+' name="'+name+'" />')
         },
         function(){
           var element = document.createElement(type);
           element.setAttribute( 'name', name );
           return element;
         });
    }
    • Aaron Campos
    • | #
  2. You really don’t want to use an implementation that has to do the browser capability detection on every single createElementWithName call. Instead you should do it once when defining the function. Will be much faster and cleaner.

  3. My workaround to this problem was to just manipulate the innerHTML of the object that I’m adding the element to rather than trying to create the element though the DOM.

    document.getElementById("myEle").innerHTML = "<div onclick="javascript:do();">Text</div>";

    Certainly not the most elegant way, but it works.

  4. Thanks for reading my post and continuing the fight against bad DOM implementations.

    Firefox would actually execute the createElement intended for IE and end up with an element named “<input name="company" />

    Hmm, this didn’t happen in my testing. Firefox executes createElement with the IE argument; this fails and throws an exception (because the argument is not a valid element name).  Then, since the element was not created, it calls createElement again with the correct arguments and all is well.

    It would be nicer to try the standards-compliant method first (createElement followed by setAttribute) and then only if that fails, try the special IE code. Unfortunately, on IE the setAttribute fails in such an insidious way that I can’t see how to use JavaScript to detect the failure. You can set the name attribute, and read it back and it seems fine. But the name is not really set, which messes up form submissions. This is why I had to use the nasty IE call first. As I said, in my testing, this fails on non-IE browsers, which is why the fallback to the “proper” createElement works.

    I’m disappointed that it didn’t work for you. I agree that browser-sniffing is not good; that’s why I used the method I did. But if it doesn’t work for you, then presumably it doesn’t work for some others too. What hope do we have?

  5. I had high hopes for your script because it made perfect sense (well, about as much as it could, given the situation). I would have thought the first call would have failed and thrown an exception. I was actually shocked that Firefox tried to make it work with an element name so plainly wrong. I am not sure which Firefox version you were running when you did your testing, but maybe something changed between that version and the version I just tested on (1.0.6 on Windows).

    It would seem logical that the only reason Firefox should accept any string for createElement is that it needs to be able to work on the XML DOM—where element names are up to the discretion of the developer—as well as the HTML/XHTML one. One would think, however, that there would be some restriction placed on use of “<” and “>” within the string as inclusion of these characters would invalidate the tag.

    One thing I did not try was a simple document.createElement(tag+’ name="’+name+’"’); which may have passed muster with Firefox altogether. It may be worth an experiment.

  6. Hi again. Curiouser and curiouser. I am also using Firefox 1.0.6 on Windows, and the function works fine.

    I have created a test page. If simply uses the function to add radio buttons to a form; I’ve created a couple of variations of the function as well. If you could go to that page using Firefox and let me know what you see (and whether the radio buttons work), that would be helpful.

    I expect that I don’t see the problem you do because I haven’t tested every possibility — you must have found something I missed. I appreciate your help on this.

  7. I used the following function when I hit this problem. It doesn’t use browser sniffing.

    document.createNamedElement = function(type, name) {
      var element;
      try {
        element = document.createElement('<'+type+' name="'+name+'">');
      } catch (e) { }
      if (!element || !element.name) { // Not in IE, then
        element = document.createElement(type)
        element.name = name;
      }
      return element;
    }

    Basically, if the evil IE-style createElement call doesn’t work as intended, it will return nothing (undefined or null), throw an exception (with the same result), or create an object whose name attribute is not set. In all of these cases, therefore, try again the correct way.

    I’m also a believer in adding functions to native objects if they seem to belong there. YMMV ;-)

  8. I think Chris has it. That works like a charm.

  9. Thanks for posting this - f@#k IE - I also just spent several hours losing my mind trying to get to the bottom of this.

    I couldn’t use the solutions above as I need to rename a copied DOM element.  The hack I ended up with was to mess with innerHTML, eg:

    element.parentNode.innerHTML.replace(/sourceFormName/,'newFormName')

    If anyone has a better suggestion, I’m all ears :)

    OT: I use FireBug with FireFox (if you don’t have it, stop what you are doing and install it now). I can’t help but thinking I could have saved the last several hours if I had an equivalent for IE—any suggestions?

    • Pete Moore
    • | #
  10. Thanks for blogging this—you sacrificed the year of your life for a good cause ;)

    • Evan Fribourg
    • | #
  11. Having just discovered this page, and seeing some very sensical comments, I came up with this version:

    function createElementWithName(){}
     (function(){
       try {
         var el=document.createElement( '<div name="foo">' );
         if( 'DIV'!=el.tagName ||
             'foo'!=el.name ){
           throw 'create element error';
         }
         createElementWithName = function( tag, name ){
           return document.createElement( '<'+tag+' name="'+name+'"></'+tag+'>' );
         }
       }catch( e ){
         createElementWithName = function( tag, name ){
           var el = document.createElement( tag );
           // setAttribute might be better here ?
           el.name = name;
           return el;
         }
       }
    })();
  12. @bob: Exactly my thinking and Anthony beat me to the execution.

    @pete: It’s been a while since I’ve done major JS surgery in IE, so I haven’t been looking for tools, but when I need to read out what’s been triggered and what hasn’t to see where a script is failing, I usually use my homegrown app jsTrace.

    @anthony: Thanks, that’s great. I was planning to tackle the same thing myself, but ended up too bogged down to work on it. And, yes, I’d prefer the DOM method to add the name as it’s the most forward-compatible. Great work.

  13. document.createNamedElement = function(type, name)
    function createElementWithName()

    This may be a silly question but how do you activate these functions? That is how do you actually call them? I’m relatively new to javascript. Normally you call a java funtion by simply var return = function()

    • Mark Griffin
    • | #
  14. Ooops! Actually you can call function by simply say function() itself…

    • Mark Griffin
    • | #

Post a Comment

Comments are now closed on this entry.