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 ID
ed 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 ID
s are used instead
of NAME
s (though not nearly as much as would be lost if
ID
ed 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).
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 ID
s (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.
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) ID
ed 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 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.
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 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]; }
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.