YUI 2.8: Learning the Library
上QQ阅读APP看书,第一时间看更新

Common DOM scripting techniques

The DOM is best known among web developers for its ability to dynamically alter the structure and content of web pages using easily implemented scripting techniques. JavaScript contains a useful (although somewhat restricted) set of built-in methods for accessing, manipulating, and even replacing DOM nodes.

Nodes are a fundamental part of the DOM; each object in the DOM is represented as a node. Each node may be a branch node, which has child nodes and possibly parent nodes and sibling nodes, or it may be a leaf node, which may have siblings and parents but not children of its own.

In the previous screenshot, you can clearly see that the page is made up of a series of objects, where each individual object is a node. The nodes shown in that example are branch nodes because each one has at least one child. The HTML node, for example, has HEAD and BODY child nodes, and both of these have their own child nodes.

A special node is the document node, commonly referred to as the document object. It is the root of the whole tree, as can be seen in the previous screenshot, where it is shown as #document. Due to this privileged status, it has some extra properties and methods that are only available to it. Other methods may be called on any type of node.

Common DOM methods

We've already seen the two main ways of obtaining elements from the DOM—.getElementById() and .getElementsByTagName(). Once an element has been obtained, it's very easy to navigate your way through the tree using properties such as: firstChild, parentNode, or previousSibling for example.

So you can grab an element from the page and then move up, down, or sideways across the tree, navigating parents, siblings, or child elements alike. Each branch node also has an accessible childNodes[] collection that exposes information about each of the child nodes.

The DOM gives you methods that allow you to create different objects such as .createElement() or .createTextNode(). Both of these methods are only available under the document node.

Once you've created your new element or text node, you can insert it into the DOM (and therefore the document) using .appendChild(), .insertBefore(), or .replaceChild(). You can also copy nodes using .cloneNode(), or remove elements using the .removeChild() method.

Alternatively, you can fill a node with new content by setting its .innerHTML property with a string containing HTML code. Writing into the .innerHTML property of the document is what happens when a new HTML document is read and is thus highly optimized. We used this technique when assembling the results of our DateMath example.

Each node in the document has a series of properties that allow you to determine various bits of information about it, such as the data it holds, its id, nodeName, nodeType, tagName, or even its title. You can also use the .hasChildNodes() method to determine whether the node is a branch node or a leaf node.

Any attributes of each node are available to you under the attributes collection. The methods .getAttribute(), .setAttribute(), and .removeAttribute() are also available for use by the discerning web programmer to make working with element attributes easier, although this is one area in which browser support can vary wildly between platforms.

Further reading

The DOM, together with events (which will see in this same chapter) are the cornerstones of modern web development and web application design, and some fascinating documentation exists to further your understanding of these two important concepts. The W3C site for example, provides detailed descriptions of all of the standardized DOM levels.

DOM—the old way

To better understand the benefits that using the YUI Dom utility introduces, it may be useful to see a brief example of how the DOM can be used with just plain old JavaScript. We can then recreate the same example but this time using the YUI so that you can instantly see the difference in the two approaches.

Those of you who are familiar with the workings of the traditional DOM (by this I mean non-YUI techniques) can safely skip this section, but any of you who have not had much exposure to it may find this example beneficial.

We'll create a very simple form with just a single text <input> field on it and a submit button. If the submit button is clicked while the text input field is empty, we'll then use the DOM to add a simple warning message to the form. In your text editor add the following HTML:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Traditional DOM Example</title>
</head>
<body>
<form id="form" action="">
<div id="container">
<label for="input1">Enter some text</label>
<input type="text" id="input1">
<button id="submit" type="submit">Submit</button>
</div>
</form>
</body>
</html>

This all we need for the HTML code; we won't worry about any presentational or positional CSS. Now for some JavaScript; all we have to really concern ourselves with is getting the contents of the text field and checking that it is not empty. If it is empty, we can then create the error message and add it to the page.

We'll also need a way of calling the function into action once the Submit button is clicked. We can do all of this using the following set of functions, which can be added to the <head> section of the HTML code in a single <script> block:

<script type="text/javascript">
//define the checkInput function
function checkInput() {
//get the value of the input
var input = document.getElementById("input1").value;
//if the value is an empty string…
if (input == "") {
//create a new element and a new text node
var newspan = document.createElement("span");
var newtext = document.createTextNode("You didn't enter anything!");
//get the container element
var newparent = document.getElementById("container");
//add the new text node to the new element
newspan.appendChild(newtext);
//add the new element to the container
newparent.appendChild(newspan);
return false;
}
}
//the init function adds a listener for the submit event
function init() {
document.getElementById("form").attachEvent("onsubmit", checkInput);
}
//execute the init function when the window loads
window.onload = init;
</script>

I would like to point out that the above example is absolutely how things should not be done. This is very common, but very bad coding practice, and is something that we can thankfully leave behind when working with the YUI.

Save the file and view it in IE; if you click the button without entering anything into the text field, our message is added to the page.

The way it works is very simple. We've used a mixed bag of DOM methods to achieve this basic functionality, such as getting the value of the data entered into the text field and obtaining the container element using .document.getElementBy(), and creating our new content with the .createElement() and .createTextNode() methods.

We've also made use of the .appendChild() method to first add the new textNode to the new <span> element, then to add the <span> element to the container element. The final DOM maneuver involves using the .attachEvent() method to attach the onsubmit listener to the form and calls the .checkInput() function when the event occurs.

You'll notice that the form does not work when viewed in Firefox, which highlights a classic problem of working with the DOM. The .attachEvent() method is a Microsoft-only event and so is not understood by Firefox. To get this example to work in Firefox, we must detect which browser is in use and provide the .addEventListener() method to Firefox instead:

function init() {
if (document.addEventListener) {
document.getElementById("form").addEventListener ("submit",checkInput, false);
} else { document.getElementById("form").attachEvent ("onsubmit",checkInput);
}
}

Additional code routines like this are commonplace when using the traditional DOM methods because of the inconsistencies between different browsers. There are other ways of avoiding the above problem, for example, we could use the following statement in the init() function instead:

document.getElementById("form").onsubmit = checkInput;

But I thought it would be more interesting to show one of the common pitfalls of traditional DOM manipulation.

Still, the example does not fully work. In some browsers, though the error message will be shown, the form is still submitted. You might also be wondering what that third argument in the call to addEventListener is as attachEvent has none. We could go on enumerating inconsistencies and pitfalls, but I think the point is already made; this is too complicated and fragile. The browser, after clicking Submit with an empty text, should look like this:

DOM—the YUI way

We will be referring a lot to DOM and Dom. We will use DOM, all uppercase, to refer to the Document Object Model and its implementation in a browser, thus we will have a DOM node or DOM element (the same thing) or the DOM, meaning all of it, the full page. We will use Dom to refer to the YUI Dom utility.

Now let's change our basic form from the previous example so that it makes use of the YUI Dom utility instead of relying on standard JavaScript methods. The HTML markup can stay the same, but remove the entire <script> block from the <head> of the page.

You'll need to link to the Dom utility; with this file saved in your yuisite folder, the build directory will be accessible. Link to the Dom utility using the following <script> block:

<script type="text/javascript"
src="yui/build/yahoo-dom-event/yahoo-dom-event.js">
</script>

We're linking to the yahoo-dom-event.js file because the YAHOO object is required, and we can make good use of some of the Event utility's methods (we'll be looking at the Event Utility in detail later in this chapter).

In the <body> of the page, just after the closing </form> tag, add the following code:

<script type="text/javascript">
YAHOO.util.Event.onDOMReady(function () {
var Dom = YAHOO.util.Dom, Event = YAHOO.util.Event;;
Event.on("form1", "submit", function(ev) {
//work out whether the input field is empty
if (Dom.get("input1").value === "") {
//create a new element
var newspan = document.createElement("span");
//fill it with the message
newspan.innerHTML = "You didn't enter anything!";
//insert the new element after the submit button
Dom.insertAfter(newspan, "submit1");
//prevent the form from submitting
Event.preventDefault(ev);
}
});
});
</script>

The first few lines should come as no surprise. We set to listen to make sure the DOM is ready and, when it is, our anonymous function will be called so that we have our sandbox and, within it, we create our traditional shortcuts for both the Dom and Event utilities.

In Chapter 1 we have already seen how we can respond to an event. Then it was the click of the icon that would pop up the calendar; here we listen to another event, the submit event of the form form1.

We have defined the function that will respond to the form submission in-line, much as we have done for our anonymous sandbox function all along.

This time we don't need to worry about catering for different browsers, the YUI will do that for us; whether it is attachEvent or addEventListener or the event is called submit or onsubmit it is now irrelevant.

Inserting the error message has also been simplified. Using the DOM we had to refer to the <div> with an id of container and append it there. The YUI allows us to insert the new text directly after the submit button, and allows us to refer to it by its ID, we don't need to get it first and then use it. This is something quite standard over all the YUI: if a method needs a reference to a DOM node, it can also take a string with its id attribute and it will find it for you.

Run this code in any browser. It will look exactly the same as the page in the previous screenshot, but under the hood, things are working much better. We use less code this way and although there's only one input field to validate (and only very basic validation at that), imagine the benefits of using the YUI for form validation on an entire form with many input elements.