Professional Documents
Culture Documents
Javascript Tutorial-Mainwith ALL
Javascript Tutorial-Mainwith ALL
As shown, the top-level object is window. The document object is a child of window and all the objects (i.e, elements)
that appear on the page (e.g, forms, links, images, tables, etc.) are descendants of the document object. These objects
can have children of their own. For example, form objects generally have several child objects, including textboxes,
radio buttons, and select menus.
JavaScript Syntax
Basic Rules
Syntax
// This is a single-line comment
/*
This is
a multi-line
comment.
*/
Dot Notation
In JavaScript, objects can be referenced using dot notation, starting with the highest-level object (i.e, window). Objects
can be referred to by name or id or by their position on the page. For example, if there is a form on the page named
"loginform", using dot notation you could refer to the form as follows:
Syntax
window.document.loginform
Assuming that loginform is the first form on the page, you could also refer to this way:
Syntax
window.document.forms[0]
A document can have multiple form elements as children. The number in the square brackets ([]) indicates the specific
form in question. In programming speak, every document object contains an array of forms. The length of the array
could be zero (meaning there are no forms on the page) or greater. In JavaScript, arrays are zero-based, meaning that
the first form on the page is referenced with the number zero (0) as shown in the syntax example above.
Objects can also be referenced using square bracket notation as shown below.
Syntax
window['document']['loginform']
// and
window['document']['forms[0]']
Dot notation and square bracket notation are completely interchangeable. Dot notation is much more common;
however, as we will see later in the course, there are times when it is more convenient to use square bracket notation.
After the user clicks the OK button, the page will finish loading and will appear as follows.
The text "Hello, there!" is written dynamically by the code in JavaScriptBasics/Demos/Script.js. We will look at the
code in this file and in JavaScriptBasics/Demos/JavaScript.html again shortly.
To illustrate objects, methods and properties, we will look at the code in JavaScriptBasics/Demos/JavaScript2.html, a
slightly modified version of JavaScriptBasics/Demos/JavaScript.html, which we looked at earlier, and at the code in
JavaScriptBasics/Demos/Script2.js.
Methods
window.alert()
HTML pages are read and processed from top to bottom. The JavaScript code in the initial script block at the top of
JavaScriptBasics/Demos/JavaScript2.html calls the alert() method of the window object. When the browser reads that
line of code, it will pop up an alert box and will not continue processing the page until the user presses the OK button.
Once the user presses the button, the alert box disappears and the rest of the page loads.
document.write()
The write() method of the document object is used to write out code to the page as it loads. In
JavaScriptBasics/Demos/Script2.js, it simply writes out "Hello, there!"; however, it is more often used to write out
dynamic data, such as the date and time on the user's machine.
Arguments
Syntax
object.method(argument1, argument2);
The alert() and write() methods shown in the example above each take only one argument: the message to show.
Properties
Properties are the adjectives of JavaScript. They describe qualities of objects and, in some cases are writable (can be
changed dynamically).
document.bgColor
The bgColor property of the document object is read-write. Looking back at JavaScriptBasics/Demos/JavaScript2.html,
the four span elements use the onclick event handler to catch click events. When the user clicks on a span, JavaScript is
used to change the value of the bgColor property to a new color.
The window object is always the implicit top-level object and therefore does not have to be included in references to
objects. For example, window.document.write() can be shortened to document.write(). Likewise, window.alert() can be
shortened to just alert().
A very common way to reference HTML elements is by their ID using the getElementById() method of the document
object as shown in the example below.
Event Handlers
In JavaScriptBasics/Demos/JavaScript2.html, we used the onclick event handler to call JavaScript code that changed
the background color of the page. Event handlers are attributes that force an element to "listen" for a specific event to
occur. Event handlers all begin with the letters "on". The table below lists the HTML event handlers with descriptions.
Duration: 15 to 25 minutes.
In this exercise, you will use some of the event handlers from the table above to allow the user to change the
background color of the page.
1. Add functionality so that when the user presses any key, the background color turns white.
2. Add a "Black" button. When the user hovers over this button and presses the mouse button down, the
background color should turn black. When the user releases the mouse button, the background color should
turn white.
To continue to learn JavaScript go to the top of this page and click on the next lesson in this JavaScript Tutorial's Table
of Contents.
Variables, Arrays and Operators
In this lesson of the JavaScript tutorial, you will learn...
JavaScript Variables
Variables are used to hold data in memory. JavaScript variables are declared with the var keyword.
var age;
age = 34;
A Loosely-typed Language
JavaScript is a loosely-typed language. This means that you do not specify the data type of a variable when declaring it.
It also means that a single variable can hold different data types at different times and that JavaScript can change the
variable type on the fly. For example, the age variable above is an integer. However, the variable strAge below would
be a string (text) because of the quotes.
If you were to try to do a math function on strAge (e.g, multiply it by 4), JavaScript would dynamically change it to an
integer. Although this is very convenient, it can also cause unexpected results, so be careful.
The following example uses the prompt() method of the window object to collect user input. The value entered by the
user is then assigned to a variable, which is accessed when the user clicks on one of the span elements.
As the page loads, a prompt pops up asking the user to enter a color.
This is done with the prompt() method of the window object. The prompt() method is used to get input from the user. It
takes two arguments:
If the OK button is pressed, the prompt returns the value entered in the textbox. If the Cancel button or the close button
(the red X) is pressed, the prompt returns null. The line below assigns whatever is returned to the variable
USER_COLOR.
A script block with a call to document.write() is then used to output the color entered by the user. This output is
contained within a span element, which has an onclick event handler that will be used to turn the background color of
the page to the user-entered color.
Duration: 5 to 15 minutes.
Arrays
An array is a grouping of objects that can be accessed through subscripts. At its simplest, an array can be thought of as
a list. In JavaScript, the first element of an array is considered to be at position zero (0), the second element at position
one (1), and so on. Arrays are useful for storing data of similar types.
It is also possible and very common to use the [] literal to declare a new Array object.
myarray[0] = value1;
myarray[1] = value2;
myarray[2] = value3;
The following example is similar to the previous one, except that it prompts the user for four different colors and places
each into the USER_COLORS array. It then displays the values in the USER_COLORS array in the spans and assigns
them to document.bgColor when the user clicks on the spans.
Unlike in some languages, values in JavaScript arrays do not all have to be of the same data type.
The next four lines populate the array with user-entered values.
The body of the page contains a paragraph with four span tags, the text of which is dynamically created with values
from the USER_COLORS array.
Duration: 15 to 25 minutes.
Associative Arrays
Whereas regular (or enumerated) arrays are indexed numerically, associative arrays are indexed using names as keys.
The advantage of this is that the keys can be meaningful, which can make it easier to reference an element in an array.
The example below illustrates how an associative array is used.
The tables below show some of the most useful array properties and methods. All of the examples assume an array
called BEATLES that holds "Paul", "John", "George", and "Ringo".
The file above illustrates the use of the concatenation operator and several math operators. It also illustrates a potential
problem with loosely-typed languages. This page is processed as follows:
1. The user is prompted for a number and the result is assigned to USER_NUM1.
2. An alert pops up telling the user what number she entered. The concatenation operator (+) is used to combine
two strings: "You chose " and the number entered by the user. Note that all user-entered data is always
treated as a string of text, even if the text consists of only digits.
3. The user is prompted for another number and the result is assigned to USER_NUM2.
4. Another alert pops up telling the user what number she entered.
5. Five variables are declared and assigned values.
6. var NUMS_ADDED = USER_NUM1 + USER_NUM2;
7. var NUMS_SUBTRACTED = USER_NUM1 - USER_NUM2;
8. var NUMS_MULTIPLIED = USER_NUM1 * USER_NUM2;
9. var NUMS_DIVIDED = USER_NUM1 / USER_NUM2;
var NUMS_MODULUSED = USER_NUM1 % USER_NUM2;
10. The values the variables contain are output to the browser.
So, 5 + 4 is 54! It is when 5 and 4 are strings, and, as stated earlier, all user-entered data is treated as a string. In the
lesson on JavaScript Functions, you will learn how to convert a string to a number.
Duration: 15 to 25 minutes.
To continue to learn JavaScript go to the top of this page and click on the next lesson in this JavaScript Tutorial's Table
of Contents.
JavaScript Functions
In this lesson of the JavaScript tutorial, you will learn...
Built-in Functions
JavaScript has a number of built-in functions. We will examine some of them in this section.
Number(object)
The Number() function takes one argument: an object, which it attempts to convert to a number. If it cannot, it returns
NaN, for "Not a Number."
Because STR_NUM1 and STR_NUM2 are both strings, the + operator concatenates them, resulting in "12".
After the Number() function has been used to convert the strings to numbers, the + operator performs addition,
resulting in 3.
String(object)
The String() function takes one argument: an object, which it converts to a string.
Code Sample: JavaScriptFunctions/Demos/String.html
<html>
<head>
<title>String() Function</title>
<script type="text/javascript">
var INT_NUM1 = 1;
var INT_NUM2 = 2;
var INT_SUM = INT_NUM1 + INT_NUM2; //returns 3
alert(INT_SUM);
Because INT_NUM1 and INT_NUM2 are both numbers, the + operator performs addition, resulting in 3.
var INT_NUM1 = 1;
var INT_NUM2 = 2;
var INT_SUM = INT_NUM1 + INT_NUM2; //returns 3
alert(INT_SUM);
After the String() function has been used to convert the numbers to string, the + operator performs concatenation,
resulting in "12".
isNaN(object)
The isNaN() function takes one argument: an object. The function checks if the object is not a number (or cannot be
converted to a number). It returns true if the object is not a number and false if it is a number.
The parseFloat() function takes one argument: a string. If the string begins with a number, the function reads through
the string until it finds the end of the number, hacks off the remainder of the string, and returns the result. If the string
does not begin with a number, the function returns NaN.
The parseInt() function also takes one argument: a string. If the string begins with an integer, the function reads through
the string until it finds the end of the integer, hacks off the remainder of the string, and returns the result. If the string
does not begin with an integer, the function returns NaN.
Duration: 15 to 25 minutes.
In this exercise, you will practice working with JavaScript's built-in functions.
1. Open JavaScriptFunctions/Exercises/BuiltinFunctions.html for editing.
2. Modify the file so that it outputs the sum of the two numbers entered by the user.
Create a new HTML file that prompts the user for his name, the age at which he first worked on a computer, and his
current age. After gathering this information, pop up an alert that tells the user how many years he's been working on a
Notice that the program is able to deal with numbers followed by strings (e.g, "12 years old").
User-defined Functions
Writing functions makes it possible to reuse code for common tasks. Functions can also be used to hide complex code.
For example, an experienced developer can write a function for performing a complicated task. Other developers do not
need to know how that function works; they only need to know how to call it.
Function Syntax
JavaScript functions generally appear in the head of the page or in external JavaScript files. A function is written using
the function keyword followed by the name of the function.
Syntax
function doSomething(){
//function statements go here
}
As you can see, the body of the function is contained with in curly brackets ({}). The following example demonstrates
the use of simple functions.
function changeBgWhite(){
document.bgColor = "white";
}
</script>
</head>
<body>
<p align="center">
<span onclick="changeBgRed();">Red</span> |
<span onclick="changeBgWhite();">White</span>
</p>
</body>
</html>
The functions above aren't very useful because they always do the same thing. Every time we wanted to add another
color, we would have to write another function. Also, if we want to modify the behavior, we will have to do it in each
function. The example below shows how to create a single function to handle changing the background color.
As you can see, when calling the changeBG() function, we pass a value (e.g, 'red'), which is assigned to the color
variable. We can then refer to the color variable throughout the function. Variables created in this way are called
function arguments or parameters. A function can have any number of arguments, separated by commas.
Variables created through function arguments or declared within a function with var are local to the function, meaning
that they cannot be accessed outside of the function.
Variables declared with var outside of a function and variables that are used without being declared are global, meaning
that they can be used anywhere on the page.
Duration: 15 to 25 minutes.
In this exercise, you will modify a page called ColorMania.html, which will contain a form with four buttons. Each
button will show the name of a color (e.g, red) and, when clicked, call a function that changes the background color.
The buttons you will create will be of type button. For example,
/*
Write a function called changeBg() that changes the background
color and then pops up an alert telling the user, by name, what
the new background color is.
*/
</script>
</head>
<body>
<form style="text-align:center">
<!--ADD BUTTONS HERE-->
</form>
</body>
</html>
Add another button called "custom" that, when clicked, prompts the user for a color, then changes the background color
to the user-entered color and alerts the user to the change.
The return keyword is used to return values from functions as the following example illustrates.
function getBgColor(){
return document.bgColor;
}
</script>
</head>
<body>
<form>
<input type="button" value="Set Background Color"
onclick="setBgColor();">
<input type="button" value="Get Background Color"
onclick="alert(getBgColor());">
</form>
</body>
</html>
Code Explanation
When the user clicks on the "Get Background Color" button, an alert pops up with a value returned from the
getBgColor() function. This is a very simple example. Generally, functions that return values are a bit more involved.
We'll see many more functions that return values throughout the course.
To continue to learn JavaScript go to the top of this page and click on the next lesson in this JavaScript Tutorial's Table
of Contents.
Built-In JavaScript Objects
In this lesson of the JavaScript tutorial, you will learn...
JavaScript has some predefined, built-in objects that do not fit into the HTML DOM, meaning that they are not direct
descendants of the window object.
String
In JavaScript, there are two types of string data types: primitive strings and String objects. String objects have many
methods for manipulating and parsing strings of text. Because these methods are available to primitive strings as well,
in practice, there is no need to differentiate between the two types of strings.
Some common string properties and methods are shown below. In all the examples, the variable MY_STRING contains
"Webucator".
charCodeAt(position)
Returns the Unicode character code of MY_STRING.charCodeAt(4)
the character at the specified position. //Returns 99
Math
The Math object is a built-in static object. The Math object's properties and methods are accessed directly (e.g,
Math.PI) and are used for performing complex math operations. Some common math properties and methods are
shown below.
var LOW = 1;
var HIGH = 10;
var RND1 = Math.random();
var RND2 = Math.round(RND1 * (HIGH - LOW) + 1);
Date
The Date object has methods for manipulating dates and times. JavaScript stores dates as the number of milliseconds
since January 1, 1970. The sample below shows the different methods of creating date objects, all of which involve
passing arguments to the Date() constructor.
<pre>
//Syntax: new Date(yyyy, mm, dd, hh, mm, ss, ms);
RED_SOX_WINS = new Date(2004, 9, 21, 12, 01, 00, 00);
</pre>
<b>Result:</b>
<script type="text/javascript">
RED_SOX_WINS = new Date(2004, 9, 21, 12, 01, 00, 00);
document.write(RED_SOX_WINS);
</script>
</body>
</html>
Code Explanation
• To create a Date object containing the current date and time, the Date() constructor takes no arguments.
• When passing the date as a string to the Date() constructor, the time portion is optional. If it is not included, it
defaults to 00:00:00. Also, other date formats are acceptable (e.g, "10/21/2004" and "10-04-2004").
• When passing date parts to the Date() constructor, dd, hh, mm, ss, and ms are all optional. The default of each
is 0.
• Months are numbered from 0 (January) to 11 (December). In the example above, 9 represents October.
Some common date methods are shown below. In all the examples, the variable RIGHT_NOW contains "Thu Apr 14
00:23:54:650 EDT 2005".
Common Date Methods
Method Description Example
RIGHT_NOW.getDate();
getDate() Returns the day of the month (1-31). //Returns 14
getDay()
Returns the day of the week as a number (0-6, RIGHT_NOW.getDay();
0=Sunday, 6=Saturday). //Returns 4
getMonth()
Returns the month as a number (0-11, 0=January, RIGHT_NOW.getMonth();
11=December). //Returns 3
RIGHT_NOW.getFullYear();
getFullYear() Returns the four-digit year. //Returns 2005
RIGHT_NOW.getHours();
getHours() Returns the hour (0-23). //Returns 0
RIGHT_NOW.getMinutes();
getMinutes() Returns the minute (0-59). //Returns 23
RIGHT_NOW.getSeconds();
getSeconds() Returns the second (0-59). //Returns 54
RIGHT_NOW.getMilliseconds();
getMilliseconds() Returns the millisecond (0-999). //Returns 650
getTime()
Returns the number of milliseconds since midnight RIGHT_NOW.getTime();
January 1, 1970. //Returns 1113452634650
getTimezoneOffset()
Returns the time difference in minutes between the RIGHT_NOW.getTimezoneOffset();
user's computer and GMT. //Returns 240
RIGHT_NOW.toLocaleString();
toLocaleString() Returns the Date object as a string. //Returns Thursday, April 14,
//2005 12:23:54 AM
RIGHT_NOW.toGMTString();
toGMTString()
Returns the Date object as a string in GMT //Returns Thu, 14 Apr 2005
timezone. //04:23:54 UTC
typeof Operator
The typeof operator is used to find out the type of a piece of data. The screenshot below shows what the typeof operator
returns for different data types.
Some languages have functions that return the the month as a string. JavaScript doesn't have such a built-in function.
The sample below shows a user-defined function that handles this and how the getMonth() method of a Date object can
be used to get the month.
return months[num-1];
}
function enterMonth(){
var userMonth = prompt("What month were you born?", "");
alert("You were born in " + monthAsString(userMonth) + ".");
}
function getCurrentMonth(){
var today = new Date();
alert(monthAsString(today.getMonth()+1));
}
</script>
</head>
<body>
<form>
<input type="button" value="CHOOSE MONTH" onclick="enterMonth();">
<input type="button" value="GET CURRENT MONTH" onclick="getCurrentMonth();">
</form>
</body>
</html>
Duration: 15 to 25 minutes.
In this exercise, you will create a function that returns the day of the week as a string.
To continue to learn JavaScript go to the top of this page and click on the next lesson in this JavaScript Tutorial's Table
of Contents.
Conditionals and Loops
In this lesson of the JavaScript tutorial, you will learn...
Conditionals
There are two types of conditionals in JavaScript.
1. if - else if - else
2. switch / case
Syntax
if (conditions) {
statements;
} else if (conditions) {
statements;
} else {
statements;
}
Like with functions, each part of the if - else if - else block is contained within curly brackets ({}). There can be zero or
more else if blocks. The else block is optional.
Comparison Operators
Operator Description
== Equals
!= Doesn't equal
=== Strictly equals
!== Doesn't strictly equal
> Is greater than
< Is less than
>= Is greater than or equal to
<= Is less than or equal to
Logical Operators
Operator Description
&& and (a == b && c != d)
|| or (a == b || c != d)
! not !(a == b || c != d)
The example below shows a function using and if - else if - else condition.
When the user clicks on the Age Check button, the following prompt pops up.
After the user enters his age, an alert pops up. The text of the alert depends on the user's age. The three possibilities are
shown below.
Compound Conditions
Compound conditions are conditions that check for multiple things. See the sample below.
Short-circuiting
JavaScript is lazy (or efficient) about processing compound conditions. As soon as it can determine the overall result of
the compound condition, it stops looking at the remaining parts of the condition. This is useful for checking that a
variable is of the right data type before you try to manipulate it.
<body>
<script type="text/javascript">
if (USER_PASS.toLowerCase() == PASSWORD) {
document.write("<h1>Welcome!</h1>");
} else {
document.write("<h1>Bad Password!</h1>");
}
</script>
</body>
</html>
Code Explanation
Everything works fine as long as the user does what you expect. However, if the user clicks on the Cancel button when
prompted for a password, the value null will be assigned to USER_PASS. Because null is not a string, it does not have
the toLowerCase() method. So the following line will result in a JavaScript error.
if (USER_PASS.toLowerCase() == password)
This can be fixed by using the typeof() function to first check if USER_PASS is a string as shown in the sample below.
<body>
<script type="text/javascript">
if (typeof USER_PASS == "string" && USER_PASS.toLowerCase() == PASSWORD) {
document.write("<h1>Welcome!</h1>");
} else {
document.write("<h1>Bad Password!</h1>");
}
</script>
</body>
</html>
Switch / Case
Syntax
switch (expression) {
case value :
statements;
case value :
statements;
default :
statements;
}
Like if - else if - else statements, switch/case statements are used to run different code at different times. Generally,
switch/case statements run faster than if - else if - else statements, but they are limited to checking for equality. Each
case is checked to see if the expression matches the value.
When you run this page in a browser, you'll see that all three alerts pop up, even though only the first case is a match.
That's because if a match is found, none of the remaining cases is checked and all the remaining statements in the
switch block are executed. To stop this process, you can insert a break statement, which will end the processing of the
switch statement.
The following example shows how a switch/case statement can be used to record the user's browser type.
The navigator object, which is a child of window, holds information about the user's browser. In this case we are
looking at the appName property, which has a value of "Microsoft Internet Explorer" for Internet Explorer and
"Netscape" for Mozilla-based browsers (e.g, Navigator, Firefox).
Duration: 20 to 30 minutes.
Loops
There are several types of loops in JavaScript.
• while
• do...while
• for
• for...in
Syntax
while (conditions) {
statements;
}
Something, usually a statement within the while block, must cause the condition to change so that it eventually
becomes false and causes the loop to end. Otherwise, you get stuck in an infinite loop, which can bring down the
browser.
Again something, usually a statement within the do block, must cause the condition to change so that it eventually
becomes false and causes the loop to end.
Unlike with while loops, the statements in do...while loops will always execute at least one time because the conditions
are not checked until the end of each iteration.
Syntax
for (initialization; conditions; change) {
statements;
}
In for loops, the initialization, conditions, and change are all placed up front and separated by semi-colons. This makes
it easy to remember to include a change statement that will eventually cause the loop to end.
for loops are often used to iterate through arrays. The length property of an array can be used to check how many
elements the array contains.
Syntax
for (var item in array) {
statements;
}
for...in loops are specifically designed for looping through arrays. For reasons that will be better understood when we
look at object augmentation, the above syntax has a slight flaw. If the Array class is changed, it is possible that the
for...in loop includes more items than what you anticipated.
To be on the safe side, we suggest that you use a more verbose syntax as seen below.
Syntax
for (var item in array) if (array.hasOwnProperty(item)) {
statements;
}
The hasOwnProperty() call will ensure that the item is indeed an element that you added to the array, not something
that was inherited because of object augmentation.
<body>
<h1>JavaScript Loops</h1>
<h2>while Loop</h2>
<script type="text/javascript">
INDEX = 0;
while (INDEX < 5) {
document.write(INDEX);
INDEX++;
}
</script>
<h2>do...while Loop</h2>
<script type="text/javascript">
INDEX = 1;
do {
document.write(INDEX);
INDEX++;
} while (INDEX < 5);
</script>
<h2>for Loop</h2>
<script type="text/javascript">
for (var INDEX=0; INDEX<=5; INDEX++) {
document.write(INDEX);
}
</script>
<h2>for...in Loop</h2>
<ol>
<script type="text/javascript">
for (var instrument in BEATLES) if (BEATLES.hasOwnProperty(instrument)) {
document.write("<li>Playing " + instrument + " there is " + BEATLES[instrument] + "</li>");
}
</script>
</ol>
</body>
</html>
Code Explanation
Duration: 20 to 30 minutes.
1. Open ConditionalsAndLoops/Exercises/Loops.html for editing. You will see that this file is similar to the
solution from the last exercise.
2. Declare an additional variable called greeting.
3. Create an array called presidents that contains the last names of four or more past presidents.
4. Currently, the user only gets two tries to enter a valid gender and lastName. Modify the code so that, in both
cases, the user continues to get prompted until he enters valid data.
5. Change the switch block so that it assigns an appropriate value (e.g, "Hello Ms. Smith") to the greeting
variable rather than popping up an alert.
6. After the switch block, write code that alerts the user by name if he has the same last name as a president.
There is no need to alert those people who have non-presidential names.
1. Modify the code so that the first prompt for gender reads "What gender are you: Male or Female?", but all
subsequent prompts for gender read "You must enter 'Male' or 'Female'. Try again:".
2. Modify the code so that the first prompt for last name reads "Enter your last name:", but all subsequent
prompts for last name read "Please enter a valid last name:".
3. If the user presses the Cancel button on a prompt dialog, it returns null. Test this. It very likely results in a
JavaScript error. If so, fix the code so that no JavaScript error occurs.
4. For those people who do not have presidential names, pop up an alert that tells them their names are not
presidential.
To continue to learn JavaScript go to the top of this page and click on the next lesson in this JavaScript Tutorial's Table
of Contents.
JavaScript Form Validation
In this lesson of the JavaScript tutorial, you will learn...
Elements within a form are properties of that form and are referenced as follows:
Syntax
document.FormName.ElementName
Text fields and passwords have a value property that holds the text value of the field. The following example shows
how JavaScript can access user-entered text.
document.bgColor = bgColor;
alert(userName + ", the background color is " + bgColor + ".");
}
</script>
</head>
<body>
<h1>Change Background Color</h1>
<form name="BgForm">
1. When the user clicks on the "Change Background" button, the changeBg() function is called.
2. The values entered into the UserName and BgColor fields are stored in variables (userName and bgColor).
3. This form can be referenced as forms[0] or BgForm. The UserName field is referenced as
document.forms[0].UserName.value and the BgColor field is referenced as
document.BgForm.BgColor.value.
Duration: 15 to 25 minutes.
In this exercise, you will write a function that bases the value of one text field on the value of another.
1. If the user enters a number less than 1 or greater than 12 or a non-number, have the function write "Bad
Number" in the MonthName field.
2. If the user enters a decimal between 1 and 12 (inclusive), strip the decimal portion of the number.
Of course, when validating a form, we only want the form not to submit if something is wrong. The trick is to return
false if there is an error, but otherwise return true. So instead of returning false, we call a validation function, which
will specify the result to return.
Notice that we pass the validate() function the this object. The this object refers to the current object - whatever object
(or element) the this keyword appears in. In the case above, the this object refers to the form object. So the entire form
object is passed to the validate() function. Let's take a look at a simple example.
if (userName.length === 0) {
alert("You must enter a username.");
return false;
}
if (password.length === 0) {
alert("You must enter a password.");
return false;
}
return true;
}
</script>
</head>
<body>
<h1>Login Form</h1>
<form method="post" action="Process.html"
onsubmit="return validate(this);">
1. When the user submits the form, the onsubmit event handler captures the event and calls the validate()
function, passing in the form object.
2. The validate() function stores the form object in the form variable.
3. The values entered into the Username and Password fields are stored in variables (userName and password).
4. An if condition is used to check if userName is an empty string. If it is, an alert pops up explaining the
problem and the function returns false. The function stops processing and the form does not submit.
5. An if condition is used to check that password is an empty string. If it is, an alert pops up explaining the
problem and the function returns false. The function stops processing and the form does not submit.
6. If neither if condition catches a problem, the function returns true and the form submits.
Cleaner Validation
One problem is that the validation() function only checks for one problem at a time. That is, if it finds and error, it
reports it immediately and does not check for additional errors. Why not just tell the user all the mistakes that need to
be corrected, so he doesn't have to keep submitting the form to find each subsequent error?
Another problem is that the code is not written in a way that makes it easily reusable. For example, checking for the
length of user-entered values is a common thing to do, so it would be nice to have a ready-made function to handle this.
These improvements are made in the example below.
if (!checkLength(userName)) {
errors[errors.length] = "You must enter a username.";
}
if (!checkLength(password)) {
errors[errors.length] = "You must enter a password.";
}
if (errors.length > 0) {
reportErrors(errors);
return false;
}
return true;
}
function reportErrors(errors){
var msg = "There were some problems...\n";
var numError;
for (var i = 0; i<errors.length; i++) {
numError = i + 1;
msg += "\n" + numError + ". " + errors[i];
}
alert(msg);
}
</script>
</head>
<body>
<h1>Login Form</h1>
<form method="post" action="Process.html"
onsubmit="return validate(this);">
By modularizing the code in this way, it makes it easy to add new validation functions. In the next examples we will
move the reusable validation functions into a separate JavaScript file called FormValidation.js.
Duration: 25 to 40 minutes.
In FormValidation/Exercises/FormValidation.js, modify the checkEmail() function so that it also checks to see that the
final period (.) is after the final @ symbol. The solution is included in FormValidation/Solutions/FormValidation.js.
if ( !checkRadioArray(form.container) ) {
errors[errors.length] = "You must choose a cup or cone.";
}
if (errors.length > 0) {
reportErrors(errors);
return false;
}
return true;
}
function checkRadioArray(radioButtons){
for (var i=0; i < radioButtons.length; i++) {
if (radioButtons[i].checked) {
return true;
}
}
return false;
}
function reportErrors(errors){
var msg = "There were some problems...\n";
var numError;
for (var i = 0; i<errors.length; i++) {
numError = i + 1;
msg += "\n" + numError + ". " + errors[i];
}
alert(msg);
}
</script>
</head>
<body>
<h1>Ice Cream Form</h1>
<form method="post" action="Process.html"
onsubmit="return validate(this);">
<b>Cup or Cone?</b>
<input type="radio" name="container" value="cup"> Cup
<input type="radio" name="container" value="plaincone"> Plain cone
<input type="radio" name="container" value="sugarcone"> Sugar cone
<input type="radio" name="container" value="wafflecone"> Waffle cone
<br/><br/>
<input type="submit" value="Place Order">
</form>
</body>
</html>
Code Explanation
The checkRadioArray() function takes a radio button array as an argument, loops through each radio button in the
array, and returns true as soon as it finds one that is checked. Since it is only possible for one option to be checked,
there is no reason to continue looking once a checked button has been found. If none of the buttons is checked, the
function returns false.
Validating Checkboxes
Like radio buttons, checkboxes have the checked property, which is true if the button is checked and false if it is not.
However, unlike radio buttons, checkboxes are not stored as arrays. The example below shows a simple function for
checking to make sure a checkbox is checked.
if ( !checkCheckBox(form.terms) ) {
errors[errors.length] = "You must agree to the terms.";
}
if (errors.length > 0) {
reportErrors(errors);
return false;
}
return true;
}
function checkCheckBox(cb){
return cb.checked;
}
function reportErrors(errors){
var msg = "There were some problems...\n";
var numError;
for (var i = 0; i<errors.length; i++) {
numError = i + 1;
msg += "\n" + numError + ". " + errors[i];
}
alert(msg);
}
</script>
</head>
<body>
<h1>Ice Cream Form</h1>
<form method="post" action="Process.html" onsubmit="return validate(this);">
<input type="checkbox" name="terms">
I understand that I'm really not going to get any ice cream.
<br/><br/>
<input type="submit" value="Place Order">
</form>
</body>
</html>
if ( !checkSelect(form.flavor) ) {
errors[errors.length] = "You must choose a flavor.";
}
if (errors.length > 0) {
reportErrors(errors);
return false;
}
return true;
}
function checkSelect(select){
return (select.selectedIndex > 0);
}
function reportErrors(errors){
var msg = "There were some problems...\n";
var numError;
for (var i = 0; i<errors.length; i++) {
numError = i + 1;
msg += "\n" + numError + ". " + errors[i];
}
alert(msg);
}
</script>
</head>
<body>
<h1>Ice Cream Form</h1>
<form method="post" action="Process.html"
onsubmit="return validate(this);">
<b>Flavor:</b>
<select name="flavor">
<option value="0" selected></option>
<option value="choc">Chocolate</option>
<option value="straw">Strawberry</option>
<option value="van">Vanilla</option>
</select>
<br/><br/>
<input type="submit" value="Place Order">
</form>
</body>
</html>
Focus and blur events are caught with the onfocus and onblur event handlers. These events have corresponding focus()
and blur() methods. The example below shows
Things to notice:
1. When the document is loaded, the focus() method of the text field element is used to set focus on the
MonthNumber element.
2. When focus leaves the MonthNumber field, the onblur event handler captures the event and calls the
getMonth() function.
3. The onfocus event handler of the MonthName element triggers a call to the blur() method of this (the
MonthName element itself) to prevent the user from focusing on the MonthName element.
Change
Change events are caught when the value of a text element changes or when the selected index of a select element
changes. The example below shows how to capture a change event.
This is similar to the last example. The only major difference is that MonthNumber is a select menu instead of a text
field and that the getMonth() function is called when a different option is selected.
Validating Textareas
Textareas can be validated the same way that text fields are by using the checkLength() function shown earlier.
However, because textareas generally allow for many more characters, it's often difficult for the user to know if he's
exceeded the limit. It could be helpful to let the user know if there's a problem as soon as focus leaves the textarea. The
example below, which contains a more complete form for ordering ice cream, includes a function that alerts the user if
there are too many characters in a textarea.
if ( !checkRadioArray(form.container) ) {
errors[errors.length] = "You must choose a cup or cone.";
}
if ( !checkCheckBox(form.terms) ) {
errors[errors.length] = "You must agree to the terms.";
}
if ( !checkSelect(form.flavor) ) {
errors[errors.length] = "You must choose a flavor.";
}
if (errors.length > 0) {
reportErrors(errors);
return false;
}
return true;
}
function checkRadioArray(radioButtons){
for (var i=0; i < radioButtons.length; i++) {
if (radioButtons[i].checked) {
return true;
}
}
return false;
}
function checkCheckBox(cb){
return cb.checked;
}
function checkSelect(select){
return (select.selectedIndex > 0);
}
function reportErrors(errors){
var msg = "There were some problems...\n";
var numError;
for (var i = 0; i<errors.length; i++) {
numError = i + 1;
msg += "\n" + numError + ". " + errors[i];
}
alert(msg);
}
</script>
</head>
<body>
<h1>Ice Cream Form</h1>
<form method="post" action="Process.html" onsubmit="return validate(this);">
<p><b>Cup or Cone?</b>
<input type="radio" name="container" value="cup"> Cup
<input type="radio" name="container" value="plaincone"> Plain cone
<input type="radio" name="container" value="sugarcone"> Sugar cone
<input type="radio" name="container" value="wafflecone"> Waffle cone
</p>
<p>
<b>Flavor:</b>
<select name="flavor">
<option value="0" selected></option>
<option value="choc">Chocolate</option>
<option value="straw">Strawberry</option>
<option value="van">Vanilla</option>
</select>
</p>
<p>
<b>Special Requests:</b><br>
<textarea name="requests" cols="40" rows="6" wrap="virtual"
onblur="checkTextArea(this, 100);"></textarea>
</p>
<p>
<input type="checkbox" name="terms">
I understand that I'm really not going to get any ice cream.
</p>
<input type="submit" value="Place Order">
</form>
</body>
</html>
Duration: 15 to 25 minutes.
In this exercise, you will make some improvements to the registration form from the last exercise.
1. Open FormValidation/Exercises/FormValidation2.js in your editor. You will see that the functions discussed
above have been added: checkRadioArray(), checkCheckBox(), checkSelect(), and checkTextArea().
2. Open FormValidation/Exercises/Register2.html for editing.
o Notice that the following changes have been made to the form:
State has been changed from a textfield to a select menu. The first option is meaningless.
The next 51 options are U.S. states. The rest of the options are Canadian provinces.
Country has been changed to a radio array.
A Comments field has been added.
A Terms checkbox has been added.
o Write code that:
Checks that a country is selected.
Checks that the country and state selection are in sync.
Checks that the terms have been accepted.
o Add an event handler to the Comments textarea that alerts the user if the comment is too long.
3. Test your solution in a browser.
To continue to learn JavaScript go to the top of this page and click on the next lesson in this JavaScript Tutorial's Table
of Contents.
Working with Images
In this lesson of the JavaScript tutorial, you will learn...
Image Rollovers
Image rollovers are commonly used to create a more interesting user experience and to help highlight navigation points.
When the user hovers the mouse over an image, the source of the image is modified, so that a different image appears.
When the user hovers the mouse back off the image, the original source is restored. The code below shows how to
create a simple rollover.
The mouse-over event is captured with the img tag's onmouseover event handler. When this happens, the following
JavaScript code is called.
this.src = 'Images/Hulk.jpg';
The this object refers to the current object - whatever object (or element) the this keyword appears in. In the case
above, the this object refers to the img object, which has a property called src that holds the path to the image. The code
above sets the src to "Images/Hulk.jpg".
Likewise, the mouse-out event is captured with the img tag's onmouseout event handler. When this happens, the
following JavaScript code is called.
this.src = 'Images/Banner.jpg';
This code sets the src to "Images/Banner.jpg," which is what it originally was.
Backward Compatibility
The code above should work fine in Firefox, Internet Explorer 4.0 and later, and Netscape 6 and later. However, in
earlier versions of browsers, images could not capture mouse events. The workaround is to wrap the <img> tag in an
<a> tag and to put the event handlers in the <a> tag as shown below.
Code Sample: DynamicImages/Demos/SimpleRollover-backward.html
<html>
<head>
<title>Simple Image Rollover</title>
</head>
<body>
<div style="text-align:center;">
<h1>Simple Image Rollover</h1>
<a href="#"
onmouseover="document.MyImage.src = 'Images/Hulk.jpg';"
onmouseout="document.MyImage.src = 'Images/Banner.jpg';">
<img name="MyImage" src="Images/Banner.jpg" border="0"></a>
<p>Who are you calling simple?</p>
</div>
</body>
</html>
Image rollovers can be handled by a function as well. The two examples below show an image rollover function for
modern browsers and a backward-compatible image rollover function.
Why the check for document.images? Some early versions of browsers don't support the images array.
Preloading Images
When working with files on a local machine, image rollovers like the ones we have seen in previous examples work
just fine. However, when the user first hovers over an image rollover image, the new image file has to be found and
delivered to the page. If the new image is on a far-away server, this can take a few moments, causing an ugly pause in
the rollover effect. This can be prevented by preloading images.
Images can be preloaded by creating an Image object with JavaScript and assigning a value to the src of that Image. A
sample is shown below.
Notice that the code is not in a function. It starts working immediately as follows:
1. An array called IMAGE_PATHS is created to hold the paths to the images that need to be preloaded.
4. An array called IMAGE_CACHE is created to hold the Image objects that will hold the preloaded images.
Duration: 20 to 30 minutes.
In this exercise, you will practice working with images by creating a slide show.
1. Add a dropdown menu below the Previous and Next buttons that contains the names of the images without
their extensions: "Banner", "Hulk", "Bruce" and "Batman".
2. When the user selects an image from the dropdown, have that image appear above.
3. When the user changes the image above using the Previous and Next buttons, have the dropdown menu keep
in sync (i.e, show the name of the current image).
To continue to learn JavaScript go to the top of this page and click on the next lesson in this JavaScript Tutorial's Table
of Contents.
Regular Expressions
In this lesson of the JavaScript tutorial, you will learn...
Getting Started
Regular expressions are used to do sophisticated pattern matching, which can often be helpful in form validation. For
example, a regular expression can be used to check whether an email address entered into a form field is syntactically
correct. JavaScript supports Perl-compatible regular expressions.
Assuming you know the regular expression pattern you are going to use, there is no real difference between the two;
however, if you don't know the pattern ahead of time (e.g, you're retrieving it from a form), it can be easier to use the
RegExp() constructor.
The regular expression method in JavaScript has two main methods for testing strings: test() and exec().
The exec() method takes one argument, a string, and checks whether that string contains one or more matches of the
pattern specified by the regular expression. If one or more matches is found, the method returns a result array with the
starting points of the matches. If no match is found, the method returns null.
The test() method also takes one argument, a string, and checks whether that string contains a match of the pattern
specified by the regular expression. It returns true if it does contain a match and false if it does not. This method is very
useful in form validation scripts. The code sample below shows how it can be used for checking a social security
number. Don't worry about the syntax of the regular expression itself. We'll cover that shortly.
function checkSsn(ssn){
if (RE_SSN.test(ssn)) {
alert("VALID SSN");
} else {
alert("INVALID SSN");
}
}
</script>
</head>
<body>
<form onsubmit="return false;">
<input type="text" name="ssn" size="20">
<input type="button" value="Check"
onclick="checkSsn(this.form.ssn.value);">
</form>
</body>
</html>
Code Explanation
1. First, a variable containing a regular expression object for a social security number is declared.
2. Next, a function called checkSsn() is created. This function takes one argument: ssn, which is a string. The
function then tests to see if the string matches the regular expression pattern by passing it to the regular
expression object's test() method. If it does match, the function alerts "VALID SSN". Otherwise, it alerts
"INVALID SSN".
3. function checkSsn(ssn){
4. if (RE_SSN.test(ssn)) {
5. alert("VALID SSN");
6. } else {
7. alert("INVALID SSN");
8. }
}
9. A form in the body of the page provides a text field for inserting a social security number and a button that
passes the user-entered social security number to the checkSsn() function.
10. <form onsubmit="return false;">
11. <input type="text" name="ssn" size="20">
12. <input type="button" value="Check"
13. onclick="checkSsn(this.form.ssn.value);">
</form>
Flags
Flags appearing after the end slash modify how a regular expression works.
• The i flag makes a regular expression case insensitive. For example, /aeiou/i matches all lowercase and
uppercase vowels.
• The g flag specifies a global match, meaning that all matches of the specified pattern should be returned.
String Methods
The search() method takes one argument: a regular expression. It returns the index of the first character of the substring
matching the regular expression. If no match is found, the method returns -1.
"Webucator".search(/cat/); //returns 4
"Webucator".split(/[aeiou]/);
/*
returns an array with the following values:
"W", "b", "c", "t", "r"
*/
The replace() method takes two arguments: a regular expression and a string. It replaces the first regular expression
match with the string. If the g flag is used in the regular expression, it replaces all matches with the string.
The match() method takes one argument: a regular expression. It returns each substring that matches the regular
expression pattern.
"Webucator".match(/[aeiou]/g);
/*
returns an array with the following values:
"e", "u", "a", "o"
*/
A caret (^) at the beginning of a regular expression indicates that the string being searched must start with this pattern.
A dollar sign ($) at the end of a regular expression indicates that the string being searched must end with this pattern.
Number of Occurrences ( ? + * {} )
The following symbols affect the number of occurrences of the preceding character: ?, +, *, and {}.
A questionmark (?) indicates that the preceding character should appear zero or one times in the pattern.
• The pattern foo? can be found in "food" and "fod", but not "faod".
A plus sign (+) indicates that the preceding character should appear one or more times in the pattern.
• The pattern fo+ can be found in "fod", "food" and "foood", but not "fd".
A asterisk (*) indicates that the preceding character should appear zero or more times in the pattern.
Curly brackets with one parameter ( {n} ) indicate that the preceding character should appear exactly n times in the
pattern.
• The pattern fo{3}d can be found in "foood" , but not "food" or "fooood".
Curly brackets with two parameters ( {n1,n2} ) indicate that the preceding character should appear between n1 and n2
times in the pattern.
• The pattern fo{2,4}d can be found in "food","foood" and "fooood", but not "fod" or "foooood".
Curly brackets with one parameter and an empty second paramenter ( {n,} ) indicate that the preceding character
should appear at least n times in the pattern.
• The pattern fo{2,}d can be found in "food" and "foooood", but not "fod".
Common Characters ( . \d \D \w \W \s \S )
• The pattern fo.d can be found in "food", "foad", "fo9d", and "fo*d".
• The pattern fo\dd can be found in "fo1d", "fo4d" and "fo0d", but not in "food" or "fodd".
• The pattern fo\Dd can be found in "food" and "foad", but not in "fo4d".
Backslash-w ( \w ) represents any word character (letters, digits, and the underscore (_) ).
• The pattern fo\wd can be found in "food", "fo_d" and "fo4d", but not in "fo*d".
• The pattern fo\Wd can be found in "fo*d", "fo@d" and "fo.d", but not in "food".
Backslash-s ( \s) represents any whitespace character (e.g, space, tab, newline, etc.).
• The pattern fo\sd can be found in "fo d", but not in "food".
• The pattern fo\Sd can be found in "fo*d", "food" and "fo4d", but not in "fo d".
Grouping ( [] )
Square brackets ( [] ) are used to group options.
• The pattern f[aeiou]d can be found in "fad" and "fed", but not in "food", "faed" or "fd".
• The pattern f[aeiou]{2}d can be found in "faed" and "feod", but not in "fod", "fed" or "fd".
Negation ( ^ )
When used after the first character of the regular expression, the caret ( ^ ) is used for negation.
• The pattern f[^aeiou]d can be found in "fqd" and "f4d", but not in "fad" or "fed".
Subpatterns ( () )
• The pattern f(oo)?d can be found in "food" and "fd", but not in "fod".
Alternatives ( | )
• The pattern foo$|^bar can be found in "foo" and "bar", but not "foobar".
Escape Character ( \ )
• The pattern fo\.d can be found in "fo.d", but not in "food" or "fo4d".
Backreferences
Backreferences are special wildcards that refer back to a subpattern within a pattern. They can be used to make sure
that two subpatterns match. The first subpattern in a pattern is referenced as \1, the second is referenced as \2, and so
on.
For example, the pattern ([bmpw])o\1 matches “bob†, “mom†, “pop†, and
“wow†, but not "bop" or "pow".
A more practical example has to do matching the delimiter in social security numbers. Examine the following regular
expression.
Within the caret (^) and dollar sign ($), which are used to specify the beginning and end of the pattern, there are three
sequences of digits, optionally separated by a hyphen or a space. This pattern will be matched in all of following strings
(and more).
• 123-45-6789
• 123 45 6789
• 123456789
• 123-45 6789
• 123 45-6789
• 123-456789
The last three strings are not ideal, but they do match the pattern. Backreferences can be used to make sure that the
second delimiter matches the first delimiter. The regular expression would look like this.
^\d{3}([\- ]?)\d{2}\1\d{4}$
The \1 refers back to the first subpattern. Only the first three strings listed above match this regular expression.
function validate(form){
var email = form.Email.value;
var password = form.Password.value;
var errors = [];
if (!RE_EMAIL.test(email)) {
errors[errors.length] = "You must enter a valid email address.";
}
if (!RE_PASSWORD.test(password)) {
errors[errors.length] = "You must enter a valid password.";
}
if (errors.length > 0) {
reportErrors(errors);
return false;
}
return true;
}
function reportErrors(errors){
var msg = "There were some problems...\n";
for (var i = 0; i<errors.length; i++) {
var numError = i + 1;
msg += "\n" + numError + ". " + errors[i];
}
alert(msg);
}
</script>
</head>
<body>
<h1>Login Form</h1>
<form method="post" action="Process.html" onsubmit="return validate(this);">
1. The caret (^) says to start at the beginning. This prevents the user from entering invalid characters at the
beginning of the email address.
2. (\w+[\-\.])* allows for a sequence of word characters followed by a dot or a dash. The * indicates that the
pattern can be repeated zero or more times. Successful patterns include "ndunn.", "ndunn-", "nat.s.", and "nat-
s-".
3. \w+ allows for one or more word characters.
4. @ allows for a single @ symbol.
5. (\w+\.)+ allows for a sequence of word characters followed by a dot. The + indicates that the pattern can be
repeated one or more times. This is the domain name without the last portion (e.g, without the "com" or
"gov").
6. [A-Za-z]+ allows for one or more letters. This is the "com" or "gov" portion of the email address.
7. The dollar sign ($) says to end here. This prevents the user from entering invalid characters at the end of the
email address.
1. The caret (^) says to start at the beginning. This prevents the user from entering invalid characters at the
beginning of the password.
2. [A-Za-z\d]{6,8} allows for a six- to eight-character sequence of letters and digits.
3. The dollar sign ($) says to end here. This prevents the user from entering invalid characters at the end of the
password.
Duration: 25 to 40 minutes.
1. Add regular expressions to test Canadian and United Kingdom postal codes:
o Canadian Postal Code - A letter followed by a digit, a letter, a space, a digit, a letter, and a digit
(e.g, M1A 1A1)
o United Kingdom Postal Code - One or two letters followed by a digit, an optional letter, a space, a
digit, and two letters (e.g, WC1N 3XX)
2. Modify Register.html to check the postal code against these two new regular expressions as well as the
regular expression for a US postal code.
Earlier, we showed how the replace() method of a string object can be used to replace regular expression matches with
a string. The replace() method can also be used with backreferences to replace a matched pattern with a new string
made up of substrings from the pattern. The example below illustrates this.
function cleanSsn(ssn){
if (RE_SSN.test(ssn)) {
var cleanedSsn = ssn.replace(RE_SSN, "$1-$2-$3");
return cleanedSsn;
} else {
alert("INVALID SSN");
return ssn;
}
}
</script>
</head>
<body>
<form onsubmit="return false;">
<input type="text" name="ssn" size="20">
<input type="button" value="Clean SSN"
onclick="this.form.ssn.value = cleanSsn(this.form.ssn.value);">
</form>
</body>
</html>
Code Explanation
The cleanSsn() function is used to "clean up" a social security number. The regular expression contained in RE_SSN,
^(\d{3})[\- ]?(\d{2})[\- ]?(\d{4})$, contains three subexpressions: (\d{3}), (\d{2}), and (\d{4}). Within the replace()
method, these subexpressions can be referenced as $1, $2, and $3, respectively.
When the user clicks on the "Clean SSN" button, the cleanSsn() function is called. This function first tests to see that
the user-entered value is a valid social security number. If it is, it then cleans it up with the line of code below, which
dash-delimits the three substrings matching the subexpressions.
Duration: 15 to 25 minutes.
1. Open RegularExpressions/Exercises/PhoneCleaner.html for editing.
2. Where the comment indicates, declare a variable called cleanedPhone and assign it a cleaned-up version of
the user-entered phone number. The cleaned up version should fit the following format: (555) 555-1212
3. Test your solution in a browser.
Some phone numbers are given as a combination of numbers and letters (e.g, 877-WEBUCATE). As is the case with
877-WEBUCATE, such numbers often have an extra character just to make the word complete.
To continue to learn JavaScript go to the top of this page and click on the next lesson in this JavaScript Tutorial's Table
of Contents.
Jump Menus
A jump menu is a select menu that contains a list of websites or pages to visit. There are two main types of jump
menus. One jumps to the selected page as soon as the user makes a selection. The other jumps to a page only after the
user has made a selection and clicked on a "Go" button. We'll look at the latter type first and then create the former type
in an exercise.
Select menus contain an array of options. Like with all JavaScript arrays, the first item has an index of 0. So, in this
example, the first option, which reads "--SELECT A STATE--" is option 0.
Select menus have a selectedIndex property that holds the index of the currently selected option. For example, in the
sample above, if North Carolina is selected, State.selectedIndex would hold 5. To see if the first option of a select menu
is selected, check if selectedIndex is 0.
1. The "GO" button has an onclick event handler that, when clicked, passes the State select menu to the
jumpMenu() function.
<input type="button" value="GO" onclick="jumpMenu(this.form.State);">
Form elements can be disabled by setting the element's disabled property to true. They can be re-enabled by setting the
disabled property to false.
Syntax
FormElement.disabled = true;
FormElement.disabled = false;
The example below is a modified version of the jump menu that disables the "GO" button unless a state is selected.
function toggleButton(form){
if (form.State.selectedIndex === 0) {
form.btnJump.disabled = true;
} else {
form.btnJump.disabled = false;
}
}
</script>
</head>
<body onload="toggleButton(document.forms[0]);">
<form>
<select name="State" onChange="toggleButton(this.form);">
<option value="0">--SELECT A STATE--</option>
<option value="alabama">Alabama</option>
<option value="illinois">Illinois</option>
<option value="massachu">Massachusetts</option>
<option value="montana">Montana</option>
<option value="ncarolin">North Carolina</option>
<option value="wvirgini">West Virginia</option>
</select>
<input type="button" name="btnJump" value="GO"
onclick="jumpMenu(this.form.State);">
</form>
</body>
</html>
Code Explanation
Notice that the jumpMenu() function no longer checks if a state has been selected. It is only called when the user clicks
the "Go" button, which is only enabled if a state is selected.
Duration: 15 to 25 minutes.
In this exercise, you will modify the jump menu from the demo so that it jumps to the selected page as soon as the user
makes a selection.
The jumpMenu() function isn't reusable. Why not? Fix the function so that it is portable and then modify the form
accordingly.
switch (mainMenu.selectedIndex) {
case 0:
subMenuItems = NEW_YORKERS;
break;
case 1:
subMenuItems = CALIFORNIANS;
break;
}
</script>
</head>
<body>
<form name="Menus">
<select name="State" onchange="populateSub(this, this.form.City);">
<option value="NY">New York</option>
<option value="CA">California</option>
</select>
<select name="City">
<option value="NYC">New York City</option>
<option value="SYR">Syracuse</option>
<option value="ALB">Albany</option>
<option value="ROC">Rochester</option>
</select>
</form>
</body>
</html>
Code Explanation
1. As the page loads, two arrays (NEW_YORKERS and CALIFORNIANS) are created and then populated with
Options using the Option() constructor. The Option() constructor takes four parameters: text, value,
defaultSelected, and selected. Only text is required.
2. The body of the page contains a form with two select menus: State and City. When a state is selected, the
onchange event handler triggers a call to the populateSub() function, passing in the State menu as mainSel
and the City menu as subSel.
3. A few variables are created and then the City menu is emptied with the following code.
subMenu.options.length = 0;
4. A switch statement based on the option selected in the main menu is used to determine the array to look in for
the submenu's options.
5. switch (mainMenu.selectedIndex) {
6. case 0:
7. arrSubMenu = NEW_YORKERS;
8. break;
9. case 1:
10. arrSubMenu = CALIFORNIANS;
11. break;
}
12. A for loop is used to loop through the array populating the submenu with options.
A problem with the code in DynamicForms/Demos/Interdependent.html is that it would need to be modified in several
places to add or change options. This next example, though more complex, is much more modular, and hence, reusable.
This example uses a two-dimensional array to hold the menus. The first item of each array holds the State options,
which is used in the main menu. The rest of the items in each array hold the City options used to populate the submenu.
The State select menu starts out empty and the City menu starts out with just a single "Please Choose" option. The two
functions populateMain() and populateSub() are used to populate the two menus. Both functions are completely generic
and reusable.
Duration: 20 to 30 minutes.
In this exercise, you will modify DynamicForms/Exercises/Interdependent.html, so that the second select menu is
dependent on the first.
function jumpMenu(select){
var i = select.selectedIndex;
var url = select.options[i].value;
if (i > 0) {
location.href = url;
}
}
/*
Populate the arrays so that:
-The Bands select menu will be populated with
Beatles, Rolling Stones, Genesis, and Eagles
</script>
</head>
<body onload="populateMain(arg1, arg2);">
<form name="Menus">
Band: <select name="Bands" onchange="populateSub(arg1, arg2);"></select>
Artist: <select name="Artists" onchange="jumpMenu(this);">
<option value="0">--Please Choose--</option>
</select>
</form>
</body>
</html>
Where is the solution?
The window object's setTimeout() method is used to execute a block of JavaScript code every n milliseconds. The
syntax is shown below.
Or
setTimeout(functionToCall, n);
The example below shows how the setTimeout() method can be used to create a timer.
function resetTimer(seconds){
SECONDS_LEFT = seconds;
document.Timer.TimeLeft.value = SECONDS_LEFT;
clearTimeout(TIMER);
document.Timer.btnStart.disabled = false;
document.Timer.btnReset.disabled = true;
}
function decrementTimer(){
TIMES_UP = false;
document.Timer.TimeLeft.value = SECONDS_LEFT;
document.Timer.btnStart.disabled = true;
document.Timer.btnReset.disabled = false;
SECONDS_LEFT--;
if (SECONDS_LEFT >= 0) {
TIMER = setTimeout(decrementTimer, 1000);
} else {
alert("Time's up!");
resetTimer(10);
}
}
</script>
</head>
<body onload="init();">
<form name="Timer" onsubmit="return false;">
Timer: <input type="text" name="TimeLeft" size="2"
style="text-align:center" onfocus="this.blur();">
seconds left<br>
<input type="button" name="btnStart"
value="Start" onclick="decrementTimer();">
<input type="button" name="btnReset"
value="Reset" onclick="resetTimer(10);">
</form>
</body>
</html>
Code Explanation
1. As the page loads, three global variables are created: SECONDS_LEFT to hold the number of seconds left,
TIMER - to hold the timer, and TIMES_UP - to flag if the timer has run out.
2. The body of the page contains a form with a text field holding the number of seconds left, a "Start" button
and a "Reset" button.
3. <form name="Timer" onsubmit="return false;">
4. Timer: <input type="text" name="TimeLeft" size="2"
5. style="text-aliagn:center" onfocus="this.blur();">
6. seconds left<br>
7. <input type="button" name="btnStart"
8. value="Start" onclick="decrementTimer();">
9. <input type="button" name="btnReset"
10. value="Reset" onclick="resetTimer(10);">
</form>
11. The onload event handler of the body tag triggers the init() function, which disables the "Start" button.
12. function init(){
13. document.Timer.btnStart.disabled = true;
}
14. When the user clicks on the "Reset" button, the resetTimer() function is called. This function does the
following:
o Sets SECONDS_LEFT to the number passed into the function.
SECONDS_LEFT = seconds;
document.Timer.TimeLeft.value = SECONDS_LEFT;
clearTimeout(TIMER);
document.Timer.btnStart.disabled = false;
document.Timer.btnReset.disabled = true;
15. When the user clicks on the "Start" button, the decrementTimer() function is called. This function does the
following:
o Sets TIMES_UP to false.
TIMES_UP = false;
document.Timer.TimeLeft.value = SECONDS_LEFT;
document.Timer.btnStart.disabled = true;
document.Timer.btnReset.disabled = false;
SECONDS_LEFT--;
Duration: 10 to 20 minutes.
phases.
3. Test your solution in a browser.
function init(){
MENU[0] = [];
MENU[1] = [];
MENU[2] = [];
MENU[3] = [];
populateMain(document.Quiz.Operator, document.Quiz.Question);
resetTimer(TOTAL_TIME);
document.Quiz.btnCheck.disabled = true;
document.Quiz.Answer.disabled = true;
}
function resetTimer(seconds){
TIMES_UP = true;
SECONDS_LEFT = seconds;
document.Quiz.TimeLeft.value = SECONDS_LEFT;
clearTimeout(TIMER);
document.Quiz.Answer.value = "";
}
function decrementTimer(){
TIMES_UP = false;
document.Quiz.TimeLeft.value = SECONDS_LEFT;
SECONDS_LEFT--;
if (SECONDS_LEFT >= 0) {
TIMER = setTimeout(decrementTimer, 1000);
} else {
alert("Time's up! The answer is " + getAnswer() + ".");
resetTimer(TOTAL_TIME);
}
}
function checkAnswer(answer){
var correctAnswer = getAnswer();
if (answer === correctAnswer) {
alert("Right! The answer is " + correctAnswer + ".");
} else {
alert("Sorry. The correct answer is " + correctAnswer + ".");
}
removeOption();
questionChange();
}
function removeOption(){
var i = document.Quiz.Operator.selectedIndex;
var j = document.Quiz.Question.selectedIndex;
MENU[i].splice(j, 1);
if (MENU[i].length == 1) {
MENU.splice(i, 1);
if (MENU.length === 0) {
endQuiz();
}
}
populateMain(document.Quiz.Operator, document.Quiz.Question);
resetTimer(TOTAL_TIME);
}
function questionChange(){
if (document.Quiz.Question.selectedIndex === 0) {
document.Quiz.btnCheck.disabled = true;
document.Quiz.Answer.disabled = true;
resetTimer(TOTAL_TIME);
} else {
document.Quiz.btnCheck.disabled = false;
document.Quiz.Answer.disabled = false;
decrementTimer();
}
}
function endQuiz(){
resetTimer(TOTAL_TIME);
alert("Thanks for playing! The quiz will now reload.");
init();
}
function getAnswer(){
var i = document.Quiz.Question.selectedIndex;
var answer = document.Quiz.Question[i].value;
return answer;
}
</script>
</head>
<body onload="init();">
<form name="Quiz" onsubmit="return false;">
<table border="1" cellspacing="0" cellpadding="4" align="center">
<tr>
<td>Category:</td>
<td>
<select name="Operator"
onchange="populateSub(this, this.form.Question);
resetTimer(TOTAL_TIME);">
</select>
</td>
</tr>
<tr>
<td>Question:</td>
<td>
<select name="Question"
onchange="questionChange();">
<option value="0">--Please Choose--</option>
</select>
</td>
</tr>
<tr>
<td>Answer:</td>
<td>
<input type="text" name="Answer" size="2">
<input type="button" name="btnCheck" value="Check Answer"
onclick="checkAnswer(this.form.Answer.value);">
</td>
</tr>
<tr>
<td>Timer:</td>
<td><input type="text" name="TimeLeft" size="2"
style="text-align:center" onfocus="this.blur();">
seconds left</td>
</tr>
</table>
</form>
</body>
</html>
Code Explanation
1. The Question select menu is always populated with questions in the indicated category.
2. The "Check Answer" button and the Answer text field are only enabled when a question is selected.
3. The timer starts when a question is selected and stops when it runs out or when the "Check Answer" button is
clicked.
4. When the "Check Answer" button is clicked, the user is alerted as to whether or not the answer is correct and
the question is removed from the question menu.
5. When all questions in a category are gone, the category is removed from the category menu.
6. When all questions in all categories have been completed, the user is thanked for taking the quiz and the
entire quiz is restarted.
Spend some time reviewing this code. You shouldn't see anything new, except in the way the code is designed.
To continue to learn JavaScript go to the top of this page and click on the next lesson in this JavaScript Tutorial's Table
of Contents.
A very good way to manage this is to create a JavaScript file that stores many environment-related variables that hold
flags indicating the type of browser or support for a specific object, property or method.
//Object Support
var SUPPORTS_IMAGES = (typeof document.images != "undefined");
//Cookies
var COOKIES_ON = navigator.cookieEnabled;
Duration: 10 to 20 minutes.
In this exercise, you will modify a simple page so that it redirects a user to another page if cookies are not enabled.
To continue to learn JavaScript go to the top of this page and click on the next lesson in this JavaScript Tutorial's Table
of Contents.
All material in this The User's Environment is copyright 2010 Webucator. The purpose of this website is to help you
learn
Dynamic HTML
In this lesson of the JavaScript tutorial, you will learn...
Introduction
Dynamic HTML is not a technology in and of itself, but rather is a combination of three technologies: HTML,
Cascading Style Sheets (CSS), and JavaScript. It usually involves using JavaScript to change a CSS property of an
HTML element. In modern browsers, most CSS properties can be modified dynamically. This can be done by changing
an individual style of element (using the style property of the element) or by changing the class name assigned to the
element (using the className property).
Standard Syntax
window.getComputedStyle(Element, Pseudo-Element)
//for example:
var curStyle = window.getComputedStyle(
document.getElementById("divTitle"), null);
alert(curStyle.fontWeight);
Element.currentStyle.Property
//for example:
alert(document.getElementById("divTitle").currentStyle.fontWeight);
Cross-browser Solution
var curStyle;
if (window.getComputedStyle) {
curStyle = window.getComputedStyle(
document.getElementById("divTitle"), "");
} else {
curStyle = document.getElementById("divTitle").currentStyle;
}
alert(curStyle.fontWeight);
Another solution is to use JavaScript to set the styles of the objects on the page.
function init(){
document.getElementById("divTitle").style.fontWeight = "bold";
}
Now this style can be referenced later in the script; however, this solution isn't always practical.
function changeVisibility(TR){
if (document.getElementById(TR).style.visibility=="hidden") {
document.getElementById(TR).style.visibility = "visible";
} else {
document.getElementById(TR).style.visibility = "hidden";
}
}
function changeDisplay(TR){
if (document.getElementById(TR).style.display == "none") {
document.getElementById(TR).style.display = TR_DISPLAY;
} else {
document.getElementById(TR).style.display = "none";
}
}
</script>
</head>
<body>
<h1>Hiding and Showing Elements</h1>
<table border="1">
<tr id="tr1"><td>tableElem Row 1</td></tr>
<tr id="tr2"><td>tableElem Row 2</td></tr>
<tr id="tr3"><td>tableElem Row 3</td></tr>
<tr id="tr4"><td>tableElem Row 4</td></tr>
</table>
<form>
<h2>visibility</h2>
<input type="button" onclick="changeVisibility('tr1')" value="TR1">
<input type="button" onclick="changeVisibility('tr2')" value="TR2">
<input type="button" onclick="changeVisibility('tr3')" value="TR3">
<input type="button" onclick="changeVisibility('tr4')" value="TR4">
<h2>display</h2>
<input type="button" onclick="changeDisplay('tr1')" value="TR1">
<input type="button" onclick="changeDisplay('tr2')" value="TR2">
<input type="button" onclick="changeDisplay('tr3')" value="TR3">
<input type="button" onclick="changeDisplay('tr4')" value="TR4">
</form>
</body>
</html>
Code Explanation
This page has two functions: changeVisibility() and changeDisplay(). The changeVisibility() function checks the value
of the visibility style of the passed element and changes it to its opposite. The changeDisplay() function does the same
with the display style. The functions are called with buttons on the page and are passed in ids of table rows from the
table on the page.
The main difference between setting visibility to hidden and setting display to none is that setting visibility to hidden
does not modify the layout of the page; it simply hides the element. Setting display to none, on the other hand,
collapses the element, so that the surrounding relatively positioned elements re-position themselves.
In the screenshot below, tableElem Row 1 has visibility set to hidden and tableElem Row 3 has display set to none.
) ? "block" : "table-row";
According to the CSS specification, the proper way to display a table row is by setting the display property to table-
row; however Internet Explorer 6 and earlier do not support this value. To get the script to behave correctly in IE6, the
display must be set to block; however, that messes up Mozilla, which displays blocks as blocks, not as table rows. The
solution is to create a variable, TR_DISPLAY, whose value depends on the browser being used. That's exactly what the
line of code above does.
Duration: 20 to 30 minutes.
In this exercise, you will modify a Math Quiz to only show the countdown timer when it is running.
running. Note that the variable TR_DISPLAY is already set to the proper browser-dependent value.
Duration: 10 to 20 minutes.
In this exercise, you will modify the Math Quiz so that it displays messages in plain text, rather than in form fields and
alerts.
function decrementTimer(){
TIMES_UP = false;
document.getElementById("trTimer").style.display = TR_DISPLAY;
document.getElementById("divMessage").innerHTML = "";
document.getElementById("divMessage").style.display = "none";
INTERVAL--;
if (TIME_LEFT >= 0) {
TIMER = setTimeout(decrementTimer, 1000);
} else {
showMessage("Time's up! The answer is " + getAnswer() + ".", "red");
resetTimer(INTERVAL);
}
}
---- Code Omitted ----
Manipulating Tables
HTML tables can be created and manipulated dynamically with JavaScript. Each table element contains a rows array
and methods for inserting and deleting rows: insertRow() and deleteRow(). Each tr element contains a cells array and
methods for inserting and deleting cells: insertCell() and deleteCell(). The following example shows how these objects
can be used to dynamically create HTML tables.
<body>
<table id="tblPeople" border="1">
<tr>
<th>First Name</th>
<th>Last Name</th>
</tr>
</table>
<hr>
<form name="formName">
First Name: <input type="text" name="FirstName"><br>
Last Name: <input type="text" name="LastName"><br>
<input type="button" value="Add Name"
onclick="addRow('tblPeople',
[this.form.FirstName.value, this.form.LastName.value] );">
<hr>
Remove Row: <input type="text" size="1" name="RowNum">
<input type="button" value="Delete Row"
onclick="deleteRow('tblPeople', this.form.RowNum.value)">
</form>
</body>
</html>
Code Explanation
The body of the page contains a table with an id of formName. The table contains a single row of headers.
Below the table is a form that allows the user to enter a first and last name. When the "Add Name" button is clicked,
the addRow() function is called and passed in the id of the table (tblPeople) and a new array containing the user-entered
values.
The addRow() function uses the insertRow() method of the table to add a new row at the end of the table and then loops
through the passed-in array, creating and populating one cell for each item. The function also returns the new row.
Although the returned value isn't used in this example, it can be useful if you then want to manipulate the new row
further.
The form also contains a "Delete Row" button that, when clicked, passes the id of the table (tblPeople) and the number
entered by the user in the RowNum text field.
The deleteRow() function checks to see if the row specified exists and is not the first row (the header row). If both
conditions are true, it deletes the row. Otherwise, it alerts "Failed".
Duration: 15 to 25 minutes.
In this exercise, you will dynamically create a table that shows the user how she is doing on the Math Quiz. The
Modify the code so that the new row's font color is green if the answer is correct and red if it is not. Hint: the addRow()
function returns the newly added row.
function grow(elem){
var objElem = elem;
var curWidth = parseInt(objElem.style.width);
var curHeight = parseInt(objElem.style.height);
objElem.style.width = (curWidth * 1.5) + 'px';
objElem.style.height = (curHeight * 1.5) + 'px';
}
function shrink(elem){
var objElem = elem;
var curWidth = parseInt(objElem.style.width);
var curHeight = parseInt(objElem.style.height);
objElem.style.width = (curWidth / 1.5) + 'px';
objElem.style.height = (curHeight / 1.5) + 'px';
}
</script>
</head>
<body onload="init();">
<div id="divBlock" onmouseover="grow(this);"
onmouseout="shrink(this);"></div>
</body>
</html>
Code Explanation
In this case, we use the init() function to set the height and width of the divBlock div, thus making the properties
accessible to JavaScript.
The grow() function uses parseInt() to cut off the units (e.g, px) from the value of the width and height of the div and
assign the resulting integers to variables: curWidth and curHeight. It then modifies the width and height properties of
the element by multiplying the current values by 1.5.
The shrink() function does the same thing, but it divides by 1.5 instead of multiplying.
The functions are triggered with onmouseover and onmouseout event handlers.
The example below shows how a timed slider can be created by dynamically changing an element's dimensions.
#divSliderBG {
height:12px;
width:102px;
background-color:blue;
}
</style>
<script type="text/javascript">
function decrementTimer(){
TIMES_UP = false;
var slider = document.getElementById("divSlider");
var curWidth = parseInt(slider.style.width);
if (curWidth < 100) {
slider.style.width = curWidth + 1 + "px";
TIMER = setTimeout(decrementTimer, 100);
} else {
alert("Time's up!");
resetTimer();
}
}
</script>
</head>
<body onload="resetTimer();">
<form>
<input type="button" value="Start Timer" onclick="decrementTimer();">
</form>
</body>
</html>
We again use the init() function, this time to set the top and left properties of the divBlock div, thus making the
properties accessible to JavaScript.
The moveH() function uses parseInt() to cut off the units (e.g, px) from the value of the left property of the div and
assign the resulting integer to the curLeft variable. It then modifies the left property of the element by adding the value
passed in for distance.
The moveV() function does the same thing, but it modifies the top property rather than the left property.
The example below shows how a different type of timed slider can be created by dynamically changing an element's
position.
#divSliderBG {
height:12px;
width:102px;
background-color:blue;
}
</style>
<script type="text/javascript">
function decrementTimer(){
TIMES_UP = false;
var slider = document.getElementById("divSlider");
var curLeft = parseInt(slider.style.left);
if (curLeft < 100) {
slider.style.left = curLeft + 1 + "px";
TIMER = setTimeout(decrementTimer, 100);
} else {
alert("Time's up!");
resetTimer();
}
}
</script>
</head>
<body onload="resetTimer();">
<form>
<input type="button" value="Start Timer" onclick="decrementTimer();">
</form>
</body>
</html>
Duration: 15 to 25 minutes.
In this exercise, you will modify the Math Quiz so that the timer is a slider rather than a count down. The result will
11. Also notice that the resetTimer() function sets the width of the slider.
12. var slider = document.getElementById("divSlider");
slider.style.width = "0px";
Modify the slider so that it extends the full width of the table cell it is within.
The z-index of an object can be changed by modifying the zIndex property of the element's style property. The
following example demonstrates this.
The variable z always holds the highest z-index. The function changeZ() simply adds 10 to z and assigns the resulting
value to the zIndex property of the passed in object.
var Z = 20;
function changeZ(elem){
Z += 10;
var objElem = elem;
objElem.style.zIndex = Z;
}
To continue to learn JavaScript go to the top of this page and click on the next lesson in this JavaScript Tutorial's Table
of Contents.
Quick JavaScript Recap
In this lesson of the JavaScript tutorial, you will learn...
Here we will go quickly over the fundamental building blocks of JavaScript and try to clarify some concepts that
sometimes can be a little blurry.
Null
Null is a data type that has only one possible value, null, which is also a reserved word in the language. We use null to
represent a value that we don't know or that is missing.
In the above example we know what to put in the name variable but we don't know yet what to put in the ssn variable.
Maybe we will know what to put in there later in our program, but maybe not.
Undefined
The Undefined type also has a single value, undefined, and it is similar to null but not exactly the same thing.
JavaScript uses undefined as the default value for any variable that has not been initialized yet. Let's modify our
previous example.
Now the value of ssn is undefined because it is no longer initialized to null or anything else.
The undefined type is used a lot when we want to detect if a global variable has been already declared. Which is kind of
a code smell anyway, as we will see in an upcoming lesson.
Boolean is a very common data type in every language. It has only two values, true and false, which are reserved words
and, I hope, self-explanatory.
Number
The Number data type can represent two types of numeric values, 32-bit integers or 64-bit floating point numbers.
Number values are created using number literals, which can be in a few different formats.
String
String is a very popular data type and they are used to represent text. We spend a lot of time manipulating strings in
pretty much any programming language.
We create strings using literal values enclosed in single or double quotation marks. These literal also support a few
special encodings for common characters like new line, tab, and the quotation marks themselves. This is similar to what
happens with strings in many other programming languages.
Every value in JavaScript can be converted to a string by using the toString() method, like var s = myValue.toString();.
Native Types
In addition to the primitive data types we just saw, JavaScript also provides a few other data types, which are
implemented as objects.
Date
We can store date values using Date objects. The Date object stores the date and time information internally as the
number of milliseconds since January 1st 1970.
There aren't date literals in the language, so we have to explicitly create a Date object when we need one.
Array
How useful would be our programs if we couldn't organize the data in arrays or other collection structures? I guess not
very much.
Arrays are very powerful in JavaScript and they kind of blur the lines between arrays and custom objects.
The Array object can be instantiated with both a constructor call or using literals. The array indices are not necessarily
contiguous or numeric or even of the same data type.
Object
The Object type serves as the base for all the objects in JavaScript, regardless of their data type or how they were
created.
The Object type is also used when we want to create custom objects. We can create new objects using a constructor call
or a literal. We will cover this is greater detail in a future lesson.
Regular Expressions
Regular Expressions is a syntax used to find occurrences of a string pattern inside a larger string. It has historically
been more popular in Unix environments and in Perl programs, but it has gained some adoption in many other
programming languages as well.
Regular expressions is one of these technologies with a measurable learning curve but that can have a big payoff
depending on the type of work you do.
JavaScript implements regular expressions with RegExp objects. It also support the Perl-syle literals.
Functions
Functions in JavaScript are more than just static blocks of code. They are Function objects that we use just like any
other data type value, e.g. we can pass functions to other functions, we can store a function in a variable, we can
modify a function, etc.
We will have a lot to talk about functions in one of our lessons. For now let's just remember how we declare and call
functions.
JavaScript being an interpreted language with a runtime execution engine, it needs a host environment to instantiate the
engine and forward the JavaScript code to it. The browser is one of many hosts for JavaScript. Other hosts are Adobe
Flash plugins (via ActionScript), desktop widgets (like Yahoo! Widgets, MS Gadgets, OS X Dashboard Widgets),
Firefox browser add-ons, and even some kinds of electronic equipment.
All that said, the browser was the originally intended host for JavaScript and by far the most common one.
The DOM
It's important to understand that the DOM is a standard that has nothing to do with JavaScript. It was created by the
W3C to normalize the browser vendors' implementations of Dynamic HTML.
The DOM is an API that enables programatic access to the HTML document structure, for reading or modification
purposes. When we write code like document.getElementById('abc') we are using the DOM.
With the DOM we can traverse our entire HTML document looking for specific HTML elements, which are called
nodes, or even create new elements and append them pretty much anywhere we want inside the HTML document.
In browser scripts, the document object is actually a property of the window object, which is the default (or global)
object of JavaScript in that environment. So typing window.document.body is the same as typing document.body. The
DOM starts at the document object.
There are other things one may think are part of JavaScript when, in fact, they're browser-specific features, like the
alert(), prompt(), setTimeout(), and open() functions. These are just methods of the window object, not part of
JavaScript per se.
We won't cover AJAX here but it suffices to understand that this object allows our scripts to initiate a request to an
URL and collect the server response without the need to reload the entire page.
Because of the syntax similarities with C-style languages, it's often possible that we mix-up what is available in
JavaScript so this lesson can serve as a quick reminder or cheat-sheet when needed.
To continue to learn JavaScript go to the top of this page and click on the next lesson in this JavaScript Tutorial's Table
of Contents.
Advanced Techniques
In this lesson of the JavaScript tutorial, you will learn...
These techniques will help your code become more expressive and more reusable at the same time. They are
programming patterns that leverage some language features that may not be present in other programming languages.
If you don't have experience with any other dynamically typed or functional language, the patterns you will see here
may look very strange at first. But fear not, we will provide plenty of explanations and examples to make you feel
comfortable enough to read and write JavaScript code that employ what you just learned.
But this does not guarantee that our function will always be called with 3 arguments. It's perfectly valid for someone to
call our function passing more than 3 or even less than 3 arguments.
Both calls will return surprising results (surprising from the caller's perspective.)
In the first case, since we are not expecting more than 3 arguments, the extra values, 2 and 7, will simply be ignored.
It's bad because the returned value is probably not what the calling code expected.
It's even worse when we look at the second example. We are passing only 2 arguments. What happens to val3? It will
have the value of undefined. This will cause the resulting sum to be NaN, which is clearly undesirable.
If we run our example again, we will see that we no longer get NaN for the second function call. Instead we get 32,
which is probably what the calling code expected.
We now have a pretty robust function that adds 3 numbers but it still doesn't feel all that useful. Sooner or later we will
need to add four or five numbers and we don't want to be updating our function to accept a val4 then a val5 parameters.
That would be a less than desirable maintenance task. Fortunately JavaScript can help us with that too.
Every function, when called, receives a hidden parameter called arguments, which is an array of all the arguments
passed to the function. Back to our example, the first time we call sumValues, the arguments array will contain [3, 5, 6,
2, 7] and in the second call [12, 20].
What this means is that we can ignore the passed parameters altogether an deal only with the arguments array. Let's
update our function once again.
function sumValues() {
var sum = 0;
for (var i = 0; i < arguments.length; i++) {
sum += arguments[i];
}
return sum;
}
Note how we got rid of the parameter list and now we get the values directly from the arguments array. When we run
our example now we see that the returned values are correct and precisely what was expected by the caller. We now
have a function that accepts as many parameters as are thrown at it and will always return the sum of all those
arguments.
When used in a context that expects a boolean value, any JavaScript expression can be used. See below.
var NUMBER = 0;
if (NUMBER) {
alert('You should not see this');
}
NUMBER = 1;
if (NUMBER) {
alert('You should be reading this');
}
var TEXT;
if (TEXT) {
alert('You should not see this');
}
TEXT = "";
if (TEXT) {
alert('You should not see this');
}
TEXT = "hi";
if (TEXT) {
alert('You should be reading this');
}
In the example above we are using non-boolean expressions (NUMBER and TEXT) in the if statement and some of
these expressions are being understood as true and some others false.
What is happening here is Type Coercion. JavaScript does its best to convert the given expression into the desired type
(boolean.)
The above values are all referred to as falsy, which is a way of saying "Not the same as false but can be interpreted like
such."
Every other value, including the strings "0" and "false", will resolve to true and will be referred to as truthy. Again,
"Not the same as true but can be interpreted as such."
Type coercion is the reason we have the === (triple-equal or strictly equal) comparison operator in JavaScript. The
regular equality operator == applies type coercion and sometimes your comparisons will not result as expected. Look at
the following sample code.
var NUM = 0;
if (NUM == "") {
alert('Hey, I did not expect to see this.');
}
if (NUM === "") {
alert('This will not be displayed.');
}
In the first if conditional is comparing two falsy values, and the type coercion will resolve both of them to false,
causing the result of the comparison to be true, which is probably not the original intent of the code.
To detect the type difference (string vs. number) we would need to use the triple equal operator, as shown in the second
if statement.
Default Operator
The boolean operators && and || also use truthy and falsy to resolve each of the operands to a boolean value.
From your previous experiences with other programming languages you may be led to believe that the result of a
boolean operation is always true and false. This is not the case in JavaScript.
In JavaScript, the result of a boolean operation is the value of the operand that determined the result of the operation.
Let's clarify that with an example.
The first boolean expression ( A || B || C || D ) is evaluated from left to right until a conclusion is made. The || is the
boolean OR operator, which only needs one of the operands to be true for the operation result in true. A is 0, which is
falsy. The evaluation continues with the remaining operands because falsy didn't determine anything yet. When
checking B, which is NaN and also falsy, we have the same situation - we need to continue evaluating. Then we check
C, which is 1 and resolves to true. We no longer need to continue evaluating the remaining operands because we
already know the expression will result true. But here's the catch. Instead of resulting strictly true, it will result in the
truthy value that ended the evaluation, C in our case. The message displayed will be "Result = 1".
As you might already expect, the && (boolean AND operator) works in a similar fashion, but with opposite conditions.
The AND operator returns false as soon as it finds an operand that is false. If for the expression ( D && C && B && A
) we follow the same sequence we did for the OR operator we will see that, D and C are both truthy so we need to keep
going, then we get to B, which is falsy and causes the evaluation to stop and return B. The message displayed then is
"Result = NaN".
You may be reading all this and thinking how can this be of any use. It turns out that this behavior of returning the first
conclusive value can be very handy when ensuring that a variable is initialized.
Let's take another look at a function we saw back in the Form Validation lesson.
The first two lines in this function make sure that min and max always have a valid value. This allow the function to be
called like checkValue("abc"). In this case the min and max parameters will both start with the undefined value.
When we reach the line min = min || 1; we are simply assigning 1 to min, ensuring it overrides the undefined. Similarly
we assign 1000 to max.
If we had passed actual values for these parameters as in checkLength("abc", 2, 10) these values would be kept because
they are truthy.
With this usage of the || we are effectively providing default values for these two parameters. That's why this operator,
in this context, is also called the Default Operator.
The default operator replaces more verbose code like:
var contactInfo;
if (email) {
contactInfo = email;
} else if (phone) {
contactInfo = phone;
} else if (streetAddress) {
contactInfo = streetAddress;
}
// is greatly shortened to
var contactInfo = email || phone || streetAddress;
Duration: 15 to 25 minutes.
Here we will revisit an earlier example and use the default operator to handle optional parameters.
<body>
<h1>Sum all numbers, default operator</h1>
<script type="text/javascript" src="../../Libraries/DebugHelp.js" ></script>
<script type="text/javascript">
insertDebugPanel();
</body>
</html>
Where is the solution?
Let's see how we can use functions as parameters to other functions. Consider the following example.
function sum(a, b) {
return a+b;
}
function multiply(a, b) {
return a*b;
}
You may be wondering what the following function call means: var SUM = combineAll(VALUES, sum);. In this
statement we are passing the function sum as the second parameter of combineAll. We are not invoking sum yet, just
passing a reference to it. Note that the open and close parenthesis aren't used after sum, that should serve as a tip off
that this is not a function invocation.
The line that ultimately invokes sum is runningResult = operation(initialValue, list[i]);, which received a reference to
sum in the operation parameter. When operation is invoked, in reality, it is sum that is getting called, returning the sum
of the two values passed in.
This is a very important technique and the combineAll function is often called reduce. Take your time to review the
code and run the example until you feel comfortable with it. We will be using this capability extensively in the
remaining lessons.
Anonymous Functions
Going back to our previous example, the functions sum and multiply are only referred to once, in each call to
combineAll. Furthermore, if we stick to that pattern, any new combination behavior that we desire, such as concatenate
the values or compute the average value, will need a new function just to be passed to combineAll. That seems like too
much overhead for such a simple thing. It would also not be very interesting to have all these functions that do such
simple things scattered through out the code.
Thankfully, we don't actually need to declare each of these functions. We don't even need to come up with names for
them. JavaScript allow us to create functions on the spot, any time we need a function that will only be used at that
spot.
Syntax
function (arg1, arg2) {
//function statements here
}
Because the functions created this way don't have names, they are aptly called anonymous functions.
Let's revisit our previous example and use anonymous functions to replace the single-use functions we declared.
The highlighted code represent the two anonymous functions, located where previously we had sum and multiply. This
coding style can understandably be harder to read, but it also avoids all that jumping around to look up what that
function that you are passing by name really does. The code of that function is right there, next to the code that is using
it.
Inner Functions
Since functions in JavaScript are just one more type of object, we can create a function inside another function. These
are called inner functions.
The example below shows how to create and use a function inside another one.
function analyzeText(text) {
var index = 0;
function getNextCharacter() {
if (index < index.legth) {
return text.charAt(index);
}
return false;
}
var c = getNextCharacter();
while (c) {
alert(index + ' ---> ' + c );
c = getNextCharacter();
}
}
analyzeText('abcdef');
The above example is not particularly useful. We will see more important uses of inner function when we look at
private members. For the time being, just notice how getNextCharacter() has access to index and text, which are scoped
to the analyzeText() function.
eval interprets a string containing JavaScript code. It can be a simple expression like "1 + 2" or a long and complex
script, with functions and all.
Here's one example that is not too different from what we can find in live sites on the web.
This function creates a JavaScript expression by concatenating an object name, with a dot and a property name. Then it
uses eval to evaluate that expression.
As we can see eval is a powerful function, but it is also potentially dangerous and incredibly inefficient. It's dangerous
because it's typically used to evaluate user entered input, which not always is a safe thing to do. It's inefficient because
each call to eval starts a JavaScript compiler.
The use of eval normally reveals lack of knowledge from the developer that wrote the script. For example, the sample
that we just used is not necessary. Probably what happened was that the developer did not know about the [ ] accessor
for properties. The same effect would be obtained with alert(window[PROP]);, with the advantage of not firing up a
compiler just to retrieve the property value.
Remember this, eval is evil. Avoid it as much as you can. If you think you need it, maybe it's because you did not learn
yet about an alternative way in JavaScript.
Variable Scope
Variable scope defines which parts of your code have access to a variable that you define. This typically varies
depending where you declare the variable.
Variables in JavaScript are either global or function scoped. When a variable is declared outside of any function body,
then it will become a global variable, meaning that any portion of your script will have access to it.
The previous sample showed that the Name variable is visible inside the function displaName.
There's a catch, though. If you forget to declare the variable using the var operator before using the variable, the
variable will be created as a global variable even if you are declaring it inside a function or inside a for loop
declaration.
function prepare() {
TEST = 123;
alert(typeof TEST);
//let's forget the "var" in the "for" declaration
for (abc=0; abc<5; abc++) {
//...
}
}
alert(typeof TEST);
prepare();
alert(TEST);
alert(abc);
Function Scope
Variables declared with var inside a function will be visible only inside that function. It doesn't matter if the variable
was declared inside an if block or a for block. Once declared, the variable becomes visible throughout the remainder of
the function. What that means is that JavaScript doesn't have block scope.
If you still feel unsure of how these techniques work, re-read the lesson and look for alternative explanations on the
web. Sometimes it helps reading a different phrasing of the same topic. It's important that you can at least read this type
of code fluently in order to understand some of what we will be looking at in the upcoming lessons.
To continue to learn JavaScript go to the top of this page and click on the next lesson in this JavaScript Tutorial's Table
of Contents.
Object Oriented Aspects of JavaScript
In this lesson of the JavaScript tutorial, you will learn...
In this lesson and the next one we will provide quick recipes of how common OO techniques are implemented with
JavaScript. We will also present some additional techniques that may not be available in other OO languages that you
are familiar with.
In this lesson we will cover most of the OO concepts as implemented in JavaScript. The screaming exception is
Inheritance, which we we will defer to the next lesson.
You need those objects because they encapsulate a lot of functionality that we can reuse over and over. It should not be
surprising that soaring demand for Rich Internet Applications (RIA), the need for objects to assist with the UI logic also
increases.
What we are suggesting here is that it is much more appropriate to produce reusable JavaScript objects than vast
collections of JavaScript functions. The problem with the functions, as we know from procedural programming
languages, is that they are either too complex to use (because they want to solve all the possible variants of the
problem) or they need to be called in a specific sequence.
Imagine that you have the common scenario of cascading drop down lists, one with a list of countries, the other with
states or provinces. When the country changes you want to update the states list accordingly.
If we were doing this in a procedural fashion, we would create a function to handle the change event of the countries
list, in this function we would call another function, passing the country name to refresh the states list. This second
function would receive the country name, retrieve the states list and update the states drop down.
function countryChanged() {
var sel = document.getElementById("country");
var country = sel.options[sel.selectedIndex].value;
updateStateList(country);
}
function updateStateList(country) {
var states = STATES_PER_COUNTRY[ country ];
var list = document.getElementById("state");
list.options.length = 0;
for (var i=0; i<states.length; i++) {
list.options.add( new Option(states[i]) );
}
}
document.getElementById("country").onchange = countryChanged;
---- Code Omitted ----
A few days later we may need to implement a similar scheme, but this time with a Company/Employee pair of drop
downs. Here we go writing all that code again.
If we were using an OO approach, we could create an object called CascadingListPair that knows how to capture the
change events of the first drop down, call some code to retrieve the list for the second drop down, and update the
second dropdown.
Don't worry about how complex the above code looks right now. We will discuss what is going on during this lesson.
For now just appreciate how much shorter this is and how much logic you did not have to write.
Now if we were to create a cascading pair for Company/Employee, it would be so much simpler.
1 - Building objects
The first approach is to create an empty object and progressively add its properties and methods.
var GUITAR = { };
GUITAR.color = 'black';
GUITAR.strings = ['E', 'A', 'D', 'G', 'B', 'e'];
GUITAR.tune = function (newStrings) {
this.strings = newStrings;
};
GUITAR.play = function (chord) {
alert('Playing chord: ' + chord);
};
GUITAR.print = function (price, currency) {
alert('This guitar is ' +
this.color +
', it has ' + this.strings.length + ' strings' +
' and it costs ' + price + currency);
};
//using the object
GUITAR.play('Dm7');
GUITAR.tune( ['D', 'A', 'D', 'G', 'B', 'e' ] );
debugWrite('this guitar is: ' + GUITAR.color);
GUITAR.print(850, 'USD');
The above methodology isn't too hard to understand but it is certainly more work than we are used to in more popular
programming languages. What we did here is quite simple. We just created the object and appended each property and
method as desired.
2 - Declaring objects
JavaScript also has a literal notation for objects. The previous example could have been rewritten in literal notation as
follows.
var GUITAR = {
color: 'black',
The syntax is easy to understand. It is a comma-delimited list of name: value pairs. Note that the method declaration is
easy to be confused with a regular function declaration. Just remember that a function can be used as a value and that's
what is happening here. You can also think of the methods as properties that contain a function as their values, if that
helps you understand the notation.
JSON
JavaScript Object Notation, or JSON, is a subset of the literal notation that we just saw. JSON was first proposed by
Douglas Crockford as a neutral way to represent and transport data, usually replacing XML.
JSON, just like the literal notation, is also a list of name/value pairs. The main difference is that the values can only be
a string, an Array, a Number, true, false, null, or another JSON object. The field names are also enclosed in double-
quotes.
Here's the GUITAR object represented in JSON. Note that we cannot represent the methods because JSON doesn't
accept them. It makes sense because JSON is meant only for data interchange, where behaviors are irrelevant.
var GUITAR = {
"color":"black",
"strings":['E', 'A', 'D', 'G', 'B', 'e']
};
One important thing to notice in the previous two approaches is that we did not need to create a formal class to serve as
the template of the GUITAR object. If we needed a second guitar object we would need to create it the same way we
did for the first one.
We could just encapsulate that logic in a function that can create a brand new guitar object on demand.
4 - Constructors
There's a variation of the factory function methodology that may feel more natural to you. In JavaScript, when function
is called preceded by the new operator, the function receives an implicit this argument that is a brand new object, ready
to be assembled with properties and methods. Also, if we do not return anything explicitly, the new operator
automatically returns this.
Let's rework our last example into a constructor. A good convention is to start constructor functions with a capital
letter, to differentiate from a regular function, signaling to the programmer that it needs to be called with the new
operator.
var GUITAR = new Guitar('black', ['E', 'A', 'D', 'G', 'B', 'e']);
debugWrite('this guitar is: ' + GUITAR.color);
GUITAR.play('Dm7');
GUITAR.tune( ['D', 'A', 'D', 'G', 'B', 'e' ] );
GUITAR.print(850, 'USD');
The biggest differences here are two. First we no longer need to create the new object because the new operator has
already taken care of that and passed the new object under the this identifier. The other difference is that we will use
this wherever we were using guitar before. We could have returned this but that, as we explained above, is no longer
necessary.
Duration: 30 to 60 minutes.
In this exercise we will implement the ever popular Yellow Fade Technique, where we set the background color of an
element to yellow and use timeouts to progressively fade the background back to white.
The exercise starts with working code that is not object oriented and has some problems because of that. We will
convert the existing code into an object type that has the same functionality and fixes the original shortcomings.
<body>
<h1>Yellow Fade</h1>
<p>
Add items by name to your shopping cart.
</p>
<h2>Shopping cart</h2>
<table id="shoppingCart">
<tr><th>Item</th><th>price</th><th></th></tr>
<tr id="newItemRow" style="background-color:#ddd;">
<td colspan="3"><b>New item:</b></td>
</tr>
<tr>
<td><input type="text" id="itemName" /></td>
<td><input type="text" id="itemPrice" size="5" /></td>
<td><input type="button" id="addItem" value="Add" onclick="addingItem();" /></td>
</tr>
</table>
<script type="text/javascript">
function addingItem() {
var name = document.getElementById('itemName').value;
var price = document.getElementById('itemPrice').value;
var newItemRow = document.getElementById('newItemRow');
var table = document.getElementById('shoppingCart');
var shoppingCart = table.getElementsByTagName("tbody")[0];
var row = document.createElement('tr');
addCellToRow(row, name);
addCellToRow(row, price);
var buttonCell = addCellToRow(row, '');
var button = document.createElement('input');
button.type = 'button';
button.value = 'remove';
button.onclick = removingItem;
buttonCell.appendChild(button);
shoppingCart.insertBefore(row, newItemRow);
highLightElement(row);
cell.innerHTML = cellText;
row.appendChild(cell);
return cell;
}
function removingItem() {
//'this' it the remove button
var row = this.parentNode.parentNode;
row.parentNode.removeChild(row);
}
/*
All the above code can stay. The only thing we are
interested in this exercise is to replace the
functions below this comment with an object that
does the same thing.
Instead of doing this in addingItem():
highLightElement( row );
we want to write:
new HighLightEffect( row )
challenge:
-- Add extra properties and constructor parameters
to the object to control the duration of the effect
challenge 2:
-- Add more properties to configure the start and end color of the effect
*/
function highLightElement(element) {
ELEMENT = element;
LEVEL = 128;
setYellowLevel(element, LEVEL);
setTimeout(increaseYellow, INTERVAL);
}
function increaseYellow() {
LEVEL += 10;
if (LEVEL > 255) {
LEVEL = 255;
}
setYellowLevel(ELEMENT, LEVEL);
if (LEVEL < 255) {
setTimeout(increaseYellow, INTERVAL);
}
}
</script>
<script type="text/javascript">
insertDebugPanel();
</script>
</body>
</html>
1. Change the HighLightEffect() function to take an extra parameter to configure the length of the effect in
seconds.
2. Make the new parameter optional with a default value of 1 second.
3. Change the example to pass in 2 seconds when creating the object.
1. Change the HighLightEffect() function to take another two extra parameters to configure start and end colors
of the effect.
2. Create a method that splits the RGB color components in a 3-element array with integer values.
3. In each timeout step, calculate the next value for each RGB component of the background color and set it.
4. Make these two extra parameters optional and default them to "#ffff99" and "#ffffff" respectively.
5. Change the background color of the shopping cart table to "#80ffff" and change the sample to use the start
color of "#cc99cc" and end color the same as this new background.
Memory usage
One thing is common in all the presented ways to create an object may go unnoticed. The play() and tune() methods
being added to each objects are always recreated from scratch for each object instance.
This will result in multiple copies of identical methods, which is clearly waste of memory. It may be a negligible waste
for those tiny methods and in just a couple of object instances, but it's generally not a good practice.
We will present the most adequate solution in the next lesson, but there's a middle-of-the-road alternative that we can
use to address the duplication issue in the meantime.
The idea is to create a single function for each of those methods and use them in the creation of the instance methods.
The example below illustrates this.
function tuneGuitar(newStrings) {
this.strings = newStrings;
}
function playGuitar(chord) {
alert('Playing chord: ' + chord);
}
var GUITAR = new Guitar('black', ['E', 'A', 'D', 'G', 'B', 'e'] );
This reduces the number of identical functions created but it has a different cost. The code now looks more dispersed.
The functions defined outside of the constructor method don't give any hint that they are used as methods of the Guitar
objects.
Although the object instantiation via constructors may look as if we are defining classes, we will learn in the next
lesson that this is not really true.
I JavaScript, most of the times, the this contains the object that owns the current method. One big exception are the
constructor functions as we just saw. When we call a function directly like tuneGuitar( someArray ); there's no owning
object for the stand alone tuneGuitar() function so this will be uninitialized. Or will it?
As it turns out, there is a default value for this, which is a global object. That object holds all our global variables as its
properties and global functions as its methods. When we are running JavaScript in a browser this global object is the
window object.
Back to our problem, if we call tuneGuitar() directly, we will end up adding a strings property to the window object,
which is the same as creating a global variable called strings.
Even worse is when we call a constructor without the new operator. In this case we will create a series of global
variables and functions. Another problem is that the variable that we are trying to assign the new object to will remain
undefined:
The bottom line here is that we have to make sure that when we a function has a this identifier then that function is
always called as an object's method or that we are providing the value for this explicitly, as we will see now.
We can use apply() and call() to invoke the function as if they were methods of whatever object we want. This is done
by effectively setting the desired value of this.
Back once again to our Guitar objects and this time the printInfo() function.
function printInfo(price, currency) {
alert('This guitar is ' +
this.color +
', it has ' + this.strings.length + ' strings' +
' and it costs ' + price + currency);
}
We already know that if we call this function directly, it will cause problems because this will be the window object.
Since window doesn't have a strings property, the expression this.strings.length will fail.
var GUITAR1 = new Guitar('black', ['E', 'A', 'D', 'G', 'B', 'e']);
printInfo.call( GUITAR1, '349.99', 'USD');
// OR
printInfo.apply( GUITAR1, ['349.99', 'USD']);
Both apply() and call() will produce the same result. The only difference is how the parameters are passed to the
invoked function. apply() just expects all the parameters listed right after the target object. call() expects that the second
parameter will be an array of the parameters to be passed to the function.
This difference can become useful when dealing with an unknown (or flexible) number of arguments.
var UTILS = {
getMaxLength: function () {
var max = 0;
for (var i=0; i<arguments.length; i++) {
if (arguments[i].length > max) {
max = arguments[i].length;
}
}
return max;
},
alignRight: function () {
var maxLength = this.getMaxLength.apply(this, arguments);
var result = [];
for (var i=0; i<arguments.length; i++) {
result.push( this.pad(arguments[i], maxLength) );
}
return result;
}
};
To illustrate this, let's find a DOM element in a page, which is clearly an object not created by us, and let's extend it by
adding new members.
Code Sample: ObjectOrientedJS/Demos/extending-objects.html
---- Code Omitted ----
<div>
<div id='userName'>Joe Doe</div>
<input type="button" value="Prepare" onclick="extend('userName', '#ffff99');" />
<input type="button" value="Highlight" onclick="show('userName');" />
</div>
<script type="text/javascript">
function show(elementName) {
var el = document.getElementById(elementName);
el.highlight();
}
</script>
---- Code Omitted ----
In this example the first button calls extend() to extend the userName div element. To extend the element, it tries to find
the element in the document, if the element is found then a new property called highlightColor is added with the chosen
color value. A new method is then added with the name highlight, which will simply set the element's background color
style to the value contained in highlightColor.
The second button calls show(), which will once again find the element and call the brand new method highlight(). This
causes the userName element to display a yellow background.
This type of object modification is an important characteristic of Dynamically Typed programming languages like
JavaScript.
Merging Objects
The technique of adding new members to an existing object is so common in JavaScript libraries that some of them
formalize and encapsulate this operation.
The code below is from the Prototype.js library and show the Object object itself being extended with a function that
helps extending any object.
With this new method we can make the process of augmenting an existing object look more like we are merging it with
a second one. Prototype.js uses this method many times in its own source code as shown below.
Object.extend(String.prototype, {
escapeHTML: function() {
return this.replace(/&/g,'&').
replace(/</g,'<').
replace(/>/g,'>');
},
unescapeHTML: function() {
return this.replace(/&/g,'&').
replace(/</g,'<').
replace(/>/g,'>');
}
});
Dynamic Languages
A dynamic language is one where the type of the objects is only loosely bound to the way it was created. Objects may
be created through a constructor method and inherit from a base class or a prototype but that doesn't mean that the
object is locked for alterations.
Code can, at any time, add, remove or modify existing properties or methods of the objects. For that reason the base
type of an object is less important in a dynamic language than it is in a statically typed language like Java or C#.
Duck Typing
A famous sentence reflects very well the importance given to base types in dynamic languages: "If it walks like a duck
and quacks like a duck, I would call it a duck."
The gist of this statement is that in order for your code to work with a given object, you don't need the object to derive
from a particular base type or prototype. You only need it to honor the expected public interface. As an example, as
long as the given object has a method called sort(), your code will be able to call that method to sort the contents of the
object. It doesn't matter if the object is an array or a DOM tree or a custom hash-like object.
You may think that dynamic typing is a recipe for disaster but in practice that's not the case. There are two important
factors that make dynamic languages very appealing.
Productivity
How many times when programming in statically typed languages we were in the situation where we had this class that
was almost perfect but it lacked one important property or method? The usual route is to inherit a new class from that
one and add the missing functionality.
That works well in the beginning but it quickly leads to class explosion in the application. Before you notice you'll have
dozens of classes that are minor improvements over other existing classes.
Dynamic languages offer the capability of "fixing" the original classes on the spot and keep the code-base smaller and
more manageable. That leads to greater programmer efficiency.
Unit Testing
Dynamic languages walk hand-in-hand with unit testing. We will take a closer look at unit testing in the next lesson but
let's just say that automated unit testing will help detecting a bug at the moment it is introduced in the code.
I wouldn't try tell you that unit testing is very popular in JavaScript, but in other dynamic languages like Ruby and
Python it's common practice in enterprise-quality software. JavaScript is in a way still discovering the importance of
unit testing.
Private members
Up to now all the properties and methods that we have been adding to our objects are accessible from any code that
interacts with the object. In other object oriented programming languages it's often possible to define some of the
properties and methods as private to the object itself. This private members are only accessible by code in the object
itself but not from code that simply uses the object.
JavaScript, at the current version, does not support private members as a language feature. But not all is lost. With a
clever little trick we can create objects with private members.
//private members
var current = 0;
var itemCount = arguments.length;
var that = this;
//public members
this.next = function () {
moveIndex();
return that.values[current];
};
};
The trick is related to JavaScript's variable scoping rules. Local variables inside the constructor function are visible
inside the constructor, including in the functions defined inside the constructor, like moveIndex() and next(). Even
moveIndex() is declared as a local variable.
In the next lesson we will explore JavaScript objects even further, when we explain how inheritance is implemented
using protype objects.
To continue to learn JavaScript go to the top of this page and click on the next lesson in this JavaScript Tutorial's Table
of Contents.
Prototype-Based Programming
In this lesson of the JavaScript tutorial, you will learn...
Software developers that work with Object Oriented programming languages are familiar with the concept of classes.
As it turns out, that's common but it's not the only way to accomplish object orientation and the JavaScript language
designers chose not to use the most common one.
In this lesson we will understand how JavaScript implements inheritance and how we can use it to build rich object
hierarchies.
Class-Based Programming
If you have previous experience with Object Oriented languages such as Java, C++, C# or Visual Basic, chances are
that you have employed class-based inheritance. You may have even concluded that was the only way to write object
oriented software.
In class-based inheritance, there's a clear distinction between the classes (or class objects) and the instances (or instance
objects.) The classes define the behavior and structure of instance objects, which in turn simply contain instance data.
The examples below illustrate a class being defined and then used in two different class-based programming languages.
//java or C#
class Door{
public void open(){
//...code omitted
}
}
Door frontDoor = new Door();
frontDoor.open();
'Visual Basic
Class Door
Public Sub Open()
'...code omitted
End Sub
End Class
Another important characteristic of class-based programs is how inheritance is implemented. Each class that you create
has a base class (or super class) explicitly or implicitly defined. Members of the base class will become available to the
new class (the derived or inherited class.)
//java
class SafeDoor extends Door{
public void unlock(string secretCombination){
//...code omitted
}
}
SafeDoor safe = new SafeDoor();
safe.unlock("4-8-15-16-23-42");
safe.open();
'Visual Basic
Class SafeDoor
Inherits Door
Public Sub UnLock(ByVal secretCombination As String)
'...code omitted
End Sub
End Class
Prototyping
Prototyping is a way to create objects by replicating another object (the so called prototype.) The new object may or
may not have a link to the original object, depending on the language implementation. JavaScript maintains a link
between the two as we will see shortly.
In prototype-based languages there's usually an operator to effect the object creation by copying another object.
Surprisingly JavaScript does not offer such operator, which is often consider a design flaw of the language.
What we are looking for in such operator is a way to write the following code.
Syntax
//attention, this is invalid syntax
var BRAND_NEW_OBJ = object( EXISTING_OBJ );
Unfortunately, the object function above does not come with JavaScript. On the other hand, nothing stops us from
creating our own implementation of that operator.
function object(original) {
function F() {}
F.prototype = original;
return new F();
};
We will defer the explanation of the above function for a little later, after we explain some of the language features
used in this code.
Prototypal Inheritance
Let's now take a look at prototypes in action and create an object hierarchy. Hopefully this will clarify how prototype
objects relate to the new objects that derive from them.
var vehicle = {
wheels: 0,
color: 'white',
make: 'ACME',
model: 'Unknown',
year: 1998
};
This will produce an object that we will illustrate in the following diagram. In the diagram the box represents the object
and each property/value stored in the object pair is listed.
We can easily derive a more specialized car from the vehicle object with the help of the object function we mentioned
above.
The above code first creates the car object by linking to the existing vehicle object that is given to the object function.
This link is represented by the arrow in the diagram below. After creating the car object, we add a new property called
doors with the value of 2.
Note in the above diagram that car does not have a copy of all the properties from vehicle. Only the new doors property
is stored in car. Will this work? Let's try it.
<html>
<head>
<title>Prototype-based inheritance</title>
<script type="text/javascript">
function object(original) {
function F() {}
F.prototype = original;
return new F();
};
var vehicle = {
wheels: 0,
color: 'white',
make: 'ACME',
model: 'Unknown',
year: 1998
};
When we load this example we see that it all works as intended, but how exactly this all worked?
The first three properties that we print are not hard to understand. They are standard properties just like we have seen in
previous lessons. The interesting line is the one that prints car.color. We did not explicitly add a color property to the
car object, so when JavaScript interpreter executes and tries to find that property in car it won't. But the interpreter
doesn't stop there. It will check if the object has a link to the object that was used during the creation process (the
prototype object.) In our case car does have a prototype so the interpreter proceeds to that object, vehicle, and tries to
find a member named color there. It will then find the property and print white.
The important thing to keep in mind here is that JavaScript knows about that arrow that we have in our diagram, and
follows that arrow when something is not found in the object at hand. If the prototype object at the end of said arrow
does not have the desired member, JavaScript will check if the prototype object has a prototype of its own and continue
to do that recursively until the member is found or no more prototypes are available, in which case an error will be
reported.
Overriding Properties
After closer inspection, we realize that our car has zero in its wheels property, which we don't agree with and want to
change to 4. We decide to change our car creation a little bit.
This causes our diagram to change slightly, showing a new wheels property in the car object.
When we execute the updated example, we can see that indeed our car is listed with 4 wheels. This happens because, as
we explained a few paragraphs before, the JavaScript interpreter only inspects the object's prototype when the desired
member is not found in the object itself. This mechanism allows us to override the prototype's members. The prototype
remains unchanged, we're just not reading the value from it anymore.
Every function has a prototype property that gets automatically assigned to the new object's (the one identified by this)
prototype. By default the function prototype will be Object.prototype but we can change that.
Let's rewrite one of the examples in the last lesson using a constructor function and its prototype.
Now the Guitar objects will have a prototype property that no longer points to the default Object.prototype. Instead, it
will point to Guitar.prototype and automatically inherit all the methods we just added to it.
In the following example we will use the above Guitar constructor to create an object. After that we will add a new
method to Guitar.prototype and we will see that the previously created object will have immediate access to this new
method.
To demonstrate what we can do with this knowledge, let's add a trim() method to all our strings.
Augmenting the prototypes of the native objects is a very powerful technique, taken to great lengths by popular
libraries like Prototype.js, which we are about to discuss.
To continue to learn JavaScript go to the top of this page and click on the next lesson in this JavaScript Tutorial's Table
of Contents.
Error Handling and Debugging
In this lesson of the JavaScript tutorial, you will learn...
No matter how careful you are, it always seems that errors find their way into your code. Sometimes they
are runtime errors caused by unpredicted scenarios. Sometimes the errors are just incorrect behavior of
your code, popularly known as bugs.
Fortunately, we have tools to deal with either type of problem. In this lesson we will talk about detecting
and handling errors and also about tracing down bugs and take them out of our applications.
Runtime Errors
Web browsers are such an hostile environment that it is almost guaranteed that we will constantly deal with
runtime errors. Users provide invalid input in ways you didn't think of. New browser versions change their
behavior. An AJAX call fails for a number of reasons.
Many times we can't prevent runtime errors from happening, but at least we can deal with them in a manner
that makes the user experience less traumatic.
function getInput() {
var name = window.prompt('Type your name', '');
alert('Your name has ' + name.length + ' letters.');
}
It may not be obvious, but this code has a bug waiting to break free. If the user clicks Cancel or presses Esc
the prompt() function will return null, which will cause the next line to fail with a null reference error.
If you as a programmer don't take any step to deal with this error, it will simply be delivered directly to the
end user, in the form of a utterly useless browser error message like the one below.
Depending on the user's browser or settings, the error message may be suppressed and only an
inconspicuous icon shows up in the status bar. This can be worse than the error message, leaving the users
thinking the application is unresponsive.
As you can see, the event will pass 3 arguments to the invoked function. The first one is the actual error
message. The second one is the URL of the file containing the error (useful if the error is in an external .js
file.) The last argument is the line number in that file where the error happened.
Returning true tells the browser that you have taken care of the problem. If you return false instead, the
browser will proceed to treat the error as unhandled, showing the error message and the status bar icon.
Syntax
try {
statements;
} catch (error) {
statements;
} finally {
statements;
}
The idea is simple. If anything goes wrong in the statements that are inside the try block's statements then
the statements in the catch block will be executed and the error will be passed in the error variable. The
finally block is optional and, if present, is always executed last, regardless if there was an error caught or
not.
function getInput(){
try {
var name = window.prompt('Type your name', '');
alert('Your name has ' + name.length + ' letters.');
} catch (error) {
alert('The error was: ' + error.name +
'\n The error message was: ' + error.message);
} finally {
//do cleanup
}
}
The error object has two important properties: name and message. The message property contains the same
error message that we have seen before. The name property contains the kind of error that happened and we
can use that to decide if we know what to do with that error.
With that in place, if we reload the page and cancel out of the prompt, that's what we will see:
It's a good programming practice to only handle the error on the spot if you are certain of what it is and if
you actually have a way to take care of it (other than just suppressing it altogether.) To better target our
error handling code, we will change it to only handle errors named "TypeError", which is the error name
that we have identified for this bug.
function getInput(){
try {
var name = window.prompt('Type your name', '');
alert('Your name has ' + name.length + ' letters.');
} catch (error) {
if (error.name == 'TypeError') {
alert('Please try again.');
} else {
throw error;
}
} finally {
//do cleanup
}
}
Now if a different error happens, which is admittedly unlikely in this simple example, that error will not be
handled. The throw statement will forward the error as if we never had this try...catch...finally block. It is
said that the error will bubble up.
throw {
name: 'InvalidColorError',
message: 'The given color is not a valid color value.'
};
Debugging
One of the most important activities in software development is debugging. It can also be one of the most
costly. That's why we need to do our best to reduce the amount of time spent in debugging.
One way to reduce this time is to create automated unit tests, which we will see in the lesson Production
Grade JavaScript.
Another way is to use the best tools available and try to remove the pain associated with debugging. It used
to be the case that debugging tools for JavaScript were archaic or close to non-existent. This situation has
improved a lot and now we can confidently say we have feasible ways to debug JavaScript without
resorting to horrendous tactics, such as sprinkling alert() calls across our code.
We won't waste your time discussing all the existing tools for debugging. Instead we will focus on the tool
that singlehandedly re-wrote the JavaScript debugging history.
Firebug
Firebug is an extension for the Mozilla Firefox browser. Once installed, Firebug will turn Firefox into
almost an IDE for web development.
In this exercise we are trying to understand why our BackgroundHighlighter object is not working as
expected. The object is supposed to change the background color of an input field when it gets focus and
revert it when it loses focus.
For some reason the code is not working. Here is the code.
<script type="text/javascript">
var BackgroundHighlighter = function (field, color) {
this.field = document.getElementById(field);
this.color = color;
this.field['onfocus'] = this.onGotFocus;
this.field['onblur'] = this.onLostFocus;
};
BackgroundHighlighter.prototype = {
onLostFocus: function () {
this.field.style.backgroundColor = '';
},
onGotFocus: function () {
this.field.style.backgroundColor = this.color;
}
};
function onPageLoad() {
//this function runs as soon as the page loads
new BackgroundHighlighter('userName', '#ff9');
new BackgroundHighlighter('company', '#ff9');
}
observeEvent(window, 'load', onPageLoad );
</script>
</head>
<body>
<form action="#">
Name: <input type="text" name="userName" value="" id="userName"/> <br/>
Company: <input type="text" name="company" value="" id="company">
</form>
</body>
</html>
Now if you click the Name textbox, Firebug will tell you that there's an error. See the picture below and
note the error message in the status bar.
The Console tab in Firebug shows that the error message is "this.field has no properties". The error
problem seems to be on line number 32.
Expand the error message by clicking the "+" icon next to it. We will get one extra piece of information, the
Call Stack, which in our case is simply one method call onGotFocus() as we can see in the image below.
When we click on onGotFocus() we will jump to the actual line of code in the Script tab. Let's place a
breakpoint on that line by clicking on the gutter on the left margin, right to the left of the line number.
Breakpoints are represented by a red circle on that margin.
Now let's click on the Name field again. Firebug kicks in an halts execution at the breakpoint we just set.
Looking at the Watch tab on the right, we can see that it is already tracking the value of this. And, to our
surprise, this does not contain a reference to an instance of our BackgroundHighlighter. Instead it contains
a reference to the input element.
Remember when we said you should be careful when using the this keyword in our objects? That was back
in The perils of this. Well, that is precisely the problem we are having right now. Our onGotFocus() method
is being called as an event handler for the onfocus event of the input field, and that call is made with the
input field being the value of this.
Our problem is not on line 32 though. The problem is back a few lines before:
this.field['onfocus'] = this.onGotFocus;
this.field['onblur'] = this.onLostFocus;
We cannot just pass a reference to one of our methods like that. We need to create some context that forces
the this inside those methods to contain our object. This is not hard. Let's change those two lines to:
this.field['onfocus'] = function() {
that.onGotFocus();
};
this.field['onblur'] = function() {
that.onLostFocus();
};
Now save the page and refresh the browser. The page should work now.
The console tab is an interactive interface between you and the executing code. It contains a prompt
denoted by >>> where you can inspect objects and variables, change their values, create new ones, etc.
The HTML tab allows us to inspect the page's structure. As we click elements in the displayed tree, they are
highlighted in the page right above. It is very useful to understand where in the page hierarchy a given
element is.
Inspect CSS
The CSS tab allows you to see all the CSS rules that exist on the page, from the various files that may
contain CSS, and you can change or disable any individual attribute. The effects of changing the attributes
are immediately reflected on the page. This feature is great for tweaking the CSS rules of your page to fix
CSS bugs.
Using the Scripts tab you can see all the scripts in the page and set breakpoints to assist during debugging.
Network traffic
One of the most interesting tabs is the Net tab. It shows all the requests made during the load and operation
of the page.
This tab is especially useful during the debugging of AJAX calls, where after expanding one of the requests
we can see all the HTTP traffic information for that particular request. The HTTP headers can become very
useful when tracing a problem.
Firebug has many more features but you're better off playing with it and learning which ones become more
useful to you. This tool is under active development, so make sure you check their site often to get newer
versions as they become available.
In case we didn't make it clear enough, we believe you should choose Firefox as your main development
browser and install Firebug immediately.
To continue to learn JavaScript go to the top of this page and click on the next lesson in this JavaScript
Tutorial's Table of Contents.
Production Grade JavaScript
In this lesson of the JavaScript tutorial, you will learn...
As JavaScript becomes a larger part of your application, it's important that we treat it with the same care we
treat our server side code. We will see how to implement proper documentation, unit tests, coding
conventions, and a few other important aspects of any maintainable code base.
The way we include JavaScript in our pages can affect the page performance and we will be comparing the
various alternatives.
We will also look at a host of utilities to help us along the way, including debuggers and code pre-
processors.
JavaScript Delivery
As we have seen in the early lesson in our course, there are a few different ways of including JavaScript
code in our pages. Let's quickly revisit them.
We can put our code inside script tags in our HTML markup. The code within these tags are evaluated as
they are encountered by the browser when loading the HTML, from top to bottom.
Syntax
<script type="text/javascript">
var MY_VAR = 123;
function doSomething() {
//.. code here
}
</script>
In external files
The code can also be placed in separate files that are retrieved and loaded by the browser when a script tag
with a src attribute is found.
Syntax
<script type="text/javascript" src="library.js"></script>
Sometimes we also see some script in attributes of HTML tags. These are usually there to handle some
event associated with the tag like a button's click.
Syntax
<input type="button"
value="Validate"
onclick="if(!validInput()) { alert('Please fix input');" />
Traditionally, the choice of delivery mechanism has always revolved around code reuse and client caching.
If we have scripts that are used in more than one page, then it is appropriate that we put these scripts in
external files and add them to each page.
There are two immediate benefits to this beyond the code reuse. First, the code will be maintained in a
single place, the .js file. Avoiding code duplication is always a good practice. Second, browsers will cache
the individual files, speeding up the page load process in subsequent visits.
Lately, though, there has been a lot of emphasis being placed on what has been called Unobtrusive
JavaScript. Read on.
Unobtrusive JavaScript
Current web development trends point to greater separation between content, style, and behavior. What that
means is removing as much as possible scripts from the HTML markup and placing them exclusively in
external files.
This may sound impossible, especially for the event handling scripts, but it's actually quite feasible. Take a
look at this "old school" event trickery.
This click event code can be moved to an external file with a relatively straight forward equivalent.
If we were using a helper library like Prototype.js, the code could become a little more compact.
The detail here is that our script can only reference DOM elements (like that div) after they are rendered.
The window object's load event is the appropriate place for that.
There's a problem here. If we have more than one external file trying to implement unobtrusive JavaScript,
it's possible that they hi-jack the window load event from each other. To avoid that we would recommend
using a helper library (again, like Prototype) to assign concurrent event handlers. Our Prototype.js-based
example above does just that.
The clean separation of HTML and JavaScript makes us recommend that you use the unobtrusive
JavaScript approach as much as possible. This practice tends to produce more organized and maintainable
JavaScript.
Performance implications
The presence of JavaScript in our pages directly affects how the browser loads and renders the page. As
soon as the browser finds a script tag in the HTML, it will stop rendering the HTML and will process that
JavaScript, which may involve downloading an external .js file. Even if the file had been previously cached
by the browser, it will still fetch it from the cache and then evaluate its contents.
The pauses in the page rendering can become quite noticeable depending on how many script tags we have
and where in the page they are located. This is compounded by the fact that many browsers only retrieve 2
files at most simultaneously.
Since the browser stops rendering the page when scripts are found, it makes sense that we try to place
scripts after most of the HTML has already been parsed and rendered.
The recommendation here is that we move our script tags as further down in the page as we can afford to
without breaking the scripts. If we are following unobtrusive JavaScript this should not be a problem at all.
We will put all the script tags immediately before the closing </body> tag.
This technique can be very easy to implement and it has a surprisingly effective result. It is a quick win that
we should not overlook.
Another way to speed up the load process of our pages is to compress the files that the page retrieves. In
terms of our external .js files, we should definitely remember to configure our web server to compress the
files before transmitting them.
When the browser requests a resource from the web server, it informs the web server about its de-
compression capabilities. This is done by sending a HTTP header like the following.
Syntax
Accept-encoding: gzip, deflate
If properly configured, as soon as the web server detects that header it will know it can return a compressed
version of the requested content. When compressed content is served, the response will carry a HTTP
header similar to the one below.
Syntax
Content-Encoding: gzip
The vast majority of the existing browsers will accept compressed content and de-compress it correctly. A
surprising number of web sites do not compress scripts and that has a negative effect on their performance.
Each web server is configured differently to enable the compression. We won't get into the details of how
to do that but you should not have any trouble finding the appropriate instructions in your web server's
documentation.
Minification of files
A similar technique is to minify our .js files. Minify was the term chosen to describe the process of
removing irrelevant white space and comments from JavaScript.
Note that we are not suggesting that we should stop adding comments to our JavaScript code or that we
remove all indentation at the same time. That would make our code incomprehensible even to its authors
(ourselves.)
The suggestion is that we use a utility to minify the code before it gets deployed on the server. We
definitely want to have the commented and indented code in our development environment, where we
typically do not experience the page load performance problems anyway.
JSMin
JSMin is one of the most popular tools to minify JavaScript code. On its web site you will find several
versions written in different programming languages. That includes a version written in JavaScript itself,
which we can try online at http://fmarcia.info/jsmin/test.html.
If you prefer, you can download the .exe version of JSMin and run it from the command line like this.
Syntax
jsmin.exe < myLibrary.js > myLibrary-minified.js
When we run JSMin on DebugHelp.js, which we have been using in many of our examples, we can see
what it does to reduce the file size. Here's the original file.
/**
* Sets up an event handler for the given element.
* @param {DOM Element} target The element that will publish the event.
* @param {string} eventName The name of the event, like 'click', 'load', 'mouseover'.
* @param {function} observerFunction The function that will be invoked when the event
happens.
*/
function observeEvent(target, eventName, observerFunction){
if (target.addEventListener) {
target.addEventListener(eventName, observerFunction, false);
} else if (target.attachEvent) {
target.attachEvent('on' + eventName, observerFunction);
} else {
target["on" + eventName] = observerFunction;
}
}
/** The default id used for the debug div in case not explicitly set*/
var WC_DEFAULT_DEBUG_PANEL_ID = "wc_debug";
/**
* Inserts a div element to print debug messages inside a given element or
* at the bottom of the page.
* @param {string} [containerID] The element to create the div in.
* Defaults to the page body.
* @param {string} [debugPanelID] The id of the created div. Defaults to 'wc_debug'
*/
function insertDebugPanel(containerID, debugPanelID){
debugPanelID = debugPanelID||WC_DEFAULT_DEBUG_PANEL_ID;
var container = document.getElementById(containerID);
container = container||document.body;
var panel = document.createElement('DIV');
panel.id = debugPanelID;
panel.className = 'wc_debug';
container.appendChild(panel);
debugWrite('<h4 style=\"border-bottom:1px solid black;\">Output:</h4>', debugPanelID);
}
After we minify it using the online tool or jsmin.exe we get the following file.
Syntax
jsmin.exe < DebugHelp.js > DebugHelp-minified.js
- Original Minified
Lines 55 3
Size in bytes 2057 945
Relative size 100% 45.9%
Note that the gains will vary according to the file contents. It's not rare to see the size shrink to below 30%
in files with good amounts of comments.
Some people may be uncomfortable with the fact that JavaScript code is delivered in clear text to the
browser. For this reason obfuscators were created.
An obfuscator will scan the source code, performs minification and renames a lot of the variables,
functions, etc. By using very short and cryptic names for the renamed items, an obfuscator reduces the code
size even more than a plain minifier.
Obfuscation is not without its problems. As you can imagine, renaming things in your code can be very
risky if that thing is being referenced outside the processed file.
We are not going to recommend obfuscation because of the unreasonable potential it has for introducing
bugs. A much better approach is to use minification combined with file compression. Compressed minified
files compared to compressed obfuscated ones are almost of the same size.
The intellectual property or security concerns that may prompt the use of an obfuscator aren't really
addressed by this tool. Obfuscation will only stop the casual observer and only slow down a little bit
anyone that is looking for vulnerabilities in your code.
Combine files
One simple way to speed up the page load is to reduce the number of external files referenced by the
HTML. Since the browser downloads only a few files simultaneously, if we minimize the number of
downloads we will be contributing to the faster rendering of the page.
Both JavaScript and CSS files are good candidates to be combined in one big file of each kind. If your page
or site uses a big number of small .js files, consider combining all of them into a single larger file.
Although pretty obvious, this is our number one recommendation for effectively use and deliver JavaScript
in your pages. Do all you can to serve your .js files concatenated. Most server side technologies like
ASP.NET and Ruby on Rails will provide you with an easy way to do this, even if you prefer to keep them
separated during development.
More techniques
There are a few more techniques that you can apply to make your pages load faster. We would like to
recommend that you take a look at YSlow, which is a Firefox extension that will try to identify
performance problems in your pages and suggest some improvements.
Documenting JavaScript
If we plan to create our own JavaScript libraries that can be reused across projects and by more than one
developer, it becomes important that we provide some level of documentation for them.
Documentation is one of those things that quickly gets out of synch with the actual code unless these two
things are kept close to each other. One popular way to do this is to include the source code documentation
in comments embedded in the code. A tool is later run to extract all these comments and produce browsable
documentation. Because the documentation is maintained within the code itself, it increases the chances
that the documentation reflects the current code.
JsDoc Toolkit
Early on there was a tool called JSDoc that would do just that but it has one mild problem, it was written in
Perl, what didn't help its popularization. Recently, a similar tool, that uses the same comment format, was
written in JavaScript and invoked via Java. This tool is called JsDoc Toolkit and can be found at
http://jsdoctoolkit.org
That web site will provide you with all the documentation and samples. As you can imagine, you'll need to
have some recent version of Java installed in your system, which you probably already do anyway.
To demonstrate this tool, let's generate the documentation for DebugHelp.js, which we have already seen in
some of our examples.
1. Your class material includes the following file with JsDoc Toolkit:
ClassFiles\ProductionGradeJS\Exercises\jsdoc-toolkit.zip
2. Extract all files from this .zip file into a directory named jsdoc-toolkit in that same directory
(Exercises.)
3. The utility is run from the command line but it takes too many parameters. Let's create a file
named buildDocs.bat so that we can simply pass a file or directory name to the tool.
4. Execute the above .bat file to generate the documentation for DebugHelp.js.
5. Open a command line and go to ClassFiles\ProductionGradeJS\Exercises
6. Execute buildDocs DebugHelp.js
After running the command, you should see a new directory called docs under Exercises. Inside that
directory, find and open the file index.html on your browser and you should see the generated
documentation, just like the screen shot below.
/** The default id used for the debug div in case not explicitly set*/
var WC_DEFAULT_DEBUG_PANEL_ID = "wc_debug";
/**
* Inserts a div element to print debug messages inside a given element or
* at the bottom of the page.
* @param {string} [containerID] The element to create the div in.
* Defaults to the page body.
* @param {string} [debugPanelID] The id of the created div. Defaults to 'wc_debug'
*/
function insertDebugPanel(containerID, debugPanelID){
debugPanelID = debugPanelID||WC_DEFAULT_DEBUG_PANEL_ID;
var container = document.getElementById(containerID);
container = container||document.body;
var panel = document.createElement('DIV');
panel.id = debugPanelID;
panel.className = 'wc_debug';
container.appendChild(panel);
debugWrite('<h4 style=\"border-bottom:1px solid black;\">Output:</h4>', debugPanelID);
}
Coding Standards
It's common for many organizations to establish coding standards across the development team. It's also
very common for developers to disagree and even ignore these standards. When writing JavaScript code,
trust me, some standards are actually very much necessary.
Because of its flexible nature and also because of some evil implementations of JavaScript by the browsers,
it is way too easy to fall into traps and create bugs that are very hard to trace.
Let's go through some of the most important JavaScript coding standards that we would like to encourage
you to follow. Some are there for pure legibility and some are there for your own protection.
Avoid globals at all costs
Global variables are the hallmark of badly structured code. They might be acceptable when the use of
JavaScript in the page is limited, but as the usage grows, global variables are dangerous and can produce
unexpected behavior.
Especially when we have more than one external .js file being used in the page, nothing prevents the code
in one file from declaring a global variable or function with the same name as in another file.
To remove this problem from our code we suggest, interestingly enough, that we create global namespacing
objects. See the following example.
//in wc_library.js
var COMPANY_NAME = 'Webucator';
function doSomething() {
//...
}
//in amazon.js
var COMPANY_NAME = 'Amazon.com';
function doSomething() {
//...
}
As we can see, if we include both files (wc_library.js and amazon.js) in the same page they will clobber
each other and who knows what the consequences will be. See how namespacing objects would avoid that
problem.
//in wc_library.js
var WEBUCATOR = {
companyName: 'Webucator'
doSomething: function () {
//...
}
};
//in amazon.js
var AMAZON = {
companyName: 'Amazon.com',
doSomething: function () {
//...
}
};
Now, instead of globals, we have methods and properties, locked inside well-organized objects. They are
the only type of global objects we want to encourage.
Avoid eval()
This is another problem we discussed previously. Again, just remember, eval() is evil.
Do not pass strings to setTimeout() or setInterval()
Passing strings to these functions is just another form of eval() in disguise. Instead of a string with
JavaScript code, pass an actual function variable or an anonymous function.
//instead of this:
setTimeout("alert('Time is up!');", 9000);
//prefer this:
setTimeout( function () {alert('Time is up');}, 9000);
if (flag = getFlag() ) {
alert(flag);
}
//instead of this:
var x;
if (flag) {
x = 'success';
} else {
x = 'failure';
}
//prefer this:
var x = flag ? 'success' : 'failure';
We recommend that you always use a block, even if it has only one statement. The absence of the braces
can hide bugs or make the code look different than its intent.
Syntax
if (condition) {
statements;
} else if (condition) {
statements;
} else {
statements;
}
Syntax
for (initialization; condition; update) {
statements;
}
Syntax
while (condition) {
statements;
}
Syntax
do {
statements;
} while (condition);
Syntax
switch (expression) {
case expression:
statements;
break; //(or return or throw)
default:
statements;
}
Syntax
try {
statements;
} catch (variable) {
statements;
} finally {
statements;
}
JSLint will analyze your JavaScript code and warn you of any unadvisable practices present in the code. It
has quite a few options for you to ignore some of the rules if desired.
Adopters of TDD write the unit tests before they write the code. The idea is that this will force you to write
code that is testable, which happens to also be better designed code in most cases.
Unit testing is especially attractive to code written in dynamic languages like JavaScript. There's just too
much flexibility in JavaScript and there isn't a compiler to give you a minimum level of confidence that
your code is correct. You have to actually run the code after every change to be certain you didn't break
anything.
This is where unit testing frameworks are helpful. We will take a look at the most popular one, called
JsUnit. If you have used any other testing framework of the x-Unit family, you should feel right at home in
JsUnit.
JsUnit comes with a series of functions that you can use in your unit testing scripts. The process typically
involves creating a new HTML page to hold a group of your tests, add the required JsUnit scripts
references, and write a few functions to perform the actual tests. These functions have to be named starting
with "test" (there are ways around this).
To execute the tests and see the results, we use the test runner and ask it to load the page with the tests. The
test runner will find the "testXXXXXXX" functions and call one by one, collecting the results and
reporting progress and results in a red or green progress bar.
To demonstrate how this works, look at this test file, which tests a couple of methods of the FaderEffect
object we saw in an earlier lesson. We included JsUnit in the class files for this lesson.
<head>
<title>Tests for Yellow Fade Effect</title>
<script type="text/javascript" src="../jsunit/app/jsUnitCore.js" ></script>
<script type="text/javascript" src="faderEffect.js" ></script>
<script type="text/javascript" charset="utf-8">
function test_intToColorByte() {
var el = document.getElementById('testElement');
var effect = new FaderEffect(el, 2, '#010203', '#0f0e0d');
assertEquals('Convert number lower than 16', '0a', effect.intToColorByte(10));
assertEquals('Convert number greater than 16', '20', effect.intToColorByte(32));
}
function test_colorToArray() {
var el = document.getElementById('testElement');
var effect = new FaderEffect(el, 2, '#010203', '#0f0e0d');
var array = effect.colorToArray('#04FF01')
assertEquals('Convert RED element', 4, array[0]);
assertEquals('Convert GREEN element', 255, array[1]);
assertEquals('Convert BLUE element', 1, array[2]);
}
</script>
</head>
<body>
<div id="testElement"></div>
</body>
</html>
this.fromRGB = this.colorToArray(this.fromColor);
this.toRGB = this.colorToArray(this.toColor);
this.frameNo = 0;
this.updateBgColor = function () {
if (that.frameNo < (that.totalFrames-1)) {
var newRGB = that.calcNewColor(that.frameNo);
that.element.style.backgroundColor = that.arrayToColor(newRGB);
that.frameNo += 1;
setTimeout(that.updateBgColor, that.interval);
} else {
that.element.style.backgroundColor = that.arrayToColor(that.toRGB);
}
};
this.calcNewColor = function () {
var frac = (1.0 * this.frameNo)/this.totalFrames;
return [
Math.floor((this.toRGB[0]-this.fromRGB[0])*frac + this.fromRGB[0]),
Math.floor((this.toRGB[1]-this.fromRGB[1])*frac + this.fromRGB[1]),
Math.floor((this.toRGB[2]-this.fromRGB[2])*frac + this.fromRGB[2])
];
};
this.updateBgColor();
}
To use the test runner you will need to have Java installed in your machine. With that, navigate to
ProductionGradeJS/jsunit/testRunner.html, select the file ProductionGradeJS/Demos/js-unit.html and click
"Run". You should see something similar to the image below.
Unit testing, and TDD in special, are very large topics for which many books have been written. Our goal
here is to simply show you that, unit testing is a very good practice for JavaScript code and that there's is
support similar to other programming languages.
With the available tools and guidance you can produce very maintainable JavaScript and abandon the
practice of blindly copying and pasting JavaScript (if you've been in web development for long enough,
you know you've done it.)
To continue to learn JavaScript go to the top of this page and click on the next lesson in this JavaScript
Tutorial's Table of Contents.
The Prototype Library
In this lesson of the JavaScript tutorial, you will learn...
Back in 2005, when the Web rediscovered JavaScript and the AJAX term was coined, a few JavaScript
libraries emerged as the platform behind many of the new web sites collectively known as Web 2.0. One of
the most popular libraries, arguably the most popular at the time, was Prototype.
The Prototype library was originally written by Sam Stephenson when the company he worked for was
creating some of the most well-known pioneer AJAX applications, like Basecamp and Campfire. This
library is also what powers the client side scripts of applications built using Ruby on Rails.
Today the library is still maintained by Sam Stephenson but now it has its own team of maintainers that
continuously contribute new features, bug fixes, and tools to produce JavaScript with Prototype.
Prototype is largely a framework type of library. It does not have any eye candy or UI widgets. On the
other hand, because it is a good platform, there is a full blown ecosystem of other libraries built on top of
Prototype to provide almost anything you need at the client side.
During this lesson and the next, whenever we are referring to the Prototype library we will use the word
with a capital P. We will use lower case prototype to refer to the prototype property that all JavaScript
objects have.
Creating classes
Prototype comes with an object named Class that we can use to create new classes and optionally specify
their base classes.
var WEBUCATOR = { };
WEBUCATOR.Beverage = Class.create( {
initialize: function (name) {
this.name = name;
},
describe: function () {
return "I'm a beverage named " + this.name + ".";
}
});
var water = new WEBUCATOR.Beverage('Water');
alert(water.describe());
The object passed to Class.create will become the implementation of the class, copied to the new class'
prototype object. The only special method is the one called initialize(); it will serve as the class' constructor.
Class inheritance
If the object passed to Class.create is another class object created with Class.create, that will be detected
and this object (class) will become the base class for the new class.
We can add new members or replace members in the base class by using a second argument containing an
object. When we are replacing methods in the new class, we can define the first argument of the new
method called $super, which will be a reference to the same method in the base class. We can call it if
needed. The example below shows all that in action.
<head>
<title> Test Page </title>
<script src="../../Libraries/prototype.js" type="text/javascript"></script>
<script src="../../Libraries/DebugHelp.js" type="text/javascript"></script>
<script type="text/javascript">
function test() {
var d = $('myDiv');
d.hide();
alert(d.innerHTML);
d.show();
d.addClassName('active');
}
//add the debud panel at the bottom of the page
observeEvent(window, 'load', function () {insertDebugPanel();} );
</script>
<style type="text/css" media="screen">
.active {background-color:#ff9;}
</style>
</head>
<body>
<div id="myDiv">
<p>This is a paragraph</p>
</div>
<div id="myOtherDiv">
<p>This is another paragraph</p>
</div>
<input type="button" value="Test $()" onclick="test();"/><br/>
</body>
</html>
This type of classical inheritance is used extensively in Prototype's source code, in case you are curious to
inspect it (and we encourage you to do so.)
Unlike the DOM function, though, this one goes further. The returned element object will be augmented
with some extra methods. These extra methods simplify many tasks, like hiding/showing the element,
getting its size, scrolling to the element, etc. You can get a list of the methods that are added to the returned
element object in the reference for the Element.Methods object. Furthermore, if the element is a form it will
also receive copies of the utility methods from Form.Methods and if the element is a form field (input,
select, or textarea) it will additionally receive copies of the utility methods from Form.Element.Methods.
<head>
<title> Test Page </title>
<script src="../../Libraries/prototype.js" type="text/javascript"></script>
<script src="../../Libraries/DebugHelp.js" type="text/javascript"></script>
<script type="text/javascript">
function test() {
var d = $('myDiv');
d.hide();
alert(d.innerHTML);
d.show();
d.addClassName('active');
}
//add the debud panel at the bottom of the page
observeEvent(window, 'load', function () {insertDebugPanel();} );
</script>
<style type="text/css" media="screen">
.active {background-color:#ff9;}
</style>
</head>
<body>
<div id="myDiv">
<p>This is a paragraph</p>
</div>
<div id="myOtherDiv">
<p>This is another paragraph</p>
</div>
<input type="button" value="Test $()" onclick="test();"/><br/>
</body>
</html>
Because many of the new methods added to the element return the element itself, you can chain the method
calls to make more compact code:
//change the text, the CSS class, and make the element visible
$('messageDiv').update('Your order was accepted.').addClassName('operationOK').show();
Another nice thing about this function is that you can pass either the id string or the element object itself,
which makes this function very useful when creating other functions that can also take either form of
argument.
A quick note on performance. The current implementation of the $$() function in prototype.js is not
regarded as particularly efficient. If you plan on traversing deep and complex HTML documents using this
function frequently, you may want to consider other freely available implementations, possibly simply
substituting the $$() function itself.
This function, combined with the extensions for the Array class, makes it easier to convert or copy any
enumerable list into an Array object. One suggested use is to convert DOM NodeLists into regular arrays,
which can be traversed more efficiently. See example below.
<script type="text/javascript">
function showOptions() {
var someNodeList = $('lstEmployees').getElementsByTagName('option');
var nodes = $A(someNodeList);
function testHash() {
//let's create the object
var a = {
first: 10,
second: 20,
third: 30
};
<script type="text/javascript">
function demoDollar_R() {
var range = $R(10, 20, false);
range.each( function (value, index) {
alert(value);
});
}
</script>
<input type="button" value="Sample Count" onclick="demoDollar_R();" />
In the example below, the function xmlNode.text works in some browsers, and xmlNode.textContent works
in the other browsers. Using the Try.these() function we can return the one that works.
<script type="text/javascript">
function getXmlNodeValue(xmlNode) {
return Try.these(
function () {return xmlNode.text;},
function () {return xmlNode.textContent;}
);
}
</script>
Strings, reloaded
Strings are powerful objects. Prototype takes that power and elevates it by another level of magnitude.
String substitutions
When it comes string substitutions JavaScript already has the methods like String.Replace, which even
works with regular expressions, but it's still not as flexible as the alternative introduced by Prototype.
Meet the new String.gsub method. With this method you can not only find and replace a fixed string or a
regular expression pattern, but you also have much more control over the replacement process. You can, for
example, use a string template to instruct the method on how you would like the found elements to be
transformed (rather than simply replaced.)
The example below searches for words containing 't' and replaces the portion that comes after the 't' with
'tizzle'. In case the example is not very clear, the regular expression we chose has a capture group
declaration: the \w+ enclosed in parenthesis. We can get the value captured by this group using #{1} in the
replacement template string.
In our example we are capturing what comes before the 't' and appending 'tizzle' to it. If we had more
capture groups in the regular expression, we would get the values with #{2}, #{3}, and so on.
function talkLikeYouKnowSomething() {
var s = 'prototype string extensions can help you';
var snoopdogfy = /\b(\w+)t\w+\b/;
var snooptalk = s.gsub(snoopdogfy, '#{1}tizzle' );
alert(snooptalk); // shows: "prototizzle stizzle extizzle can help you"
}
Let's not stop there. The substitution we have just made is not all that powerful because we are limited to
pattern matching and substitutions. What if we could operate on the matches with custom logic to produce
the desired substitution values? We can do that if we pass a function as the second argument to gsub(). The
function will receive an array with the matched text (index 0) and any capture group values (index 1 to N.)
function scamCustomers() {
var prices = 'book1 $12.5, magazine $5.50, pencil $1.20';
var priceFinder = /\$([0-9\.]+)/;
var r = prices.gsub(priceFinder, jackUp);
alert(r);//shows: "book1 $13.75, magazine $6.05, pencil $1.32"
}
function jackUp(matches) {
//increases the prices by 10%
var price = parseFloat(matches[1]);
return '$' + Math.round(110 * price)/100;
}
String templates
As you increase the amount of JavaScript code in your applications, increasingly you'll find yourself with
collections of objects of the same type and that you need to list or present in a formatted way.
It's not rare to find code in your applications that loops through a list of objects, building a string based on
the object properties and some fixed formatting elements. Prototype comes with the Template class, which
aims at helping you with exactly this type of scenarios.
The example below shows how to format a list of items in a shopping cart in multiple HTML lines.
function printCart() {
//creating a sample cart
var cart = { };
cart.items = [ ];
//putting some sample items in the cart
cart.items.push({product: 'Book 123', price: 24.50, quantity: 1});
cart.items.push({product: 'Set of Pens', price: 5.44, quantity: 3});
cart.items.push({product: 'Gift Card', price: 10.00, quantity: 4});
alert(formatted);
/* SHOWS:
You are ordering 1 units of Book 123 at $24.5 each<br/>
You are ordering 3 units of Set of Pens at $5.44 each<br/>
You are ordering 4 units of Gift Card at $10 each<br/>
*/
}
For a more complete list of new methods, see the String extensions reference.
DOM Manipulation
Another great strength of Prototype is it's repertoire of methods to assist with dynamically changing the
HTML document structure.
We've already sees that Prototype augments the DOM elements with a host of utility methods. Many of
those methods are related to DOM traversing and manipulation. We can create, show, hide, alter elements
in our HTML with a much simpler syntax than the one that comes as part of the DOM API.
We can get a lot done with a few basic methods with suggestively enough names: insert(), show(), hide(),
remove(), update(), and wrap(). We also can create elements from scratch using the new Element(tagName)
syntax.
All of these DOM operations are shown in the following example. Once again, note how we can chain
these method calls to reduce the amount of code typed.
<h4>Insertions</h4>
<script type="text/javascript" charset="utf-8">
function insertElTop() {
var el = new Element('li').update( $F('newItem') );
$('list1').insert({top: el });
}
function insertElBottom() {
var el = new Element('li').update( $F('newItem') );
$('list1').insert({bottom: el });
}
function insertElBefore() {
var el = new Element('li').update( $F('newItem') );
$('list1').insert({before: el });
}
function insertElAfter() {
var el = new Element('li').update( $F('newItem') );
$('list1').insert({after: el });
}
</script>
<input type="text" name="newItem" value="Text here" id="newItem"/>
<input type="button" value="Insert at TOP" id="btn2a" onclick="insertElTop();" />
<input type="button" value="Insert at BOTTOM" id="btn2b" onclick="insertElBottom();" />
<input type="button" value="Insert BEFORE" id="btn2c" onclick="insertElBefore();" />
<input type="button" value="Insert AFTER" id="btn2d" onclick="insertElAfter();" />
<h4>Hide/Show</h4>
<input type="button" value="Hide" onclick="$('box3').hide();" />
<input type="button" value="Show" onclick="$('box3').show();" />
<input type="button" value="Toggle" onclick="$('box3').toggle();" />
<div class="bigBox"><div id="box3" class="smallBox" /> </div>
<h4>Wrapping</h4>
<script type="text/javascript" charset="utf-8">
function wrapInDiv() {
$('img4').wrap('div', {'class':'lightBox'});
}
</script>
<input type="button" value="Wrap in Div" onclick="wrapInDiv();" />
<div style="width:330px;">
<img id="img4" src="wc.png" alt="webucator"/>
<h4>Removing elements</h4>
<script type="text/javascript" charset="utf-8">
function removeEl() {
try {
$('img5').remove();
} catch (ex) {
alert('The element does not exist!!!!');
}
}
</script>
<input type="button" value="Remove Image" onclick="removeEl();" />
I'm sure that your interest in Prototype is driven mostly by its AJAX capabilities. So let's explain how the
library makes your life easier when you need to perform AJAX logic.
The Ajax object is a pre-defined object, created by the library to wrap and simplify the tricky code that is
involved when writing AJAX functionality. This object contains a number of classes that provide
encapsulated AJAX logic. Let's take a look at some of them.
To assist with AJAX functionality, the library defines the Ajax.Request class.
Let's say you have an application that can communicate with the server via the url
http://yourserver/app/get_sales?empID=1234&year=1998, which returns an XML response like the
following.
Talking to the server to retrieve this XML is pretty simple using an Ajax.Request object. The sample below
shows how it can be done.
<script type="text/javascript">
function searchSales() {
var empID = $F('lstEmployees');
var y = $F('lstYears');
var url = 'http://yourserver/app/get_sales';
var pars = 'empID=' + empID + '&year=' + y;
function showResponse(response) {
//put returned XML in the textarea
$('result').value = response.responseText;
}
</script>
<select id="lstEmployees" size="10" onchange="searchSales()">
<option value="5">Buchanan, Steven</option>
<option value="8">Callahan, Laura</option>
<option value="1">Davolio, Nancy</option>
</select>
<select id="lstYears" size="3" onchange="searchSales()">
<option selected="selected" value="1996">1996</option>
<option value="1997">1997</option>
<option value="1998">1998</option>
</select>
<br/><textarea id="result" cols=60 rows=10 ></textarea>
Can you see the second parameter passed to the constructor of the Ajax.Request object? The parameter
{method: 'get', parameters: pars, onComplete: showResponse} represents an anonymous object in literal
notation (a.k.a. JSON). What it means is that we are passing an object that has a property named method
that contains the string 'get', another property named parameters that contains the querystring of the HTTP
request, and an onComplete property/method containing the function showResponse.
There are a few other properties that you can define and populate in this object, like asynchronous, which
can be true or false and determines if the AJAX call to the server will be made asynchronously (the default
value is true.)
This parameter defines the options for the AJAX call. In our sample, we are calling the url in the first
argument via a HTTP GET command, passing the querystring contained in the variable pars, and the
Ajax.Request object will call the showResponse function when it finishes retrieving the response.
As you may know, the XMLHttpRequest reports progress during the HTTP call. This progress can inform
four different stages: Loading, Loaded, Interactive, or Complete. You can make the Ajax.Request object
call a custom function in any of these stages, the Complete being the most common one. To inform the
function to the object, simply provide property/methods named onXXXXX in the request options, just like
the onComplete from our example. The function you pass in will be called by the object with two
arguments, the first one will be an Ajax.Response object (the response) and the second one will be the
evaluated X-JSON response HTTP header (if one is present). You can then use the first argument to get the
returned data and maybe check the status property, which will return the HTTP result code of the call. The
X-JSON header is useful if you want to return some script or JSON-formatted data. The same JSON data is
also available in the response's responseJSON property.
Two other interesting options can be used to process the results. We can specify the onSuccess option as a
function to be called when the AJAX call executes without errors and, conversely, the onFailure option can
be a function to be called when a server error happens. Just like the onXXXXX option functions, these two
will also be called passing the XHR that carried the AJAX call and the evaluated X-JSON header.
Our sample did not process the XML response in any interesting way. We just dumped the XML in the
textarea. A typical usage of the response would probably find the desired information inside the XML and
update some page elements, or maybe even some sort of XSLT transformation to produce HTML in the
page.
There's also another form of event callback handling available. If you have code that should always be
executed for a particular event, regardless of which AJAX call caused it to happen, then you can use the
new Ajax.Responders object.
Let's suppose you want to show some visual indication that an AJAX call is in progress, like a spinning
icon or something of that nature. You can use two global event handlers to help you, one to show the icon
when the first call starts and another one to hide the icon when the last one finishes. See example below.
<script type="text/javascript">
var myGlobalHandlers = {
onCreate: function () {
Element.show('systemWorking');
},
onComplete: function () {
if(Ajax.activeRequestCount === 0){
Element.hide('systemWorking');
}
}
};
Ajax.Responders.register(myGlobalHandlers);
</script>
<div id='systemWorking'><img src='spinner.gif'>Loading...</div>
For more complete explanations, see the Ajax.Request reference and the options reference.
We will use Prototype AJAX support to call a server-side search service and display the results without
ever reloading our page.
Don't get too worried about the PHP file that we use. Just treat it as some generic URL that will find news
items that contain a given word from a database. The search.php script will simply read the value posted in
the q parameter and look into the cnn.xml file looking for items that contain that value in the description
element.
Once the page is ready, you should be able to type a word in the input field and click the Go! button to see
all the stories that contain that word be listed.
WEBUCATOR.onload = function () {
};
</script>
<form action="#">
<input type="text" name="searchTerm" value="" id="searchTerm"/>
<input type="button" name="goButton" value="Go!" id="goButton"/>
</form>
</body>
</html>
$searchTerm = $_POST['q'];
?>
Where is the solution?
<script type="text/javascript">
function getHTML() {
var url = 'http://yourserver/app/getSomeHTML';
var pars = 'someParameter=ABC';
}
</script>
<input type="button" value="GetHtml" onclick="getHTML();"/>
<div id="placeholder"></div>
As you can see, the code is very similar to the previous example, with the exclusion of the onComplete
function and the element id being passed in the constructor. Let's change the code a little bit to illustrate
how it is possible to handle server errors on the client.
We will add more options to the call, specifying a function to capture error conditions. This is done using
the onFailure option. We will also specify that the placeholder only gets populated in case of a successful
operation. To achieve this we will change the first parameter from a simple element id to an object with
two properties, success (to be used when everything goes OK) and failure (to be used when things go bad.)
We will not be using the failure property in our example, just the reportError function in the onFailure
option.
<script type="text/javascript">
function getHTML() {
var url = 'http://yourserver/app/getSomeHTML';
var pars = 'someParameter=ABC';
function reportError(response) {
alert('Sorry. There was an error.');
}
</script>
<input type="button" value="GetHtml" onclick="getHTML();"/>
<div id="placeholder"></div>
We will use Prototype AJAX support to call a server-side search service and display the results without
ever reloading our page.
The search.php script is the same from the previous exercise. You may have noticed that it returns a series
of li elements. We will use these elements directly inside the resultList element.
<head>
<title> Ajax Updater </title>
<script src="../../Libraries/prototype.js" type="text/javascript"></script>
<script src="../../Libraries/DebugHelp.js" type="text/javascript"></script>
<style type="text/css" media="screen">
#resultList
{
background-color:#ddd;
width:500px;
}
#resultList li
{
background-color:#ddf;
padding:4px;
border:1px solid #33a;
margin:4px;
}
</style>
</head>
<body>
<script type="text/javascript">
//add the debud panel at the bottom of the page
Event.observe(window, 'load', function () {insertDebugPanel();} );
</script>
<script type="text/javascript">
var WEBUCATOR = { };
/*
The file ..\Common\cnn.xml contains an old copy
of the xml from the URL:
http://rss.cnn.com/rss/cnn_topstories.rss
If you wish, you can get a newer version of the xml and
update that file.
WEBUCATOR.onload = function () {
};
</script>
<form action="#">
<input type="text" name="searchTerm" value="" id="searchTerm"/>
<input type="button" name="goButton" value="Go!" id="goButton" />
</form>
<h2>Results:</h2>
<ol id="resultList">
<li><strong>test:</strong><br/>
test test test test test
test test test test test
test test test test test
test test test test test
test test test test test
test test test test test
</li>
<li><strong>test:</strong><br/>
test test test test test
test test test test test
test test test test test
test test test test test
test test test test test
test test test test test
</li>
</ol>
</body>
</html>
Where is the solution?
If your server logic returns JavaScript code along with HTML markup, the Ajax.Updater object can
evaluate that JavaScript code. To get the object to treat the response as JavaScript, you simply add
evalScripts: true; to the list of properties in the last argument of the object constructor. But there's a caveat.
Those script blocks will not be added to the page's script. As the option name evalScripts suggests, the
scripts will be evaluated. What's the difference, you may ask? Lets assume the requested URL returns
something like this:
<script language="javascript" type="text/javascript">
function sayHi() {
alert('Hi');
}
</script>
<input type="button" value="Click Me" onclick="sayHi();"/>
In case you've tried it before, you know it doesn't work. The reason is that the script block will be
evaluated, and evaluating a script like the above will not create a function named sayHi. It will do nothing.
To create this function we need to change our script to create the function. See below.
Note that in the previous example we did not use the var keyword to declare the variable. Doing so would
have created a function object that would be local to the script block (at least in IE). Without the var
keyword the function object is scoped to the window, which is our intent.
For more complete explanations, see the Ajax.Updater reference and the options reference.
Your first suspect is Prototype, Of course, it seemed too easy to be true. But don't blame the library just yet.
Ask yourself how much you really understand character encoding, code pages, and how the browser deals
with it. If you have a positive answer then I bet you are on your way to fix the problem. If you are among
the other 80% (another useless, imprecise author's estimate) of web developers that take character encoding
for granted, keep reading.
I won't pretend to be an authority on the topic, much less give you a complete explanation of how this is
best handled. Instead you go straight to the solution that I use and provide hints on how this could be fixed
in your own scenario.
Simply put, the solution revolves around the following statement: Serve what the browser is expecting you
to serve. If we are going to update the page with text that contains Unicode/UTF-8 characters then we
better make the browser aware of that.
Let's start with the simple case when you are just updating the page with text from a static HTML file that
resides on your server. When you created that file, depending on which text editor you employed, it is very
possible that the file was saved in ANSI (or better said, non-Unicode) format. This is the default for many
text editors, especially source code editors, because the file size will be smaller and it's rather unusual to
edit source code with Unicode characters in it.
Suppose you have the following file named static-content.html on your server. You saved this file saved in
ANSI format.
<div>
Hi there, José. Yo no hablo español.
</div>
Your main page updates itself using something like the snippet below.
<script type="text/javascript">
function updateWithFile() {
var url = 'static-content.html';
var pars = '';
var myAjax = new Ajax.Updater(
'placeholder', url,
{method: 'get', parameters: pars});
}
</script>
<div id="placeholder">(this will be replaced)</div>
<input id="btn" value="Test With Static File"
onclick="updateWithFile();" type="button"/>
When you click the button the static file is retrieved but the non-English characters are replaced by question
marks or some other symbol. The displayed text will look similar to "Hi there, Jos?. Yo no hablo espa?ol."
or "Hi there, Jos?Yo no hablo espa?", depending on your browser.
In this case, the solution is straightforward, simply save the static file in an appropriate format. Let's save it
in UTF-8 and run the script again (any decent text editor will have an option in the Save As dialog.) You
should now see the correct text (if not, your browser may have cached the old version, try using a different
file name.)
If the HTML that you are serving is not static, if it is being dynamically generated by some application
framework (like ASP.NET, PHP, or even Perl,) make sure the code that generates this HTML is producing
the text in the appropriate encoding and code page, and include in the HTTP response headers one header
that informs this. Each platform has a different way to achieve this, but they are very similar.
For example, in ASP.NET you can set this globally in your web.config file and the default configuration is
good enough to avoid this problem in the first place. You should already have the following section in your
web.config.
In classic ASP 3.0 you can fix this problem using the following code.
Response.CodePage = 65001
Response.CharSet = "utf-8"
In PHP the syntax to add the response header looks like this.
In any case, your ultimate goal is to have the following HTTP header sent with your response.
We used UTF-8 in our examples above, but if you need a different setting you can easily change.
When you come to think about it, almost every time you have an array in your code it means that you'll be
using that array in a loop sooner or later. Wouldn't it be nice if the array objects had more functionality to
deal with these iterations? Yes, it would, and many programming languages provide such functionality in
their arrays or equivalent structures (like collections and lists.)
Well, it turns out that Prototype gives us the Enumerable object, which implements a plethora of tricks for
us to use when dealing with iterable data. The Prototype library goes one step further and extends the Array
class with all the methods of Enumerable.
Loops, Ruby-style
In standard javascript, if you wanted to sequentially display the elements of an array, you could very well
write something like this.
<script type="text/javascript">
function showList() {
var simpsons = ['Homer', 'Marge', 'Lisa', 'Bart', 'Maggie'];
for (i=0; i<simpsons.length; i++) {
alert(simpsons[i]);
}
}
</script>
<input type="button" value="Show List" onclick="showList();" />
With our new best friend, Prototype, we can rewrite this loop like this.
function showList() {
var simpsons = ['Homer', 'Marge', 'Lisa', 'Bart', 'Maggie'];
simpsons.each( function (familyMember) {
alert(familyMember);
});
}
You are probably thinking "big freaking deal...just a weird syntax for the same old thing." Well, in the
above example, yes, there's nothing too earth shattering going on. After all, there's not much to be changed
in such a drop-dead-simple example. But keep reading, nonetheless.
Before we move on. Do you see this function that is being passed as an argument to the each method? Let's
start referring to it as an iterator function.
<script type="text/javascript">
function findEmployeeById(emp_id) {
var listBox = $('lstEmployees')
var options = listBox.getElementsByTagName('option');
options = $A(options);
var opt = options.find( function (employee) {
return
(employee.value === emp_id)
;
});
alert(opt.innerHTML); //displays the employee name
}
</script>
Now let's kick it up another notch. See how we can filter out items in arrays, and then retrieve just a desired
member from each element.
<script type="text/javascript">
function showLocalLinks(paragraph) {
paragraph = $(paragraph);
var links = $A(paragraph.getElementsByTagName('a'));
//find links that do not start with 'http'
var localLinks = links.findAll( function (link) {
//we'll just assume for now that external links
// do not have a '#' in their url
return link.href.indexOf('#') >= 0;
});
//now the link texts
var texts = localLinks.pluck('innerHTML');
//get them in a single string
var result = texts.inspect();
alert(result);
}
</script>
<p id="someText">
This <a href="http://othersite.com/page.html">text</a> has
a <a href="#localAnchor">lot</a> of
<a href="#otherAnchor">links</a>. Some are
<a href="http://wherever.com/page.html">external</a>
and some are <a href="#someAnchor">local</a>
</p>
<input type="button" value="Find Local Links" onclick="showLocalLinks('someText');"/>
It takes just a little bit of practice to get completely addicted to this syntax. Take a look at the Enumerable
and Array references for all the available functions.
Handling Events
Events are a big part of most UI programming. On the web this part has been historically difficult because
of the different ways browser makers chose to implement it.
If you have written cross-browser event handling before, you are probably familiar with all the forks in you
code to support Mozilla and IE. Prototype helps us by providing a uniform syntax for taking care of that.
We mentioned unobtrusive JavaScript in a previous lesson and Prototype offers great support for that
through its event subscription infrastructure.
Observing Events
The onXXXXXX properties of the DOM elements is a great source of contention for event handling code.
Traditionally you would see scripts that, for example, run when the page loads by changing the body
element's onload property.
window.onload = function(){alert('loaded');};
// or in the tag itsef:
<body onload="alert('loaded');">
The obvious problem here is that if another script also needs to know about that load event, it's possible
(and sadly often common) that this script would overwrite the original value of the onload property,
causing the page to misbehave.
The way Prototype addresses this is by using either the DOM standard for event subscription (when
supported by the browser) or browser specific code (mostly IE) to add or remove listeners to these events.
Prototype calls these listeners observers.
Observers do not overwrite each other. They are just appended to a list of observers for each individual
element event. The previous code would be written differently with Prototype.
To make it even easier, the event subscription mechanism is added to the DOM elements, simplifying the
syntax even further.
DOM Loaded
To support unobtrusive JavaScript even more, Prototype extends the document object with an important
event called dom:loaded. This event is fired when all the elements in the document are loaded in the DOM
and ready to be programmed against. Maybe some images or CSS didn't finish loading yet, but the DOM is
good to go.
When the DOM is loaded is the right time to add the observers to the other elements.
document.observe('dom:loaded' function () {
$('panelRight').observe('click', function (evt) {
collapseRightPanel();
});
Event Information
You may have noticed the evt parameter that is being passed for the observer functions in the previous
examples. This parameter carries important information about the event itself. Things like what element
was the source of the event, the mouse pointer coordinates and which mouse button was clicked. The
complete list of properties and methods of this object can be found at the Event object reference.
Official Site
Before we start using the library reference below, it's important to note that Prototype is a project on the
move and any reference material in print is bound to quickly become obsolete.
To get the most up-to-date documentation of the library we should always consult Prototype's official site
at http://www.prototypejs.org.
If you like to always have the latest possible version of the library or if you're interested in tracking the
library development as it happens, you can visit the project development site or even the current source
code.
When you have questions and you need to ask for help, a good place to go is the mailing list. A lot of very
knowledgeable Prototype developers hang out in this list and may be able to help you.
<script type="text/javascript">
function demoTimes() {
var n = 10;
n.times( function (index) {
alert(index);
});
/***************************
* you could have also used:
* (10).times( .... );
***************************/
}
</script>
<input type="button" value="Test Number.times()" onclick="demoTimes();"/>
Extensions for the Function objects
Method Kind Arguments Description
object: the Returns an instance of the function pre-bound to
bind(object [, arg1 [, arg2 object that the function(=method) owner object. The returned
instance
[...]]]) owns the function will use the same arguments as the
method original one (arg1, arg2, ... etc).
Returns an instance of the function pre-bound to
object: the
the function(=method) owner object. The returned
bindAsEventListener(object [, object that
instance function will have the current event object as its
arg1 [, arg2 [...]]]) owns the
first argument followed optionally any other
method
arguments passed after the object argument.
this.chkBox.onclick =
this.showMessage.bindAsEventListener(this, ' from checkbox');
},
<script type="text/javascript">
var A = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
alert(A.inspect()); // "['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']"
var B = A.without('e','f');
alert(B.inspect()); // "['a', 'b', 'c', 'd', 'g', 'h']"
alert(A.inspect()); // did not change A: "['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']"
A.push(null);
A.push('x');
A.push(null);
A.push('y');
alert(A.inspect()); // "['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', null, 'x', null, 'y']"
A = A.compact();
alert(A.inspect()); // "['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'x', 'y']"
var e = A.shift();
alert(e); // "a"
alert(A.inspect()); // "['b', 'c', 'd', 'e', 'f', 'g', 'h', 'x', 'y']"
alert(A.indexOf('c')); // 1
alert(A.first()); // 'b'
alert(A.last()); // 'y'
A.clear();
alert(A.inspect()); // "[]"
A = ['a', 'b', 'c'];
B = A.reverse(false);
alert(B.inspect()); // "['c', 'b', 'a']"
alert(A.inspect()); // A left untouched: "['a', 'b', 'c']"
A.reverse(true);
alert(A.inspect()); // "['c', 'b', 'a']"
A = ['a', 'b', ['c1','c2','c3'] , 'd', ['e1','e2'] ];
B = A.flatten();
alert(B.inspect()); // "['a','b','c1','c2','c3','d','e1','e2']"
alert(A.inspect()); // unchanged: "['a','b',['c1','c2','c3'],'d',['e1','e2']]"
</script>
Extensions for the document DOM object
Method Kind Arguments Description
className: name of a CSS Returns all the elements
class associated with the that are associated with the
getElementsByClassName(className elements, parentElement: given CSS class name. If no
instance
[, parentElement]) object or id of the element parentElement id given, the
that contains the elements entire document body will
being retrieved. be searched.
Extensions for the Event object
Property Type Description
KEY_BACKSPACE Number 8: Constant. Code for the Backspace key.
KEY_TAB Number 9: Constant. Code for the Tab key.
KEY_RETURN Number 13: Constant. Code for the Return key.
KEY_ESC Number 27: Constant. Code for the Esc key.
KEY_LEFT Number 37: Constant. Code for the Left arrow key.
KEY_UP Number 38: Constant. Code for the Up arrow key.
KEY_RIGHT Number 39: Constant. Code for the Right arrow key.
KEY_DOWN Number 40: Constant. Code for the Down arrow key.
KEY_DELETE Number 46: Constant. Code for the Delete key.
List of cached observers. Part of the internal implementation details of the
observers: Array
object.
Method Kind Arguments Description
Returns element that originated the
element(event) static event: an Event object
event.
event: an Event object, Traverses the DOM tree upwards,
findElement(event, tagName) static
tagName: name of the searching for the first element with the
Method Kind Arguments Description
desired tag. given tag name, starting from the
element that originated the event.
Returns true if the left mouse button
isLeftClick(event) static event: an Event object
was clicked.
Returns true if the middle mouse button
isMiddleClick(event) static event: an Event object
was clicked.
Returns true if the right mouse button
isRightClick(event) static event: an Event object
was clicked.
Returns the x coordinate of the mouse
pointerX(event) static event: an Event object
pointer on the page.
Returns the y coordinate of the mouse
pointerY(event) static event: an Event object
pointer on the page.
Use this function to abort the default
stop(event) static event: an Event object behavior of an event and to suspend its
propagation.
element: object or id,
name: event name (like
observe(element, name, Adds an event handler function to an
static 'click', 'load', etc),
observer) event.
observer: Function(evt) to
handle the event.
Removes an event handler from the
element: object or id, event. If the observer isn't passed, it
stopObserving(element [, name name: event name (like will unregister all observers for the
static
[, observer]]) 'click'), observer: function given event name. If the event name
that is handling the event. isn't passed it will unregister all
observers for the element.
_observeAndCache(element,
static  Private method, do not worry about it.
name, observer, useCapture)
Private method, do not worry about it.
unloadCache() static (none) Clears all cached observers from
memory.
Let's see how to use this object to add an event handler to the load event of the window object.
<script type="text/javascript">
Event.observe(window, 'load', page_loaded, false);
function page_loaded(evt) {
Event.observe('parent_node', 'click', item_clicked, false);
}
function item_clicked(evt) {
var child = Event.element(evt);
alert('The child node with id=' + child.id + ' was clicked');
Event.stop(evt); //avoid another call related to 'parent_node' itself
}
</script>
...
<div id="parent_node">
<div id="child1">First</div>
<div id="child2">Second</div>
<div id="child3">Third</div>
</div>
Many other objects extend the Enumerable object to leverage its useful interface.
Each item in a Hash object is an array with two elements: first the key then the value. Each item also has
two properties: key and value, which are pretty self-explanatory.
This object maintains a list of objects that will be called when Ajax-related events occur. You can use this
object, for example, if you want to hook up a global exception handler for AJAX operations.
Used when the requested url returns HTML that you want to inject directly in a specific element of your
page. You can also use this object when the url returns <script type="text/javascript"> blocks that will be
evaluated upon arrival. Use the evalScripts option to work with scripts.
This class repeatedly instantiates and uses an Ajax.Updater object to refresh an element on the page, or to
perform any of the other tasks the Ajax.Updater can perform. Check the Ajax.Updater reference for more
information.
All the methods defined here are automatically added to any element accessed using the $() function. When
they are added to the elements, the first argument (always element) is removed because it's always the
current element itself. So, in short, writing Element.show('myDiv'); is the same as writing
$('myDiv').show();
You can add your own methods to the Element.Methods object and they will also be copied to the elements
automatically. You only need to ensure that your methods also take the element as the first argument and (if
possible) returns the element object. Here's an example of how to do that. I'll add three new utility methods
to the elements.
The methods below are not the only methods in this object at runtime. All the methods from
Form.Element.Methods will also be added to this object.
Similarly to the Element.Methods methods, when they are copied, the first argument is dropped and
becomes the element itself. These methods are also copied to the Form.Element object so you don't
typically use the methods from Form.Element.Methods directly, but rather via the Form.Element object for
consistency.
Subclasses can be created to monitor things like the input value of an element, or one of the style
properties, or number of rows in a table, or whatever else you may be interested in tracking changes to.
Method Kind Arguments Description
element: element object or id,
[ctor](element, frequency: interval in seconds, Creates an object that will monitor
constructor
frequency, callback) callback: function to be called the element.
when the element changes
Derived classes have to implement
instance, this method to determine what is
getValue() (none)
abstract the current value being monitored
in the element.
This method is typically not called
externally. It is called by the object
registerCallback() instance (none)
itself to start monitoring the
element.
This method is typically not called
externally. It is called by the object
onTimerEvent() instance (none)
itself periodically to check the
element.
Property Type Description
element Object The element object that is being monitored.
frequency Number This is actually the interval in seconds between checks.
Function(Object, The function to be called whenever the element changes. It will receive
callback
String) the element object and the new value.
lastValue String The last value verified in the element.
Implementation of an Abstract.TimedObserver that monitors the value of form input elements. Use this
class when you want to monitor an element that does not expose an event that reports the value changes. In
that case you can use the Form.Element.EventObserver class instead.
Implementation of an Abstract.TimedObserver that monitors any changes to any data entry element's value
in a form. Use this class when you want to monitor a form that contains a elements that do not expose an
event that reports the value changes. In that case you can use the Form.EventObserver class instead.
Multiple objects of type Abstract.EventObserver can be bound to the same element, without one wiping out
the other. The callbacks will be executed in the order they are assigned to the element.
The triggering event is onclick for radio buttons and checkboxes, and onchange for textboxes in general
and listboxes/dropdowns.
Implementation of an Abstract.EventObserver that monitors any changes to any data entry element
contained in a form, using the elements' events to detect when the value changes. If the form contains
elements that do not expose any event that reports changes, then you can use the Form.Observer class
instead.
It's solid class creation pattern and AJAX support can confidently be used as the backbone for all your
client-side work.
When combined with the great number of Prototype-based 3rd party libraries, Prototype can make your
JavaScript coding very productive.
To continue to learn JavaScript go to the top of this page and click on the next lesson in this JavaScript
Tutorial's Table of Contents.
script.aculo.us
In this lesson of the JavaScript tutorial, you will learn...
script.aculo.us is the most popular add-on to Prototype that exists. It was developed and is maintained by
Thomas Fuchs.
script.aculo.us is also bundled in Ruby on Rails and is extensively used on that platform. The number of
developers already familiar with this library and it's user community make it a great choice for our projects.
On its official site (http://script.aculo.us/) you will find the most current documentation and lists of
additional resources like user-contributed extensions (new visual effects, transitions, etc.) and mailing lists.
As we will see later, script.aculo.us is divided in several modules, each one implementing a specific
functionality. We can load all the modules by adding only two library references as shown below.
Syntax
<script type="text/javascript" src="scripts/prototype.js"></script>
<script type="text/javascript" src="scripts/scriptaculous.js"></script>
We need a reference to Prototype because script.aculo.us was build completely dependent of Prototype
(actually script.aculo.us, early on, was part of Prototype before spinning off.)
By adding a reference to scriptaculous.js all the modules will be dynamically loaded. The modules are
builder.js, effects.js, dragdrop.js, controls.js, slider.js, and sound.js.
Loading all the modules is clearly an exaggeration and a disrespect with our users unless we are actually
using each one of the modules, what is pretty unlikely. For that reason we can specify the modules we need
explicitly when we reference scriptaculous.js.
Syntax
<script type="text/javascript" src="scripts/prototype.js"></script>
<script type="text/javascript" src="scripts/scriptaculous.js?effects,controls"></script>
The above format will make scriptaculous.js only load effects.js and controls.js.
Although quite handy, we would like to suggest that you avoid this dynamic loading of modules altogether
and be explicit about which modules you need. Just remember to load them in the same order that we listed
a few paragraphs above. The previous example would is rewritten below, which we believe will be clearer
for another developer reading your code.
Syntax
<script type="text/javascript" src="scripts/prototype.js"></script>
<script type="text/javascript" src="scripts/effects.js"></script>
<script type="text/javascript" src="scripts/controls.js"></script>
Effects
One of the main reasons developers use script.aculo.us is because of the visual effects that it implements.
The visual effects can be very useful to call the user's attention to some important event that happened on
the page. Effects can also be used to make visual changes in the page gradually, instead of an abrupt
element insertion or relocation.
The effects are part of the effects.js file and they need Prototype on the page as well.
There are at least two ways to trigger the effects on a given element.
Syntax
//using the constructor (don't forget the 'new'!)
new Effect.EffectName('elementID' [, options]);
//or via the extension added to the DOM elements
$('elementId').visualEffect('effectName' [, options]);
Just like in many parts of Prototype, the options argument is a hash-like object that can provide non-
mandatory adjustments for the effects.
Effects are divided in two main groups, Core Effects and Combined Effects.
Core Effects
Think of the core effects as the simplest and more fundamental effects. Later we will be used to combine
these effects to create richer effects. They are our building blocks.
Let's see how easy it is to apply script.aculo.us effects. In this exercise we will use script.aculo.us to change
the opacity of an element to 50%.
Combined Effects
There's another core effect that we have not shown. It's more of an infrastructure effect. It's the
Effect.Parallel. This effect is used to execute two or more effects simultaneously.
new Effect.Parallel(
[
new Effect.Opacity('notice', { sync: true }),
new Effect.Scale('notice', 100, { sync: true, scaleFrom: 50 })
],
{ duration: 2 });
Note the sync option being passed to both inner effects. This is the clue for script.aculo.us to wait until all
effects are created and execute them all at once.
script.aculo.us comes with a series of effects that are built by pre-configuring or combining the core effects
with Effect.Parallel.
All the combined effects have shortcuts added to the DOM elements but can also be called like the core
effects.
Syntax
//we don't need the 'new' for combined effects
//but if you use it, it doesn't cause any problem
Effect.EffectName('element' [, options]);
//or
$('element').visualEffect('effectName' [, options]);
//or
$('element').effectName( [options] );
script.aculo.us Combined Effects
Effect Description Options
offset: vertical offset after
Effect.ScrollTo Scrolls the page until the element is reached. the element. Will be added
to the scrolling distance.
Shakes the element from left to right a few times to call
Effect.Shake
attention.
Decreases and increases the opacity few times to call
Effect.Pulsate
attention.
Effect.DropOut The element falls down and fades.
Effect.Appear The element's opacity goes from 0.0 to 1.0.
The element's opacity goes from 1.0 to 0.0, then the element
Effect.Fade
is hidden.
The element's height is decreased until it disappears. The
Effect.BlindUp
element is hidden on completion.
The element's height is increased from zero until the
Effect.BlindDown
element's original height.
The element's height is decreased until it disappears. The
contents need to be inside an extra div inside the element
Effect.SlideUp
and it will move up instead of shrinking. The element is
hidden on completion.
The element's height is increased from zero to the element's
original height. The contents need to be inside an extra div
Effect.SlideDown
inside the element and it will move down instead of
expanding.
The element grows and fades. The element is hidden on
Effect.Puff
completion.
The element shrinks towards the upper-left corner. The
Effect.Squish
element is hidden on completion.
Effect.SwitchOff The element flickers quickly then shrinks like and old CRT
script.aculo.us Combined Effects
Effect Description Options
TV set picture. The element is hidden on completion.
The narrows vertically then horizontally until it disappears.
Effect.Fold
The element is hidden on completion.
Effects pout-pourri
The following demo showcases the available effects and the code used to create them.
<title>effects</title>
<style type="text/css" media="screen">
th {text-align:left; font-size:18px; background-color:#777; color:#fff;}
.demo { display:none;}
.demo-content {background-color:#FFB855; padding:10px;}
.demo-content pre {background-color: #aaa;}
#demoArea {height:400px; width:550px; background-color:#eee; }
#demoList { margin:0; list-style-type: none; padding:0;}
#demoList a { text-decoration:none;}
</style>
<script type="text/javascript"
src="../../Libraries/prototype.js"></script>
<script type="text/javascript"
src="../../Libraries/scriptaculous/effects.js"></script>
<script type="text/javascript" charset="utf-8">
var WEBUCATOR = {
currentDemo: '',
restorePreviousDemo: function () {
if (this.currentDemo !== '') {
var prevDemo = $(this.currentDemo);
var prevContent = $('demoArea').select('.demo-content')[0];
prevContent.remove();
prevDemo.insert(prevContent);
}
},
doMainDemoAction: function () {
this[this.currentDemo]();
},
demoHighlight_pre: function () {
$('theTime').update('');
},
demoOpacity_pre: function () {
$('topBox').setOpacity(1);
},
demoScale_pre: function () {
$('smallBox').setStyle({
fontSize: '11px',
width: '80px', height: '40px',
left: '100px', top: '40px'});
$('smallBox').update('Scriptaculous');
},
demoMove_pre: function () {
$('mover').setStyle({left: '25px', top: '25px'});
},
demoDropOut_pre: function () {
$('droppingBox').show();
},
demoAppear_pre: function () {
$('appearingBox').hide();
},
demoFade_pre: function () {
$('fadingBox').show();
},
demoBlindUp_pre: function () {
$('rollUpBox').show();
},
demoBlindDown_pre: function () {
$('rollDownBox').hide();
},
demoSlideUp_pre: function () {
$('slideUpBox').show();
},
demoSlideDown_pre: function () {
$('slideDownBox').hide();
},
demoPuff_pre: function () {
$('puffingBox').show();
},
demoSwitchOff_pre: function () {
$('tvBox').show();
},
demoSquish_pre: function () {
$('squishable').show();
},
demoFold_pre: function () {
$('napkin').show();
},
demoMorph_pre: function () {
$('willMorph').setStyle(
{ left: '100px', width:'100px', color: '', backgroundColor: '#77E66C'});
},
temp: null
}
</script>
</head>
<body>
<h1>Scriptaculous Effects Sampling</h1>
<table border="0" cellspacing="5" cellpadding="5">
<thead><tr><th>Effect</th><th>Demo</th></tr></thead>
<tbody>
<tr valign="top">
<td>
<ul id="demoList">
<li>
<a href="javascript:void(0);"
onclick="WEBUCATOR.loadDemo('demoOpacity');">Effect.Opacity</a>
</li>
<li>
<a href="javascript:void(0);"
onclick="WEBUCATOR.loadDemo('demoMove');">Effect.Move</a>
</li>
<li>
<a href="javascript:void(0);"
onclick="WEBUCATOR.loadDemo('demoHighlight');">Effect.Highlight</a>
</li>
<li>
<a href="javascript:void(0);"
onclick="WEBUCATOR.loadDemo('demoScale');">Effect.Scale</a>
</li>
<li>
<a href="javascript:void(0);"
onclick="WEBUCATOR.loadDemo('demoMorph');">Effect.Morph</a>
</li>
<li>
<a href="javascript:void(0);"
onclick="WEBUCATOR.loadDemo('demoScroll');">Effect.ScrollTo</a>
</li>
<li>
<a href="javascript:void(0);"
onclick="WEBUCATOR.loadDemo('demoDropOut');">Effect.DropOut</a>
</li>
<li>
<a href="javascript:void(0);"
onclick="WEBUCATOR.loadDemo('demoShake');">Effect.Shake</a>
</li>
<li>
<a href="javascript:void(0);"
onclick="WEBUCATOR.loadDemo('demoPulsate');">Effect.Pulsate</a>
</li>
<li>
<a href="javascript:void(0);"
onclick="WEBUCATOR.loadDemo('demoAppear');">Effect.Appear</a>
</li>
<li>
<a href="javascript:void(0);"
onclick="WEBUCATOR.loadDemo('demoFade');">Effect.Fade</a>
</li>
<li>
<a href="javascript:void(0);"
onclick="WEBUCATOR.loadDemo('demoBlindUp');">Effect.BlindUp</a>
</li>
<li>
<a href="javascript:void(0);"
onclick="WEBUCATOR.loadDemo('demoBlindDown');">Effect.BlindDown</a>
</li>
<li>
<a href="javascript:void(0);"
onclick="WEBUCATOR.loadDemo('demoSlideUp');">Effect.SlideUp</a>
</li>
<li>
<a href="javascript:void(0);"
onclick="WEBUCATOR.loadDemo('demoSlideDown');">Effect.SlideDown</a>
</li>
<li>
<a href="javascript:void(0);"
onclick="WEBUCATOR.loadDemo('demoPuff');">Effect.Puff</a>
</li>
<li>
<a href="javascript:void(0);"
onclick="WEBUCATOR.loadDemo('demoSwitchOff');">Effect.SwitchOff</a>
</li>
<li>
<a href="javascript:void(0);"
onclick="WEBUCATOR.loadDemo('demoSquish');">Effect.Squish</a>
</li>
<li>
<a href="javascript:void(0);"
onclick="WEBUCATOR.loadDemo('demoFold');">Effect.Fold</a>
</li>
</ul>
</td>
<td>
<div id="demoArea"></div>
</td>
</tr>
</tbody>
</table>
</div>
<br/><br/>
<strong>Code:</strong>
<pre><code class="demoCode">new Effect.Opacity('topBox', {to: 0.50, duration:
2});</code></pre>
<strong>Alternatives:</strong>
<pre><code>$('topBox').visualEffect('opacity', {to: 0.50, duration: 2});</code></pre>
</div>
</div>
</div>
<br/><br/>
<strong>Code:</strong>
<pre><code class="demoCode">new Effect.Move('mover', {x: 300, y: 75});</code></pre>
<strong>Alternatives:</strong>
<pre><code>$('mover').visualEffect('move', {x: 300, y: 75});</code></pre>
</div>
</div>
<br/><br/>
<strong>Code:</strong>
<pre><code class="demoCode">new Effect.ScrollTo('bottomBox');</code></pre>
<strong>Alternatives:</strong>
<pre><code>$('bottomBox').visualEffect('scrollTo');</code></pre>
</div>
</div>
</div>
<br/><br/>
<strong>Code:</strong>
<pre><code class="demoCode">new Effect.Scale('smallBox', 200,
{scaleFromCenter: true});</code></pre>
<strong>Alternatives:</strong>
<pre><code>$('smallBox').visualEffect('scale', 200,
{scaleFromCenter: true});</code></pre>
</div>
</div>
<br/><br/>
<strong>Code:</strong>
<pre><code class="demoCode">new Effect.Morph('willMorph',
{ style:
{ backgroundColor:'#ff0000',
color: '#ffffff',
width: '220px',
left: '250px'
}
});</code></pre>
<strong>Alternatives:</strong>
<pre><code>$('willMorph').visualEffect('morph',
{ style:
{ backgroundColor:'#ff0000',
color: '#ffffff',
width: '220px',
left: '250px'
}
});
//or
$('willMorph').morph(
{ backgroundColor:'#ff0000',
fontColor: '#ffffff',
width: '220px'
});</code></pre>
</div>
</div>
<strong>Alternatives:</strong>
<pre><code>$('theTime').visualEffect('highlight', {to: 0.50, duration: 2});
//or
$('theTime').highlight({to: 0.50, duration: 2});</code></pre>
</div>
</div>
</div>
<br/><br/>
<strong>Code:</strong>
<pre><code class="demoCode">Effect.DropOut('droppingBox');</code></pre>
<strong>Alternatives:</strong>
<pre><code>$('droppingBox').visualEffect('dropOut');
//or
$('droppngBox').dropOut();</code></pre>
</div>
</div>
</div>
<br/><br/>
<strong>Code:</strong>
<pre><code class="demoCode">Effect.Shake('willShake');</code></pre>
<strong>Alternatives:</strong>
<pre><code>$('willShake').visualEffect('shake');
//or
$('willSahke').shake();</code></pre>
</div>
</div>
</div>
<br/><br/>
<strong>Code:</strong>
<pre><code class="demoCode">Effect.Pulsate('willPulsate');</code></pre>
<strong>Alternatives:</strong>
<pre><code>$('willPulsate').visualEffect('pulsate');
//or
$('willPulsate').pulsate();</code></pre>
</div>
</div>
</div>
<br/><br/>
<strong>Code:</strong>
<pre><code class="demoCode">Effect.Appear('appearingBox');</code></pre>
<strong>Alternatives:</strong>
<pre><code>$('appearingBox').visualEffect('appear');
//or
$('appearingBox').appear();</code></pre>
</div>
</div>
</div>
<br/><br/>
<strong>Code:</strong>
<pre><code class="demoCode">Effect.Fade('fadingBox');</code></pre>
<strong>Alternatives:</strong>
<pre><code>$('fadingBox').visualEffect('fade');
//or
$('fadingBox').fade();</code></pre>
</div>
</div>
</div>
<br/><br/>
<strong>Code:</strong>
<pre><code class="demoCode">Effect.BlindUp('rollUpBox');</code></pre>
<strong>Alternatives:</strong>
<pre><code>$('rollUpBox').visualEffect('blindUp');
//or
$('rollUpBox').blindUp();</code></pre>
</div>
</div>
</div>
<br/><br/>
<strong>Code:</strong>
<pre><code class="demoCode">Effect.BlindDown('rollDownBox');</code></pre>
<strong>Alternatives:</strong>
<pre><code>$('rollDownBox').visualEffect('blindDown');
//or
$('rollDownBox').blindDown();</code></pre>
</div>
</div>
</div>
<br/><br/>
<strong>Code:</strong>
<pre><code class="demoCode">Effect.SlideUp('slideUpBox');</code></pre>
<strong>Alternatives:</strong>
<pre><code>$('slideUpBox').visualEffect('slideUp');
//or
$('slideUpBox').slideUp();</code></pre>
</div>
</div>
</div>
<br/><br/>
<strong>Code:</strong>
<pre><code class="demoCode">Effect.SlideDown('slideDownBox');</code></pre>
<strong>Alternatives:</strong>
<pre><code>$('slideDownBox').visualEffect('slideDown');
//or
$('slideDownBox').slideDown();</code></pre>
</div>
</div>
</div>
<br/><br/>
<strong>Code:</strong>
<pre><code class="demoCode">Effect.Puff('puffingBox', {duration: .5});</code></pre>
<strong>Alternatives:</strong>
<pre><code>$('puffingBox').visualEffect('puff', {duration: .5});
//or
$('puffingBox').puff({duration: .5});</code></pre>
</div>
</div>
</div>
<br/><br/>
<strong>Code:</strong>
<pre><code class="demoCode">Effect.SwitchOff('tvBox');</code></pre>
<strong>Alternatives:</strong>
<pre><code>$('tvBox').visualEffect('switchOff');
//or
$('tvBox').switchOff();</code></pre>
</div>
</div>
</div>
<br/><br/>
<strong>Code:</strong>
<pre><code class="demoCode">Effect.Squish('squishable');</code></pre>
<strong>Alternatives:</strong>
<pre><code>$('squishable').visualEffect('squish');
//or
$('squishable').squish();</code></pre>
</div>
</div>
</div>
<br/><br/>
<strong>Code:</strong>
<pre><code class="demoCode">new Effect.Fold('napkin');</code></pre>
<strong>Alternatives:</strong>
<pre><code>$('napkin').visualEffect('fold');
//or
$('napkin').fold();</code></pre>
</div>
</div>
</body>
</html>
Option Description
delay Number o seconds before the effect starts. Defaults to 0.0.
Used to trigger effects in succession. It defaults to 'parallel' but can also be 'front', 'end', and
queue
'with-last'
duration Length of the effect in seconds. Defaults to 1.0.
Number of frames per second. Defaults to 100 but 25 is good enough for the human eye and
fps
avoids unnecessary CPU usage.
sync Use true when running effects in parallel.
from Starting point of the effect. A number between 0.0 and 1.0.
Option Description
to Ending point of the effect. A number between 0.0 and 1.0.
beforeStart Callback function that is invoked just before the effect starts.
beforeSetup Callback function that is invoked just before the first frame is calculated.
Callback function that is invoked right after the first frame is calculated and before it is
afterSetup
rendered.
beforeUpdate Callback function that is invoked before each frame is rendered.
afterUpdate Callback function that is invoked after each frame is rendered.
beforeFinish Callback function that is invoked before the effect runs its finalization logic.
afterFinish Callback function that is invoked after the effect runs its finalization logic.
Specifies the pace of the effect progression. It defaults to Effect.Transitions.sinoidal but is ca
also be Effect.Transitions.spring, Effect.Transitions.linear, Effect.Transitions.flicker,
Effect.Transitions.wobble, Effect.Transitions.pulse, Effect.Transitions.none,
transition
Effect.Transitions.full, and Effect.Transitions.reverse. It can also be customized by passing a
function that receives a number between 0.0 and 1.0 and returns another number in the same
range.
The dragging and dropping cycle involves a number of decisions. Among them: what to drag, what to grab
it by, where to drop it, what to do after it is dropped. script.aculo.us helps with each of these problems.
It's very easy to make an element draggable. See the example below.
<script type="text/javascript"
src="../../Libraries/prototype.js"></script>
<script type="text/javascript"
src="../../Libraries/scriptaculous/effects.js"></script>
<script type="text/javascript"
src="../../Libraries/scriptaculous/dragdrop.js"></script>
All we needed to make the img element draggable was to instantiate a new Draggable object associated
with that element.
Syntax
new Draggable('theImage');
That was easy indeed, but that's not where the story ends. Just like with the Effect objects, we also have a
number of options that we can pass in the second argument of the constructor.
Syntax
new Draggable(element, { /* options here */ });
Option Description
Specifies if the drag will be limited. Defaults to false but can also be 'vertical' or
constraint
'horizontal'.
Amount of time (in seconds) to initiate the dragging after the mouse button is pressed.
delay This can be useful to avoid dragging when the user just wanted to click a button or link.
Defaults to zero.
Effect that will be applied to the element when the dragging ends. The default is to change
endeffect the opacity to 100% in .2 seconds. This is a function that takes the dragged element as the
argument.
Determines if a clone element will be created during the drag, leaving the original element
ghosting
in its original place until a drop occurs. Default is false.
Use this when we want to define one child element in the draggable element to serve as
handle the handle for dragging the bigger element. It defaults to false but can also be a DOM
element, an id, or a CSS class name.
Function that will be called during dragging when the mouse is moved and the element is
onDrag about to be have its position changed. The function will receive the Draggable object as
the first parameter.
Function that will be called when the dragging ends. The function will receive the
onEnd
Draggable object as the first parameter.
Function that will be called when the dragging starts. The function will receive the
onStart
Draggable object as the first parameter.
When working with Droppables this will indicate if the element should go back to the
revert
original position if it's not dropped in a Droppable. Defaults to false.
This creates the effect that will be used when reverting the drag. Defaults to an
revertEffect Effect.Move but can be replaced with a function that takes the element, the dragged X, and
the dragged Y in pixels.
The scroll container for the drag. When defined, the container will scroll when the dragged
scroll element nears its edges. It can be the window object or any other element that has
scrolling enabled.
scrollSensitivity Number of pixels from the edges of the container that trigger the scrolling.
A number representing the pixels per second that the container will scroll when the
scrollSpeed element is dragged near the edges. Defaults to 15. This is actually the minimum speed
because the speed will be greater when the closer to the border the drag is.
Defines a grid that will cause the dragging to snap to a grid. It can be a number, which will
snap be the number of pixels between each snapping position in the grid. It can also be a 2-
element array, containing the number of pixel in the X and Y direction respectively.
Effect that will be applied to the element when the dragging starts. The default is to
starteffect change the opacity to 50% in .2 seconds. This is a function that takes the dragged element
as the argument.
Number that will be used as the element's z-index while being dragged to ensure it is
zindex
above the other elements. Default is 1000.
The next example illustrates how the use of dragging and effects to create a simple puzzle game.
Code Sample: Scriptaculous/Demos/shuffle.html
---- Code Omitted ----
var WEBUCATOR = {
image: { width: 600.0, height:380.0 },
tiles: [],
createGrid: function () {
var columns = 4, rows = 3;
var cellWidth = this.image.width/columns;
var cellHeight = this.image.height/rows;
var left, top, cell, tile;
$('grid').insert(tile);
this.tiles.push(tile);
}
}
this.makeTilesDraggable();
},
return tile;
},
makeTilesDraggable: function () {
this.tiles.each( function (tile) {
new Draggable(tile);
});
},
shuffleGrid: function () {
this.tiles.each( function (tile) {
var left = Math.floor(Math.random() * WEBUCATOR.image.width);
var top = Math.floor(Math.random() * WEBUCATOR.image.height);
restoreGrid: function () {
this.tiles.each( function (tile) {
tile.visualEffect('move',
Object.extend( tile.originalPosition, {mode:'absolute'} )
);
});
}
};
We will create a Draggable that is a little bit more than the default.
1. Find the place where you are suppose to add your code (see comments.)
2. Create a Draggable that will enable dragging of both the image and the title bar together.
3. Check that it works.
4. Change the code to allow dragging only by the title bar.
5. Check that it still works.
6. Add code to make the dragging carry only a copy of the picture.
7. Check that it still works.
8. Add code to force the dragging to happen in chunks of 30 pixels horizontally and 50 vertically.
</script>
---- Code Omitted ----
<h1>This image is draggable by the title bar</h1>
<div id="picture" class="draggablePicture">
<div class="titlebar">Olympics</div>
<div><img id="theImage" src="../Common/olympics.png" alt="olympics" /></div>
</div>
---- Code Omitted ----
Member Description
destroy() A method that will remove the dragging capabilities of the element.
dragging A Boolean value indicating if the element is currently being dragged.
The DOM element that is associated with this object. In other words, that's the element that can
element
be dragged.
If specified as an option, this will contain a reference to the DOM element that will be used as the
handle
handle for dragging the main element.
The options set for this Drggable. This includes both the options specified at the constructor call
options
and all the default options that were not overridden.
Droppables
Many times dragging items around is not all that useful unless you have a designated place to drop the
items. The Drop part of Drag and Drop is handled by the Droppable object.
We create dropping zones in our page by making adding an element to the Droppables list.
Syntax
Droppables.add(element [, options]);
Option Description
Name of a CSS class that will restrict which elements can be dropped in this drop zone. Only
accept
draggables with that class will be successfully dropped.
Instead of a CSS class we can pass an element in this option so only draggables that are
containment
children of this element will be accepted.
Name of a CSS class that will be added to the drop zone element while an element in dragged
hoverclass
over it.
Takes a function that will be called when an element is dropped. The function will receive
onDrop three arguments: the element being dropped, the drop zone element, and the event
information.
We will create two dropping zones in a page, where we can drop book cover images dragged from another
part of the page.
1. Find the place where you are suppose to add your code (see comments.)
2. Write code to make all the existing book covers draggable
3. Check that it works.
4. Make the green and yellow areas drop zones for the books
5. When a book is dropped we want to create a duplicate image of the book in the drop zone.
6. Make the drop zones show a border when the books are dragged over them.
});
var WEBUCATOR = {
/* here we will create a function to
handle the drop event for both drop zones
*/
};
</script>
---- Code Omitted ----
<body>
<h1>Books that I must read</h1>
<table>
<tr>
<th>Available<br/>books</th>
<th>Books<br/>I've read</th>
<th>Books I<br/>should read</th>
</tr>
<tr>
<td>
<div id="available">
<img id="book1" src="../Common/poeaa.jpg" alt="Poeaa" class="book"/>
<img id="book2" src="../Common/ddd.jpg" alt="ddd" class="book"/>
<img id="book3" src="../Common/wewlc.jpg" alt="wewlc" class="book"/>
<img id="book4" src="../Common/tpp.jpg" alt="pragprog" class="book"/>
</div>
</td>
<td><div id="alreadyRead"></div></td>
<td><div id="willRead"></div></td>
</tr>
</table>
</body>
---- Code Omitted ----
Sortables
It's very common to use dragging to reorder items in a list. That's a much better user interface design than
using "move up" or "move down" buttons. For that reason script.aculo.us offers that behavior pre-packaged
in the Sortable object.
To make items sortable we just need to make the parent element sortable, like we can see below.
</script>
---- Code Omitted ----
<ul id="books">
<li>
<div class="book">
<img src="../Common/poeaa.jpg" alt="Poeaa" align="left"/>
<h2>Patterns of Enterprise Application Architecture</h2>
<h3>Martin Fowler</h3>
</div>
</li>
<li>
<div class="book">
<img src="../Common/ddd.jpg" alt="ddd" align="left"/>
<h2>Domain-Driven Design</h2>
<h3>Eric Evans</h3>
</div>
</li>
<li>
<div class="book">
<img src="../Common/wewlc.jpg" alt="wewlc" align="left"/>
<h2>Working Effectively With Legacy Code</h2>
<h3>Michael Feathers</h3>
</div>
</li>
<li>
<div class="book">
<img src="../Common/tpp.jpg" alt="pragprog" align="left"/>
<h2>The Pragmatic Programmer</h2>
<h3>Andy Hunt and Dave Thomas</h3>
</div>
</li>
</ul>
---- Code Omitted ----
When creating a Sortable object we can also pass some options in the second argument of Sortable.create().
One such option is the onUpdate, which takes a function that will receive the parent element as the
argument.
To be able to use the onUpdate callback we need to add an id attribute to each li element in our list. The ids
need to be in the format xxxxxx_value, where the value part will be extracted and considered the value of
the item.
The Sortable gives us the current sequence via the Sortable.serialize(listElementID) method. This returns a
string formated like listElementID[]=value1&listElementID[]=value3&listElementID[]=value2. Although
this format looks a little funky, it's due to script.aculo.us strong bond with Ruby on Rails and it's not too
hard to live with in non-Ruby on Rails platforms. Our next exercise will show how to use that in PHP.
In this exercise we will use a Sortable to reorder a list of items and submit each order update to the server
using AJAX.
};
document.observe('dom:loaded', function () {
//We will add code here to create the sortable
});
</script>
---- Code Omitted ----
<div id="result" > </div>
<ul id="books">
<li>
<div class="book">
<img src="../Common/poeaa.jpg" alt="Poeaa" align="left"/>
<h2>Patterns of Enterprise Application Architecture</h2>
<h3>Martin Fowler</h3>
</div>
</li>
<li>
<div class="book">
<img src="../Common/ddd.jpg" alt="ddd" align="left"/>
<h2>Domain-Driven Design</h2>
<h3>Eric Evans</h3>
</div>
</li>
<li>
<div class="book">
<img src="../Common/wewlc.jpg" alt="wewlc" align="left"/>
<h2>Working Effectively With Legacy Code</h2>
<h3>Michael Feathers</h3>
</div>
</li>
<li>
<div class="book">
<img src="../Common/tpp.jpg" alt="pragprog" align="left"/>
<h2>The Pragmatic Programmer</h2>
<h3>Andy Hunt and Dave Thomas</h3>
</div>
</li>
</ul>
---- Code Omitted ----
$received = "";
for($i = 0; $i < count($books); $i++)
{
//here we would for example
// save the posted sequence to the database
//In our sample we will just echo back the posted values
$index = $i + 1;
$received = $received . " [$index: $books[$i]]";
}
echo $received
?>
Where is the solution?
Controls
script.aculo.us contains a few classes to assist creating user interface elements commonly known as
widgets. The approach script.aculo.us takes is to attach objects of those classes to existing HTML elements
and enhance them with added behaviors.
In order to use the controls in script.aculo.us we need to include the controls.js file, which will need
effects.js and prototype.js
Syntax
<script type="text/javascript" src="prototype.js"></script>
<script type="text/javascript" src="effects.js"></script>
<script type="text/javascript" src="controls.js"></script>
Autocompleter
The auto-completion functionality was popularized by Google Suggest, where a regular input text field
shows a list of possible search terms before you're done typing the entire term. This is now a more or less
common feature in many sites.
What happens behind the scenes is that the web page is issuing AJAX requests to the server, passing
whatever you have typed so far, getting a list of possible words or phrases that you might be planning to
enter, and displaying the list. Then you can just select one from the list instead of finishing typing it off.
script.aculo.us comes with two flavors of autocompletion controls. One is fed by an AJAX call just like
described above. The other one (referred to as local) gets its list from an array already in the page. The two
controls support a lot of the same functionality, differing obviously in the source of the suggested items and
the options that are related to the AJAX request itself.
Syntax
var states = ['Alabama', 'Alaska', ... ,'Wisconsin', 'Wyoming'];
Autocompleter.Local(fieldElement, popUpElement, states);
Syntax
new Ajax.Autocompleter(fieldElement, popUpElement, '/url/to/getSuggestions' [, options]);
In both cases fieldElement is the element or id of an input text field where the typing will occur and
popUpElement is the element or id of a div that will be used as the pop-up list that will be displayed under
the text field.
Below is a working example of a local Autocompleter that looks up an array with the list of the U.S. states.
document.observe('dom:loaded', function () {
new Autocompleter.Local('userState', 'suggestPopUp', WEBUCATOR.usStates);
} );
</script>
---- Code Omitted ----
What State do you live in?
<input type="text" id="userState" />
<div id="suggestPopUp" class="suggestions"/>
---- Code Omitted ----
As you can see there isn't a lot of code to look at. Surprisingly enough the majority of the work that we
have to do is related to CSS rules to make the pop-up look nice. In this example we removed the bullet
points from each li element in the pop-up and highlighted the currently selected item by adding a different
background color to the item with the class name selected (script.aculo.us takes care of putting that class
name in the correct item for us.)
The same example can be converted to an AJAX version provided we write the server side code. This
particular example isn't actually a good candidate for being AJAX-fied because it draws results from a
relatively short and fixed list, but let's do it anyway for illustrative purposes.
$term = $_POST["state"];
echo "<ul>";
$returns = 0;
foreach($states as $st) {
$pos1 = stripos($st, $term);
if($returns == 10){
break;
}
}
echo "</ul>";
?>
The lookup-state.php receives the value entered by the user in a POSTed form field with the same name as
the input field's name attribute (not the id, watch out for this possible tripwire.)
With the value in hands, the server script is responsible for figuring out what the returned suggestions
should be and return them in ul/il elements. In the above example we highlight the typed text in the result
list.
The Autocompleter takes a number of options. Three are shared by both the local and AJAX versions.
Option Description
If this is set to true and there's only one value being suggested, then that value is automatically
autoSelect
selected. This option defaults to false.
This is a misnomer. This option specifies the number of seconds between refreshing the
frequency
suggestions. Defaults to 0.4.
Number of characters that need to be typed before the first suggestions list is produced. Defaults
minChars
to 1.
Option Description
Defaults to false and determines if the suggestions should include any items that contain the
fullSearch
typed text anywhere, not just beginnings of words.
ignoreCase Determines if character casing is disregarded in the comparisons. Defaults to true.
When set to true the search algorithm will look for suggestions that begin with the typed text.
partialSearch The default value is false where it matches any suggestion that has a word that begins with
the typed text.
Minimum number of typed characters that cause a partial search to be performed. Defaults to
partialChars
2.
Method that performs the actual work. The default value is the algorithm that obeys all the
selector above options. In the rare event that you need something completely different (like searching
in a XML document,) you can pass this option with a function that receives the autocompleter
Option Description
instance, builds and returns the ul/li DOM structure.
As expected, the Ajax.Autocompleter also has a number of options that control how it works.
Option Description
If you need to customize the creation of the submitted value and parameter name, this option
callback lets you pass a function that will receive a reference to the input element and the typed text
and returns the querystring-formatted 'param=value'.
Id or reference to a DOM element that will automatically be displayed and hidden during the
indicator AJAX calls. This is usually a reference to an img element with some animated glyph that lets
the user know some work is being done.
Just like the original Ajax.Request, this lets us change the HTTP method from the default
method
POST to GET.
Fixed set of parameters to always be send to the AJAX URL. These parameters are encoded in
parameters
URL querystring format, like 'par1=abc&par2=xyz'.
The submitted parameter name is the same as the name attribute of the input field. This
paramName
options lets you change that to whatever name you desire.
Class name of an element inside the suggested li that will provide the text for the selection.
select
This is useful when the suggested items are richer than just text-only li items.
String containing a separator character or an array of separators to be used when the text field
tokens is expected to collect more than one suggestions, like a To: field in an email form, where you
can choose more than one recipient for the message.
Let's use Ajax.Autocompleter objects to create a very interesting user interface for finding books.
1. Add the necessary references to external .js files to use the Ajax.Autocompleter.
2. Find the place where you are suppose to add your code (see comments.)
3. Add code to the method WEBUCATOR.createAutocompleter() to take a number as an argument
and create an Ajax.Autocompleter for the input field named 'bookN'.
4. The autocompleter needs to call the URL ../Common/lookup-book.php.
5. lookup-book.php expects the desired text in a parameter named bookTitle.
6. Add code to the document load event to create the 3 autocompleters.
7. Run the code and verify that the suggestPopUp gets populated with the books list.
8. The book selection is still broken. Let's fix that by collecting only the text inside the element with
a CSS class name of book_title.
9. Check that this last change works.
10. To add a little more of flair, let's make an animated progress icon show to the side of each input
field when the AJAX call is in progress.
11. Use the image file ../Common/ajax-loader.gif.
12. Modify WEBUCATOR.createAutocompleter() to create and insert the img element with this icon
right after each text box, initially hidden.
document.observe('dom:loaded', function () {
//add code here to create each of the 3
// autocompleters using the WEBUCATOR.createAutocompleter() method
});
</script>
---- Code Omitted ----
<body>
<h1>Name 3 important programming books:</h1>
1 - <input type="text" id="book1" name="book1" /> <br />
2 - <input type="text" id="book2" name="book2" /> <br />
3 - <input type="text" id="book3" name="book3" /> <br />
$term = $_POST["bookTitle"];
echo "<ul>";
foreach($books as $b) {
echo "</ul>";
?>
Where is the solution?
InPlaceEditor
The InPlaceEditor adds behavior to static text elements so that they, upon being clicked, become input text
fields and a pair or buttons or links to accept or cancel the edit operation. Accepting the input causes an
AJAX call to happen so the value can be persisted immediately.
Creating either type of editor can be very simple but there are also a large number of options, which we will
discuss shortly. First let's see how the minimal usage looks like.
</script>
---- Code Omitted ----
<p>
<span class="fieldLabel">Your Name:</span>
<br/>
<span id="userName" class="field">[your name here]</span>
</p>
<p>
<span class="fieldLabel">How would you rate our service:</span>
<br/>
<span id="rate" class="field">Fair</span>
</p>
---- Code Omitted ----
The above code uses the following helper PHP script named echo.php.
This very basic example gives us the behavior displayed in the images below.
Before clicking After clicking
The default operation of these controls is to accept the value when the Return key is pressed or the ok
button is clicked. The control will abort the update if Esc is pressed or the cancel link is clicked.
When the value is accepted an AJAX call to the given URL is made, posting the entered text in the value
form parameter and the id of the element in the editorId parameter.
The server is expected to return the final value of the operation, i.e. the HTML text that will become the
new static text. It usually is simply the exact same value that was posted, but we will see that there are
options to enable richer operations.
One important thing to note is that the control is capable of multi-line edits. For that to happen the
element's content must have line break characters (\r: carriage returns or \n: line feeds) or that the rows
option is greater than 1. When that happens, the editor will be a textarea field instead of a simple input text
field.
As you can see, there's a lot of functionality in the control even with the default settings, but there's a lot
that can be customized just by specifying a few options.
Option Description
These are the options that are passed to the Ajax.Updater. Defaults to { evalScripts:
ajaxOptions
true }.
If the rows option is 1 and the editor is multi-line, then this value will be used
autoRows
instead. Defaults to 3.
Function that is called to format the values posted in the AJAX call. It will be
callback passed the form element created around the editor and the value entered in the
editor.
Specifies what type of element will be created to cancel the operation. The values
cancelControl
can be 'button', 'link', or false. The last one avoids any cancel button or link.
clickToEditText Text that is shown in a tooltip when the text is not in edit mode.
Array of items used to create the drop down list in the
collection
Ajax.InPlaceCollectionEditor.
cols Number of columns of the created input text field. Defaults to 40.
Reference or id of an element used to put the text in edit mode. The text itself
externalControl
remains capable of entering edit mode upon being clicked.
Specifies if the input field will be activated or just receive focus after being created.
fieldPostCreation
Possible values are 'focus' or 'activate', which is the default (it selects the text.)
CSS class name applied to the form element that is created to house the edit field.
formClassName
Defaults to inplaceeditor-form.
The id of the created form. Defaults to ELEMENT_ID-inplaceeditor, where
formId
ELEMENT_ID is the id of the original text element.
hoverClassName CSS class that is applied to the text element when the mouse is over it.
Specifies that the we expect the server to return the final HTML content of the
element. It defaults to true. We use false when we want to process the server
htmlResponse
response and then determine the element's final content. This option works with the
onComplete option.
loadingText When using the loadTextURL option, this is the text that will be displayed while the
Option Description
text of the edit field is being fetched. Defaults to 'Loading...'.
URL that will be called to provide the text content of the edit field. This can be
loadTextURL useful when the text we want to edit is not HTML and we want the user to type in a
syntax like Textile, Markdown, Wikitext, etc.
Specifies what type of element will be created to accept the operation. The values
okControl
can be 'button', 'link', or false. The last one avoids any ok button or link.
Called when the AJAX request ends. It gets passed the Ajax.Response and the
onComplete
element.
onEnterEditMode Called when the control enters edit mode. It gets passed the control instance.
Called when the mouse hovers over the element. It gets passed the control instance.
onEnterHover
The default value causes the element to be highlighted.
Called when there's an AJAX error. It gets passed the control and the
onFailure
Ajax.Response. By default a simple alert box is displayed.
This is called to give you a chance to tweak the form when it's created. It is passed
onFormCustomization
the control and the form element.
onLeaveEditMode Called when the control leaves edit mode. It gets passed the control instance.
Called when the mouse hovers over the element. It gets passed the control instance.
onLeaveHover
The default value causes an Effect.Highlight to be applied to the element.
If the default parameter name of value doesn't work for you, you can choose a
paramName
different one with this option.
rows Number of rows in the textarea input field that is created for multi-line edits.
savingClassName CSS class name that is applied to the saving text when it is being displayed.
savingText Text that is displayed while the AJAX call is in progress.
size The desired size of the single-line editor field. No default value.
Determines if HTML tags will be removed from the AJAX response text. Defaults
stripLoadedTextTags
to false.
When true causes the control to accept the value when it looses focus. The default is
submitOnBlur
false.
script.aculo.us Conclusion
We can get a lot done with the effects and UI widgets that come with script.aculo.us. Learning when to use
the effects is what will make the difference between a smooth user experience and that annoying website
that insists in getting in your way with unnecessary tricks.
The best part of script.aculo.us is the framework mentality that guided it's design. The library is full of
extensions points that let you customize as little or as much as you need and still leverage reusable
functionality.
To continue to learn JavaScript go to the top of this page and click on the next lesson in this JavaScript
Tutorial's Table of Contents.