Another look at the DOM - Collections
As we saw in part 1, the DOM stores the objects of a document and information relevant to these objects, and allows JavaScript to access the information and alter it. Changing the DOM changes the displayed page.
Individual named elements can be identified in the DOM structure readily. But we might have several instances of a particular element. To name all these individually is a fair effort, especially through multiple edits.
So the DOM uses collections. A collection is a grouping of all the same tag name elements in a document.
Some collections are images, forms, links and rows. The images collection holds all the images in a document, the forms collection holds all the forms in a document, the links collection holds all the links in a document, and rows holds all the rows for a table.
Some collections have child members, for example forms have elements, and we can address the individual elements of a form through the elements collection of a member of the form collection.
There are many more collections, and one important one is the all collection. The all collection holds every element, and is useful for elements that are not directly absorbed into a defined collection.
Accessing Collections
Collections save us from having to name every HTML element. They also provide a ready interface for running through a list of elements.
To see how a collection resembles the loaded document, let us look at an example of the forms collection.
The following has two forms, and each form has some elements. The elements are all named so that we may see the one-to-one relationship between the forms/elements collections and the HTML document.
<HTML>
<HEAD>
<TITLE>Twin Forms</TITLE>
<SCRIPT>
function go() {
msg='';
for (f=0;f<document.forms.length;f++) {
msg+='Form : '+f+' - Form Name : '+document.forms[f].name+'\n';
for (e=0;e<document.forms[f].elements.length;e++)
msg+='Element : '+e+' - Element Type : '+document.forms[f].elements[e].type+' - Element Name : '+document.forms[f].elements[e].name+'\n';
}
alert(msg);
}
</SCRIPT>
</HEAD>
<BODY onload="go();">
<FORM NAME="topForm" STYLE="background-color:yellow">
<INPUT TYPE="RADIO" NAME="myRadio">Radio1
<BR>
<INPUT TYPE="RADIO" NAME="myRadio">Radio2
<BR>
<INPUT TYPE="RADIO" NAME="myRadio">Radio3
<BR>
<INPUT TYPE="TEXT" NAME="myText">
</FORM>
<FORM NAME="hobbies" STYLE="background-color:red">
<INPUT TYPE="CHECKBOX" NAME="sport">Sport
<BR>
<INPUT TYPE="CHECKBOX" NAME="travel">Travel
<BR>
<INPUT TYPE="CHECKBOX" NAME="music">Music
<BR>
<INPUT TYPE="CHECKBOX" NAME="puzzles">Puzzles
<BR>
<INPUT TYPE="SUBMIT" NAME="submit">
</FORM>
</BODY>
</HTML>
The go() function runs through the forms collection and then through the elements collection for each form. The result is displayed in an alert box.
Note that we must address document.forms here - forms[0] is not an object in its own right, it only exists as a descendant on the document object.
Tables
While there is no need to highlight tables, the syntax and use of the collections for tables does need explaining.
If we have a table, say, myTable, then a rows collection is created for myTable. This rows collection holds all the rows in myTable, and the <TH> (table head) and <TD> cells for each row are stored in the cells collection.
<HTML>
<HEAD>
<TITLE>Tables</TITLE>
<STYLE>
TD {text-align:center}
</STYLE>
<SCRIPT>
function go() {
msg='';
for (r=1;r<myTable.rows.length;r++) {
mrcl=myTable.rows[r].cells.length;
for (c=0;c<mrcl;c++)
msg+=myTable.rows[r].cells[c].innerText+((c<mrcl-1)?':':'\n');
}
alert(msg);
}
</SCRIPT>
</HEAD>
<BODY onload="go();">
<TABLE ID="myTable" border="2">
<TR><TH>City</TH><TH>Smiles per 1000 people</TH><TH>Suits per 1000 people</TH></TR>
<TR><TD>London</TD><TD>500</TD><TD>500</TD></TR>
<TR><TD>New York</TD><TD>650</TD><TD>500</TD></TR>
<TR><TD>Sydney</TD><TD>300</TD></TR>
<TR><TD>Moscow</TD><TD>400</TD><TD>450</TD></TR>
<TR><TD>Delhi</TD><TD>501</TD></TR>
</TABLE>
</BODY>
</HTML>
Remember that tables do not have a NAME attribute - we must use the ID, although NAME and ID are often interchanged.
The collections data is displayed in an alert message. We can see how the rows and cells collection traverse a table to reveal its data. The table has data missing to demonstrate the versatility of the code.
We start the r loop at 1 because we are not concerned with the first row.
The statement:
((c<mrcl-1)?':':'\n')
is a neat and short-hand alternative for:
if (c<mrcl-1) msg+=':' else msg+='\n';
We are adding the result of the ternary operator, the ternary operator returns ':' if c<mrcl-1, and '\n' is not, and we add the returned string to the current msg string, which saves us one line of code.
Other collections
The images collection is referenced with:
document.images[imagenumber]
The links collection is referenced with:
document.links[linknumber]
The all collection is referenced with:
document.all[elementnumber]
The all collection is a good place to look for DIV, P and SPAN elements.
Dynamic Styles
As the Internet gained popularity, HTML became increasing weakened. People started demanding more and more features, and this caused the creation of DHTML. It also created CSS, or Cascading Style Sheets.
Styles allow us to separate the content of the HTML page from its presentation. DHTML makes very good use of styles - we can dynamically alter them in the same way we can alter attributes of HTML elements.
This is a very powerful development - now we can move images around the screen, change an element's color in response to events, change the visibility of elements, and much more.
As a re-cap, styles are attached to an element through the style attribute, e.g.
<P STYLE="color:blue">Peter</P>
renders Peter in blue.
Or we can attach styles inside a style tag, e.g.
<STYLE>
P {color:blue}
</STYLE>
<P>Peter</P>
also renders a blue Peter.
Changing Color
When we move the mouse over a hyperlink, it changes color. No other element does this, but we can easily add the effect to any element.
<HTML>
<HEAD>
<TITLE>Book Reader</TITLE>
<STYLE>
P {color:black}
</STYLE>
</HEAD>
<BODY>
<P onmouseover="style.color='red';" onmouseout="style.color='black';">The is the beginning of this book.</P>
<P onmouseover="style.color='red';" onmouseout="style.color='black';">Move your mouse over the text,</P>
<P onmouseover="style.color='red';" onmouseout="style.color='black';">to highlight a line to read.</P>
</BODY>
</HTML>
As we move the mouse over an element, we change its color to red, and when we leave the element, we change the color to black.
The syntax for changing styles is slightly different, we refer to styles by referring to the style object of the element in question.
So,
myP.style.color
means read the color property of the style object of the myP element.
All styles are addressed this way - forgetting to add the style keyword is a common mistake.
attachEvent
You may be thinking that the code is over-complicated, and that it could be simplified. It can.
Advanced DHTML provides extra keywords to allow for the dynamic nature of DHTML. One of these is attachEvent, which allows us to attach an event handler to an element from within the script tag.
The new, improved code looks like:
<HTML>
<HEAD>
<TITLE>Book Reader 2</TITLE>
<STYLE>
P {color:black}
</STYLE>
<SCRIPT>
function go() {
for (e=0;e<document.all.length;e++)
if (document.all[e].tagName=='P') {
document.all[e].attachEvent('onmouseover',scred);
document.all[e].attachEvent('onmouseout',scblack);
}
}
function scred() {
event.srcElement.style.color='red';
}
function scblack() {
event.srcElement.style.color='black';
}
</SCRIPT>
</HEAD>
<BODY onload="go();">
<P>The is the beginning of this book.</P>
<P>Move your mouse over the text,</P>
<P>to highlight a line to read.</P>
</BODY>
</HTML>
One advantage with this code is that we no longer have to add the event handlers to every <P> element - the code does this for us by iterating through the all collection, and attaching the onmouseover and onmouseout event handlers to all P elements.
Another advantage is that if we change our mind, say we wish the highlighted color to be blue, we only have to change one line in the function scred(), rather than all the instances of 'red' as with the first program.
Changing fontSize
And we can easily enhance the code, try changing the functions to:
function scred() {
with (event.srcElement.style) {
color='blue';
fontSize=16;
}
}
function scblack() {
with (event.srcElement.style) {
color='black';
fontSize=12;
}
}
Notice that the scred() function is now mis-named, while this is not a technical problem, we might be better off calling the function scblue, and making the appropriate change in the attachEvent section.
Also notice that the CSS style is font-size, whereas in script we use fontSize. All hyphenated styles folow the same pattern - remove the hyphen and capitalize the first letter of all words after the first.
So,
background-color
becomes
backgroundColor
and so on.
If you are wondering what event.srcElement means, when the event is captured, it is captured on an element. So we have an event object created by the event, and the event object has many properties about the event. One of these is srcElement, which returns the element that fired the event.
And you may think that the program jerks the text around a bit too much. This can be fixed by using:
function go() {
pc=0;
for (e=0;e<document.all.length;e++)
if (document.all[e].tagName=='P') {
pc++;
document.all[e].style.top=50*pc;
document.all[e].attachEvent('onmouseover',scred);
document.all[e].attachEvent('onmouseout',scblack);
}
}
and changing the style declaration to
P {position:absolute;color:black}
We fix this problem by manually positioning each P element. Setting the position style to absolute means that all the elements refer to an exact and absolute position on the browser screen. We could also set position to relative, which means that each element is relatively positioned to any parent elements.
Changing the position
There are two styles that affect the position of an element, namely top and left. These two styles hold the distance of the element from the top of the screen and from the left of the screen respectively.
With DHTML and Dynamic Styles, we can change the values of these styles to move an element around.
You may have seen scrolling text displays - where large bodies of text of are scrolled in a neat container. These are created by changing the position of the text inside a DIV element (see example).
<HTML>
<HEAD>
<TITLE>Text Scroller</TITLE>
<SCRIPT>
sp=100;
function scroll() {
if (sp<-350) sp=100;
sp--;
sctext.style.top=sp;
setTimeout('scroll()',50);
}
</SCRIPT>
</HEAD>
<BODY onload="scroll();">
<DIV STYLE="position:absolute;overflow:hidden;top:100;left:100;width:100;
height:100;background-color:yellow">
<SPAN ID="sctext" STYLE="position:relative;top:100;left:0;width:100;height:100">
Here is the text that we see scrolling on the browser screen.
As that one sentence is a bit short all by itself, I have added another one to provide some substance to the body of text.
And I figure that if I also say Goodbye, then the text is even longer.
</SPAN>
</DIV>
</BODY>
</HTML>
The DIV element contains the overflow style, this defines the behavior of the element when its contents overflow the parent's elements boundaries.
The SPAN element needs an ID, and is positioned relative to its parent DIV element. This means that the co-ordinate system places (0,0) as the top-left of the DIV element.
The text that is scrolled is placed inside the SPAN element.
The scroll function decrements sp, the scroll position, and then adjust the relative position of the SPAN element accordingly. This gives the movement, and the display is kept tidy because any text outside the DIV element is not shown.
The animation is performed by the setTimeout function, and the scroll is repeated by detecting sp being at the end of the text (sp=-350), and resetting it to the starting position of the text (sp=100).
Dynamic Expressions
We can simplify the previous code by 'cutting out the middle man'. At the moment, we have sp mimicking the style of the SPAN element, and we manually connect the two.
DHTML provides expressions to link the two features automatically.
An expression is set as a style, and uses variables. Then, when we change the variable, the style value also changes.
For example, make the following changes to the previous text scrolling code.
Replace the scroll() function with:
function scroll() {
if (sp<-350) sp=100;
sp--;
document.recalc();
setTimeout('scroll()',50);
}
and change the SPAN element to:
<SPAN STYLE="position:relative;top:expression(sp);left:0;width:100;height:100">
Here is the text that we see scrolling on the browser screen.
As that one sentence is a bit short all by itself, I have added another one to provide some substance to the body of text.
And I figure that if I also say Goodbye, then the text is even longer.
</SPAN>
(the change is in the top style, and we can remove the ID).
What we have done here is connect the top style of the SPAN to the variable sp. This means that when sp changes in the code, the top style also changes.
Unfortunately, JavaScript 'hogs' the code, meaning that JavaScript prefers to finish a calculation before updating the screen. This can get very annoying with graphics, as we want to see all the intermediate steps.
Using document.recalc() forces the document to update itself, and this way we can see each individual step of the animation.
We are not limited to simple expressions, try
expression(sp*2)
and the text will scroll twice as fast.
currentStyle
Another popular problem on the newsgroup comp.lang.javascript is 'Why can I only read in-line styles?'
This is referring to the fact that only styles directly assigned to an element can be read, and styles set inside a style tag do not seem to be visible.
Run this program, and see how the third alert displays an empty value.
<HTML>
<HEAD>
<TITLE>currentStyle</TITLE>
<STYLE>
P {color:blue}
</STYLE>
<SCRIPT>
function go() {
alert('style.fontSize:'+myP.style.fontSize);
alert('currentStyle.fontSize:'+myP.currentStyle.fontSize);
alert('style.color:'+myP.style.color);
alert('currentStyle.color:'+myP.currentStyle.color);
myP.style.color='green';
alert('style.color:'+myP.style.color);
alert('currentStyle.color:'+myP.currentStyle.color);
}
</SCRIPT>
</HEAD>
<BODY onload="go();">
<P ID="myP" STYLE="font-size:20pt">Hello</P>
</BODY>
</HTML>
As you have probably guessed by now, the secret is in using the currentStyle object. The currentStyle object mirrors the style object, but holds the result of cascading all the styles applied to an object, whereas the style object only holds in-line styles.
Of course, if we define a style in script, then the style object does now have the style. And note that currentStyle is read-only.
And this concludes this article. In the next and final article we shall look at the BOM in more detail, and examine some of the more esoteric features that DHTML has to offer.