Friday, October 24, 2008

Cross-Browser JavaScript: Creating DOM Nodes That Set The name Property

Creating and appending DOM nodes to a form can be tricky. Internet Explorer needs special care when you create a DOM node and then set the name property. This handy JavaScript gets around these cross browser issues efficiently, and without browser detection.

Web browsers have come a long way. We can create Document Object Model nodes at will using the universally supported document.createElement function, then you try creating form fields in Internet Explorer and you hit a snag.

Form fields, in order to be useful, need a name attribute in HTML. In JavaScript, you set the name property. It's two simple lines of code. Nothing special. No hoops to jump through:

// Create a new form input to hold a first name:
var el = document.createElement("input");
el.name = "fname";

You append this new input to your form and then try to access it using the form.elements.field_name syntax and you can't find the "fname" field. Turns out, Internet Explorer requires a little fudging:

// Create a new form input for Internet Explorer:
var el = document.createElement("<input name=fname>");

In one fell swoop, you must create the new element AND assign the name property before Internet Explorer will append this element to the form's document object model, making it available through the form.elements.field_name syntax. The problem is, standards compliant browsers, i.e. "every other browser developed within the last 8 years," will throw a JavaScript error if they try executing that code.

You could try browser detection, which usually involves parsing the user agent string of the browser. Most browsers allow you to change the user agent string, so this is no longer a viable method. Plus, what do you do when a new browser or browser version comes out? You'd have to change your function. Turns out, a simple try-catch block is the answer.

Creating a named DOM node the coss browser way

We can create our own createElement function to account for the difference in Internet Explorer's implementation of document.createElement:

function createElement(nodeName, name) {
var node;
try {
node = document.createElement("<"+nodeName+" name="+name+">");
} catch (e) {
node = document.createElement(nodeName);
node.name = name;
}
return node;
}

It works fine and dandy. Every time you call the function, it tries doing it Internet Explorer's way, but you take a performance hit because of the try-catch block that executes every time the createElement function executes. With a little runtime retooling, we can get around this using three functions instead of one:

/**
* Cross browser method for creating new DOM nodes with name attributes. This
* gets around an Internet Explorer bug that prevents that get the name property
* assigned to them and their values aren't sent when the form submits.
*
* @param string required Node name, like "div" or "input"
* @param string required Value of the name attribute
* @return object New DOM node
*/
function createElement(nodeName, name) {
var node;
try {
node = createElementMsie(nodeName, name);
createElement = createElementMsie;
} catch (e) {
node = createElementStandard(nodeName, name);
createElement = createElementStandard;
}
return node;
}

/**
* Code required by Internet Explorer when creating a new DOM node with the
* name attribute set.
*/
function createElementMsie(nodeName, name) {
return document.createElement("<"+nodeName+" name="+name+">");
}

/**
* Code required by all other browsers that support web standards.
*/
function createElementStandard(nodeName, name) {
var node = document.createElement(nodeName);
node.name = name;
return node;
}

When the page first loads, you've got three functions. The createElement function, which is what you will always use, has a try-catch block that calls two other functions. The createElementMsie function attempts to create the new DOM node using Internet Explorer's implementation. If the function call succeeds, the createElement function is reassigned to be the createElementMsie function. All subsequent calls to createElement will run the code in createElementMsie.

In the catch clause, the browser has failed to execute createElementMsie, and therefore doesn't support Internet Explorer's implementation of document.createElement. Here we assume it is a standards compliant browser and doesn't need any hand holding. It calls createElementStandard, and when that succeeds it reassigns the createElement function to be the createElementStandard function. All subsequent calls to createElement run the code in createElementStandard.

You still take a performance hit with these three functions because the try-catch block is executed, but only for the first time you call createElement. From the second time you call createElement onwards, it directly executes the code the browser supports. The try-catch block doesn't exist anymore, and you've gotten around Internet Explorer's faulty implementation of the document.createElement function. You can now create form fields at will and feel safe in knowing Internet Explorer will actually send those field values to the server when the form is submitted.

Using createElement:
var el = createElement("input", "fname");
// append the new INPUT to the form.

2 comments:

amichelins said...

When you use XHTML the :
document.createElement('< input name=test>'); is not run.

But if you write in this way :

document.createElement('< input name="test">');

Work ok;

Thanks for your article.

Greg Burghardt said...

Thanks for the catch, amichelins. I'll update this post when I've got some time.