Them Bones
The "web-savvy" part of this book means that it assumes you know at least a little bit about HTML and CSS. So why are you reading a chapter on writing HTML? The answer is not that I've lost the plot, but that the HTML you write for JavaScript is often a bit different from the HTML that you write for static pages. In this chapter we'll examine some of the ways that you can write markup that's friendlier toward interactivity, and how this can improve both kinds of code.
When we write HTML that's meant to support JavaScript, part of what we're doing is leaving ourselves clues in a machine-readable format. This is not a new idea for the web--in fact, it's a very old idea, and one that the web development community continues to discuss to this day (see also: the new "semantic" tags in HTML5, such as <article> and <aside>). But just as a good semantic markup can be relatively human-readable, good interactive HTML can be clear and readable: it should help explain the behavior that will be created by JavaScript, without tightly coupling your code to your page structure.
In some ways, we can think of your page as the "skeleton," and JavaScript as the "muscles." HTML that's laid and styled correctly will let your page move freely and flexibly, but still be structured, just as a skeleton may flex along joints but be strong across its bones. A good skeleton, both on the web and in an animal, has a functional beauty of its own.
Classes Versus IDs
By now, you've seen that jQuery allows you to select parts of the page in many ways, but the most common will probably be by class or by ID. Which one is better? Although it is a slightly controversial opinion, it's my belief that classes are a better way to approach styling both for looks and for JavaScript. In part, this is because IDs can only be used once in a document, which means you must be absolutely sure that you only want to do something in a single place before using an ID. I find this is rarely the case: I may start with a single instance of a scripted behavior, but I often end up duplicating it elsewhere.
Classes also allow us to easily compose behavior, just as they let us compose styling. In an "object-oriented" CSS file, classes are used to add different attributes to an element, such as color schemes, brand styling, and font choice. Likewise, we might want a button that uses certain scripted animations, connects to a form handler, or triggers behavior in multiple places. If we use an ID, we will have to write script that combines all those behaviors for a single element. With classes, however, we can compose changes and behaviors onto elements the same way that we combine the styles of any classes applied to them.
But just as importantly, using only classes for as much of our styling as possible makes it simpler to predict which rules will affect a given element, because it keeps the selector specificity as regular as possible. When the browser examines a CSS rule to decide which styles will be overridden, it ranks them by the parts of the selector according to the following scheme:
Selector | Point Value |
---|---|
Element (li, div, a) | 1 |
Class (.example) | 10 |
ID (#menu) | 100 |
According to this ranking, it takes eleven tag selectors to override a single class, and eleven classes to override an ID. If both the following CSS rules applied to a single element, it will be red, even though the selector length and positioning appears to make it blue.
/* 1 ID, 2 elements: 102 points */
body #main-article div {
background: red;
}
/* 2 elements, 4 classes: 42 points */
body .container .column .feature.section div {
background: blue;
}
For this reason, if nothing else, it is better to style and control the page using classes only. If your selectors all use the same type, it's easy to see which one wins, both visually and by simply counting the number of parts in the selector.
Attributes
Once an element is found, what do we do with it? We may often want to create a basic behavior via JavaScript, but tweak it slightly depending on the element involved. For example, different gallery thumbnails perform the same basic task--to show a lightboxed image in response to a click--but they must obviously display different pictures depending on which one is clicked. One way to distinguish between these is by giving elements different attributes, or using existing attributes to control JavaScript behavior.
The HTML5 specification defines a method for creating attributes that are meant to be used with JavaScript, known as data attributes. These can be named anything you want, as long as they have a data- prefix. The following element boasts several data attributes for triggering a floating YouTube video in response to a click.
<img src="video.jpg"
data-video-id="dQw4w9WgXcQ"
data-video-title="Adorable kittens"
data-video-start="0:13"
>
Once we add these attributes to an element, jQuery can get them in one of two ways: the attr() function, which gets or sets HTML attributes, and the data() function, which can store arbitrary values on an element and is seeded with the values of the data attributes. The following code gets and sets attributes on an element.
var element = $('img');
//one argument gets the existing value
element.attr('data-video-id'); //"dQw4w9WgXcQ"
//two arguments sets it
element.attr('data-video-start', '0:00');
//data() lets us dispense with the prefix
element.data('video-title'); //"Adorable kittens"
//Unlike attr(), data() stores values without
//altering the actual element
element.data('video-length', '2:34');
element.attr('data-video-length'); //no value
element.data('video-length'); //'2:34'
Data attributes have their own uses, and in HTML5 they hook into new JavaScript APIs. According to the W3C standards group, new attributes should be created this way. But the not-so-dirty secret of web browsers is that they have always allowed custom attributes, data- prefix or not. You can create any attribute you want, add it to an element, and get to it from JavaScript in every browser in existence. Even though they are reliable, useful, and well-supported, custom attributes will cause your page to fail W3C validation. This would be one of the many reasons that validation is completely useless in the real world.
<a href="link.html" quote="Hello, World">
The W3C doesn't like it, but this tag's quote attribute
is completely valid and usable in web browsers.
</a>
Structure
Often in JavaScript, we'll be manipulating the styles of various elements to change their appearance, such as adding a class to show that a button is selected or to create a progress indicator. It can be difficult sometimes to test these features, since you may not be sure whether a bug is in your style or your JavaScript. In these cases, you have two options for checking your code.
The first is to remember that classes are far more testable than styling. When testing a page that will be styled via JavaScript, it's not only easier to use classes from the jQuery side, but it's also easier to simply add those classes to your page while designing, so that you can tweak its appearance without having to run any script at all (especially handy when it would normally take many clicks to get the page set up that way, such as when creating dialog boxes or outputs from a long workflow). Once everything looks the way it's supposed to, you can remove the classes from the source and concentrate on getting the JavaScript to add them back at the correct times.
The other crucial tool for creating good page structures is to learn your browser's development tools inside and out. The Webkit developer tools and Firebug add-on for Firefox are particularly deep, allowing you to change and test almost everything about the page, including editing the raw HTML and seeing it change in front of you. Luckily, although there are differences and most people prefer one or the other, most of the functionality between the various developer toolkits is interchangeable, so you won't be left helpless when not in your favorite browser. To really dig into the dev tools, try watching the Discover Devtools interactive guide for Chrome, or check the Firebug wiki for links to books and tutorials.
Polyfills
In this book, we are mostly concerned with writing JavaScript on top of and around HTML. But one of the biggest strengths of JavaScript's integration into web browsers is that it can be used to polyfill new HTML features into old browsers. Projects like Modernizr can "upgrade" the browser using JavaScript, adding support for new CSS features and HTML tags. Although it's not always possible, for many features, you're no longer at the mercy of old browsers when it comes to adopting the latest web tech.
One of the best resources for HTML5 polyfills is HTML5 Please, a web site that catalogs new browser features and tracks when they're usable for a wide audience (its predecessor, Can I Use, is more comprehensive but not quite as user-friendly). For example, the HTML5 <video> element can be polyfilled in browsers that don't support it through a combination of JavaScript and Flash. It's not a great solution, but it means you can write one page using standards instead of one standards-based page and one legacy page.