Javascript
Javascript
In this lesson of the JavaScript tutorial, you will learn... 1. 2. 3. 4. 5. 6. 7. 8. To work with the HTML DOM. To follow JavaScript syntax rules. To write JavaScript inline. To write JavaScript in script blocks. To create and link to external JavaScript files. To work with JavaScript objects, methods, and properties. To reference HTML elements with JavaScript. To use event handlers.
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
1. JavaScript statements end with semi-colons. 2. JavaScript is case sensitive. 3. JavaScript has two forms of comments: o Single-line comments begin with a double slash (//). o Multi-line comments begin with "/*" and end with "*/". 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.
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.
Code Explanation
As this page loads, an alert will pop up that says "The page is loading" as shown below.
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.
Methods
Methods are the verbs of JavaScript. They cause things to happen.
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
Methods can take zero or more arguments separated by commas. 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.
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. HTML Event Handlers Event Handler onblur onchange onclick Elements Supported a, area, button, input, label, select, textarea input, select, textarea All elements except applet, base, basefont, bdo, br, font, frame, frameset, head, html, iframe, isindex, meta, param, script, style, title All elements except applet, base, basefont, bdo, br, font, frame, frameset, head, html, iframe, isindex, meta, param, script, style, title a, area, button, input, label, select, textarea All elements except applet, base, basefont, bdo, br, font, frame, frameset, head, html, iframe, isindex, meta, param, script, style, title All elements except applet, base, basefont, bdo, br, font, frame, frameset, head, html, iframe, isindex, meta, param, script, style, title All elements except applet, base, basefont, bdo, br, font, frame, frameset, head, html, iframe, isindex, meta, param, script, style, title frameset body Description the element lost the focus the element value was changed a pointer button was clicked a pointer button was double clicked the element received the focus a key was pressed down a key was pressed and released a key was released all the frames have been loaded the document has been loaded a pointer button was pressed down
onkeypress
All elements except applet, base, basefont, bdo, br, onmousedown font, frame, frameset, head, html, iframe, isindex, meta, param, script, style, title
HTML Event Handlers Event Handler Elements Supported Description a pointer was moved within a pointer was moved away a pointer was moved onto a pointer button was released the form was reset some text was selected the form was submitted all the frames have been removed the document has been removed
All elements except applet, base, basefont, bdo, br, onmousemove font, frame, frameset, head, html, iframe, isindex, meta, param, script, style, title All elements except applet, base, basefont, bdo, br, onmouseout font, frame, frameset, head, html, iframe, isindex, meta, param, script, style, title All elements except applet, base, basefont, bdo, br, onmouseover font, frame, frameset, head, html, iframe, isindex, meta, param, script, style, title All elements except applet, base, basefont, bdo, br, onmouseup font, frame, frameset, head, html, iframe, isindex, meta, param, script, style, title onreset form onselect onsubmit onunload onunload input, textarea form frameset body
when the mouse button is released over the "Blue" button, the background color turns blue and an alert pops up reading "The background color is now Blue."
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. Where is the solution?
JavaScript Variables
Variables are used to hold data in memory. JavaScript variables are declared with the var keyword.
var age;
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.
var strAge = "34";
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.
Code Explanation 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: 1. The message in the dialog box (e.g., "Enter a color."). 2. The default value that appears in the text box. In the example above this is an empty string (e.g, "").
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.
var USER_COLOR = window.prompt("Enter a 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.
<span onclick="document.bgColor = USER_COLOR;"> <script type="text/javascript"> document.write(USER_COLOR); </script> </span>
onclick="alert('George');"/> <br/><br/> <input type="button" value="Ringo" onclick="alert('Ringo');"/> <br/><br/> <!--ADD BUTTON HERE--> </form> </body> </html>
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. Arrays are declared using the new keyword.
var myarray = new Array();
It is also possible and very common to use the [] literal to declare a new Array object.
var myarray = [];
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.
USER_COLORS[0] USER_COLORS[1] USER_COLORS[2] USER_COLORS[3] = = = = window.prompt("Choose window.prompt("Choose window.prompt("Choose window.prompt("Choose a a a a color.", color.", color.", color.", ""); ""); ""); "");
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.
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.
Array Properties Property Description Example length Holds the number of elements in an array. BEATLES.length // 4 Array Methods Property Description Returns a delimited list of the items join(delimiter) indexed with integers in the array. The default delimiter is a comma. Removes the last item in an array and pop() returns its value. shift() Removes the first item in an array and Example BEATLES.join(":") // Paul:John:George:Ringo BEATLES.pop() // Returns Ringo BEATLES.shift() // Returns
Array Methods Property Description returns its value. Returns a subarray from start to end. If slice(start, end) end is left out, it includes the remainder of the array. splice(start, Removes count items from start in the count) array and returns the resulting array.
Example Paul BEATLES.slice(1 ,2) //Returns [John, George] BEATLES.splice(1, 2) //Returns [Paul, Ringo]
JavaScript Operators
Arithmetic Operators Operator Description + Addition Subtraction * Multiplication / Division % Modulus (remainder) ++ Increment by one -Decrement by one Operator = Assignment += One step addition and assignment (a+=3 is the same as a=a+3) -= One step subtraction and assignment (a-=3 is the same as a=a-3) *= One step multiplication and assignment (a*=3 is the same as a=a*3) /= One step division and assignment (a/=3 is the same as a=a/3) %= One step modulus and assignment (a%=3 is the same as a=a%3) String Operators Operator Description + Concatenation (var greeting = "Hello " + firstname;) One step concatenation and assignment (var greeting = "Hello "; greeting += += firstname;) Ternary Operator Operator Description Conditional evaluation (var evenOrOdd = (number % 2 == 0) ? "even" : ?: "odd";) The following code sample shows these operators in use. Assignment Operators Description
Code Explanation 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. 7. var NUMS_ADDED = USER_NUM1 + USER_NUM2; var NUMS_SUBTRACTED = USER_NUM1 - USER_NUM2;
8. 9.
var NUMS_MULTIPLIED = USER_NUM1 * USER_NUM2; 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.
/* Ask the user how many of this rockstar's CDs she owns and store the result in a variable. */ ROCK_STARS[1] = prompt("And your next favorite rock star?", ""); /* Ask the user how many of this rockstar's CDs she owns and store the result in a variable. */ </script> </head> <body> <!-Let the user know how many more of her favorite rockstar's CDs she has than of her second favorite rockstar's CDs. --> </body> </html>
1. Open VariablesArraysOperators/Exercises/Operators-challenge.html for editing. 2. Modify it so that it outputs an unordered list as shown below.
JavaScript Functions
In this lesson of the JavaScript tutorial, you will learn... 1. To work with some of JavaScript's built-in functions. 2. To create your own functions. 3. To return values from functions.
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."
Code Explanation Because STR_NUM1 and STR_NUM2 are both strings, the + operator concatenates them, resulting in "12".
var STR_NUM1 = "1"; var STR_NUM2 = "2"; var STR_SUM = STR_NUM1 + STR_NUM2; //returns 12 alert(STR_SUM);
After the Number() function has been used to convert the strings to numbers, the + operator performs addition, resulting in 3.
var INT_NUM1 = Number(STR_NUM1); var INT_NUM2 = Number(STR_NUM2); var INT_SUM = INT_NUM1 + INT_NUM2; //returns 3 alert(INT_SUM);
String(object)
The String() function takes one argument: an object, which it converts to a string.
Code Explanation 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".
var STR_NUM1 = String(INT_NUM1); var STR_NUM2 = String(INT_NUM2); var STR_SUM = STR_NUM1 + STR_NUM2; //returns 12 alert(STR_SUM);
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.
Code Explanation
document.write("<tr><td>parseFloat(RACE)</td>"); document.write("<td>" + parseFloat(RACE) + "</td></tr>"); document.write("<tr><td>parseInt(RACE)</td>"); document.write("<td>" + parseInt(RACE) + "</td></tr>"); RACE = "Marathon"; document.write("<tr><td>parseFloat(RACE)</td>"); document.write("<td>" + parseFloat(RACE) + "</td></tr>"); document.write("<tr><td>parseInt(RACE)</td>"); document.write("<td>" + parseInt(RACE) + "</td></tr>"); </script> </table> </body> </html>
Code Explanation
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 computer. The images
Notice that the program is able to deal with numbers followed by strings (e.g, "12 years old"). Where is the solution?
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.
<html> <head> <title>JavaScript Simple Functions</title> <script type="text/javascript"> function changeBgRed(){ document.bgColor = "red"; } function changeBgWhite(){ document.bgColor = "white"; } </script> </head> <body> <p align="center"> <span onclick="changeBgRed();">Red</span> | <span onclick="changeBgWhite();">White</span> </p> </body> </html>
Code Explanation 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.
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. Where is the solution?
<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.
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". Common String Properties Property Description Read-only value containing the number of characters in length the string.
Example
MY_STRING.length //Returns 9
Common String Methods Method Description Example Returns the character MY_STRING.charAt(4) charAt(position) at the specified //Returns c position. Returns the Unicode character code of the MY_STRING.charCodeAt(4) charCodeAt(position) //Returns 99 character at the specified position. Returns the text representation of the specifies commafromCharCode(characterCode delimited character String.fromCharCode(169) s) //Returns codes. Used with String rather than a specific String object. Searches from MY_STRING.indexOf("cat"); startPosition for //Returns 4 substring. Returns indexOf(substring, the position at which MY_STRING.indexOf("cat", startPosition) the substring is 5); found. If substring is //Returns -1 not found, returns -1.
lastIndexOf(substring, endPosition)
MY_STRING.lastIndexOf("cat" ); //Returns 4
Common String Methods Method Description Example substring until MY_STRING.lastIndexOf("cat" endPosition is , 5); reached. Returns the //Returns 4 position at which the substring is found. If substring is not found, returns -1. Returns the substring beginning at startPosition and ending with the MY_STRING.substring(4, 7); character before //Returns cat endPosition. substring(startPosition, endPosition) endPosition is MY_STRING.substring(4); optional. If it is //Returns cator excluded, the substring continues to the end of the string. Returns the substring of Length characters beginning at MY_STRING.substr(4, 3); startPosition. length //Returns cat substr(startPosition, is optional. If it is length) MY_STRING.substr(4); excluded, the substring continues //Returns cator to the end of the string. Same as slice(startPosition, MY_STRING.slice(4, 7); substring(startPositio //Returns cat endPosition) n, endPosition). positionFromEnd is a negative integer. Returns the the substring beginning slice(startPosition, MY_STRING.slice(4, -2); at startPosition and //Returns cat positionFromEnd) ending positionFromEnd characters from the end of the string. Returns an array by var s = "A,B,C,D"; split(delimiter) splitting a string on var a = s.split(","); document.write(a[2]); the specified
Method
toLowerCase() toUpperCase()
Common String Methods Description Example //Returns C delimiter. Returns the string in MY_STRING.toLowerCase() all lowercase letters. //Returns webucator Returns the string in MY_STRING.toUpperCase(); all uppercase letters. //Returns WEBUCATOR
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. Common Math Properties Description Example Pi ( ) Square root of 2.
Math.PI; //3.141592653589793 Math.SQRT2; //1.4142135623730951
Property
Math.PI Math.SQRT2
Method
Math.abs(number) Math.ceil(number) Math.floor(number) Math.max(numbers)
Common Math Methods Description Absolute value of number. number rounded up. number rounded down. Highest Number in numbers. Lowest Number in numbers. number to the power of power. Rounded number. Random number between 0 and 1.
Example
Math.abs(-12); //Returns 12 Math.ceil(5.4); //Returns 6 Math.floor(5.6); //Returns 5 Math.max(2, 5, 9, 3); //Returns 9 Math.min(2, 5, 9, 3); //Returns 2 Math.pow(2, 5); //Returns 32 Math.round(2.5); //Returns 3 Math.random(); //Returns random //number from 0 to 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.
<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>
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 Description Example Returns the day of the RIGHT_NOW.getDate(); //Returns 14 month (1-31). Returns the day of the RIGHT_NOW.getDay(); week as a number (0-6, //Returns 4 0=Sunday, 6=Saturday). Returns the month as a RIGHT_NOW.getMonth(); number (0-11, 0=January, //Returns 3 11=December). Returns the four-digit year. //Returns 2005 Returns the hour (0-23). Returns the minute (0-59). Returns the second (0-59).
RIGHT_NOW.getFullYear(); RIGHT_NOW.getHours(); //Returns 0 RIGHT_NOW.getMinutes(); //Returns 23 RIGHT_NOW.getSeconds(); //Returns 54 RIGHT_NOW.getMilliseconds(); //Returns 650 RIGHT_NOW.getTime(); //Returns 1113452634650 RIGHT_NOW.getTimezoneOffset(); //Returns 240
Method
getDate()
getDay()
Returns the millisecond (0999). Returns the number of getTime() milliseconds since midnight January 1, 1970. Returns the time difference getTimezoneOffset() in minutes between the user's computer and GMT.
toLocaleString()
Returns the Date object as RIGHT_NOW.toLocaleString(); //Returns Thursday, April 14, a string. //2005 12:23:54 AM Returns the Date object as RIGHT_NOW.toGMTString(); //Returns Thu, 14 Apr 2005 a string in GMT timezone. //04:23:54 UTC
toGMTString()
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.
months[6] = "July"; months[7] = "August"; months[8] = "September"; months[9] = "October"; months[10] = "November"; months[11] = "December"; 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>
In this lesson of the JavaScript tutorial, you have learned to work with some of JavaScript's most useful built-in 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.
Conditionals
There are two types of conditionals in JavaScript. 1. if - else if - else 2. switch / case
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.
Code Explanation
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.
if (age > 18 && isCitizen) { alert("You can vote!"); } if (age >= 16 && (isCitizen || hasGreenCard)) { alert("You can work in the United States"); }
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. To illustrate, take a look at the following sample.
<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.
Switch / Case
Syntax
switch (expression) {
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. Take a look at the following example.
Code Explanation 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 corrected code is shown in the example below.
The following example shows how a switch/case statement can be used to record the user's browser type.
Code Explanation
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).
Loops
There are several types of loops in JavaScript.
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.
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.
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.
<h2>for Loop</h2> <script type="text/javascript"> for (var INDEX=0; INDEX<=5; INDEX++) { document.write(INDEX); } </script> <h2>for Loop with Arrays</h2> <ol> <script type="text/javascript"> for (var INDEX=0; INDEX<RAMONES.length; INDEX++) { document.write("<li>" + RAMONES[INDEX] + "</li>"); } </script> </ol> <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 The sample above shows demos of all the aforementioned loops.
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. Where is the solution?
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.
Code Explanation Some things to notice: 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.
In this exercise, you will write a function that bases the value of one text field on the value of another. 1. Open FormValidation/Exercises/TextfieldToTextField.html for editing. 2. Write a function called getMonth() that passes the month number entered by the user to the monthAsString() function in DateUDFs.js and writes the result in the MonthName field.
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. Where is the solution?
<form action="Process.html" onsubmit="return false;"> <!--Code for form fields would go here--> <input type="submit" value="Submit Form"> </form>
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.
<form action="Process.html" onsubmit="return validate(this);">
Code Explanation 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
There are a few improvements we can make on the last example. 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(password)) { errors[errors.length] = "You must enter a password."; } if (errors.length > 0) { reportErrors(errors); return false; } return true; } function checkLength(text, min, max){ min = min || 1; max = max || 10000; if (text.length < min || text.length > max) { 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);"> Username: <input type="text" name="Username" size="10"><br/> Password: <input type="password" name="Password" size="10"><br/> <input type="submit" value="Submit"> <input type="reset" value="Reset Form"> </p> </form> </body> </html>
Code Explanation Some things to notice: 1. Two additional functions are created: checkLength() and reportErrors().
2. 3. 4.
5.
The checkLength() function takes three arguments, the text to examine, the required minimum length, and the required maximum length. If the minimum length and maximum length are not passed, defaults of 1 and 10000 are used. o The reportErrors() function takes one argument, an array holding the errors. It loops through the errors array creating an error message and then it pops up an alert with this message. The \n is an escape character for a newline. In the main validate() function, a new array, errors, is created to hold any errors that are found. userName and password are passed to checkLength() for validation. o If errors are found, they are appended to errors. If there are any errors in errors (i.e, if its length is greater than zero), then errors is passed to reportErrors(), which pops up an alert letting the user know where the errors are. The validate() function then returns false and the form is not submitted. If no errors are found, the validate() function returns true and the form is submitted.
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.
Checks that the middle initial is a single character. Checks that the state is exactly two characters. Checks that the email is a valid email address. Checks that the values entered into Password1 and Password2 are the same. If there are errors, passes reportErrors() the errors array and returns false. If there are no errors, returns true. 3. Test your solution in a browser. 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. Where is the solution?
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.
<html> <head> <title>Checkboxes</title> <script type="text/javascript"> function validate(form){ var errors = []; 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>
Code Explanation 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.
Code Explanation 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 (radioButtons[i].checked) { return true; } } return false; } function checkCheckBox(cb){ return cb.checked; } function checkSelect(select){ return (select.selectedIndex > 0); } function checkLength(text, min, max){ min = min || 1; max = max || 10000; if (text.length < min || text.length > max) { return false; } return true; } function checkTextArea(textArea, max){ var numChars, chopped, message; if (!checkLength(textArea.value, 0, max)) { numChars = textArea.value.length; chopped = textArea.value.substr(0, max); message = 'You typed ' + numChars + ' characters.\n'; message += 'The limit is ' + max + '.'; message += 'Your entry will be shortened to:\n\n' + chopped; alert(message); textArea.value = chopped; } } 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>
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.
Code Explanation
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.
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.
<img name="MyImage" src="Images/Banner.jpg" border="0"></a> <p>Who are you calling simple?</p> </div> </body> </html>
Code Explanation 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.
onmouseout="imageRollover(this, 'Images/Banner.jpg');"> <img name="Bruce" src="Images/Bruce.jpg" border="0" onmouseover="imageRollover(this, 'Images/Batman.jpg');" onmouseout="imageRollover(this, 'Images/Bruce.jpg');"> <p>Who are you calling simple?</p> </div> </body> </html>
Code Explanation 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.
var IMAGE_PATHS = [];
4. An array called IMAGE_CACHE is created to hold the Image objects that will hold the preloaded images.
var IMAGE_CACHE = [];
5. A for loop is used to create an Image object and load in an image for each image path in IMAGE_PATHS.
6. 7. 8. } for (var i=0; i<IMAGE_PATHS.length; i++) { IMAGE_CACHE[i]=new Image(); IMAGE_CACHE[i].src=IMAGE_PATHS[i];
If dir equals -1, it changes the slide to the previous image as stored in the IMAGE_PATHS array. o If the user is viewing the last slide and clicks the Next button, the first slide should appear. o If the user is viewing the first slide and clicks the Previous button, the last slide should appear. 4. Test your solution in a browser. 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). The solution should look like the screenshot below.
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... 1. To use regular expressions for advanced form validation. 2. To use regular expressions and backreferences to clean up form entries.
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. There are two ways to create a regular expression in JavaScript: 1. Using literal syntax
var reExample = /pattern/;
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 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.
Code Explanation Let's examine the code more closely: 1. First, a variable containing a regular expression object for a social security number is declared.
var RE_SSN = /^[0-9]{3}[\- ]?[0-9]{2}[\- ]?[0-9]{4}$/;
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. 4. 5. 6. 7. 8. } function checkSsn(ssn){ if (RE_SSN.test(ssn)) { alert("VALID SSN"); } else { alert("INVALID SSN"); }
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
There are several String methods that use regular expressions.
"Webucator".split(/[aeiou]/); /* returns an array with the following values: "W", "b", "c", "t", "r" */
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 )
A period ( . ) represents any character except a newline.
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 ( () )
Parentheses ( () ) are used to capture subpatterns.
The pattern f(oo)?d can be found in "food" and "fd", but not in "fod".
Alternatives ( | )
The pipe ( | ) is used to create optional patterns.
The pattern foo$|^bar can be found in "foo" and "bar", but not "foobar".
Escape Character ( \ )
The backslash ( \ ) is used to escape special characters.
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.
^\d{3}([\- ]?)\d{2}([\- ]?)\d{4}$
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).
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.
Email: <input type="text" name="Email" size="25"><br/> Password: <input type="password" name="Password" size="10"><br/> *Password must be between 6 and 10 characters and can only contain letters and digits.<br/> <input type="submit" value="Submit"> <input type="reset" value="Reset Form"> </p> </form> </body> </html>
Code Explanation This code starts by defining regular expressions for an email address and a password. Let's break each one down.
var RE_EMAIL = /^(\w+\.)*\w+@(\w+\.)+[A-Za-z]+$/;
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.
var RE_PASSWORD = /^[A-Za-z\d]{6,8}$/;
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.
starts with capital letter followed by one or more letters or apostophes may be multiple words (e.g, "New York City") zero or one capital letters
2. Initial
3. State
two capital letters 4. US Postal Code five digits (e.g, "02138") possibly followed by a dash and four digits (e.g, "-1234") 5. Username between 6 and 15 letters or digits 2. Open RegularExpressions/Exercises/Register.html for editing. o Add validation to check the following fields: 1. first name 2. middle initial 3. last name 4. city 5. state 6. zip 7. username 3. Test your solution in a browser.
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. Where is the solution?
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.
var cleanedSsn = ssn.replace(RE_SSN, "$1-$2-$3");
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, 877WEBUCATE). As is the case with 877-WEBUCATE, such numbers often have an extra character just to make the word complete. 1. Add a function called convertPhone() that: o strips all characters that are not numbers or letters o converts all letters to numbers ABC -> 2 DEF -> 3 GHI -> 4 JKL -> 5 MNO -> 6 PRS -> 7 TUV -> 8 WXY -> 9 QZ -> 0 o passes the first 10 characters of the resulting string to the cleanPhone() function o returns the resulting string 2. Modify the form, so that it calls convertPhone() rather than cleanPhone(). 3. Test your solution in a browser. Where is the solution?
Dynamic Forms
In this lesson of the JavaScript tutorial, you will learn... 1. To create jump menus.
2. To create interdependent select menus. 3. To create a JavaScript TIMER 4. To build a simple JavaScript testing tool.
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.
Code Explanation
2. The jumpMenu() function does three things: 1. Creates the variable i that holds the selectedIndex property of the passedin select menu. 2. Creates the variable selection that holds the value of the selected option. 3. Checks to see if the first option is selected. If it is, the function alerts the user to select an option.
If it is not, the function creates a variable, url, to hold the destination page and then loads that page by changing the href property of the location object to url.
The example below is a modified version of the jump menu that disables the "GO" button unless a state is selected.
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.
= = = = = = = =
Option("New York City", "NYC"); Option("Syracuse", "SYR"); Option("Albany", "ALB"); Option("Rochester", "ROC"); Option("Los Angeles", "LAN"); Option("San Diego", "SDI"); Option("San Francisco", "SFR"); Option("Oakland", "OAK");
function populateSub(mainSel, subSel){ var mainMenu = mainSel; var subMenu = subSel; var subMenuItems; subMenu.options.length = 0; switch (mainMenu.selectedIndex) { case 0: subMenuItems = NEW_YORKERS; break; case 1: subMenuItems = CALIFORNIANS; break; } for (var i = 0; i < subMenuItems.length; i++) { subMenu.options[i] = subMenuItems[i]; } } </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 Let's look at the code above in detail. 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.
function populateMain(mainSel, subSel){ var mainMenu = mainSel; var subMenu = subSel; mainMenu.options.length = 0; for (var i = 0; i < MENU.length; i++) { mainMenu.options[i] = MENU[i][0]; } populateSub(mainMenu, subMenu); } function populateSub(mainSel, subSel){ var mainMenu = mainSel; var subMenu = subSel; var optMainMenu; subMenu.options.length = 1; optMainMenu = mainMenu.selectedIndex; for (var i = 1; i < MENU[optMainMenu].length; i++) { subMenu.options[i] = MENU[optMainMenu][i]; } } </script> </head> <body onload="populateMain(document.Menus.State, document.Menus.City);"> <form name="Menus"> <select name="State" onchange="populateSub(this, this.form.City);"></select> <select name="City"> <option value="0">--Please Choose--</option> </select> </form> </body> </html>
Code Explanation 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.
2. Notice that an external JavaScript file, Select.js, is included. This file is shown below. 3. An array, MENU, is created and populated with four internal arrays: MENU[0], MENU[1], MENU[2], and MENU[3]. 4. Populate the arrays so that: o The Bands select menu will be populated with "Beatles", "Rolling Stones", "Genesis", and "Eagles". o When "Beatles" is chosen from the Bands select menu, the Artists select menu contains: Paul McCartney with the value of "http://www.paulmccartney.com" John Lennon with the value of "http://www.johnlennon.it" George Harrison with the value of "http://www.georgeharrison.com" Ringo Starr with the value of "http://www.ringostarr.com" o When "Rolling Stones" is chosen, the Artists select menu contains: Mick Jagger with the value of "http://www.mickjagger.com" Keith Richards with the value of "http://www.keithrichards.com" Charlie Watts with the value of "http://www.rosebudus.com/watts" Bill Wyman with the value of "http://www.billwyman.com" o When "Genesis" is chosen, the Artists select menu contains: Phil Collins with the value of "http://www.philcollins.co.uk" Peter Gabriel with the value of "http://www.petergabriel.com" Mike Rutherford with the value of "http://www.mikemechanics.com" o When "Eagles" is chosen, the Artists select menu contains: Don Henley with the value of "http://www.donhenley.com" Joe Walsh with the value of "http://www.joewalsh.com" Glenn Frey with the value of "http://www.imdb.com/name/nm0004940" 5. Change the values of arg1 and arg2 in the calls to populateMain() and populateSub() in the event handlers in the HTML code so that the correct arguments are passed to these functions.
var subMenu = subSel; var optMainMenu; subMenu.options.length = 1; optMainMenu = mainMenu.selectedIndex; for (var i = 1; i < MENU[optMainMenu].length; i++) { subMenu.options[i] = MENU[optMainMenu][i]; } } function jumpMenu(select){ var i = select.selectedIndex; var url = select.options[i].value; if (i > 0) { location.href = url; } }
-When Eagles is chosen, the Artists select menu contains: TEXT VALUE Don Henley http://www.donhenley.com Joe Walsh http://www.joewalsh.com Glenn Frey http://www.imdb.com/name/nm0004940 Change the values of arg1 and arg2 in the calls to populateMain() and populateSub() in the event handlers in the HTML code so that the correct arguments are passed to these functions. */ </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>
Or
setTimeout(functionToCall, n);
The example below shows how the setTimeout() method can be used to create a timer.
var SECONDS_LEFT, TIMER, TIMES_UP; function init(){ document.Timer.btnStart.disabled = true; } 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 Let's look at the code above in detail. 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.
var SECONDS_LEFT, TIMER, TIMES_UP;
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; o
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; o
o o o o o o
If SECONDS_LEFT is greater than or equal to zero, setTimeout() is used to re-call decrementTimer() after 1000 milliseconds (1 second). This creates a timer object, which is assigned to TIMER. If SECONDS_LEFT is less than zero, an alert pops up notifying the user that time is up and resetTimer(), which clears the timer, is called.
buttons are enabled at the right times. The diagram below shows the four different
<html> <head> <title>Math Quiz</title> <script type="text/javascript" src="Select.js"></script> <script type="text/javascript"> var MENU = []; var SECONDS_LEFT, TIMER, TIMES_UP, TOTAL_TIME = 10; function MENU[0] MENU[1] MENU[2] MENU[3] init(){ = []; = []; = []; = []; = = = = = = = = = = = = = = = = = = = = new new new new new new new new new new new new new new new new new new new new Option("Addition"); Option("5 + 4", "9"); Option("9 + 3", "12"); Option("7 + 12", "19"); Option("13 + 24", "37"); Option("Subtraction"); Option("5 - 1", "4"); Option("12 - 4", "8"); Option("23 - 11", "12"); Option("57 - 19", "38"); Option("Multiplication"); Option("1 * 3", "3"); Option("4 * 8", "32"); Option("7 * 12", "84"); Option("13 * 17", "221"); Option("Division"); Option("4 / 1", "4"); Option("12 / 3", "4"); Option("21 / 7", "3"); Option("121 / 11", "11");
MENU[0][0] MENU[0][1] MENU[0][2] MENU[0][3] MENU[0][4] MENU[1][0] MENU[1][1] MENU[1][2] MENU[1][3] MENU[1][4] MENU[2][0] MENU[2][1] MENU[2][2] MENU[2][3] MENU[2][4] MENU[3][0] MENU[3][1] MENU[3][2] MENU[3][3] MENU[3][4]
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 Here's how the quiz works: 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.
var IS_MAC = (navigator.userAgent.indexOf("Mac") >= 0); var IS_UNIX = (navigator.userAgent.indexOf("X11") >= 0); //Browser Info var IS_IE = (navigator.appName == "Microsoft Internet Explorer"); var IS_FF = (navigator.userAgent.indexOf("Firefox") >= 0); var IS_NS = (navigator.vendor != "undefined" && navigator.vendor == "Netscape"); var IS_MOZ = (navigator.userAgent.indexOf("Mozilla/5") >= 0); var IS_NEW = ((IS_MOZ && parseInt(navigator.appVersion) >= 5) || (IS_IE && parseInt(navigator.appVersion) >= 4)); //Object Support var SUPPORTS_IMAGES = (typeof document.images != "undefined"); //Cookies var COOKIES_ON = navigator.cookieEnabled;
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.
var name = "Homer"; var ssn = null;
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.
//Check if we already have a start time if (START_TIME === undefined) { START_TIME = new Date(); }
Boolean
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.
var enabled = true; var disabled = false;
Number
The Number data type can represent two types of numeric values, 32-bit integers or 64bit floating point numbers. Number values are created using number literals, which can be in a few different formats.
var age = 25; // simple, decimal, integer var price = 45.95; // floating point var permissions = 0775; // integer in octal, 509 in decimal // (note the leading zero) var flags = 0x1c; // integer in hexadecimal, 28 in decimal // (note the 0x prefix) var measurement = 5.397e-9; // floating point in // scientific notation
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.
var var var var name = 'Homer', lastName = "Simpson"; host = 'Conan O\'Brien'; path = 'c:\\temp\\dir\\myfile.txt'; tabDelimited = "COL1\tCOL2\tCOL3\nVAL1\tVAL2\tVAL3";
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.
var rightNow = new Date(); // current date and time var holiday = new Date(2008, 6, 4); // 4th of July, note the // 0-based month number var birth = Date.parse('7/4/2008'); // 4th of July, format varies with browser // locale (avoid this)
There two important pitfalls in the above example: the month is a number from 0 to 11 when passed as a parameter and the parse-able string formats vary by browser implementation and by user locale, so we'd better off just avoid parsing altogether.
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.
var CITIES = new Array(); CITIES[0] = 'Albuquerque'; CITIES[9] = 'Tampa'; var TEAMS = [ 'Cubs', 'Yankees', 'Mariners' ]; var BANDS = [ ]; BANDS['rock'] = "Beatles, Rolling Stones, Pink Floyd"; BANDS['punk'] = "Sex Pistols, Ramones, Dead Kennedys"; BANDS[1992] = "Nirvana, Pearl Jam, Soundgarden, Metallica";
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.
var employee = new Object(); employee.name = 'Homer Simpson'; employee.badgeNumber = 35739; var boss = { }; //literal syntax, empty though boss.name = 'Montgomery Burns'; boss.badgeNumber = 1; employee.reportsTo = boss;
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 Perlsyle literals.
var text = "Webucator"; var pattern = new RegExp('cat', 'g'); var samePattern = /cat/g; //using the literal syntax alert( pattern.test( text ) );// shows 'true'
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.
//declare the function function sayHowMuch(name, price, quantity) { var finalPrice = price * quantity; alert('The price for ' + quantity + ' ' + name + '(s) is $' + finalPrice); } //call the function with arguments sayHowMuch('ice cream cone', 1.99, 3); sayHowMuch('Movie ticket', 10.00, 5);
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.
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 browserspecific features, like the alert(), prompt(), setTimeout(), and open() functions. These are just methods of the window object, not part of JavaScript per se.
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... 1. 2. 3. 4. 5. To use the default operator. To pass a flexible number of arguments to a function. To pass a function as an argument to another function. To create anonymous functions. Other techniques related to functions.
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.
var R1 = sumValues(3, 5, 6, 2, 7); var R2 = sumValues(12, 20);
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. Let's fix our function to deal with these types of situations.
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.
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.
var NUMBER = 0; if (NUMBER) { alert('You should } NUMBER = 1; if (NUMBER) { alert('You should } var TEXT; if (TEXT) { alert('You should } TEXT = ""; if (TEXT) { alert('You should } TEXT = "hi"; if (TEXT) { alert('You should }
be reading this');
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.) JavaScript resolves the following values to false.
false (of course) null undefined 0 (zero) NaN "" (empty string)
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.
var A = 0, B = NaN, C = 1, D = "hi"; var RESULT = ( A || B || C || D ); alert("Result = " + RESULT); RESULT = ( D && C && B && A ); alert("Result = " + RESULT);
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.
function checkLength(text, min, max){ min = min || 1; max = max || 10000; if (text.length < min || text.length > max) { return false; } return true; }
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:
if (min === undefined) { min = 1; } // becomes simply min = min || 1; 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;
var R2 = sumValues(12, 20); //print the results debugWrite(R1); debugWrite(R2); </script> </body> </html>
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. The syntax is rather compact. 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.
var VALUES = [5, 2, 11, -7, 1]; function combineAll(list, initialValue, operation) { var runningResult = initialValue; for (var i=0; i< list.length; i++) { runningResult = operation(runningResult, list[i]); } return runningResult; } var SUM = combineAll(VALUES, 0, function (a, b) { return a+b; }); var PRODUCT = combineAll(VALUES, 1, function (a, b) { return a*b; }); ---- Code Omitted ----
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.
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.
var NAME = 'my global value'; function displayName() { alert(NAME); } alert(NAME); displayName();
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.
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.
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.
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.
var helper = new CascadingListPair( "country", "state", function (country) { return STATES_PER_COUNTRY[ country ]; }); ---- Code Omitted ----
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.
var helper2 = new CascadingListPair( "company", "employee", function (company) { return EMPLOYEES_PER_COMPANY[ company ]; });
1 - Building objects
The first approach is to create an empty object and progressively add its properties and methods.
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'); ---- Code Omitted ----
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.
//using the object GUITAR.play('Dm7'); GUITAR.tune( ['D', 'A', 'D', 'G', 'B', 'e' ] ); debugWrite('this guitar is: ' + GUITAR.color); GUITAR.print(850, 'USD'); ---- Code Omitted ----
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'] };
function createGuitar(color, strings) { var guitar = { }; guitar.color = color; guitar.strings = strings; 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); }; return guitar; } var GUITAR1 = createGuitar('black', ['E', 'A', 'D', 'G', 'B', 'e'] ); var GUITAR2 = createGuitar('maple', ['F', 'Bb', 'D#', 'G#', 'C', 'f'] ); ---- Code Omitted ----
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.
this.play = function (chord) { alert('Playing chord: ' + chord); }; this.print = function (price, currency) { alert('This guitar is ' + this.color + ', it has ' + this.strings.length + ' strings' + ' and it costs ' + price + currency); }; } 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'); ---- Code Omitted ----
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.
<style type="text/css"> .wc_debug { background-color:#ffc; } </style> <script type="text/javascript" src="../../Libraries/DebugHelp.js" ></script> </head> <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); } function addCellToRow(row, cellText) { var cell = document.createElement('TD'); 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 */ var LEVEL = 128; var ELEMENT = null; var INTERVAL = 100; 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); } } function setYellowLevel(element, level) { var hex = level.toString(16); element.style.backgroundColor = '#ffff' + hex; } </script>
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. Where is the solution?
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-theroad 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.
---- Code Omitted ---function tuneGuitar(newStrings) { this.strings = newStrings; } function playGuitar(chord) { alert('Playing chord: ' + chord); } function printInfo(price, currency) { alert('This guitar is ' + this.color + ', it has ' + this.strings.length + ' strings' + ' and it costs ' + price + currency); } function Guitar(color, strings) { this.color = color; this.strings = strings; this.tune = tuneGuitar; this.play = playGuitar; this.print = printInfo; } var GUITAR = new Guitar('black', ['E', 'A', 'D', 'G', 'B', 'e'] ); ---- Code Omitted ----
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.
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:
var GUITAR1 = Guitar('black', ['E', 'A', 'D', 'G', 'B', 'e']); debugWrite(GUITAR1); // => 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 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. Here's how we can overcome this problem.
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.
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.
Object.extend = function(destination, source) { for (var property in source) destination[property] = source[property]; return destination; };
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.
this.values = arguments; //private members var current = 0; var itemCount = arguments.length; var that = this; var moveIndex = function () { current = (current + 1) % itemCount; } //public members this.next = function () { moveIndex(); return that.values[current]; }; }; var WEEKDAYS = new Cycler('Mon', 'Tue', 'Wed', 'Thu', 'Fri'); ---- Code Omitted ----
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.
Prototype-Based Programming
In this lesson of the JavaScript tutorial, you will learn... 1. The differences between class-based and prototype-based programming
2. 3. 4. 5. 6.
To construct objects from other objects How JavaScript accomplishes inheritance To define constructor functions How JavaScript resolves object members The prototype object
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 classbased 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 Dim frontDoor as new Door() frontDoor.Open()
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.) Let's expand our examples a little bit to show class inheritance.
//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 Dim safe as new SafeDoor() safe.UnLock("4-8-15-16-23-42") safe.Open()
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. Consider the following simple implementation of an vehicle object.
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.
var car = object(vehicle); car.doors = 2;
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 }; var car = object(vehicle); car.doors = 2; </script> </head> <body> The vehicle color is <script type="text/javascript">document.write(vehicle.color)</script> <br /> The vehicle has <script type="text/javascript">document.write(vehicle.wheels)</script> wheels <br /> The car has <script type="text/javascript">document.write(car.doors)</script> doors <br /> The car color is <script type="text/javascript">document.write(car.color)</script> <br />
</body> </html>
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.
var car = object(vehicle); car.doors = 2; car.wheels = 4;
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.
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.
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.
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.
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. Here's the message box that we will be showing to the user.
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. Let's fix our example to catch that error.
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.
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. Let's learn about Firebug's capabilities by debugging an issue in practice.
</style> <script type="text/javascript" src="../../Libraries/DebugHelp.js" ></script> <script type="text/javascript"> //add the debud panel at the bottom of the page observeEvent(window, 'load', function () {insertDebugPanel();} ); </script> <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>
1. Open the above file in Firefox. 2. Activate Firebug. Tools menu, Firebug, uncheck Disable Firebug (if checked) then Tools menu, Firebug, Open Firebug. 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:
var that = this; this.field['onfocus'] = that.onGotFocus(); }; function() {
Now save the page and refresh the browser. The page should work now. Where is the solution?
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.
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.
1. 2. 3. 4. 5. 6.
To choose the correct way to deliver JavaScript code What Unobtrusive JavaScript is Techniques to reduce page load times To use existing tools for code documentation To adopt coding style conventions How to write and execute unit tests
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.
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');" />
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.
<div id="editableTitle" onclick="enterEditMode(this);"> Click to Edit </div>
This click event code can be moved to an external file with a relatively straight forward equivalent.
//this code is in an external file window.load = function () { var element = document.getElementById('editableTitle'); element.onclick = function () { enterEditMode(element); }; };
If we were using a helper library like Prototype.js, the code could become a little more compact.
//this code is in an external file document.observe('dom:loaded', function () { $('editableTitle').observe('click', function () { enterEditMode(this); }); });
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.
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 decompress 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.
} /** 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); } /** Appends a message to in the debug panel * @param {string} message The HTML text to be printed * @param {string} [debugPanelID] The id of the target debug panel, * in case there's more than one. Defaults to 'wc_debug'. */ function debugWrite(message, debugPanelID){ debugPanelID = debugPanelID||WC_DEFAULT_DEBUG_PANEL_ID; var panel = document.getElementById(debugPanelID); var msgObj = document.createElement('P'); msgObj.innerHTML = message; panel.appendChild(msgObj); }
After we minify it using the online tool or jsmin.exe we get the following file. Syntax
jsmin.exe < DebugHelp.js > DebugHelp-minified.js
C_DEFAULT_DEBUG_PANEL_ID;var container=document.getElementById(containerID);container=container||doc ument.body;var panel=document.createElement('DIV');panel.id=debugPanelID;panel.classNa me='wc_debug';container.appendChild(panel);debugWrite('<h4 style=\"border-bottom:1px solid black;\">Output:</h4>',debugPanelID);} function debugWrite(message,debugPanelID){debugPanelID=debugPanelID||WC_DEFAULT_ DEBUG_PANEL_ID;var panel=document.getElementById(debugPanelID);var msgObj=document.createElement('P');msgObj.innerHTML=message;panel.appen dChild(msgObj);}
The resulting file is clearly much smaller. Here's the comparison. 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. -
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.
Duration: 20 to 30 minutes. 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.
* @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); } /** Appends a message to in the debug panel * @param {string} message The HTML text to be printed * @param {string} [debugPanelID] The id of the target debug panel, * in case there's more than one. Defaults to 'wc_debug'. */ function debugWrite(message, debugPanelID){ debugPanelID = debugPanelID||WC_DEFAULT_DEBUG_PANEL_ID; var panel = document.getElementById(debugPanelID); var msgObj = document.createElement('P'); msgObj.innerHTML = message; panel.appendChild(msgObj); }
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.
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 () { //... } };
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.
Break lines right after operators or comma Indent the line after a line break with one extra tab
Control structures like if, while or for can take either a single statement or a block of statements inside curly braces. 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.
Use only A-Za-z0-9_ for identifiers Don't start identifiers with _ Don't use $ or \ Start constructors with upper case Global variables in ALL_CAPS Start all other identifiers with lower case
Between any keyword and a (, like for ( ... ) No spaces between a function name and the parenthesis: execute(abc) Put a space after a comma Each ; in the control part of a for statement should be followed with a space.
Syntax
if (condition) { statements; } else if (condition) { statements; } else { statements; }
}; this.colorToArray = function (color) { return [ this.parseColorByte(color, 0), this.parseColorByte(color, 1), this.parseColorByte(color, 2) ]; } this.parseColorByte = function (color, whichByte) { return parseInt(color.substr(1 + whichByte*2, 2), 16); }; this.arrayToColor = function (arr) { return '#' + this.intToColorByte(arr[0]) + this.intToColorByte(arr[1]) + this.intToColorByte(arr[2]); }; 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.
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.
<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.)
or textarea) it will additionally receive copies of the utility methods from Form.Element.Methods.
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.
The $$() function will help you a lot if you consistently separate CSS from the content wireframe. It parses one or more CSS filtering expressions, analogous to the ones used to define CSS rules, and returns the elements that match these filters. It's so easy to use it's ridiculous. Check this out.
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.
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}); //here we create our template for formatting each item var itemFormat = new Template( 'You are ordering #{quantity} units ' + 'of #{product} at $#{price} each' ); var formatted = ''; for (var i=0; i<cart.items.length; i++) { var cartItem = cart.items[i];
formatted += itemFormat.evaluate(cartItem) + '<br/>\n'; } 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.
} 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();" /> <div style="border:solid 1px black;"> <ol id="list1"> <li>Existing item #1</li> <li>Existing item #2</li> </ol> </div> <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();" /> <img id="img5" src="wc.png" alt="webucator"/> ---- Code Omitted ----
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; var myAjax = new Ajax.Request( url, { method: 'get', parameters: pars, onComplete: showResponse }); } 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.
//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. 1 - Create a method WEBUCATOR.doSearch(text) 2 - Call that method from the Go button's click 3 - Use the textbox's value as the search term (the argument to the method) 4 - Make the doSearch method perform an AJAX call to ../Common/search.php passing the search term as the "q" posted parameter. 5 - Print the response returned form that method (you can use debugWrite or alert) */ WEBUCATOR.onload = function () { }; Event.observe(window, 'load', WEBUCATOR.onload ); </script> <form action="#"> <input type="text" name="searchTerm" value="" id="searchTerm"/> <input type="button" name="goButton" value="Go!" id="goButton"/> </form> </body> </html>
if ($pos1 !== false) { $title = $item->getElementsByTagName( "title" )->item(0)->nodeValue; $link = $item->getElementsByTagName( "link" )->item(0)->nodeValue; echo "<li><strong><a href=\"$link\">$title</a></strong><br/>$content</li>"; } } ?>
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'; var myAjax = new Ajax.Updater( {success: 'placeholder'}, url, { method: 'get', parameters: pars, onFailure: reportError }); } function reportError(response) { alert('Sorry. There was an error.'); } </script> <input type="button" value="GetHtml" onclick="getHTML();"/> <div id="placeholder"></div>
#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. 1 - Create a method WEBUCATOR.doSearch(text) 2 - Call that method from the Go button's click 3 - Use the textbox's value as the search term (the argument to the method) 4 - Using the Ajax.pdater class, make the doSearch method perform an AJAX call to ../Common/search.php passing the search term as the "q" querystring parameter. 5 - The returned HTML should automatically uplade the resultList element */ WEBUCATOR.onload = function () { }; Event.observe(window, 'load', WEBUCATOR.onload ); </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.
<script language="javascript" type="text/javascript"> sayHi = function () { alert('Hi'); }; </script> <input type="button" value="Click Me" onclick="sayHi();"/>
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 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.
<globalization requestEncoding="utf-8" responseEncoding="utf-8" />
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.
<?php header('Content-Type: text/html; charset=utf-8'); ?>
In any case, your ultimate goal is to have the following HTTP header sent with your response.
Content-Type: text/html; charset=utf-8
We used UTF-8 in our examples above, but if you need a different setting you can easily change.
We are all familiar with for loops. You know, create yourself an array, populate it with elements of the same kind, create a loop control structure (for, foreach, while, repeat, etc,) access each element sequentially, by its numeric index, and do something with the element. 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.
Like we mentioned above, it's very common for all the elements in your array to be of the same kind, with the same properties and methods. Let's see how we can take advantage of iterator functions with our new souped-up arrays. Here's how to find an element according to criteria.
<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> <select id="lstEmployees" size="10" > <option value="5">Buchanan, Steven</option> <option value="8">Callahan, Laura</option> <option value="1">Davolio, Nancy</option> </select> <input type="button" value="Find Laura" onclick="findEmployeeById(8);" />
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.
Event.observe(window, 'load', function (evt) { alert('loaded'); } ); //to unsubscribe from the event later, the code is: Event.stopObserving(window, 'load');
To make it even easier, the event subscription mechanism is added to the DOM elements, simplifying the syntax even further.
$('myElement').observe('click', function (evt) { alert('myElement was clicked'); } );
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(); }); $('userNameField').observe('keypress', function (evt) { submitOnEnterKey(); }); });
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 Returns an instance of the function object: the pre-bound to the bind(object [, arg1 [, arg2 object that function(=method) owner object. instance [...]]]) owns the The returned function will use the method same arguments as the original one (arg1, arg2, ... etc). Returns an instance of the function pre-bound to the object: the function(=method) owner object. bindAsEventListener(object object that The returned function will have the instance [, arg1 [, arg2 [...]]]) owns the current event object as its first method argument followed optionally any other arguments passed after the object argument. Let's see one of these extensions in action.
<input type="checkbox" id="myChk" value="1"/> Test? <script type="text/javascript"> //declaring the class var CheckboxWatcher = Class.create(); //defining the rest of the class implementation CheckboxWatcher.prototype = { initialize: function (chkBox, message) { this.chkBox = $(chkBox); this.message = message; //assigning our method to the event this.chkBox.onclick = this.showMessage.bindAsEventListener(this, ' from checkbox'); },
showMessage: function (evt, extraInfo) { alert(this.message + ' (' + evt.type + ')' + extraInfo); } }; var watcher = new CheckboxWatcher('myChk', 'Changed'); </script>
Method camelize()
gsub(pattern, replacement)
Extensions for the String objects Kind Arguments Description Converts a hyphen-delimited-string into a camelCaseString. This function instance (none) is useful when writing code that deals with style properties, for example. Converts the first character to upper instance (none) case. Replaces underscores '_' with dashes instance (none) '-'. Returns the string with any HTML instance (none) markup characters properly escaped Evaluates each <script /> block found instance (none) in the string. Returns an Array object containing instance (none) all the <script /> blocks found in the string. Returns a string that results from finding (or matching) the pattern string (or regular expression) in the current string and replacing it with the replacement string or the result of pattern: string or calling the replacement function regular expression passing an array with the strings that being searched. matched the pattern, including replacement: simple instance eventual regular expression string, template string, groupings. When the replacement is a or Function(strings[]) string, it can contain special to produce the templating tokens like #{n}, where n replacements. is the index of a grouping in the regular expession. #{0} will be replaced by the entire match, #{1} the first grouping, #{2} the second, and so on. instance (none) Same as toQueryParams(). pattern: string or Provides a way to iterate over instance regular expression matched patterns in the string and
Extensions for the String objects Method Kind Arguments Description being searched. operate on them. The pattern replacement: argument can be a string or a RegExp Function(strings[]) to but a RegExp is evidently more iterate over the useful. Similarly, the replacement matches. argument can be a string or a function but it probably only makes sense to pass in a function to be able to produce anything useful. Returns the string without any strip() instance (none) leading or trailing white spaces. Returns the string with any <script /> stripScripts() instance (none) blocks removed Returns the string with any HTML or stripTags() instance (none) XML tags removed pattern: string or regular expression being searched. replacement: string, or Very similar to gsub but only sub(pattern, Function(strings[]) to performs a limited number of replacement [, instance produce the replacements, specified by the count count]) replacements. count: parameter. number or replacements to perform - defaults to 1. Splits the string into an Array of its toArray() instance (none) characters. Splits a querystring into an toQueryParams() instance (none) associative Array indexed by parameter name (more like a hash). Used to produce a string of a known length: maximum maximum length. In case the string length of the resulting needs to be truncated to maintain the string. truncation: truncate(length [, maximum length, the text given in instance string used to replace truncation]) the truncation argument is used to the last characters of replace the last few characters. (e.g.: the resulting string var s='123456790'; defaults to '...' alert(s.truncate(5)); //displays '12...' ) Converts a CamelizedStringValue into a uderscore_formatted_string. underscore() instance (none) (e.g.: var s='Namespace::MyClass123'; alert(s.underscore()); //displays
Extensions for the String objects Method Kind Arguments Description 'namespace/my_class123' ). This function seems to be directly target at supporting Ruby on Rails functionality. unescapeHTML() instance (none) The reverse of escapeHTML()
first()
flatten()
indexOf(value)
inspect() last()
reverse([applyToSelf])
Extensions for the Array objects Method Kind Arguments Description Returns the first element and shift() instance (none) removes it from the array, reducing the array's length by 1. value1 ... valueN: Returns the array excluding the without(value1 [, value2 values to be elements that are included in the instance [, .. valueN]]) excluded if present list of arguments. This method does in the array. not change the array itself. Let's see some of these methods in action.
<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 getElementsByClassName(className instance className: name of Returns all the
Extensions for the document DOM object Method Kind Arguments [, parentElement]) a CSS class associated with the elements, parentElement: object or id of the element that contains the elements being retrieved.
Description elements that are associated with the given CSS class name. If no parentElement id given, the entire document body will 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 observers: Array implementation details of the object. Method element(event) Kind Arguments event: an Event static object Description Returns element that originated the event. Traverses the DOM tree upwards, searching for the first element with the given tag name, starting from the element that originated the event. Returns true if the left mouse button was clicked. Returns true if the middle mouse button was clicked. Returns true if the right mouse button was clicked. Returns the x coordinate of the mouse pointer on the
event: an Event object, tagName: findElement(event, tagName) static name of the desired tag. isLeftClick(event) isMiddleClick(event) isRightClick(event) pointerX(event) event: an Event object event: an Event static object event: an Event static object event: an Event static object static
Method
Kind
Description page. Returns the y coordinate of the mouse pointer on the page. Use this function to abort the default behavior of an event and to suspend its propagation.
pointerY(event)
static
stop(event)
static
element: object or id, name: event name observe(element, name, (like 'click', 'load', Adds an event handler static observer) etc), observer: function to an event. Function(evt) to handle the event. Removes an event handler element: object or id, from the event. If the name: event name observer isn't passed, it will stopObserving(element [, name (like 'click'), unregister all observers for static [, observer]]) observer: function the given event name. If the that is handling the event name isn't passed it event. will unregister all observers for the element. _observeAndCache(element, Private method, do not static name, observer, useCapture) worry about it. Private method, do not worry about it. Clears all unloadCache() static (none) 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> ...
onTimerEvent()
instance
(none)
Description The function to be called. objExecuter: the callback Function(objExecuter) PeriodcalExecuter making the call. A handle to the underlying timer object timer Timer responsible for repeatedly invoking the callback method frequency Number This is actually the interval in seconds currentlyExecuting Boolean Indicates if the callback is underway.
Property
Type
The Prototype object does not have any important role, other than declaring the version of the library being used. Property Type Version String emptyFunction Function() Description The version of the library An empty function object A function object that just echoes back the given K Function(obj) parameter. ScriptFragment String A regular expression to identify scripts
each(iterator)
all([iterator])
any([iterator])
Description element itself resolves to true. You can simply read it as "check if any element passes the test." Calls the iterator function for each element in the collection iterator: a function and returns each result in an collect(iterator) instance object conforming to Array, one result element for Function(value, index) each element in the collection, in the same sequence. Calls the iterator function for each element in the collection iterator: a function and returns the first element that detect(iterator) instance object conforming to caused the iterator function to Function(value, index) return true (or, more precisely, not-false.) If no element returns true, then detect returns null. entries() instance (none) Same as toArray(). iterator: a function find(iterator) instance object conforming to Same as detect(). Function(value, index) Calls the iterator function for each element in the collection iterator: a function and returns an Array with all the findAll(iterator) instance object conforming to elements that caused the iterator Function(value, index) function to return a value that resolves to true. This function is the opposite of reject(). Tests the string value of each element in the collection against pattern: a RegExp the pattern regular expression . object used to match The function will return an Array grep(pattern [, the elements, iterator: a containing all the elements that instance iterator]) function object matched the regular expression. conforming to If the iterator function is given, Function(value, index) then the Array will contain the result of calling the iterator with each element that was a match. Tries to find the given object in include(obj) instance obj: any object the collection. Returns true if the object is found, false otherwise. number: number of Returns the collection broken in inGroupsOf(number, instance items per group, groups containing as many items fillWith) fillWith: value used to as specified by the first
Method
Kind
Arguments
Method
Kind
inject(initialValue, iterator)
initialValue: any object to be used as the initial value, iterator: a instance function object conforming to Function(accumulator, value, index)
methodName: name of the method that will be invoke(methodName called in each element, instance [, arg1 [, arg2 [...]]]) arg1..argN: arguments that will be passed in the method invocation. iterator: a function map(iterator) instance object conforming to Same as collect(). Function(value, index) Returns the element with the iterator: a function greatest value in the collection or max([iterator]) instance object conforming to the greatest result of calling the Function(value, index) iterator for each element in the collection, if an iterator is given. member(obj) instance obj: any object Same as include(). Returns the element with the iterator: a function lowest value in the collection or min([iterator]) instance object conforming to the lowest result of calling the Function(value, index) iterator for each element in the collection, if an iterator is given. iterator: a function Returns an Array containing two partition([iterator]) instance object conforming to other arrays. The first array will Function(value, index) contain all the elements that
Description argument. If the quantity of items in the initial collection is not divisible by the number in the first argument, the resulting empty items at the end of the last group will be filled with null or with the value of the second argument, if provided. Quick example: ['a','b','c','d'].inGroupsOf(3,'?') creates [ ['a','b','c'] , ['d','?','?'] ] Combines all the elements of the collection using the iterator function. The iterator is called passing the result of the previous iteration in the accumulator argument. The first iteration gets initialValue in the accumulator argument. The last result is the final return value. Calls the method specified by methodName in each element of the collection, passing any given arguments (arg1 to argN), and returns the results in an Array object.
Method
Kind
Arguments
Description caused the iterator function to return true and the second array will contain the remaining elements. If the iterator is not given, then the first array will contain the elements that resolve to true and the other array will contain the remaining elements.
propertyName name of Retrieves the value to the the property that will be property specified by read from each pluck(propertyName) instance propertyName in each element of element. This can also the collection and returns the contain the index of the results in an Array object. element Calls the iterator function for each element in the collection iterator: a function and returns an Array with all the reject(iterator) instance object conforming to elements that caused the iterator Function(value, index) function to return a value that resolves to false. This function is the opposite of findAll(). iterator: a function select(iterator) instance object conforming to Same as findAll(). Function(value, index) iterator: a function Returns an Array with all the sortBy(iterator) instance object conforming to elements sorted according to the Function(value, index) result the iterator function call. Returns an Array with all the toArray() instance (none) elements of the collection. Merges each given collection with the current collection. The merge operation returns a new array with the same number of collection1 .. elements as the current collection collectionN: and each element is an array zip(collection1[, enumerations that will (let's call them sub-arrays) of the collection2 [, ... instance be merged, transform: a elements with the same index collectionN function object from each of the merged [,transform]]]) conforming to collections. If the transform Function(value, index) function is given, then each subarray will be transformed by this function before being returned. Quick example: [1,2,3].zip([4,5,6],
Method
Kind
Arguments
inspect()
instance (none)
Method
Kind
Arguments Description that we are looking for part of the range. Returns true or false.
Method Kind Arguments Description create(*) instance (any) Defines a constructor for a new class
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. Property Type Kind Description responders Array instance The list of objects registered for AJAX events notifications. Description The object passed in the responderToAdd argument should contain methods named like the AJAX events (e.g. onCreate, onComplete, responderToAdd: onException, etc.) register(responderToAdd) instance object with methods When the that will be called. corresponding event occurs all the registered objects that contain a method with the appropriate name will have that method called. The object passed in the responderToRemove: responderToRemove unregister(responderToRemove) instance object to be removed argument will be from the list. removed from the list of registered objects. Runs through the list of registered objects callback: name of the looking for the ones AJAX event being that have the method reported, request: the determined in the Ajax.Request object callback argument. responsible for the Then each of these dispatch(callback, request, event, transport: the methods is called instance transport, json) XMLHttpRequest passing the other 3 object that carried (or arguments. If the is carrying) the AJAX AJAX response call, json: the X-JSON contains a X-JSON header of the response HTTP header with (if present) some JSON content, then it will be evaluated and passed Method Kind Arguments
Method
Kind
Arguments
Description in the json argument. If the event is onException, the transport argument will have the exception instead and json will not be passed.
[ctor](url, options)
Method
Kind
evalJSON()
instance
evalResponse()
instance
header(name)
instance
onStateChange()
instance
request(url)
instance
respondToReadyState(readyState) instance
setRequestHeaders()
instance
Description browser's security settings. In many cases the browser will not fetch the url if it is not from the same host (domain) as the current page. You should ideally use only local urls to avoid having to configure or restrict the user's browser. (Thanks Clay). This method is typically not called externally. It is called internally to evaluate the (none) content of an eventual XJSON HTTP header present in the AJAX response. This method is typically not called externally. If the AJAX response has a Content-type header of (none) text/javascript then the response body will be evaluated and this method will be used. Retrieves the contents of any HTTP header of the name: HTTP AJAX response. Call this header name only after the AJAX call is completed. This method is typically not called externally. It is called (none) by the object itself when the AJAX call status changes. This method is typically not url: url for called externally. It is the AJAX already called during the call constructor call. This method is typically not readyState: called externally. It is called state number by the object itself when the (1 to 4) AJAX call status changes. This method is typically not (none) called externally. It is called
Arguments
Method
Kind
Arguments
Description by the object itself to assemble the HTTP header that will be sent during the HTTP request.
contentType
String
encoding
String
encoding
Boolean
encoding
Boolean
method
String
'application/ x-wwwSets the Content-Type HTTP formheader of the Ajax request. urlencoded' The character encoding used in the body of a request (especially POST requests.) 'UTF-8' UTF-8 should be enough in most cases, but if you know what you're doing, you can use a different encoding. Apply eval() to the responseText if the request comes from the same origin true as the page. Use false to never eval or "force" to always eval regardless of origin. If the response is of content type application/json, applies eval() to the responseText and put the result in responseJSON. Only happens true if the request comes from the same origin as the page. Use false to never eval or "force" to always eval JSON regardless of origin. 'post' Method of the HTTP request
Property
Type
Default
parameters
String or Object
''
postBody
String
undefined
requestHeaders Array
undefined
onXXXXXXXX
Description The url-formatted list of values passed to the request (for example 'employee=john&month=11') or a hash-like object that represents the parameters (for example {employee:'john', month:11}.) Content passed to in the request's body in case of a HTTP POST List of HTTP headers to be passed with the request. This list must have an even number of items, any odd item is the name of a custom header, and the following even item is the string value of that header. Example:['my-header1', 'this is the value', 'my-otherheader', 'another value'] Custom function to be called when the respective event/status is reached during the AJAX call. There are several alternatives for the "XXXXXXXX" in this option, among the alternatives are the statuses in Ajax.Request.Events, and the HTTP status codes. Example var myOpts = {on403: notAllowed, onComplete: showResponse, onLoaded: registerLoaded};. The function used will receive one argument, containing the XMLHttpRequest object that is carrying the AJAX operation and another argument containing the evaluated X-JSON response HTTP header.
Property
Type
Default
onSuccess
onFailure
onException
Function(Ajax.Request, exception)
undefined
insertion
String
undefined
Description Custom function to be called when the AJAX call completes successfully. The function used will receive one argument, containing the XMLHttpRequest object that is carrying the AJAX operation and another argument containing the evaluated X-JSON response HTTP header. Custom function to be called when the AJAX call completes with error. The function used will receive one argument, containing the XMLHttpRequest object that is carrying the AJAX operation and another argument containing the evaluated X-JSON response HTTP header. Custom function to be called when an exceptional condition happens on the client side of the AJAX call, like an invalid response or invalid arguments. The function used will receive two arguments, containing the Ajax.Request object that wraps the AJAX operation and the exception object. A string that will determine how the new content will be inserted. Applies only to Ajax.Updater objects and if nothing is specified, the new content will completely replace the existing content. If a value s given, it will determine where the content will be inserted in relation to the container element. It can
Property
Type
evalScripts
Boolean
decay
Number
frequency
Number
Description be 'before', 'top', 'bottom', or 'after'. Determines if script blocks undefined, will be evaluated when the false response arrives. Applies only to Ajax.Updater objects. Determines the progressive slowdown in a Ajax.PeriodicalUpdater object refresh rate when the received response is the same as the last one. For example, if you use 2, after one of the undefined, refreshes produces the same 1 result as the previous one, the object will wait twice as much time for the next refresh. If it repeats again, the object will wait four times as much, and so on. Leave it undefined or use 1 to avoid the slowdown. Interval (not frequency) between refreshes, in undefined, seconds. Applies only to 2 Ajax.PeriodicalUpdater objects.
Default
Kind
updateContent() instance
Arguments Description an element, the element object that will call the given object itself, or an object with url using the given options. two properties object.success element (or id) that will be used when the AJAX call succeeds, and object.failure element (or id) that will be used otherwise. url: the url to be fetched, options: AJAX options This method is typically not called externally. It is called by the object itself when the response is received. It will update the appropriate element with the HTML or (none) call the function passed in the insertion option. The function will be called with two arguments, the element to be updated and the response text.
Property timer
Type Object
Description The handle to the timer being used to notify the object instance when it is time for the next refresh. Kind Description
Kind
Method
Arguments container:this can be the id of an element, the element object itself, or an object with two properties object.success element (or id) constructor that will be used when the AJAX call succeeds, and object.failure element (or id) that will be used otherwise. url: the url to be fetched, options: AJAX options
Creates one instance of this object that will call the given url using the given options.
start()
instance
(none)
stop()
instance
(none)
updateComplete() instance
(none)
onTimerEvent()
instance
(none)
This method is typically not called externally. It is called by the object itself to start performing its periodical tasks. Causes the object to stop performing its periodical tasks. After stopping, the object will call the callback given in the onComplete option (if any.) This method is typically not called externally. It is called by the currently used Ajax.Updater after it completes the request. It is used to schedule the next refresh. This method is typically not called externally. It is called internally when it is time for the next update.
This object provides some utility functions for manipulating elements in the DOM.
Method
Kind
cleanWhitespace(element)
instance
element: element object or id, source: element clonePosition(element, source [, to be cloned instance options]) options: object with settings for fine-tuning the clone operation.
cumulativeOffset(element)
Description given element. Removes any white space text node children of the element Copies the size and position of the source element into the current element. The options argument can be used to adjust this process. The available options are: setLeft, , setTop, setWidth, setHeight, which are booleans (true/false,) and offsetTop, offsetLeft, which are number of pixels. Returns an Array Error. This text should not be shown. Please email [email protected] to report it: Array with the total offsets of the element, including any of the element's ancestors offsets. The resulting array is similar to [total_left_offset, total_top_offset] Error. This text should not be shown. Please email [email protected] to report it: [total_left_offset, total_top_offset] Returns an Array Error. This text should not be shown. Please email [email protected] to report it: Array with the total offsets of the element, including any of the element's ancestors offsets. se this when the element is inside parent elements that scroll. The resulting array is similar to [total_left_offset, total_top_offset] Error. This
Method
Kind
Arguments
Description text should not be shown. Please email [email protected] to report it: [total_left_offset, total_top_offset] Returns a Boolean value indicating if the element is a descendant (child, grandchild, etc) of the given ancestor. Returns an Array with all the descendants (child nodes children, grandchildren, etc) of the element. Checks which of the element's descendants match the given rule and returns the first one or the Nth one given by the zero-based index argument. Lower index values mean closer to the element. Returns a Boolean value indicating if the element tag is empty (or has only whitespaces) Returns the dimensions of the element. The returned value is an object with two properties: height and width. Returns the offsetHeight of the element
element: element object or id, ancestor: descendantOf(element, ancestor) instance ancestor candidate element or id descendants(element) instance element: element object or id
element: element object or id, expression: a CSS selection instance rule, index: index of the returned descendant instance element: element object or id
empty(element)
getDimensions(element)
instance
getHeight(element)
getStyle(element, cssProperty)
getWidth(element)
element: element object or id element: element object or id, cssProperty name of a CSS instance property (either format 'propname' or 'propName' works). instance element: element instance
Returns the value of the CSS property in the given element or null if not present.
Method
hasClassName(element, className)
hide(element) identify(element)
Arguments object or id element: element object or id, instance className: name of a CSS class element: element instance object or id instance element: element object or id
Kind
Description the element Returns true if the element has the given class name as one of its class names. Hides the element by setting its style.display to 'none'. Returns the element's id. If one does not exist, a new, unique one will be created. Returns a simplified string representation of the element's tag. It's useful for debugging and only contains the element's id and css class name, like <p id="result" class="hilite numeric"> Inserts new content before, after, at the top or at the bottom if the element. For example $(el).insert({top: '<li>new item</li>'}). It defaults to insertion at the bottom if the second argument is the content itself.
inspect(element)
instance
insert(element, insertion)
element: element object or id element: element Changes the element's makePositioned(element) instance object or id style.position to 'relative' Verifies if the element is element: element matched by the given CSS object or id, selector rule. For example, a selector: string paragraph element with an id match(element, selector) instance or Selector of 'message' would be object of a CSS matched by the selector rule 'p#message'. This method returns true or false. element: element Checks which of the object or id, element's posterior sibilings next(element, expression, index) instance expression: a match the given rule and CSS selection returns the first one or the rule, index: Nth one given by the zeromakeClipping(element) instance
Method
nextSiblings(element)
previousSiblings(element)
remove(element)
removeClassName(element, className)
Arguments Description index of the based index argument. Lower returned sibilng index values mean closer to the element. Returns an Array with all the siblings that come after the element: element element. The list is built by instance object or id recursively finding all the other elements via the nextSibling property. element: object or id, name: event name (like 'click', 'load', etc), observer: Function(evt) to handle the event, instance Shortcut for Event.observe. useCapture: if true, handles the event in the capture phase and if false in the bubbling phase. Checks which of the element: element element's previous sibilings object or id, match the given rule and expression: a returns the first one or the instance CSS selection Nth one given by the zerorule, index: based index argument. Lower index of the index values mean closer to returned sibilng the element. Returns an Array with all the siblings that come before the element: element element. The list is built by instance object or id recursively finding all the other elements via the previousSibling property. element: element Removes the element from instance object or id the document. element: element object or id, Removes the given class instance className: name from the element's name of a CSS class names. class
Kind
Method
replace(element, html)
scrollTo(element)
setStyle(element, cssPropertyHash)
Description Replaces the entire html of the element with the given element: element html argument. If the given object or id, html contains <script instance html: html type="text/javascript"> content blocks they will not be included but they will be evaluated. element: element Scrolls the window to the instance object or id element position. element: element Sets the value of the CSS object or id, properties in the given cssPropertyHash instance element, according to the Hash object with values in the the styles to be cssPropertyHash argument. applied. element: element Finds all the child elements object or id. instance that match one of the given expressionN: CSS rules. CSS selectors element: element Shows the element by instance object or id resetting its style.display to ''. Returns an Array with all the element: element instance siblings that come before and object or id after the element. element: object or id, name: event name (like 'click', 'load', etc), observer: Function(evt) to handle the event, Shortcut for instance useCapture: if Event.stopObserving. true, handles the event in the capture phase and if false in the bubbling phase. element: element Toggles the visibility of the instance object or id element. element: element Toggles the presence of the instance object or id, given CSS class name in the
Kind
Arguments
Method
undoClipping(element) undoPositioned(element)
Arguments className: CSS class element: element instance object or id element: element instance object or id element: element object or id, expression: a CSS selection instance rule, index: index of the returned ancestor
Kind
Description element.
update(element, html)
visible(element)
Clears the element's style.position to '' Checks which of the element's ancestors match the given rule and returns the first one or the Nth one given by the zero-based index argument. Lower index values mean closer to the element. Replaces the inner html of the element with the given element: element html argument. If the given object or id, html contains <script instance html: html type="text/javascript"> content blocks they will not be included but they will be evaluated. Returns a Boolean value element: element instance indicating if the element is object or id visible.
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.
//first a helper method var $CE = function (tagName, attributes, styles) { //short for create element var el = document.createElement(tagName); if (attributes) $H(attributes).each( function (pair) { eval("el." + pair.key + "='" + pair.value + "'"); }); if (styles) $H(styles).each( function (pair) { el.style[pair.key] = pair.value; }); return $(el); };
//adding he new methods Element.addMethods({ //removes any child noes from the element //example: <div id="myDiv"><b>hello</b></div> // $('myDiv').clearChildren(); // ==> <div id="myDiv"></div> clearChildren: function (element) { element = $(element); $A(element.childNodes).each( function (e) { e.parentNode.removeChild(e); }); return element; }, //method that creates a new element and appends to the current element // example: <div id="myDiv">Please</div> // $('myDiv').append('A',{href:'otherpage.html', className:'red'}).update('Continue...'); // ==> <div id="myDiv">Please<a href="otherpage.html" class="red">Continue...</a></div> append: function (element, tagName, attributes, styles) { element = $(element); var newEl = $CE(tagName, attributes, styles); element.appendChild(newEl); return newEl;//<-- this one returns the new element }, //appends a text node to the element // example: <div id="myDiv"><b>hello</b></div> // $('myDiv').appendText(', John'); // ==> <div id="myDiv"><b>hello</b>, John</div> appendText: function (element, text){ element = $(element); var t = document.createTextNode(text); element.appendChild(t); return element; } });
add(className)
remove(className) instance
Method
Kind
set(className)
instance
Arguments Description CSS class name from the list of class names associated with the element. Associates the element with the given className: a CSS class name, removing any other CSS class name class names from the element.
findFirstElement(form)
getElements(form)
disable(form)
Arguments element object or id form: form enable(form) instance element object or id form: form focusFirstElement(form) instance element object or id form: form reset(form) instance element object or id
Method
Kind
Description form. Enables all the input fields in the form. Activates the first visible, enabled input field in the form. Resets the form. The same as calling the reset() method of the form object.
Arguments element: element disable(element) instance object or id element: element enable(element) instance object or id element: element getValue(element) instance object or id element: element present(element) instance object or id element: element serialize(element) instance object or id
Method
Kind
Description Disables the field or button, so it cannot be changed or clicked. Enables the field of button for being changed or clicked. Returns the value of the element. Returns true if the field is not empty. Returns the element's name=value pair, like 'elementName=elementValue'
Arguments Description element: element object or id, frequency: interval in [ctor](element, Creates an object that will constructor seconds, callback: function frequency, callback) monitor the element. to be called when the element changes Derived classes have to implement this method to instance, getValue() (none) determine what is the abstract current value being monitored in the element. This method is typically not called externally. It is registerCallback() instance (none) called by the object itself to start monitoring the element. This method is typically not called externally. It is onTimerEvent() instance (none) called by the object itself periodically to check the element. Property Type element Object frequency Number Function(Object, callback String) lastValue String Description The element object that is being monitored. This is actually the interval in seconds between checks. The function to be called whenever the element changes. It will receive the element object and the new value. The last value verified in the element.
Method
Kind
Method getValue()
Kind instance
getValue()
getValue()
Method
Kind
Arguments
registerCallback()
instance
(none)
registerFormCallbacks() instance
(none)
onElementEvent()
instance
(none)
Description determine what is the current value being monitored in the element. This method is typically not called externally. It is called by the object to bind itself to the element's event. This method is typically not called externally. It is called by the object to bind itself to the events of each data entry element in the form. This method is typically not called externally. It will be bound to the element's event.
Description The element object that is being monitored. The function to be called whenever the element changes. It will receive the element object and the new value. The last value verified in the element.
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. Arguments form: form object or id, callback: function to be [ctor](form, constructor called when any data entry callback) element in the form changes getValue() instance (none) Method Kind Description Inherited from Abstract.EventObserver. Creates an object that will monitor the form for changes. Returns the serialization of all form's data.
script.aculo.us
In this lesson of the JavaScript tutorial, you will learn... 1. 2. 3. 4. 5. 6. 7. What is, who wrote, who uses, who maintains Where to get and how to make part of your site The visual effects The in-place editors How to create drag and drop elements The auto-completing input fields Where to get more information
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. The most current source code for script.aculo.us can be found at http://github.com/madrobby/scriptaculous/tree/master.
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. script.aculo.us Core Effects Effect Description Options This effect changes the opacity/transparency of the element, from: initial opacity (0.0 to 1.0), Effect.Opacity making it more or less translucid to: final opacity. progressively. By default is changes the opacity from 0.0 to 1.0. Moves the element to another point in x: X-coordinate movement in Effect.Move the page. The movement can be pixels, y: Y-coordinate specified in relative or absolute X and movement in pixels, mode:
script.aculo.us Core Effects Effect Description Options Y coordinates. coordinates mode, can be 'absolute' or 'relative' scaleX: resize on the X-axis, scaleY: resize on the Y-axis, Moves the element to another point in scaleContent: resizes the the page. The movement can be contents of the element as well, specified in relative or absolute X and Effect.Scale scaleFromCenter: resizes the Y coordinates. Sample usage: new element from its center instead Effect.Scale('elem', 150, of from the top-left corner, {scaleFromCenter: true}); scaleFrom: the starting point of the effect (a percentage value.) startcolor: background color to Remember when we implemented the start the effect, defaults to Yellow Fade Technique (YFT) a few #ffff99, endcolor: color that lessons ago? Well, scratch that and effect ends at, defaults to white, Effect.Highlight let's use this effect instead. It does the restorecolor: color that is used same thing but better and more after the effect finishes, defaults extensibly. to the element's background color. You can get a lot done with this effect alone. Simply put, it will change the element from its current appearance to what is defined by some form of CSS specification. Sample usage: new Effect.Morph Effect.Morph('elem', 'processedOrder', {duration: 1.5}); or new Effect.Morph('elem', {style: {backgroundColor: '#7f7', height: '120px'} }, {duration: 1.5});
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] );
Effect Effect.ScrollTo
script.aculo.us Combined Effects Description Scrolls the page until the element is reached.
Effect
Shakes the element from left to right a few times to call attention. Decreases and increases the opacity few times Effect.Pulsate to call 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, Effect.Fade then the element is hidden. The element's height is decreased until it Effect.BlindUp disappears. The element is hidden on completion. The element's height is increased from zero Effect.BlindDown until the element's original height. The element's height is decreased until it disappears. The contents need to be inside an Effect.SlideUp extra div inside the element 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 Effect.SlideDown to be inside an extra div inside the element and it will move down instead of expanding. The element grows and fades. The element is Effect.Puff hidden on completion. The element shrinks towards the upper-left Effect.Squish corner. The element is hidden on completion. The element flickers quickly then shrinks like Effect.SwitchOff and old CRT TV set picture. The element is hidden on completion. The narrows vertically then horizontally until it Effect.Fold disappears. The element is hidden on completion. Effect.Shake
Effects pout-pourri
The following demo showcases the available effects and the code used to create them.
}, doMainDemoAction: function () { this[this.currentDemo](); }, //cleanup code .... 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 id="demoOpacity" class="demo"> <div class="demo-content"> <h3>Effect.Opacity</h3> <input type="button" value="Change opacity?" onclick="WEBUCATOR.doMainDemoAction();"/> <div style="position:relative; height:200px;"> <div style="background-color:#7D85DA; position:absolute; left:5px; top:5px; width:100px; height:100px"> </div> <div id="topBox" style="background-color:#77E66C; position:absolute; left:25px; top:25px; width:100px; height:100px"> </div> </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 id="demoMove" class="demo"> <div class="demo-content"> <h3>Effect.Move</h3> <input type="button" value="Move it" onclick="WEBUCATOR.doMainDemoAction();"/> <div style="position:relative; height:200px; border:solid 1px #000;"> <div id="mover" style="background-color:#77E66C; position:absolute; left:25px; top:25px; width:100px; height:100px">Scriptaculous</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> <div id="demoScroll" class="demo"> <div class="demo-content"> <h3>Effect.ScrollTo</h3> <input type="button" value="Go to the element" onclick="WEBUCATOR.doMainDemoAction();"/> <div style="height:1200px; border:solid 1px #000;"> </div> <div id="bottomBox" style="background-color:#77E66C; width:180px; height:40px; text-align:center; "> Scriptaculous (the element) </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 id="demoScale" class="demo"> <div class="demo-content"> <h3>Effect.Scale</h3> <input type="button" value="Resize it" onclick="WEBUCATOR.doMainDemoAction();"/> <div style="position:relative; height:200px; border:solid 1px #000;"> <div id="smallBox" style="background-color:#77E66C; position:absolute; left:100px; top:40px; width:80px; height:40px; textalign:center; font-size:11px;">Scriptaculous</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> <div id="demoMorph" class="demo"> <div class="demo-content"> <h3>Effect.Morph</h3> <input type="button" value="Morph it" onclick="WEBUCATOR.doMainDemoAction();"/>
<div style="position:relative; height:200px; border:solid 1px #000;"> <div id="willMorph" style="background-color:#77E66C; position:absolute; left:100px; top:40px; width:100px; height:40px; text-align:center;">Scriptaculous</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> <div id="demoHighlight" class="demo"> <div class="demo-content"> <h3>Effect.Highlight</h3> <input type="button" value="What time is it?" onclick="WEBUCATOR.doMainDemoAction();"/> <strong>The time is: </strong> <span id="theTime" ></span> <br/><br/> <strong>Code:</strong> <pre><code class="demoCode">var time = new Date(); $('theTime').update(time.toString()); new Effect.Highlight('theTime', { duration: 2, endcolor:'#FFB855' } );</code></pre> <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 id="demoDropOut" class="demo"> <div class="demo-content"> <h3>Effect.DropOut</h3> <input type="button" value="Wave goodbye to it" onclick="WEBUCATOR.doMainDemoAction();"/> <div style="position:relative; height:200px; border:solid 1px #000;"> <div id="droppingBox" style="background-color:#77E66C; position:absolute; left:100px; top:40px; width:80px; height:40px; textalign:center; font-size:11px;">Scriptaculous</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 id="demoShake" class="demo"> <div class="demo-content"> <h3>Effect.Shake</h3> <input type="button" value="Hey, look at me" onclick="WEBUCATOR.doMainDemoAction();"/> <div style="position:relative; height:200px; border:solid 1px #000;"> <div id="willShake" style="background-color:#77E66C; position:absolute; left:100px; top:40px; width:80px; height:40px; textalign:center; font-size:11px;">Scriptaculous</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 id="demoPulsate" class="demo"> <div class="demo-content"> <h3>Effect.Pulsate</h3> <input type="button" value="Hey, I'm here" onclick="WEBUCATOR.doMainDemoAction();"/>
<div style="position:relative; height:200px; border:solid 1px #000;"> <div id="willPulsate" style="background-color:#77E66C; position:absolute; left:100px; top:40px; width:80px; height:40px; textalign:center; font-size:11px;">Scriptaculous</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 id="demoAppear" class="demo"> <div class="demo-content"> <h3>Effect.Appear</h3> <input type="button" value="Show it" onclick="WEBUCATOR.doMainDemoAction();"/> <div style="position:relative; height:200px; border:solid 1px #000;"> <div id="appearingBox" style="background-color:#77E66C; position:absolute; left:100px; top:40px; width:80px; height:40px; textalign:center; font-size:11px; display:none">Scriptaculous</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 id="demoFade" class="demo"> <div class="demo-content"> <h3>Effect.Fade</h3> <input type="button" value="Vanish" onclick="WEBUCATOR.doMainDemoAction();"/> <div style="position:relative; height:200px; border:solid 1px #000;"> <div id="fadingBox" style="background-color:#77E66C; position:absolute; left:100px; top:40px; width:80px; height:40px; textalign:center; font-size:11px;">Scriptaculous</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 id="demoBlindUp" class="demo"> <div class="demo-content"> <h3>Effect.BlindUp</h3> <input type="button" value="Roll it up" onclick="WEBUCATOR.doMainDemoAction();"/> <div style="position:relative; height:200px; border:solid 1px #000;"> <div id="rollUpBox" style="background-color:#77E66C; position:absolute; left:100px; top:40px; width:80px; height:40px; textalign:center; font-size:11px;">Scriptaculous</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 id="demoBlindDown" class="demo"> <div class="demo-content"> <h3>Effect.BlindDown</h3> <input type="button" value="Roll it down" onclick="WEBUCATOR.doMainDemoAction();"/> <div style="position:relative; height:200px; border:solid 1px #000;"> <div id="rollDownBox" style="background-color:#77E66C; position:absolute; left:100px; top:40px; width:80px; height:40px; textalign:center; font-size:11px; display:none;">Scriptaculous</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 id="demoSlideUp" class="demo"> <div class="demo-content"> <h3>Effect.SlideUp</h3> <input type="button" value="Push it up" onclick="WEBUCATOR.doMainDemoAction();"/> <div style="position:relative; height:200px; border:solid 1px #000;"> <div id="slideUpBox" style="background-color:#77E66C; position:absolute; left:100px; top:40px; width:80px; height:40px; textalign:center; font-size:11px;"><div>Scriptaculous</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 id="demoSlideDown" class="demo"> <div class="demo-content"> <h3>Effect.SlideDown</h3> <input type="button" value="Push it down" onclick="WEBUCATOR.doMainDemoAction();"/> <div style="position:relative; height:200px; border:solid 1px #000;"> <div id="slideDownBox" style="background-color:#77E66C; position:absolute; left:100px; top:40px; width:80px; height:40px; textalign:center; font-size:11px; display:none;"><div>Scriptaculous</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 id="demoPuff" class="demo">
<div class="demo-content"> <h3>Effect.Puff</h3> <input type="button" value="Make it go up in smoke" onclick="WEBUCATOR.doMainDemoAction();"/> <div style="position:relative; height:200px; border:solid 1px #000;"> <div id="puffingBox" style="background-color:#77E66C; position:absolute; left:100px; top:40px; width:120px; height:40px; text-align:center;">Scriptaculous</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 id="demoSwitchOff" class="demo"> <div class="demo-content"> <h3>Effect.SwitchOff</h3> <input type="button" value="Turn it Off" onclick="WEBUCATOR.doMainDemoAction();"/> <div style="position:relative; height:200px; border:solid 1px #000;"> <div id="tvBox" style="background-color:#77E66C; position:absolute; left:100px; top:40px; width:120px; height:40px; text-align:center; font-size:11px;">Scriptaculous TV</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 id="demoSquish" class="demo"> <div class="demo-content"> <h3>Effect.Squish</h3> <input type="button" value="Squish it" onclick="WEBUCATOR.doMainDemoAction();"/> <div style="position:relative; height:200px; border:solid 1px #000;">
<div id="squishable" style="background-color:#77E66C; position:absolute; left:100px; top:40px; width:80px; height:40px; textalign:center; font-size:11px;">Scriptaculous</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 id="demoFold" class="demo"> <div class="demo-content"> <h3>Effect.Fold</h3> <input type="button" value="Fold it" onclick="WEBUCATOR.doMainDemoAction();"/> <div style="position:relative; height:200px; border:solid 1px #000;"> <div id="napkin" style="background-color:#77E66C; position:absolute; left:100px; top:40px; width:80px; height:40px; textalign:center; font-size:11px;">Scriptaculous</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>
Description Used to trigger effects in succession. It defaults to 'parallel' but can also be queue 'front', 'end', and '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 fps the human eye and 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. 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 afterSetup and before it is rendered. beforeUpdate Callback function that is invoked before each frame is rendered. afterUpdate Callback function that is invoked after each frame is rendered. Callback function that is invoked before the effect runs its finalization beforeFinish 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, transition Effect.Transitions.none, 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.
Option
<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> <script type="text/javascript" charset="utf-8"> document.observe('dom:loaded', function () { //it's easy to make an element draggable: new Draggable('theImage'); } ); </script> ---- Code Omitted ---<h1>This image is draggable</h1> <img id="theImage" src="../Common/olympics.png" alt="olympics" /> ---- Code Omitted ----
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 */ });
endeffect
ghosting
handle
Description Specifies if the drag will be limited. Defaults to false but can also be 'vertical' or 'horizontal'. Amount of time (in seconds) to initiate the dragging after the mouse button is pressed. 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 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 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 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.
Description Function that will be called during dragging when the mouse is moved onDrag and the element is 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 onEnd receive the Draggable object as the first parameter. Function that will be called when the dragging starts. The function will onStart receive the Draggable object as the first parameter. When working with Droppables this will indicate if the element should revert go back to the 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. revertEffect Defaults to an 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 scroll when the dragged element nears its edges. It can be the window object or any other element that has scrolling enabled. Number of pixels from the edges of the container that trigger the scrollSensitivity scrolling. A number representing the pixels per second that the container will scroll when the element is dragged near the edges. Defaults to 15. This is scrollSpeed 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 be the number of pixels between each snapping snap 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 starteffect default is to 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 zindex to ensure it is above the other elements. Default is 1000. The next example illustrates how the use of dragging and effects to create a simple puzzle game.
Option
createGrid: function () { var columns = 4, rows = 3; var cellWidth = this.image.width/columns; var cellHeight = this.image.height/rows; var left, top, cell, tile; for (var row = 0; row < rows; row++) { for (var col = 0; col < columns; col++) { left = col * cellWidth; top = row * cellHeight; tile = this.createTile(left, top, cellWidth, cellHeight); tile.originalPosition = {x: left, y: top}; $('grid').insert(tile); this.tiles.push(tile); } } this.makeTilesDraggable(); }, createTile: function (left, top, width, height) { var tile = new Element('div', {'class':'cell'}); tile.setStyle({ width: width + 'px', height: height + 'px', left: left + 'px', top: top + 'px', backgroundPosition: '-' + left + 'px -' + top + 'px' }); 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); tile.visualEffect('move', {x: left, y: top, mode:'absolute'}); }); }, restoreGrid: function () { this.tiles.each( function (tile) { tile.visualEffect('move', Object.extend( tile.originalPosition, {mode:'absolute'} ) ); }); }
<div><img id="theImage" src="../Common/olympics.png" alt="olympics" /></div> </div> ---- Code Omitted ----
Where is the solution? The Draggable object has a number of members as listed below. 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 element that can be dragged. If specified as an option, this will contain a reference to the DOM element that handle will be used as the handle for dragging the main element. The options set for this Drggable. This includes both the options specified at the options constructor call 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]);
The available options for the Draggables are listed below. Description Name of a CSS class that will restrict which elements can be dropped in this accept drop zone. Only draggables with that class will be successfully dropped. Instead of a CSS class we can pass an element in this option so only containment draggables that are children of this element will be accepted. Name of a CSS class that will be added to the drop zone element while an hoverclass element in dragged over it. Takes a function that will be called when an element is dropped. The onDrop function will receive three arguments: the element being dropped, the drop zone element, and the event information. Option
<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.
<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.
parse_str($_POST['seq']); $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 ?>
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.
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.
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 ($pos1 !== false) { //we will build: <li>bla bla bla <strong>TERM HERE</strong> bla bla</li> $text = substr($st, 0, $pos1); $text = $text . "<strong>" . substr($st, $pos1, strlen($term)) . "</strong>"; $text = $text . substr($st, $pos1 + strlen($term)); echo "<li>$text</li>"; $returns+=1; } 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. Description If this is set to true and there's only one value being suggested, then that value autoSelect is automatically selected. This option defaults to false. This is a misnomer. This option specifies the number of seconds between frequency refreshing the suggestions. Defaults to 0.4. Number of characters that need to be typed before the first suggestions list is minChars produced. Defaults to 1. The Autocompleter.Local adds its own specific options. Option Description Option
Description Defaults to false and determines if the suggestions should include any items fullSearch that contain the typed text anywhere, not just beginnings of words. Determines if character casing is disregarded in the comparisons. Defaults ignoreCase to true. When set to true the search algorithm will look for suggestions that begin partialSearch with the typed text. 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 partialChars performed. Defaults to 2. Method that performs the actual work. The default value is the algorithm that obeys all the above options. In the rare event that you need something selector completely different (like searching in a XML document,) you can pass this option with a function that receives the autocompleter 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. Description If you need to customize the creation of the submitted value and parameter name, this option lets you pass a function that will receive a reference to the callback 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 indicator hidden during the 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 method from the default POST to GET. Fixed set of parameters to always be send to the AJAX URL. These parameters parameters are encoded in URL querystring format, like 'par1=abc&par2=xyz'. The submitted parameter name is the same as the name attribute of the input paramName field. This options lets you change that to whatever name you desire. Class name of an element inside the suggested li that will provide the text select for the selection. 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 is expected to collect more than one suggestions, like a tokens To: field in an email form, where you can choose more than one recipient for the message. Option
Option
Duration: 30 to 45 minutes. 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.
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. There are two flavors of in-place editing in script.aculo.us, Ajax.InPlaceEditor and Ajax.InPlaceCollectionEditor. The former makes the static text become a text box. The
latter uses a drop down list instead, limiting the possible resulting value to one of the list items. 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.
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 ajaxOptions autoRows callback Description These are the options that are passed to the Ajax.Updater. Defaults to { evalScripts: true }. If the rows option is 1 and the editor is multi-line, then this value will be used instead. Defaults to 3. Function that is called to format the values posted in the AJAX call. It will be 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 can be 'button', 'link', or false. The last one avoids any cancel button or link. 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 Ajax.InPlaceCollectionEditor. 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.
Description The text itself remains capable of entering edit mode upon being clicked. Specifies if the input field will be activated or just receive focus fieldPostCreation after being created. 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 formClassName house the edit field. Defaults to inplaceeditor-form. The id of the created form. Defaults to ELEMENT_IDformId inplaceeditor, where ELEMENT_ID is the id of the original text element. CSS class that is applied to the text element when the mouse is hoverClassName 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 htmlResponse want to process the server response and then determine the element's final content. This option works with the onComplete option. When using the loadTextURL option, this is the text that will be loadingText displayed while the 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 useful when the text we want to edit is not loadTextURL 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 okControl operation. The values 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 onComplete Ajax.Response and the element. Called when the control enters edit mode. It gets passed the onEnterEditMode control instance. Called when the mouse hovers over the element. It gets passed the onEnterHover control instance. The default value causes the element to be highlighted. Called when there's an AJAX error. It gets passed the control and onFailure the 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 onFormCustomization created. It is passed the control and the form element. Called when the control leaves edit mode. It gets passed the onLeaveEditMode control instance. Called when the mouse hovers over the element. It gets passed the onLeaveHover control instance. The default value causes an Effect.Highlight to
Option
Option
Description
be applied to the element. If the default parameter name of value doesn't work for you, you paramName can choose a different one with this option. Number of rows in the textarea input field that is created for rows multi-line edits. CSS class name that is applied to the saving text when it is being savingClassName 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 stripLoadedTextTags response text. Defaults to false. When true causes the control to accept the value when it looses submitOnBlur focus. The default is 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.