Difference between revisions of "JavaScript"
m (→Examples: Commenting example code) |
|||
Line 408: | Line 408: | ||
</source> | </source> | ||
− | + | '''Note:''' Many examples that you find when Googling for "AJAX" use the '''onreadystatechange''' property of the XMLHttpRequest instance to listen for the completion of the request. This approach works, but it is no longer recommended for modern web applications. | |
+ | |||
+ | ==== Response Formats ==== | ||
+ | |||
+ | More often than not, a plaintext response form the server is not very helpful: you want to transmit more information than simply a single string of text. There are two good options to get more information from a single AJAX request. | ||
+ | |||
+ | ===== XML ===== | ||
+ | |||
+ | Your server can respond with an XML document (whence, ''XMLHttpRequest''). An '''XML document''' uses syntax that is extremely close to HTML, and like HTML, it has a DOM that can be traversed in JavaScript. If you make an AJAX request to a script generating an XML document, the root node is accessible in the '''responseXML''' property. | ||
<source lang="javascript"> | <source lang="javascript"> | ||
Line 421: | Line 429: | ||
'''Note:''' The above example uses an '''anonymous function''' (a function without a name that is declared in an attribute) as the callback. If you are using a certain callback function only once in your program, this is a perfectly valid approach to reduce the amount of code you need to write. | '''Note:''' The above example uses an '''anonymous function''' (a function without a name that is declared in an attribute) as the callback. If you are using a certain callback function only once in your program, this is a perfectly valid approach to reduce the amount of code you need to write. | ||
− | ''' | + | ===== JSON ===== |
+ | |||
+ | JavaScript Object Notation, or '''JSON''', is a rising standard because of its lightweight syntax that makes it especially useful for AJAX requests. | ||
+ | |||
+ | (complete later) | ||
=== Examples === | === Examples === |
Revision as of 19:26, 11 October 2012
JavaScript is the most widely used scripting language for implementing client-side functionality in Web applications. Unlike PHP, JavaScript code is interpreted and executed in a client's web browser (not on the web server).
Contents
JavaScript? Did you mean Java?
JavaScript is a prototype-based programming language focussed on web interactivity. It was first introduced in Netscape Navigator, and it in turn gave rise to what is now ECMAScript. Originally, JavaScript was called "LiveScript". Sun, the developer of Java at the time, wanted a language that would complement its compiled Java language, and so LiveScript was renamed JavaScript.
Beyond that, Java and JavaScript are as similar as Ham and Hamster.
Java and JavaScript have similar syntax, but the under workings are quite different. For example, Java requires strict typing, but JavaScript allows for dynamic typing. Java is object-oriented, but JavaScript is prototype-oriented. Java is compiled, but JavaScript is interpreted.
JavaScript Language Components
This section highlights some features of the JavaScript language that differentiate it from other languages. For a more comprehensive JavaScript tutorial, the following tutorials are both quality:
- http://www.quirksmode.org/js/contents.html (very thorough and informative)
- http://www.w3schools.com/js/default.asp (covers the basics)
Variables and Scope
To define a variable in JavaScript, use the var keyword.
var foo = "bar";
All variables are objects. In the case above, the variable foo is an object of the class String.
Notice that like PHP, you do not have to explicitly define a type for a variable like int or String; this is because JavaScript and PHP are dynamically typed languages.
JavaScript code can access any variable higher in the “function tree”, but not vice-versa. This is called scope. All functions create their own scope.
When you reference a variable, JavaScript will look up through the "function tree" until it finds the variable you asked for. This means that you could define variables of the same name as global variables inside of functions. For demonstration of concept:
var a = "hello";
var b = "world";
function sayGoodByeWorld(){
var a = "goodbye";
alert(a); // goodbye
alert(b); // world
}
sayGoodByeWorld();
alert(a); // hello
Object Literals
A very common way of storing and transporting data in JavaScript is by using objects. You can define an object with certain properties using an object literal:
var apple = {
color: "red",
flavor: "sweet",
season: "autumn"
}
alert(apple.color); // red
Functions
To define a function in JavaScript, use the function keyword.
function sayHello(){
alert("Hello World");
}
sayHello(); // call the function
Pass a parameter to a function like this:
function sayHello(name){
alert("Hello, "+name);
}
sayHello("Todd"); // call the function and pass an argument
In JavaScript, functions are objects. This means that they can be assigned to variables, manipulated on the fly, and even passed as arguments to other functions! As we will see in a couple sections, this is an extremely important feature of JavaScript that makes it such a robust language for web development.
If you want to define a scope-specific function, you can assign it to a variable like this:
var sayHello = function(){
alert("Hello World");
}
sayHello(); // call the function
Closures
A closure is a way to separate a certain snippet of JavaScript code from the global scope. This is useful if you do not want to "muck up" global variables. Closures are nothing more than anonymous functions that are called as soon as they are created:
var a = "hello";
(function(){
var b = "world";
alert(a); // hello
alert(b); // world
})();
alert(a); // hello
alert(b); // ReferenceError: b is not defined
Most JavaScript libraries (like jQuery) write all of their code in a closure. What this means is that except for one or two variable names (jQuery or $), everything associated with the library is completely self-contained.
Events
Events are really what make JavaScript such a powerful language for web development.
Rather than being thread based, JavaScript is event based. Since all JavaScript code runs in a single thread, there are no concurrency issues. When an event occurs, JavaScript automatically calls the event callback as soon as all previous events' callbacks have finished executing.
Code in the form of a callback function is associated with events via a listener. For example, the following JavaScript causes sayHello() (the callback function) to be run whenever the button with ID "hello" is clicked (the event):
var sayHello = function(){
alert("Hello World");
}
document.getElementById("hello").addEventListener("click", sayHello, false);
The addEventListener method takes three parameters:
- The event type
- The callback function
- The callback function is passed one parameter: the event object.
- A boolean representing bubble (false) or capture (true) phase
- You usually want Bubble phase. For more information on event phase, see #Special Topic: Event Phase
Note that versions of Internet Explorer prior to version 9 required that you use a different, albeit similar, function for assigning event listeners called attachEvent. attachEvent takes the same arguments as addEventListener, except you need to put on before the event type (e.g. "onclick"), and the third argument (phase) is not necessary.
It is perfectly valid to use an anonymous function as your callback function:
document.getElementById("hello").addEventListener("click", function(event){
alert("Hello, World! Event Type: "+event.type);
}, false);
QuirksMode has perhaps the best series of articles about the ins and outs of JavaScript events on the internet: http://www.quirksmode.org/js/contents.html#events
Special Topic: Event Phase
In JavaScript, events occur in two phases: Capture Phase and Bubble Phase. Consider the following HTML document:
<!DOCTYPE html>
<html>
<head><title>Event Test</title></head>
<body>
<div id="box">
<button id="btn">
Hello
</button>
</div>
</body>
</html>
Suppose the user clicks on the "Hello" button. Events will be fired in the following order:
- Capture Phase CLICK on the Document
- Capture Phase CLICK on the Body
- Capture Phase CLICK on the Div#box
- Capture Phase CLICK on the Button#btn
- Bubble Phase CLICK on the Button#btn
- Bubble Phase CLICK on the Div#box
- Bubble Phase CLICK on the Body
- Bubble Phase CLICK on the Document
Graphically, here is the order in which an event occurs in three generic nested elements:
Why is this useful? Suppose you have a control panel (toolbar, etc) that you want to temporarily deactivate. (For example, Bold/Italic/Underline buttons on a graphic text editor that currently has no content.) You could keep bubble phase event listeners on your buttons, but prevent those events from occurring by stopping the event in the capture phase on the parent element before the event reaches your buttons! Example:
var disableToolbarFunc = function(e){
e.stopPropogation();
}
// To disable toolbar:
document.getElementById("box").addEventListener("click", disableToolbarFunc, true);
// To re-enable toolbar:
document.getElementById("box").removeEventListener("click", disableToolbarFunc, true);
Here is a JSFiddle showing the above code in action: http://jsfiddle.net/r32Dz/
Word of caution: Like many great things in web development, capture phase does not work in versions of Internet Explorer prior to version 9. There is no elegant substitution for older versions of Internet Explorer.
Prototypal Inheritance
JavaScript implements prototypal inheritance, as opposed to a language like Java or C++, which implements classical inheritance. JavaScript is one of only a handful of languages implementing prototype-based inheritance.
Here's how it works. Every callable object (function) has a prototype. When instances of that object are created, their properties and methods are copied (inherited) from the prototype. Additionally, changes made to the prototype later in the program are "copied" to every instance.
For example, if you wanted to make a method on all strings that added a “!”, you could modify String’s prototype:
String.prototype.bang = function(){
return this+"!"; // Notice: the context is the instance itself
}
alert("Hello".bang()); // alerts Hello!
To "extend" an object, just copy its prototype to a new object. However, since classical inheritance and prototypal inheritance are different concepts, copying prototypes may have results that you don't expect. This article seems to have a pretty good explanation with example implementations.
Context
Every function is called on a certain object, which is not necessarily the same object in which it was defined. This is called context.
To call a function on an arbitrary context, use either call or apply. call takes a list of arguments, while apply takes them all in an array.
To access the current context, use the this keyword. Demonstration of concept:
var sayThisColor = function(){
alert(this.color);
}
var apple = { color: "red" }
sayThisColor(); // undefined
sayThisColor.call(apple); // red
Document Object Model
The Document Object Model (DOM) is what enables JavaScript to manipulate the content of your web page.
Nodes and Traversing the DOM
Each element on your page, whether it is a <p>, <img/>, a string of text, or even an attribute, is called a node.
Every node, except for the document itself, has exactly one parent. A single node may have multiple children.
Consider the following HTML code:
<ul id="my-list">
<li class="fruits">Apples</li>
<li class="citrus">Oranges and <strong>Lemons</strong></li>
<li class="berries">Cherries</li>
</ul>
Here, the ul with ID "my-list" has three child element nodes (the three li's) as well as four child text nodes (the whitespace counts as a text node) and a child attribute node. The second LI has one child text node as well as one child element node and one child attribute node.
The DOM can be traversed in JavaScript. First, let's look at an example.
e = document.getElementById("my-list").getElementsByClassName("citrus")[0].lastChild.nodeName;
alert(e);
The above snippet gets the element with class name citrus that is a child of the element with ID my-list (the second LI in the example above), and alerts the type of its second child node (which in this case is STRONG).
Some common methods for traversing the DOM include:
- Node.parentNode
- Node.childNodes - returns an array of nodes
- Node.firstChild - same as Node.childNodes[0]
- Node.lastChild - same as Node.childNodes[Node.childNodes.length-1]
- Node.previousSibling
- Node.nextSibling
You can also search for child (including all descendants) using one of the following methods:
- Node.getElementsByTagName("tag-name") - returns an array of element nodes having the tag name tag-name that are descendants of Node
- Node.getElementById("id") - returns a single node having the ID id that is a descendant of Node
- Node.getElementsByClassName("class") - returns an array of nodes having the class class (note: this is not implemented in all browsers)
It is common in JavaScript to have long statements like the one in the example above, where you call methods on several objects in sequence.
Creating, Moving, and Removing Nodes
You will frequently find yourself wanting to modify the DOM. JavaScript provides methods that help you do this.
Consider the same HTML as above. If you wanted to add another element to the list, you could use this code:
var newLi = document.createElement("li"); // creates a node with the tag name li
newLi.appendChild(document.createTextNode("Broccoli"));
newLi.setAttribute("class", "veggies");
document.getElementById("my-list").appendChild(newLi);
If you wanted to remove the apples from the list, you could do:
var apple = document.getElementById("my-list").getElementsByClassName("fruits")[0];
document.getElementById("my-list").removeChild(apple);
If you wanted to move the apples to the end of the list, you could do this:
var apple = document.getElementById("my-list").getElementsByClassName("fruits")[0];
document.getElementById("my-list").appendChild(apple);
Notice that Node.append() both removes a node from its previous location and appends it to its new location, which is always going to be as the last child of the parent node.
In summary, here are the methods to know from this section:
- Node.append(otherNode) - removes otherNode from its current location in the DOM (if applicable) and then adds it as the last child of Node
- Node.removeChild(otherNode) - removes otherNode, a child of Node, from the DOM
- document.createElement("tag-name") - create a new node with the tag name "tag-name"
- Node.setAttribute("attribute", "value") - sets the attribute node of name "attribute" to "value"
Inspecting the DOM
Various browsers and browser plugins allow you, the developer, to look at the DOM of your web page and find what properties and methods are associated with each node. Some options include:
- Firebug. Firebug is a plugin for Firefox that is a toolkit full of web developer tools, one of which is a DOM inspector. To inspect the DOM of a web page, simply open the Firebug window while viewing the web page.
- WebKit Inspector. The WebKit Inspector, which is available in both Chrome and Safari, enables you to see the DOM tree and relationships between elements. However, its DOM inspection tools are not as robust as those of Firebug.
- Opera Dragonfly. The web browser Opera comes with a web developer toolkit similar to the WebKit Inspector called Dragonfly. It is important that you test your sites in Opera, because although Opera has only a small fraction of the desktop browser market, it is second only to WebKit on the mobile market.
Asynchronous Javascript And XML
Asynchronous Javascript And XML, or AJAX, is a group of related web development techniques, standards, and technologies used on the client (web browser) side of the standard web server-client communication model.
Here's the general concept: JavaScript is used to send requests to the web server in the background and retrieve data, without the need to refresh the page. The result: dynamic, fast, user-friendly web pages.
The requests are performed asynchronously: this means that the end user can continue interacting with your web page while the server data is being loaded.
The key idea is that a large fraction of the work happens at the web browser, and larger requests happen behind the scenes, keeping waiting times for data to come back over the network to a minimum. AJAX is generally also associated with higher-level APIs and libraries that aid in producing cleaner user interfaces for web applications.
The XMLHttpRequest Object
The JavaScript specification defines the XMLHttpRequest object, which is used to exchange data with the web servers without needing to reload the entire web page in the browser.
Note: All modern browsers support the XMLHttpRequest object as specified by the W3C. However, if you wish to support older browsers (e.g., Netscape Navigator or Internet Explorer prior to version 9), you need to do some additional work on the back end. In this class, your JavaScript needs to work in only Firefox and Chrome.
Initializing an XMLHttpRequest Object
As stated above, the XMLHttpRequest object handles communication with the web servers. To use it, you must first initialize a variable to be an instance of the XMLHttpRequest "class".
var xmlHttp = new XMLHttpRequest();
Next, use your XMLHttpRequest instance to open a connection to your server.
xmlHttp.open("METHOD", "URL_OF_SERVER_SCRIPT", ASYNCRONOUS_FLAG);
// METHOD: either GET or POST.
// URL_OF_SERVER_SCRIPT: the path to your server-side script relative to the current page. For example, process_ajax.php
// ASYNCRONOUS_FLAG: Should be true in order to perform the request asynchronously. (Having a value of false will freeze the browser until the request is completed.)
Note: For security reasons, you cannot perform AJAX requests to other domains, unless that domain has a special security policy set up to enable such requests.
Sending Data
GET Data
To send GET data, append the information to the end of the URL_OF_SERVER_SCRIPT, as you would normally do, and then call xmlHttp.send(), passing null as the parameter:
var xmlHttp = new XMLHttpRequest();
xmlHttp.open("GET", "process_ajax.php?var1=val1&var2=val2", true);
xmlHttp.send(null);
POST Data
To send POST data, pass a URL-encoded string to xmlHttp.send(). You also need to change the MIME Type of the outgoing request.
var xmlHttp = new XMLHttpRequest();
xmlHttp.open("POST", "process_ajax.php", true);
xmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xmlHttp.send("var1=val1&var2=val2");
Listening for a Response
XMLHttpRequest will fire an event when the request has been completed. We therefore need to use a callback function in order to listen for the load event on the XMLHttpRequest object.
The server's plain text response is available as the responseText property of your XMLHttpRequest instance, which is available in the target property of the event. For example:
var xmlHttp = new XMLHttpRequest();
xmlHttp.open("GET", "hello.txt", true);
xmlHttp.addEventListener("load", callbackFunc, false);
xmlHttp.send(null);
// ...
function callbackFunc(event){
alert( "Your file contains the text: " + event.target.responseText );
}
Note: Many examples that you find when Googling for "AJAX" use the onreadystatechange property of the XMLHttpRequest instance to listen for the completion of the request. This approach works, but it is no longer recommended for modern web applications.
Response Formats
More often than not, a plaintext response form the server is not very helpful: you want to transmit more information than simply a single string of text. There are two good options to get more information from a single AJAX request.
XML
Your server can respond with an XML document (whence, XMLHttpRequest). An XML document uses syntax that is extremely close to HTML, and like HTML, it has a DOM that can be traversed in JavaScript. If you make an AJAX request to a script generating an XML document, the root node is accessible in the responseXML property.
var xmlHttp = new XMLHttpRequest();
xmlHttp.open("GET", "hello.xml", true);
xmlHttp.addEventListener("load", function(event){
alert( "The root node contains " + event.target.responseXML.childNodes.length + " child nodes." );
}, false);
xmlHttp.send(null);
Note: The above example uses an anonymous function (a function without a name that is declared in an attribute) as the callback. If you are using a certain callback function only once in your program, this is a perfectly valid approach to reduce the amount of code you need to write.
JSON
JavaScript Object Notation, or JSON, is a rising standard because of its lightweight syntax that makes it especially useful for AJAX requests.
(complete later)
Examples
Logging In a User
Suppose you had the following PHP script:
<?php
// login_ajax.php
header("Content-Type: text/plain"); // Since we are sending a plaintext response here (not an HTML document), set the MIME Type to text/plain
$username = $_POST['username'];
$password = $_POST['password'];
// Check to see if the username and password are valid. (You learned how to do this in Module 3.)
if( /* valid username and password */ ){
session_start();
$_SESSION['username'] = $username;
$_SESSION['token'] = substr(md5(rand()), 0, 10);
echo "Login Success";
exit;
}else{
echo "Incorrect Username or Password";
exit;
}
?>
And suppose you had the following HTML content:
<input type="text" id="username" placeholder="Username" />
<input type="password" id="password" placeholder="Password" />
<button id="login_btn">Log In</button>
<script type="text/javascript" src="ajax.js"></script> <!-- load the JavaScript file -->
You could use the following JavaScript code to prepare, send, and receive a request from the server to see if the user is logged in, without ever having to refresh the page.
// ajax.js
function loginAjax(event){
var username = document.getElementById("username").value; // Get the username from the form
var password = document.getElementById("password").value; // Get the password from the form
// Make a URL-encoded string for passing POST data:
var dataString = "username=" + encodeURIComponent(username) + "&password=" + encodeURIComponent(password);
var xmlHttp = new XMLHttpRequest(); // Initialize our XMLHttpRequest instance
xmlHttp.open("POST", "login_ajax.php", true); // Starting a POST request (NEVER send passwords as GET variables!!!)
xmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); // It's easy to forget this line for POST requests
xmlHttp.addEventListener("load", loginCallback, false); // Bind the callback to the load event
xmlHttp.send(dataString); // Send the data
}
function loginCallback(event){
alert(event.target.responseText); // Alert the plaintext message from the server
}
document.getElementById("login_btn").addEventListener("click", loginAjax, false); // Bind the AJAX call to button click
Loading Data from an XML File
Suppose you have a file note.xml that contains the following information:
<?xml version="1.0" standalone="yes" ?>
<note>
<date year="2013" month="02" day="14" />
<to>Sally</to>
<message>Happy Valentine's Day!</message>
<from>Your Secret Admirer</from>
</note>
Further, suppose you had an HTML element with ID note:
<div id="note"></div>
You could load this XML file and then display it inside of your HTML document like this:
function getNoteAjax(event){
// The XMLHttpRequest is simple this time:
var xmlHttp = new XMLHttpRequest();
xmlHttp.open("GET", "note.xml", true);
xmlHttp.addEventListener("load", getNoteCallback, false);
xmlHttp.send(null);
}
function getNoteCallback(event){
var htmlParent = document.getElementById("note"); // Get the HTML element into which we want to write the note
var xmlDocument = event.target.responseXML; // Get the XML root node from the response
// Find the <date/> element from the XML response. We expect only 1 such element, so we can call
// xmlDocument.getElementsByTagName() and then get the first (and only) element in the array all in
// one step. This is called chaining.
var xmlDateElement = xmlDocument.getElementsByTagName("date")[0];
// Put together a YYYY-MM-DD type date into a string based on the attributes of the <date/> element.
var dateString = xmlDateElement.getAttribute("year") + "-" + xmlDateElement.getAttribute("month")
+ "-" + xmlDateElement.getAttribute("day");
// Get the content of the <to>, <message>, and <from> tags. Again, you can use a chain to do this in one line for each.
var to = xmlDocument.getElementsByTagName("to")[0].textContent;
var message = xmlDocument.getElementsByTagName("message")[0].textContent;
var from = xmlDocument.getElementsByTagName("from")[0].textContent;
// Make a <strong> tag, append the date string as its content, and then append it to the HTML element.
var htmlDateObj = document.createElement("strong");
htmlDateObj.appendChild(document.createTextNode(dateString));
htmlParent.appendChild(htmlDateObj);
// Make a <p> tag to contain the note.
var htmlParagraphObj = document.createElement("p");
htmlParagraphObj.appendChild(document.createTextNode("Dear "+to+",")); // First append the greeting
htmlParagraphObj.appendChild(document.createElement("br")); // Append a couple line feeds
htmlParagraphObj.appendChild(document.createElement("br"));
htmlParagraphObj.appendChild(document.createTextNode(message)); // Write the message itself
htmlParagraphObj.appendChild(document.createElement("br")); // Append a couple more line feeds
htmlParagraphObj.appendChild(document.createElement("br"));
htmlParagraphObj.appendChild(document.createTextNode("Love, "+from)); // Finally, append the salutation
htmlParent.appendChild(htmlParagraphObj); // Append the newly engineered <p> to the HTML element.
}
document.addEventListener("DOMContentLoaded", getNoteAjax, false); // Bind the AJAX call to page load
This example uses a lot of techniques documented in the #Document Object Model section above. The resulting DOM will be something like this:
<div id="note">
<strong>2013-02-14</strong>
<p>Dear Sally,<br /><br />Happy Valentine's Day!<br /><br />Love, Your Secret Admirer</p>
</div>
If you think that this is a lot of code to produce a relatively simple output, you are not alone. You will learn how to do this job in much fewer lines when you learn about jQuery.