Referencing Forms and Form Controls

FAQ > FAQ Notes

Introduction

Forms

When the W3C defined the HTML DOM specification much of what they included represented a formalisation of existing browser behaviour. In particular they defined "convenience" properties on the HTMLDocument interface that reproduce document level collections common in preceding browsers. Of specific interest here is the document.forms collection, which makes all of the FORM elements on a page available as (zero based) indexed members of the collection. Allowing, for example, the second FORM element on a page to be referenced as:-

var formElement = document.forms[1];

HTML (but not necessarily XHTML) FORM elements are allowed NAME attributes and the document.forms collection also makes FORM elements with NAME attributes available as named members, under a property name that corresponds with value of the NAME attribute. So given a form with the attribute name="myForm" the form can be referenced as:-

var formElement = document.forms.myForm;

/* - or - */

var formElement = document.forms["myForm"];

/* The latter, bracket notation, does not impose the same restrictions
   on the character sequence used for the name as is imposed by the
   preceding dot notation, which is restricted to only using character
   sequences that would fulfill the ECMAScript definition of an
   identifier.

   Bracket notation is often preferred when accessing form elements as
   it helps to document itself by making it clear in the source code
   which property names originate in the HTML rather than the DOM.
*/

The document.forms collection had exhibited this behaviour in all of the preceding browsers that implemented it (which included all the browsers that understood what a form was) and as a result represents the most cross-browser method of accessing FORM elements. It is both W3C HTML DOM standard compliant and back-compatible with pre-existing browsers.

The W3C went on to require FORM elements with ID attributes to also be made available as named properties of the document.forms collection. That represented a formalisation of behaviour already exhibited in IE 4 but not by Netscape 4 (and earlier). Referencing IDed FORM elements as named properties of the document.forms collection should work reliably in all W3C HTML DOM compliant browsers but some back-compatibility will be sacrificed if IDs are used instead of NAMEs (though not nearly as much as would be lost if IDed form elements were referenced using the document.getElementById method).

When writing HTML that conforms to a DTD that allows FORM elements to have NAME attributes it is possible to also give the FORM element an ID attribute that corresponds with its NAME attribute (so long as the ID is unique on the page). The form will appear as a member of the document.forms collection under a property name that corresponds with the value of the NAME and ID attributes (as they are identical).

Form Controls

Traditionally browsers that implemented the document.forms collection also made the controls within a form available as a collection accessible as a property of the FORM element under the name elements. The W3C HTML DOM also formalised this collection in the HTMLFormElement interface. Controls within a form can be referenced as integer indexed members of that collection:-

var formElement = document.forms["myForm"];
var controlElement = formElement.elements[2]; //Third control in the form.

Controlls with NAME attributes are again made available as named properties of the collection. So a control with name="myControl" can be referenced as:-

var controlElement = formElement.elements["myControl"];

Again the W3C also specified that controls with ID attributes should be made available as named members of the elements collection under their IDs (with the same implications for back-compatibility with really ancient browsers). All official (x)HTML DTDs allow form controls to have NAME attributes because without a NAME attribute the value of the control cannot be sent as a name/value pair when the form is submitted.

Shortcut Accessors

In addition to making named FORM elements available as named properties of the document.forms collection web browsers also make them available as named properties of the document object. So:-

var formElement =  document.myForm;

-will reference the same FORM element as:-

var formElement = document.forms.myForm;

And the same is true using bracket notation:-

var formElement =  document["myForm"];

/* instead of:- */ 

var formElement = document.forms["myForm"];

The W3C did not include this shortcut in the HTML DOM specifications so code that uses it cannot be described as standards compliant and, while nobody has been able to name an HTML browser where the shortcut accessors do not work when referencing named FORM elements, it would still be possible for a future standards compliant browser not to support the shortcut accessors.

FORM elements that only have ID attributes cannot be accessed as properties of the document object under a property name that corresponds with their ID attributes. While by W3C HTML DOM specification (but not necessarily on older browsers) IDed forms are made available as named properties of the document.forms collections under a property name that corresponds with the ID.

Form controls can be referenced as named properties of the FORM element that contains them with a shortcut accessor:-

var formControl = formElement.myControl;

/* instead of :- */

var formControl = fromElement.elements.myControl;

But in the case of form controls, elements with ID attributes may be available as properties of the FORM element under a property name that corresponds with their ID (at least on more recent browsers).

The main argument in favour of using shortcut accessors (apart from the reduced amount of typing) is that they are resolved fractionally quicker than accessors that employ the forms and elements collections. That follows from the fact that fewer object references need to be resolved before the reference to the element of interest is returned.

While arguments against the shortcut accessors point out that named image, embed and other elements are also made available as named properties of the document object, making it ambiguous when reading the source code whether the shortcut accessor is referring to a form or some other named property of the document object. When a FORM element is referenced as a member of the document.forms collection, or a control as a member of the elements collection, there can be no doubt while reading the source code as to the type of element that is the subject of the reference.

The Most Common Mistake

The most common mistake made when defining the form HTML that a script will interact with follows from the existence of the shortcut accessors for form controls. It is to give the control a NAME (or possibly ID) that corresponds with an existing property of FORM elements. And the most common example of that is an INPUT element of type="submit" with the NAME "submit". Because the named controls are made available as named properties of the FORM element this INPUT element is made available under the property name "submit". Unfortunately FORM elements already have a property with the name "submit", it is the submit method that can be used to submit the form with a script. The misguided choice of name for the INPUT element effectively renders the form's submit method unscriptable. And the same is true for all controls with names that correspond any with existing FORM element properties.

Because ECMAScript is case sensitive it may only be necessary to capitalise the name of the INPUT element to avoid the conflict. However, it would probably be safest to adopt a naming convention for form controls that ensured that they do not correspond with existing properties of the FORM elements regardless of case. Especially as theW3C HTML specification implies that referring to named properties of collections can be case insensitive in HTML DOMs. In reality I don't know of any implementations in which it is but it would be better to err on the side of caution.

Another naming conflict that should be avoided would arise if named form controls had names that correspond with the string representations of integers, such as name="1" as they will almost certainly result in inconsistent results (across browsers) when being referenced because the controls are already available by integer index and it would become unclear which control was being referenced by accessors such as formElement.elements[1] or formElement.elements["1"] (by the ECMAScript specification the preceding two property accessors are equivalent). Unless the control with the name "1" also happened to be the control with the index 1. This problem would also apply to FORM elements within the document.forms collection.

Generally it is best to only give form controls names that cannot conflict with existing properties of FORM elements and FORM elements names that cannot conflict with existing properties of the document.forms collection or the document object.

Anonymous Form References

Because FORM elements are available as integer indexed member of the document.forms collection it is not necessary to know the name of a form (or for the form to have a name) to acquire a reference to it. While being able to refer to a form anonymously with its index might seem like a good approach towards making more general/flexible functions for form validation and the like, in practice referring to forms by their index actually makes code less flexible and harder to maintain. As soon as the number or layout of forms on a page is changed their indexes also change, requiring that all of the references by index be located and altered.

One method of avoiding having to know the name of a FORM element within a function that is to act upon a form is to pass a reference to the form object as an argument in the function call. This is easiest achieved from the code provided as the value for an event handling attribute because that code is used by the browser to create an event handling function that is assigned as a method of the element to which it is attached. And in any function executed as a method of an object the this keyword is a reference to the object with which the execution of the method is associated. The most common need to anonymously pass a reference to a FORM element is as an argument to a form validation function in the onsubmit handler of the form. In that case the event handling function is a method of the FORM element so the this keyword refers to the form directly:-

function validateForm(formRef){
    /* Use the reference to the form passed as the formal
       parameter - formRef - to acquire a reference to the form
       control with the name "textField":
    */
    var el = formRef && formRef.elements["textField"];
    /* If the control reference exists return its value property
       converted to a boolean value (false if empty, true otherwise)
       else return true so the form is submitted and the server can
       do the validation:
    */
    return !el || Boolean(el.value);
}
...
<form action="http://example.com/somePage.asp"
 onsubmit="return validateForm(this);">
    <input type="text" name="textField" value="">
    <input type="submit" name="Submit_Button" value="Submit">
</form>

Because the above function receives the reference to the form that it is to validate as an argument when it is called it can also be used with any number of other forms. Although in the case of the above function each of those forms would need to contain a field called "textField", but that string name could also be passed as an argument, making the function more general.

Form control objects all have a property named "form" that holds a reference to the FORM element that contains them. As a result the event handling functions attached to form controls can pass an anonymous reference to the form that contains them as this.form. Obviously a function called from an event handler on a control can be passes an anonymous reference to the control itself as this.

Radio Button and Other Control Collections

Radio button controls work to provide a selection of one item of many when each of the radio button alternatives has the same NAME attribute. But it is also possible to give other types of control the same NAME attribute.

When controls that share the same NAME attribute they can still be accessed as integer indexed members of the FORM element's elements collection but when the member of the elements collection is accessed using the NAME attribute value as a property name browsers return a collection all of the elements with the corresponding NAME attributes. So given the HTML:-

<form name="testForm" action="http://example.com/somePage.jsp">
  <ul style="list-style-type:none;">
    <li><input type="radio" name="radioSet" value="R1">option 1</li>
    <li><input type="radio" name="radioSet" value="R2">option 2</li>
    <li><input type="radio" name="radioSet" value="R3">option 3</li>
    <li><input type="radio" name="radioSet" value="R4">option 4</li>
  </ul>
  <input type="submit" name="Submit_Button" value="Send">
</form>

A property accessor referring to the member of the FORM's elements collection by the property name "radioSet" will not return a reference to any one of the named radio buttons but instead returns a collection of all of the like-named radio controls.

The individual radio buttons within that collection are referred to as integer indexed members of that collection. So to find the button that is checked one might loop through all of the members of the collection of like-named radio buttons and copy a reference to the button with its checked property set to boolean true.

var radioCollection, checkedButton;
var frm = document.forms["testForm"];
if(frm){
    radioCollection = frm.elements["radioSet"];
    if(radioCollection){
        /* The collection of like-named radio buttons has a length
           property and that is used to limit a for loop:-
        */
        for(var c = 0;c < radioCollection.length;c++){
            /* The individual radio buttons are accessed as indexed
               members of the collection using the loop counter - c
               - from the for loop:
            */
            if(radioCollection[c].checked){
                /* When a radio button element is found with its
                   checked property set to boolean true a reference
                   to that element is assigned to the local variable
                   - checkedButton - and the loop is terminated with
                   the - break - statement as only one button in a set
                   of like-named radio buttons will be checked at a
                   time, so any remaining buttons in the collection
                   must have checked properties set to false:
                */
                checkedButton = radioCollection[c];
                break;
            }
        }
        if(checkedButton){
            /* Do something with the reference to the checked radio
               button (if any).
            */
            ...
        }
    }
}

It is not unusual when a form is generated by a server-side script that some forms may have one or more like-named controls. If there is only one control inserted in the form then accessing a member of the elements collection with its name will return a reference to that one control, but if there are multiple elements the returned reference will be to a collection of such controls. While it may be possible to branch client-side code that wants to interact with those controls to handle the two alternatives it adds an unnecessary maintenance burden to do so.

As handling a returned collection usually involves looping through that collection the simplest way of implementing the client-side code to deal with both collections and individual controls being returned by named properties of the elements collection is to normalise the references to individual controls so that they can be handled as if they were a collection. Looping through a collection involves using the length property of the collection to limit the loop statement and accessing the controls within the collection by integer index. This is exactly the way in which code would loop through the elements of an Array. To allow a script to handle both possibilities with the same code the return of a reference to an individual control could be detected and then that reference used to create a one-element Array. Subsequent code would then treat the Array as if it was a collection and loop through it, but as there is only one element the loop body would only be executed once.

var radioCollection, checkedButton;
var frm = document.forms["testForm"];
if(frm){
    radioCollection = frm.elements["radioSet"];
    if(radioCollection){
        /* But the returned reference might not be a collection in this
           case. Instead it may be a reference to just one control if
           there is only one in this form with the name "radioSet".
           If it is a reference to an individual control it is going to
           be necessary to normalise it. Because radio button controls
           do not have - length - properties and collections do that is
           the property that is going to be tested:
        */
        if(typeof radioCollection.length != "number"){
            /* The length property is not a number so this cannot be a
               collection and must be normalised so that the following
               loop statement can handle it correctly. Normalisation is
               done by making  a reference to the control currently
               referred to by the - radioCollection - local variable
               into the first (and only) element of a new Array object
               and then assigning a reference to that array to the - 
               radioCollection - local variable:
            */
            radioCollection = [radioCollection];
        }
        /* The execution of the body of the - for - loop is limited by
           the - length - property of the collection/Array.
        */
        for(var c = 0;c < radioCollection.length;c++){
            /* The individual radio buttons are accessed as indexed
               members of the collection/Array using the loop counter
               - c - from the for loop:
            */
            if(radioCollection[c].cheked){
                checkedButton = radioCollection[c];
                break;
            }
        }
        if(checkedButton){
            /* do something with the reference to the checked radio
               button (if any).
            */
            ...
        }
    }
}

While it is normal for radio button controls to be like-named it is also possible for all other controls to be included in a form with multiple controls of the same type and like names. The same referencing techniques can be used with any type of control (even mixed types with like-names, though that is almost never done). But deciding whether a reference needs to be normalised by making it into the only element of an Array by testing the length property of that reference to see if it doesn't exist will not work with SELECT elements as they have a length property of their own. It also would not help to be testing some other characteristic of a collection, such as their item method, as SELECT elements usually have all of the methods and properties of a collection as they are implemented as collections of OPTION elements.

Turning the problem around and testing a returned reference to see if it has the characteristics of a SELECT element (such as an options property) would be better but some collection implementations have all of the properties and methods of the first element within that collection as well as the properties and methods of a collection (e.g. on IceBrowser 5). That means that it would not be easy to distinguish a collection of SELECT controls from an individual SELECT control. However, a more elaborate test might go:-

/* Normalise a reference that may be an individual from control
   (including SELECT elements) or may be a collection of controls
   into a form that can be handled in a - for - loop controlled with
   its - length - property and accessed by integer index.
*/
if((typeof contrlCollection.length != "number")|| //no length propety:
   /*  or:-  */
   ((contrlCollection.options)&&    //it has an options colleciton and:
    ((!contrlCollection[0])|| //no object at index 0 - not a collection
     /*  or:-  */
     (contrlCollection[0] == contrlCollection.options[0])))){
     /* The object at index 0 in contrlCollection is the same object
        as is at index 0 in contrlCollection.options so this must be
        an individual SELECT element not a collection of them because a
        collection of SELECT elements would not have an OPTION element
        at index zero.
     */
       contrlCollection = [contrlCollection];
}

Efficient use of Form Accessors

Code that interacts with FORM elements and controls through the document.forms collection and the form's elements collection usually does not do enough work to make the efficiency of the code significant. But with large forms with many controls an inefficiently coded validation function (or some other interaction, like keeping running totals) can negatively impact on the user's experience. It can also be argued that considering the efficiency of implementation is a worthwhile habit even when it would make no perceivable difference.

A significant aspect of the efficient coding of form interacting code is the re-resolving of references to various objects. This trivial example code copies the values of 5 INPUT elements to local variables:-

var txt1 = document.forms["formName"].elements["field1"].value;
var txt2 = document.forms["formName"].elements["field2"].value;
var txt3 = document.forms["formName"].elements["field3"].value;
var txt4 = document.forms["formName"].elements["field4"].value;
var txt5 = document.forms["formName"].elements["field5"].value;

The shortcut accessors require the resolution of fewer object references:-

var txt1 = document["formName"]["field1"].value;
var txt2 = document["formName"]["field2"].value;
var txt3 = document["formName"]["field3"].value;
var txt4 = document["formName"]["field4"].value;
var txt5 = document["formName"]["field5"].value;

But their use still involves re-resolving the form object each time a form control is accessed. It would be unnecessary to re-resolve this reference if a reference to the form was assigned to a local variable:-

/* Assign a reference to the form object to the local variable - frm -
   and then make subsequent control references relative to that local
   variable:
*/
var frm = document.forms["formName"];
var txt1 = frm.elements["field1"].value;
var txt2 = frm.elements["field2"].value;
var txt3 = frm.elements["field3"].value;
var txt4 = frm.elements["field4"].value;
var txt5 = frm.elements["field5"].value;

The effect would be much the same as when a reference to the form object has been passed to a function and control references are accessed relative to the parameter holding the form reference.

It is still not optimum to be re-resolving the elements collection, and it is practical to assign a reference to that object to a local variable instead of a reference to the form object:-

/* Assign a reference to the form's elements collection to the local
   variable - frmEls - and then make subsequent control references
   relative to that local variable:
*/
var frmEls = document.forms["formName"].elements;
var txt1 = frmEls["field1"].value;
var txt2 = frmEls["field2"].value;
var txt3 = frmEls["field3"].value;
var txt4 = frmEls["field4"].value;
var txt5 = frmEls["field5"].value;

With the original long form accessor the resolution starts with resolving the "document" identifier. The identifier is first looked for among the local variables of the function (as a named property of the internal "Variable" object, by ECMA specification), when it is not found the scope chain is searched, object by object down the chain, for a property with the corresponding name. When the scope resolution for "document" gets to the global object (at the end of the scope chain) it will find a property called "document", a reference to the document object, and the first identifier in the accessor will have been resolved. The next identifier is "forms" and that is located as a property of the document. Then the "formName" property is identified in the forms collection. Next the "elements" property of the form is located, followed by the control name in that object and finally the "value" property of the control is returned.

When a reference to the elements collection is assigned to a local variable the first identifier in the property accessor is identified as that local variable, the control name is identified as a property of the elements collection referenced and the "value" property of the control is returned.

Obviously there is a big difference in the amount of work involved in acquiring the value of the control in each case. That difference will not be that significant if only a couple of values are accessed, but even with as few as half a dozen control property accesses the second approach will obviously be much more efficient, and with increasing numbers of controls the difference could easily become apparent to the user.