Death to bad DOM Implementations

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 hosted with ❤ 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.

Like it? Share it

Share on LinkedIn

Share on Google Plus

Comments

  1. My workaround to this problem was to just manipulate the <code class=“js”>innerHTML</code> of the object that I’m adding the element to rather than trying to create the element though the <abbr title=“Document Object Model”>DOM</abbr>.

    <code class="js">document.getElementById("myEle")[removed] = "<div >

    Certainly not the most elegant way, but it works.

  2. 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 <code class=“js”>Try.these()</code> function:

    <code class="js">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;
         }); }</code>
    • Aaron Campos
    • | #
  3. You really don’t want to use an implementation that has to do the browser capability detection on every single <code class=“js”>createElementWithName</code> call. Instead you should do it once when defining the function. Will be much faster and cleaner.

  4. Thanks for reading my post and continuing the fight against bad <abbr title=“Document Object Model”>DOM</abbr> implementations.

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

    Hmm, this didn’t happen in my testing. Firefox executes <code class=“js”>createElement</code> with the <abbr title=“Internet Explorer”>IE</abbr> 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 <code class=“js”>createElement</code> again with the correct arguments and all is well.

    It would be nicer to try the standards-compliant method first (<code class=“js”>createElement</code> followed by <code class=“js”>setAttribute</code>) and then only if that fails, try the special <abbr title=“Internet Explorer”>IE</abbr> code. Unfortunately, on <abbr title=“Internet Explorer”>IE</abbr> the <code class=“js”>setAttribute</code> 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 <abbr title=“Internet Explorer”>IE</abbr> call first. As I said, in my testing, this fails on non-<abbr title=“Internet Explorer”>IE</abbr> browsers, which is why the fallback to the “proper” <code class=“js”>createElement</code> 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 <code class=“js”>createElement</code> is that it needs to be able to work on the <abbr title=“eXtensible Markup Language”>XML</abbr> <abbr title=“Document Object Model”>DOM</abbr>—where element names are up to the discretion of the developer—as well as the <abbr title=“HyperText Markup Language”>HTML</abbr>/<abbr title=“eXtensible HyperText Markup Language”>XHTML</abbr> 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 <code class=“js”>document.createElement(tag+’ name="’+name+’"’);</code> 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.

    <code class="js">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; }</code>

    Basically, if the evil <abbr title=“Internet Explorer”>IE</abbr>-style <code class=“javascript”>createElement</code> 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. <abbr title=“Your mileage may vary”>YMMV</abbr> ;-)

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

  9. Thanks for posting this - f@#k <abbr title=“Internet Explorer”>IE</abbr> - 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 <abbr title=“Document Object Model”>DOM</abbr> element.  The hack I ended up with was to mess with innerHTML, eg:

    <code class="js">element[removed][removed].replace(/sourceFormName/,'newFormName')</code>

    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 <abbr title=“Internet Explorer”>IE</abbr>—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:

    <code class="js">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;
         }
       } })();</code>
  12. @bob: Exactly my thinking and Anthony beat me to the execution.

    @pete: It’s been a while since I’ve done major <abbr title=“JavaScript”>JS</abbr> surgery in <abbr title=“Internet Explorer”>IE</abbr>, 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 <abbr title=“Document Object Model”>DOM</abbr> method to add the <code class=“html”>name</code> as it’s the most forward-compatible. Great work.

  13. <code class="js">document.createNamedElement = function(type, name)</code>
    <code class="js">function createElementWithName()</code>

    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 <code class=“java”>var return = function()</code>

    • Mark Griffin
    • | #
  14. Ooops! Actually you can call function by simply say <code class=“js”>function()</code> itself…

    • Mark Griffin
    • | #