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).
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.
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 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.