Navigator 4.0, which is one piece of Netscape's new Communicator product, runs a new version of JavaScript, JavaScript 1.2, that has many new features. This chapter documents JavaScript 1.2 as implemented in the 4.01 release of Communicator.
The new features of JavaScript 1.2 include:
New statements and operators in the core language, such as the do/while and switch statements and the delete operator.
Support for regular expressions, including a RegExp object and methods to work with regular expressions.
Conditional comments, which provide a way to include HTML code in a document only if a certain JavaScript expression returns true. This feature is somewhat like the #if directive to the C preprocessor.
An Event object and a new event-handling model.
A Layer object that corresponds to the HTML <LAYER> tag, along with properties and methods to manipulate layers.
JavaScript style sheets, which support the ability to manipulate document style using JavaScript code.
A new security model, based on digitally-signed JavaScript programs encapsulated in Java JAR files.
New methods of the Window object that allow secure scripts access to buttons in the browser window, such as Back, Forward, and Print.
A Screen object that contains information about the display screen and new properties of the Navigator object that provide information about the human language and computer platform being used.
A number of the changes in JavaScript 1.2 are language changes--enhancements to the core syntax of JavaScript.
When using JavaScript 1.2 features in a script, you should be sure to specify "JavaScript1.2" as the value of the LANGUAGE attribute of the <SCRIPT> tag. For example:
<SCRIPT LANGUAGE="JavaScript1.2">
// Your script here
<SCRIPT>
Setting LANGUAGE="JavaScript1.2" causes browsers that do not support JavaScript 1.2 to ignore the script.
The delete operator in JavaScript 1.2 can be used to delete a property of an object, an element of an array, or an entire object. For example:
delete birthdayCandle[29];
You might use the following code to swap the value of two variables:
var temp; temp = a; a = b; b = temp; delete temp;
If the delete operation succeeds, the specified property or element is set to undefined and the operation returns true. Otherwise, the operator returns false.
JavaScript 1.2 adds support for the do/while statement, which behaves just as it does in languages like C and Java. A do/while loop is like a while loop, except that the loop expression is tested after the loop body executes, rather than before. This means that the body of the loop is always executed at least once. For example:
function factorial(n) {
var total = 1;
do {
total = total*n;
} while (--n > 0);
return total;
}
JavaScript 1.2 also adds support for the switch statement, using the same syntax that C and Java use. A switch statement evaluates a specified expression and then executes certain code within a block of code, depending on the value of the expression. You specify locations in the block of code with case labels and with the special default: label. Each case: label is followed by a value. If the expression evaluates to that value, the switch statement begins executing code immediately following that case: label. If there is no case: label that matches the value of the expression, the switch statement starts executing code following the default: label, if there is one. If there is no default: label, the switch statement simply does nothing.
In JavaScript, the expression used in a switch statement can be any kind of expression. It does not have to be an integral expression, like in Java. Thus, the following code is legal in JavaScript:
var colorname;
switch(document.myform.color_selector.selectedIndex) {
case "0":
colorname = "red";
break;
case "1":
colorname = "green";
break;
case "2":
colorname = "blue";
break;
default:
colorname = "unknown color";
break;
}
The switch statement is an unusual one because each case does not have its own unique block of code. As you can see above, the case: and default: labels simply mark various entry points into a single large block of code. Typically, each label is followed by several statements and then a break statement, which causes the flow of control to exit out of the block of the switch statement. If you don't use a break statement at the end of the code for a label, the execution of that case "drops through" to the next case. Forgetting break statements within a switch statement is a common source of bugs.
In JavaScript 1.2, any statement may be preceded by a label, which is simply an identifier followed by a colon. For example:
outer_loop:
for(i = 0; i < 10; i++) {
// more code here...
}
The purpose of a labelled statement is to provide a named position within a script for use with a break or continue statement. The break and continue statements exist in JavaScript 1.0 and JavaScript 1.1, but their syntax has been extended in JavaScript 1.2 to match the syntax of Java.
In JavaScript 1.2, a break statement may optionally be followed by a label that specifies any enclosing statement. When a break statement contains a label, it transfers control out of (or "breaks out of") the specified enclosing statement, rather than just exiting the innermost while, do/while, for, for/in, or switch statement.
Likewise, a continue statement in JavaScript 1.2 may optionally be followed by the label of any enclosing loop. Instead of just terminating the current iteration of the innermost while, do/while, for, or for/in loop and continuing with the next iteration, a labelled continue statement stops the current iteration of the specified loop and continues with the next iteration of that loop.
Here is an example that shows the use of labelled break and continue statements:
outer_loop:
for(i = 0; i < 10; i++) {
for(j = 0; j < 10; j++) {
if (f(i,j) > 0) break; // break out of inner loop
if (g(i,j) > 0) continue outer_loop; // restart outer loop
if (h(i,j) > 0) break outer_loop; // break out of outer loop
}
}
One of the important new features of JavaScript 1.2 is its support for regular expressions. Regular expressions are a powerful tool for manipulating textual data. They can be used to match patterns in a string, as well as substitute new text for matched text. With JavaScript, you use both RegularExpression objects and the global RegExp object to work with regular expressions. The String object also provides some new methods for manipulating regular expressions.
A RegularExpression object contains the pattern for a regular expression. You can create a RegularExpression object with the RegExp() constructor or with a new literal syntax supported by the language. JavaScript regular expressions use the same syntax as Perl regular expressions. When you create a RegularExpression using the literal syntax, the pattern must be enclosed by slashes (/). The RegExp() constructor does not require the slashes, however. Here are two statements that create the same RegularExpression object:
pattern = /;+/; // A reg exp matching one or more semicolons
pattern = new RegExp(";+"); // The same regular expression
See a Perl book, such as Learning Perl by Randal
L. Schwartz and Tom Christiansen from O'Reilly, for details on the
syntax of regular expressions.
Once you've created a RegularExpression object, you can use it with four new methods of the String object: replace(), search(), match(), and split(). The replace() method replaces any portion of the source string that matches a specified regular expression with a specified replacement string. For example:
t = s.replace(/javascript/, "JavaScript"); // fix up capitalization
The replacement string passed to replace() can use any of the fields of the RegExp object. This object contains properties that are set in the context of a particular regular expression operation. So, for example, the replacement string can use the properties $1 and $2 to refer to particular parenthesized substring matches of the regular expression:
t = s.replace(/^(.)(.)/, "$2$1"); // swap first 2 chars of string
A new version of the split() method of String splits the source string into substrings on a boundary specified by a regular expression. split() returns an array of the substrings, where the substrings do not contain any characters of the pattern. For example:
var s = "a, b, c, d, e";
var a = s.split(/, */); // pattern is comma followed by zero or more spaces
// now a is the array ["a", "b", "c", "d", "e"]
The search() method searches the source string for a match with the specified regular expression. This method returns the location of the first match in the string if the pattern is found, or -1 otherwise.
The match() method matches a regular expression against a string in a more sophisticated way. match() returns an array that contains at least the first match in the source string, if there is a match. If there is no match, the method returns null. If the regular expression contains subpatterns in parentheses, the array returned by match() contains the first match in the source string, followed by the substrings that matched the subpatterns. For example:
var s = "Now I know my ABC's";
var a = s.match(/a(.)(.)/i); // match a followed by two characters
// now a is the array ["abc", "B", "C"]
}
Note the use of the optional i flag to tell the regular
expression to ignore case.
A regular expression can also be used to do a global search on a string; the optional g flag specifies a global search. When you use a global search regular expression with match(), the returned array contains all of the matches in the source string. For example:
var s = "There, now that's the ticket";
var a = s.match(/th\w*/gi); // match any word starting with th
// now a is the array ["There", "that", "the"]
}
Instead of passing a regular expression to the String method match(), you can also pass a string to the exec() method of a RegularExpression object. Thus, you might rephrase the last call to match() above like this:
var pattern = /th\w*/gi;
var a = pattern.exec("There, now that's the ticket");
You can also abbreviate the call to exec() as
follows:
var a = pattern("There, now that's the ticket");
Either way, when you use the exec() to perform a
global search, the resulting array only contains the first match.
However, various properties of the RegularExpression object are
updated so that you can call exec() multiple times
to find all of the matches. Here's how you can use
exec() to find all of the matches in a string:
var pattern = /th\w*/gi;
var s = "There, now that's the ticket");
var a = pattern.exec(s);
while (a != null) {
document.write(a + "<BR>");
a = pattern.exec(s);
}
A RegularExpression object also has a test() method that is comparable to the search() method of String.
When you create a regular expression using the literal syntax, it is compiled into an efficient, internal form when it is interpreted. However, if you use the RegExp() constructor to create a RegularExpression object, that regular expression is compiled each time it is used. Thus, you should only use the RegExp() constructor if you need to repeatedly change the pattern, or if you don't know the pattern (i.e., you are obtaining it from user input). Once you have a RegularExpression object, you can call its compile() method to compile it into the faster, internal form. This is useful when you are using the same regular expression repeatedly.
JavaScript 1.2 allows you to create arrays using literal notation; this notation can be used in place of the constructor function. To do so, simply specify the array name and a list of elements within square brackets:
animals = ["dog", "cat", "horse", "cow"];
This example creates the array animals with four elements; the array has a length of four.
The Array() constructor functions differently under JavaScript 1.2. For compatibility, however, the behavior is only different for scripts that are explictly declared with the LANGUAGE="JavaScript1.2" attribute. If you specify JavaScript 1.2 in the <SCRIPT> tag, when you pass a single argument to the Array() constructor, that argument specifies the first element of the array, not the array's length. For example, the line a = new Array(12) creates an array in which the first indexed element is the value 12, not an array with 12 undefined elements.
The sort() method under JavaScript 1.2 now works on all platforms. In addition, instead of converting undefined elements to null as is done under JavaScript 1.1, sort() now leaves the elements as "undefined" and places them after all of the sorted elements in the array.
The Array object supports two new methods in JavaScript 1.2. The concat() method concatenates two arrays into a new array. In other words, concat() copies the elements from the source array and a specified array into a new array and returns that array. The slice() method extracts a section of an array and returns the elements in that section in a new array. The first argument to slice() specifies the starting element of the slice. The second argument can either be a positive number or a negative number. If it is positive, slice() extracts up to, but does not include, the specified element. If the second argument is negative, it specifies an offset from the end of the array. The second argument can also be omitted, which causes slice() to extract from the starting element through the end of the array.
JavaScript 1.2 also allows you to create an object using literal notation, rather than its constructor function. You specify an object literal by listing its properties and their values within curly braces. For example:
person = { name:"Ernest", age:32, male:true };
This example creates person with three properties. Note that you can also nest object literals as follows:
person = { name:"Ernest", age:32, male:true,
job:{title:"hacker", salary:40000 } };
In addition to the new methods for working with regular expressions, the String object also has some other new methods in JavaScript 1.2. String defines the following new methods:
The split() method of String has some new behavior in JavaScript 1.2. We've already covered the new version of split() that takes a regular expression and uses it to split the string. Both that version and the version that takes a fixed string can take an optional second parameters that limits how many splits are done.
In addition, the version of split() that takes a fixed string works differently in JavaScript 1.2. For compatibility, however, this behavior is only different for scripts that explictly declare that they use the new version of the language. If you specify "JavaScript1.2" as the value of the LANGUAGE attribute, when you call string.split(" "), the method splits the string on any sequence of one or more white space characters.
One final change that also depends on the value of the LANGUAGE attribute involves the substring() method. In earlier versions of JavaScript, if the second argument to substring() is greater than the first argument, the method swaps the two values before it extracts a substring. Now, when JavaScript 1.2 is specified, the method no longer swaps the values and instead generates an error.
In JavaScript 1.2, functions can be defined within other functions. A nested function can use the arguments and variables of the outer function. However, the outer function cannot use the arguments and variables of the nested function. A nested function is only visible within the function in which it is defined; it cannot be invoked outside of that function. Here is an example of a nested function:
function SphereVolume(radius)
{
function Cube(x) {return x * x * x};
return (4 / 3) * Math.PI * Cube(radius);
}
The arguments property of the Function object provides a number of new properties in JavaScript 1.2. Each of the arguments of the function is available as a property of arguments, as are all of the local variables of the function. The caller property contains the arguments property of the calling function, while callee is a reference to the function being called.
The Function object itself has a new arity property in JavaScript 1.2. This property is external to the function; it specifies how many arguments the function expects. Note that this is different from the arguments.length property, which is internal to the function (i.e., it is only available from within the body of the function). The arity property is only available, however, when the LANGUAGE attribute is set to "JavaScript1.2".
The equality comparison operators (== and !=) behave differently in JavaScript 1.2 than they do in previous versions of the language. For compatibility, however, the behavior is only different for scripts that are explictly declared with the LANGUAGE="JavaScript1.2" attribute.
In such scripts, if the two values being compared with == are of different types, the comparison always returns false. In other words, when the operands are of different types, JavaScript makes no attempt to convert them to the same type. This is in contrast to earlier versions of JavaScript, where the interpreter attempts to convert the two values to the same type before comparing them for equality. Thus, in JavaScript 1.1 and JavaScript 1.0, the expression (1 == "1") returns true. In JavaScript 1.2, however, it returns false.
By the same token, if two values being compared with != are of different types, the operator always returns true.
Because these changes in the behavior of the equality and inequality operators are incompatible with previous releases, the changes only happen if the LANGUAGE attribute is explicitly specified. Thus, if a JavaScript 1.2 interpreter runs the following two scripts, it produces a different result in each case:
<SCRIPT> document.write(1 == "1"); // prints true </SCRIPT> <SCRIPT LANGUAGE="JavaScript1.2"> document.write(1 == "1"); // prints false </SCRIPT>
The Number() constructor behaves slightly differently under JavaScript 1.2. If you pass a value that is not a well-formed numeric literal to Number(), it produces the value NaN instead of an error. Consider the following code:
var i = Number("billions and billions");
document.write(i);
In JavaScript 1.2, this code prints NaN.
In addition, when you pass a Date object to the Number() constructor under JavaScript 1.2, the constructor returns a value that specifies the number of milliseconds since midnight, January 1, 1970 UTC. Note that these new behaviors occur whenever a JavaScript 1.2 interpreter runs the above script; they are not dependent on the value of the LANGUAGE attribute.
When you call the toString() method on an object or array under JavaScript 1.2, the method creates a literal representation of the object or array. The literal syntax is the same as that used to create objects and arrays. For compatibility, this behavior only occurs for scripts that are explictly declared with the LANGUAGE="JavaScript1.2" attribute. Consider the following script:
<SCRIPT LANGUAGE="JavaScript1.2">
var person = { name:"Ernest", age:32, male:true };
document.write(person + "<BR>");
animals = ["dog", "cat", "horse", "cow"];
document.write(animals + "<BR>");
</SCRIPT>
When this script is run using a JavaScript 1.2 interpreter, it produces the following:
{name:"Ernest", age:32, male:true}
["dog", "cat", "horse", "cow"]
When the same script is run under a JavaScript 1.2 interpreter without the LANGUAGE attribute, however, it produces:
[object Object] dog,cat,horse,cow
Navigator 4.0 includes some new ways to embed client-side JavaScript within HTML documents.
JavaScript 1.2 includes support for “conditional comments”, a way of evaluating a JavaScript entity as part of an HTML comment. If the JavaScript entity within the comment evaluates to true, the comment is ignored and any JavaScript code within it is executed (and any HTML text is displayed). On the other hand, if the entity evaluates to false, the conditional comment behaves like a normal HTML comment and its contents are ignored.
Conditional comments allow you to write JavaScript code that only runs on platforms that can support it. The code below, for example, only runs if the navigator.platform property (also new in JavaScript 1.2) is equal to the string "win95".
<!--&{navigator.platform == "win95"};
<SCRIPT>
... // JavaScript code goes here
</SCRIPT>
-->
Communicator 4.0 allows JavaScript code to be embedded in an HTML file with the new ARCHIVE attribute of the <SCRIPT> tag. This attribute is much like the ARCHIVE attribute of the <APPLET> tag that is used to embed Java applets in a Web page. The attribute specifies an archive file in Java JAR format (the JAR format is a ZIP file with the addition of a standardized manifest file). The advantage of using a JAR file for storing JavaScript code is that files in a JAR archive can have digital signatures attached to them. A digital signature guarantees the authenticity of the signed code, and, if you trust the signer, this allows you to trust their code as well. We'll talk more about security and signed scripts later in this chapter.
You can add JavaScript methods to buttons in Navigator's new Personal Toolbar with Communicator 4.0. For example, you might add a method that opens a new window to a button in the Personal Toolbar. To do so, you define a bookmark in the Personal Toolbar file as a JavaScript method, rather than an HTML file.
Select Edit Bookmarks to open the Bookmarks window and then open the Personal Toolbar folder. Now select New Bookmark to open a Bookmark Properties dialog. Enter a name for the toolbar button in the Name field and then type the command in the Location field in the form javascript:void(command). Note that you can only use JavaScript methods for command.
For example, to create a new window that takes you directly to O'Reilly's Web site, you could use the following command:
javascript:void(window.open("http://www.ora.com/"))
Navigator 4.0 and JavaScript 1.2 support style sheets, a set of instructions that allow HTML authors greater control over document presentation. Now, text color, font, size, weight, and margins may be scripted for the entire document with only a few commands. Styles may be specified for types of HTML elements--for example, all paragraphs may use pink text--or for individual elements--for example, a single paragraph may appear in purple text. You can also define classes of styles and then assign given elements to those classes.
Navigator 4.0 supports standard Cascading Style Sheets (CSS); it also supports a variant known as JavaScript style sheets. JavaScript style sheets provide essentially the same functionality as cascading style sheets do, but allow specification of document styles using JavaScript syntax, instead of the special-purpose CSS syntax. The syntax for cascading style sheets is purely descriptive, while JavaScript style sheets are, of course, specified in JavaScript and thus have additional run-time flexibility. JavaScript style sheets allow JavaScript to be used in an entirely new way in HTML documents--within <STYLE> tags (that must appear in the <HEAD> of an HTML document).
Both cascading style sheets and JavaScript style sheets support the same basic features. For example, to specify that all <H1> tags appear in bold, red type, you can use the following JavaScript style sheet:
<STYLE TYPE="text/javascript">
document.tags.H1.color="red";
document.tags.H1.fontStyle="bold";
</STYLE>
The same specifications in a cascading style sheet are:
<STYLE TYPE="text/css">
H1 {font-style: bold;
color: red;
}
</STYLE>
Since this is a book on JavaScript, we're not going to cover the syntax of cascading style sheets here. However, you should have a good understanding of cascading style sheets before you try to use JavaScript style sheets. See HTML: The Definitive Guide by Chuck Musciano and Bill Kennedy from O'Reilly for full coverage of cascading style sheets.
With both kinds of style sheets, a style attribute is associated with a property name. For example, the property that sets the font style is fontStyle in JavaScript syntax and font-style in cascading style sheet syntax. Note that case is significant in JavaScript syntax, but not in cascading style sheet syntax. Table 1-1 shows the style sheet properties supported by JavaScript and the corresponding cascading style sheet properties.
| JavaScript Style Sheet Property | Cascading Style Sheet Property |
| align | float |
| backgroundImage | background-image |
| backgroundColor | background-color |
| borderBottomWidth | border-bottom-width |
| borderColor | border-color |
| borderLeftWidth | border-left-width |
| borderRightWidth | border-right-width |
| borderStyle | border-style |
| borderTopWidth | border-top-width |
| clear | clear |
| color | color |
| display | display |
| fontFamily | font-family |
| fontSize | font-size |
| fontStyle | font-style |
| fontWeight | font-weight |
| height | height |
| lineHeight | line-height |
| listStyleType | list-style-type |
| marginBottom | margin-bottom |
| marginLeft | margin-left |
| marginRight | margin-right |
| marginTop | margin-top |
| paddingBottom | padding-bottom |
| paddingLeft | padding-left |
| paddingRight | padding-right |
| paddingTop | padding-top |
| textAlign | text-align |
| textDecoration | text-decoration |
| textIndent | text-indent |
| textTransform | text-transform |
| verticalAlign | vertical-align |
| whiteSpace | white-space |
| width | width |
JavaScript style sheets also support the margins(), paddings(), and borderWidths() methods for setting all four of the margin width, padding, or border width properties at once. For example, here's how you can set all of the margins for <BODY> tags:
tags.BODY.margins("5px", "10px", "5px", "10px");
The margins() method takes four arguments that specify the top, right, bottom, and left margins, in that order. Thus, the above style specification is equivalent to:
tags.BODY.marginTop="5px"; tags.BODY.marginRight="10px"; tags.BODY.marginBottom="5px"; tags.BODY.marginLeft="10px";
The paddings() and borderWidths() methods work in similar ways.
Regardless of whether you use JavaScript style sheet syntax or cascading style sheet syntax, all style sheet properties are mirrored in JavaScript, so you can use JavaScript to work with style sheets.
Within <STYLE> tags (but not within <SCRIPT> tags), you can use the new tags property of the Document object to specify style attributes for various HTML tags. For example, to cause the text of all <P> tags to be displayed in purple, you can use the following JavaScript style sheet in an HTML document:
<HTML>
<HEAD>
<STYLE TYPE="text/javascript">
document.tags.P.color="purple";
</STYLE>
</HEAD>
<BODY>
<P>
All of the text in this paragraph is purple.
</BODY>
</HTML>
The tags property always applies to the Document object for the current document, so you can actually omit document when you use tags. Thus, the following style specification is identical to the one shown above:
tags.P.color="purple";
Tags in an HTML document are nested, which creates an implicit hierarchy of elements in the document. In the example above, the <P> tag is nested within the <BODY> tag, so the <BODY> element is the parent of the <P> element. If our <P> tag included some text in an <EM> tag, the <EM> element would be a child of the <P> element. In most cases, a child element inherits the styles of its parent. Consider the following document:
<HTML>
<HEAD>
<STYLE TYPE="text/javascript">
tags.P.color="purple";
</STYLE>
</HEAD>
<BODY>
<P>
Does absolutely <EM>all</EM> of the text in this paragraph appear purple?
</BODY>
</HTML>
In this case, the text in the <EM> tag appears in purple because it inherits that style from its parent <P> element. We can, however, make the text in the <EM> tag appear green, if we use the following JavaScript style sheet:
<STYLE TYPE="text/javascript">
tags.P.color="purple";
tags.EM.color="green";
</STYLE>
Since <EM> text has been assigned its own style, that style overrides the style it inherits from its parent element.
Style inheritance begins with the top-level element in the document, which is the <BODY> element. Therefore, to create a default style sheet for the entire document, you specify a style for the <BODY> element, as follows:
<STYLE TYPE="text/javascript">
tags.BODY.color="red";
</STYLE>
But what if you want to assign different colors and font styles to different <P> elements? Or specify that <EM> tags within <H1> tags are one color, while <EM> tags within <P> tags are another color? This is where the classes and ids properies of the Document object come into play.
Just like the tags property, the classes and ids properties are only available within <STYLE> tags, not within <SCRIPT> tags. These properties always refer to the current document, so again you don't need to specify document when you use them.
The classes property allows you to define a set of style properties and assign a name to the set so you can use it your document. For example, you could define a style class called purpleitalic that makes text appear in purple italics. You use the all keyword to specify that all tags within the class are affected by a style property, as follows:
<HTML>
<HEAD>
<STYLE TYPE="text/javascript">
classes.purpleitalic.all.color = "purple";
classes.purpleitalic.all.fontStyle = "italic";
</STYLE>
</HEAD>
<BODY>
<H1 CLASS=purpleital>Clothing</H1>
<P CLASS=purpleital>
The clothes of royalty were frequently made from purple cloth.
<P>
However, certain emperors preferred their clothes to be transparent.
</BODY>
</HTML>
Once you have defined a class, you assign various elements in the document to the class using the CLASS attribute. Thus, the <H1> element and the first <P> element above are displayed in purple italics. However, the second <P> element is displayed in the default style because it hasn't been assigned to the class purpleitalic.
You can also identify individual elements as members of a style class. For example, you could define purpleitalic to only apply to <H1> and <P> elements as follows:
<STYLE type="text/javascript">
classes.purpleitalic.P.color="purple";
classes.purpleitalic.P.fontStyle = "italic";
classes.purpleitalic.H1.color = "purple";
classes.purpleitalic.H1.fontStyle = "italic";
</STYLE>
This way, any element assigned to the class purpleitalic only appears in emphasized purple text if it matches the specified conditions. In other words, the text must appear within an <H1> tag or a <P> tag to be displayed in purple italics.
Use the ids property to specify exceptions to style classes. For example:
<HEAD>
<STYLE TYPE="text/javascript">
classes.purplebig.all.color = "purple";
classes.purplebig.all.fontSize = "x-large";
ids.ltpurple.color = "violet";
</STYLE>
</HEAD>
<BODY>
<P CLASS=purplebig>
The clothes of royalty were frequently made from purple cloth.
<EM ID=ltpurple>However</EM>, certain emperors preferred their clothes
to be transparent.
Note the use of the ID attribute to indicate that the text in the <EM> tag should be displayed using a different color.
JavaScript style sheets also support contextual styles, which is one of the most powerful features of cascading style sheets. A contextual style is one that is only applied to an element if that element appears in a certain context, or in other words, if it is nested in a certain way in a document. With contextual styles, you can, for example, specify style attributes for all <EM> tags within <H1> tags or for all <LI> elements nested within two <OL> elements.
You specify contextual styles using the contextual() method within <STYLE> tags. This method takes arguments that specify the nesting order, from the outermost element to the innermost element, for a contextual style. When the last element you specify appears in the context of all the other elements, in the proper order, the contextual style is applied to that element. Thus, to make all <EM> text within <P> text appear in a certain color, you could use the following:
<STYLE TYPE="text/javascript">
contextual(tags.P, tags.EM).color = "blue";
</STYLE>
Here's another example that shows how you can use contextual styles to set up the classic formatting for an outline:
<STYLE TYPE="text/javascript">
contextual(tags.OL, tags.LI).listStyleType = "upper-alpha";
contextual(tags.OL, tags.OL, tags.LI).listStyleType = "upper-roman";
contextual(tags.OL, tags.OL,
tags.OL, tags.LI).listStyleType = "lower-alpha";
contextual(tags.OL, tags.OL,
tags.OL, tags.OL, tags.LI).listStyleType = "decimal";
</STYLE>
When you specify a contextual style, you can include any combination of tags, classes, and ids. So, for example, we could redo our previous purplebig example as follows:
<HEAD>
<STYLE TYPE="text/javascript">
classes.purplebig.all.color = "purple";
classes.purplebig.all.fontSize = "x-large";
contextual(classes.purplebig.all, tags.EM).color = "violet";
</STYLE>
</HEAD>
<BODY>
<P CLASS=purplebig>
The clothes of royalty were frequently made from purple cloth.
<EM>However</EM>, certain emperors preferred their clothes
to be transparent.
In this example, any text in an <EM> tag within a purplebig element is displayed in violet.
Navigator 4.0 supports new <LAYER> and <ILAYER> tags that allow HTML text and other objects to be positioned at absolute coordinates within a window and to be stacked on top of each other (hence the name "layer"). Layers function as sub-documents that can be independently positioned, stacked, and hidden. They support some dramatic new forms of JavaScript animation and they give Web-page designers pixel-level control over the contents of a page. Each Document object has a layers[] array that lists the Layer objects it contains, and each Layer object has a document property that refers to the HTML document it contains. Layers can be dynamically created with the Layer() constructor.
You can create a Layer object with either the <LAYER> tag or the <ILAYER> tag. <LAYER> creates a layer relative to the upper-left corner of containing layer, or the document itself if it is a top-level layer, while <ILAYER> creates an inline layer. JavaScript adds the optional OnBlur, OnFocus, OnLoad, OnMouseOut, and OnMouseOver event-handler attributes to these tags.
<[I]LAYER
[ ID="layer_name" ] used to name the layer in JavaScript
[ LEFT=left_pos ] relative horizontal position of the layer
[ TOP=top_pos ] relative vertical position of the layer
[ PAGEX=x_pos ] absolute horizontal position of the layer
[ PAGEY=y_pos ] absolute vertical position of the layer
[ SRC="filename" ] HTML source file for the layer
[ WIDTH=width ] width of layer
[ HEIGHT=height ] height of layer
[ CLIP=left,top,right,bottom ] clipping rectangle of the layer
[ Z-INDEX=stack_pos ] stacking order of layer
[ ABOVE=above_layer ] layer above this one in stacking order
[ BELOW=below_layer ] layer below this one in stacking order
[ VISIBILITY=(SHOW|HIDDEN|INHERIT) ] visibility of the layer
[ BGCOLOR="color" ] background color of the layer
[ BACKGROUND="image_file" ] background image of the layer
[ OnBlur="handler" ] a handler invoked when layer loses keyboard focus
[ OnFocus="handler" ] a handler invoked when layer gets keyboard focus
[ OnLoad="handler" ] a handler invoked when layer is loaded
[ OnMouseOver="handler" ] a handler invoked when mouse enters layer
[ OnMouseOut="handler" ] a handler invoked when mouse leaves layer
>
layer contents goes here
</[I]LAYER>
Here's an example that uses two layers to create a drop shadow effect:
<HTML>
<HEAD>
<STYLE TYPE="text/javascript">
classes.bluehead.H1.color= "blue";
classes.redhead.H1.color= "red";
</STYLE>
</HEAD>
<BODY>
<LAYER ID="red" LEFT=2 TOP=2>
<H1 CLASS=redhead>Wake Up!</H1>
</LAYER>
<LAYER ID="blue" LEFT=0 TOP=0>
<H1 CLASS=bluehead>Wake Up!</H1>
</LAYER>
</BODY>
</HTML>
The <NOLAYER> tag can be used like the <NOFRAMES> tag; it specifies content to be displayed if the user's browser does not support layers.
The JavaScript Layer object corresponds to the HTML <LAYER> and <ILAYER> tags. The properties of the Layer object are:
The methods of the Layer object are:
Now that we can manipulate layers with the Layer object, we can expand upon our earlier drop shadow example. Here's a version of it that "animates" the text once the user moves the pointer over it:
<HTML>
<HEAD>
<SCRIPT>
function flicker() {
var b = document.layers["blue"];
var r = document.layers["red"];
if (b.above == r)
b.moveAbove(r);
else
r.moveAbove(b);
setTimeout("flicker()", 10);
}
</SCRIPT>
<STYLE TYPE="text/javascript">
classes.bluehead.H1.color= "blue";
classes.redhead.H1.color= "red";
</STYLE>
</HEAD>
<BODY>
<LAYER ID="red" LEFT=2 TOP=2>
<H1 CLASS=redhead>Wake Up!</H1>
</LAYER>
<LAYER ID="blue" LEFT=0 TOP=0 OnMouseOver="flicker()">
<H1 CLASS=bluehead>Wake Up!</H1>
</LAYER>
</BODY>
</HTML>
In JavaScript 1.2, every Document object has a layers[] property that contains an array of all the top-level layers in the document. You can access a layer in the layers[] array with its index or its name, as specified by the ID attribute. The Layer objects in a layers[] array appear in stacking order, from back to front, so document.layers[0] is the bottommost layer. You can set properties for a layer using the layers[] array:
document.layers[0].left=100;
To find the number of layers in the layers[] array, use the new layers.length property of the Document object.
As listed above, each Layer object has its own document property. This property refers to the Document object contained in the layer; this object has all of the usual properties of a document. This Document object in turn has its own layers[] array. This layers[] array contains all of the layers nested within the enclosing layer.
The online documentation for JavaScript 1.2 says that you can create new Layer objects with the Layer() constructor, but this constructor does not work as advertised. In fact, it doesn't work very well at all, so we're not going to discuss it here.
After you have created a layer (using the <LAYER> or <ILAYER>tag) and after the page that contains the layer has finished loading, you can modify the contents of the layer using the write() method of the layer's document object. Of course, you can also adjust any other properties of the layer, as shown in the following example:
<HTML>
<HEAD>
<SCRIPT>
function moveLayer() {
var l = document.layers["jumpy"];
l.left = 100;
l.document.write ("<H1>Skip!</H1>");
l.document.close ();
}
</SCRIPT>
</HEAD>
<BODY>
<LAYER ID="jumpy" BGCOLOR="red">
<H1>Hop!</H1>
</LAYER>
<LAYER TOP=100>
<FORM NAME="form">
<INPUT TYPE=button VALUE="Move Layer" onClick="moveLayer(); return false;">
</FORM>
</LAYER>
</BODY>
</HTML>
When you use the write() method to change the contents of a layer, the original contents is replaced by the new contents. After you write to the document of a layer, you need to call close(). You can only have one layer open for writing at a time.
Navigator 4.0 and JavaScript 1.2 support a much more flexible event handling scheme. There is a new Event object that contains properties that describe the details of an event. Every time an event handler is invoked, it is passed an Event object. In addition, there is a much larger set of event handlers, and a well defined event-handling hierarchy. It is possible, for example, for individual Layers to respond to mouse events and individual keystrokes that occur over them.
An Event object represents the properties of a JavaScript event; it is passed as an argument to an event handler when the event takes place. These properties can include the type of event, the position of the mouse, whether and/or which mouse button was pressed, whether any modifier keys were held down, and the target of the event. The actual properties used by a particular event are specific to that event type.
The Event object has the following properties:
Note that all of the properties of the Event object are read-only properties.
Table 1-2 lists the events supported in JavaScript 1.2 and the properties that they use.
| Event | Properties Set |
| Abort | type, target |
| Blur | type, target |
| Click | type, target, x, y, layerX, layerY, pageX, pageY, screenX, screenY, which, modifiers |
| Change | type, target |
| DblClick | type, target, x, y, layerX, layerY, pageX, pageY, screenX, screenY, which, modifiers |
| DragDrop | type, target, data |
| Error | type, target |
| Focus | type, target |
| KeyDown | type, target, x, y, layerX, layerY, pageX, pageY, screenX, screenY, which, modifiers |
| KeyPress | type, target, x, y, layerX, layerY, pageX, pageY, screenX, screenY, which, modifiers |
| KeyUp | type, target, x, y, layerX, layerY, pageX, pageY, screenX, screenY, which, modifiers |
| Load | type, target |
| MouseDown | type, target, x, y, layerX, layerY, pageX, pageY, screenX, screenY, which, modifiers |
| MouseMove | type, target, x, y, layerX, layerY, pageX, pageY, screenX, screenY |
| MouseOut | type, target, x, y, layerX, layerY, pageX, pageY, screenX, screenY |
| MouseOver | type, target, x, y, layerX, layerY, pageX, pageY, screenX, screenY |
| MouseUp | type, target, x, y, layerX, layerY, pageX, pageY, screenX, screenY, which, modifiers |
| Move | type, target, screenX, screenY |
| Reset | type, target |
| Resize | type, target, width, height |
| Select | type, target |
| Submit | type, target |
| Unload | type, target |
The following example shows how you can print out all of the properties of an Event object:
<SCRIPT>
function printEvent(e) {
for (prop in e) P
document.write(prop + ": " + e[prop] + "<br>");
}
}
document.onMouseDown = printEvent;
</SCRIPT>
Table 1-3 lists the event handlers supported by the various objects in JavaScript 1.2.
| Event Handler | Description | Supported By |
| onAbort | Invoked when the aborts the download of an image | Image |
| onBlur | Invoked when an object loses the keyboard focus | Element, Window, Layer |
| onClick | Invoked when the user clicks on an object | Document, Button, Checkbox, Link, Radio, Reset, Submit |
| onChange | Invoked when the value of an object changes | Element, FileUpload, Password, Select, Text, Textarea |
| onDblClick | Invokved when the user double-clicks on an object | Document, Area, Link |
| onDragDrop | Invoked when the user drops an object into a window | Window |
| onError | Invoked when an error occurs | Image, Window |
| onFocus | Invoked when an object gains the keyboard focus | Element, Window, Layer |
| onKeyDown | Invoked when the user presses down a key on the keyboard | Document, Image, Link, Textarea |
| onKeyPress | Invoked when the user presses or holds down a key on the keyboard | Document, Image, Link, Textarea |
| onKeyUp | Invoked when the user releases a key on the keyboard | Document, Image, Link, Textarea |
| onLoad | Invoked when an object is loaded | Document, Image, Window, Layer |
| onMouseDown | Invoked when the user presses down on a mouse button | Button, Document, Link |
| onMouseMove | Invoked when the user moves the mouse | None; only called when requested by captureEvents() |
| onMouseOut | Invoked when the user moves the mouse out of an object | Area, Layer, Link |
| onMouseOver | Invoked when the user moves the mouse over an object | Area, Layer, Link |
| onMouseUp | Invoked when the user releases a mouse button | Button, Document, Link |
| onMove | Invoked when a window or frame is moved | Window, Frame |
| onReset | Invoked just before a form is reset | Form |
| onResize | Invoked when a window or frame is resized | Window, Frame |
| onSubmit | Invoked just before a form is submitted | Form |
| onUnload | Invoked when an object is unloaded | Document, Window |
Note that Navigator 4.0 supports both mixed-case and lower-case event handlers. Thus, both onclick and onClick are recognized.
JavaScript 1.2 also supports event capture, whereby a window, document, or layer can capture and handle an event before that event reaches its intended target. So, for example, you can intercept mouse clicks over a hypertext link before the link is even aware of the click. Event capture is also the only way that you can get MouseMove events in JavaScript. The Window, Document, and Layer objects have the following new methods to support event capture:
captureEvents()
releaseEvents()
routeEvents()
handleEvents()
Note that the Layer object does not support the handleEvent() method.
captureEvents() is the primary method for capturing events. You pass this method an argument that specifies the event you are interested in. For example:
window.captureEvents(Event.CLICK);
The argument is a property of the Event object that indicates the type of event to capture. If you want to capture multiple events, you can use the bitwise OR operator (|) to combine events:
window.captureEvents(Event.KEYDOWN | Event.KEYUP);
After you specify the events to be captured, you need to define a function to handle the events. Such as function always takes an argument e that is the Event object. You have four choices for how to handle the event:
The event-handling function can do what it needs to do and then return true. If you've intercepted a click event for a hypertext link, the link is followed. Otherwise, this ends event handling for that event.
The event-handling function can do what it needs to do and then return false. If you've intercepted a click event for a link, the link is not followed. Otherwise, this ends event handling for that event.
The event-handling function can call the routeEvent method, which looks for other event handlers for the event. If another object is attempting to capture the event, its event handler is called. routeEvent() returns the value returned by that event handler, so the intercepting object can look at the value and decide what to do:
function eventHandler(e) {
var handled = window.routeEvent(e);
if (handled == false) {
// Do something
return false;
}
else {
// Do something different
return true;
}
}
Note, however, that if routeEvent() calls an event handler that displays a new page, the capturing object does not regain control.
If routeEvent() does not find any objects that are attempting to capture the event, it looks for an event handler for the event's intended target.
The event-handling function can call the handleEvent() method of an event receiver to bypass the capturing hierarchy. Any object that can register an event handler for a particular type of event is an event receiver for that type of event. handleEvent() explicitly calls the event handler of the event receiver.
Once you have defined an event-handling function, all that remains is to register the function as the event handler for that event. For example:
window.onClick = eventHandler;
Here's a complete example that shows how to capture MouseMove events to create a moveable wake-up call:
<HTML>
<HEAD>
<STYLE TYPE="text/javascript">
classes.bluehead.H1.color= "blue";
classes.redhead.H1.color= "red";
</STYLE>
</HEAD>
<BODY>
<LAYER ID="red" LEFT=102 TOP=2>
<H1 CLASS=redhead>Wake Up!</H1>
</LAYER>
<LAYER ID="blue" LEFT=100 TOP=0>
<H1 CLASS=bluehead>Wake Up!</H1>
</LAYER>
<SCRIPT>
function moveHandler(e) {
var b = document.layers["blue"];
var r = document.layers["red"];
var xmove = 2;
if ((b.left + b.clip.width)/2 < e.pageX) {
xmove = -2;
}
b.moveBy (xmove, 0);
r.moveBy (xmove, 0);
return false;
}
document.layers["blue"].captureEvents(Event.MOUSEMOVE);
document.layers["blue"].onMouseMove = moveHandler;
</SCRIPT>
</BODY>
</HTML>
If you have a window with frames that are loaded from different locations and you want to capture events in those frames, you must use captureEvents() in a signed script. Furthermore, you must call the enableExternalCapture method of the Document object to enable this feature.
Another major change in JavaScript 1.2 is the new security model. The experimental data tainting model has been discarded and replaced with the more robust model used by Java applets. The model is conceptually fairly simple: JavaScript code signed by an entity that the user has declared to be trusted can have privileges that untrusted code does not. Those privileges include things like viewing the contents of the History array and submitting forms by email. Essentially, the "hobbles" imposed on untrusted code are lifted for trusted code. In order to take advantage of these new capabilities, JavaScript code must be digitally signed, included in a JAR file, and it must use LiveConnect to invoke Java methods that temporarily enable additional privileges.
If one script on a page requires privileges, it must be signed--and so must every other script on the page. You can sign JavaScript files, inline scripts, and event handler scripts. Use JAR Packager to sign JavaScript files and Page Signer to sign inline scripts and event handler scripts.
Much like Java applets, signed scripts require the <SCRIPT> tag's ARCHIVE attribute to specify the name of the JAR file that contains the digital signature. For example:
<SCRIPT ARCHIVE="anArchive.jar" SRC="myScript.js"> </SCRIPT>
You need specify this archive only once per document; other scripts on the same page can utilize the same JAR file.
Since event handler scripts do not use the <SCRIPT> tag, they obviously cannot specify an ARCHIVE attribute. Instead, an event handler script must be preceded by a script that contains an ARCHIVE attribute:
<SCRIPT ARCHIVE="anArchive.jar" ID="1"> ... </SCRIPT> <A HREF="http://www.ora.com/" onMouseOver="window.status='The World Wide Web site of my employer'; return true" ID="2">O'Reilly & Associates</A>
Notice that both the inline script and the event handler script above specify ID attributes. This attribute is required for signed inline scripts and event handler scripts; it associates the script with its digital signature in the JAR file. Each script in a JAR archive has a unique ID. Should you have more than one event handler for an element, all of the event handlers are signed as a discrete whole, so the element only one ID attribute.
The four types of privileges that may be granted are as follows:
The UniversalBrowserRead privilege allows you to read privileged data from the browser.
The UniversalBrowserWrite privilege allows you to modify privileged data in the browser.
The UniversalFileRead privilege allows a script to set the value property of a FileUpload form element. This means that the script has the ability to upload an arbitrary local file to the server where the form is submitted.
The UniversalSendMail privilege allows a script to send email in the user's name.
To request a privilege, a script includes a function that calls Netscape's Java security classes. To do so, include a line of code that requests permission to access the appropriate privilege:
netscape.security.PrivilegeManager.enablePrivilege("somePrivilege")
When the script calls this function, its digital signature is verified. If the signature is valid, the privilege is granted. The privilege is only granted in the scope of the requesting function, after enablePrivilege() is called. If the requesting function calls any other functions, those functions have the privilege. However, once the script leaves the requesting function, the privilege no longer applies.
The UniversalBrowserRead privilege allows a script to:
Get the values of user preferences using the preferences() method of the Navigator object (as documented in the next section).
Get the value of the data property of a DragDrop event object.
Use an about: URL other than about:blank.
The UniversalBrowserWrite privilege allows a script to:
Set any property of the Event object.
Set the values of user preferences using the preferences() method of the Navigator object (as documented in the next section).
Set various properties and use various methods of the Window object (as discussed in the next section).
The UniversalFileRead privilege allows a script to set the value property of a FileUpload form element.
The UniversalSendMail privilege allows a script to submit a form using a mailto: or news: URL.
JavaScript 1.2 provides new Window properties and methods, a new Screen object, and new properties of the Navigator object. All of these new features give scripts more power to control their appearance and behavior within the browser window.
The new Window methods allow a program to resize and re-position windows, bring up a Print dialog, activate the Forward and Back browser buttons, and so on. Because of the power of these new methods, most of them are restricted to trusted scripts that have been digitally signed as described in the previous section.
The new Window properties are:
We've already covered the new Window methods that support event capture, namely captureEvents(), releaseEvents(), routeEvent(), handleEvent(), enableExternalCapture(), and disableExternalCapture(), so we're not going to discuss those methods here. The rest of the new Window methods are:
In JavaScript 1.2, the open() method of the Window object can specify new features of the window it creates, as part of the features argument. These new features are:
Three new Window methods are also methods of the Frame object. These methods are:
The new Screen object provides information about the size and color depth of the screen on which Navigator is running. This allows JavaScript applications to customize themselves based on available screen real-estate, for example.
The properties of the Screen object are:
The Navigator object has new properties that allow JavaScript programs to customize themselves based on the current platform and on the user's preferred language. These properties are:
The Navigator object also has a new method, preferences(), that allows a signed script to get and set certain user preferences.
JavaScript: The Definitive Guide
O'Reilly Home |
Catalog |
Customer Service |
About O'Reilly |
Contact Us |
Index