Find, Do
Technically speaking, the standard for JavaScript doesn't include any information on input and output. It's meant to be a hosted language, living inside another program--in this case, the browser. But since it has been used in browsers for so long, input and output in JavaScript usually means using functions that search and update web pages. The problem is that these functions are, in a word, terrible. They have long, confusing names. They return values that look like regular JavaScript values, but aren't. They have poor default settings.
Almost immediately after interactive web pages became possible, people began writing helper scripts that would "wrap" these poorly-engineered browser functions in more convenient forms. The most popular of these is jQuery, which was invented in 2005 by John Resig. It's not always the most advanced library, or the most comprehensive. But it has been one of the easiest to use, with a clean and fluent interface and a metric ton of plugins to cover a wide variety of use cases. For that reason, we'll be learning jQuery in this book, but you should also familiarize yourself with other popular helper libraries.
jQuery is based on a "find tags, then do something to them" philosophy. Its primary innovation was the ability to look for elements on the page using CSS selectors. If you've done any CSS, you should be familiar with selectors: they're the rules that you set up to specify which parts of the page get a certain style. This book assumes that you know enough to write some simple selectors, but here's a basic review:
Selector | Meaning | Example |
---|---|---|
tag | Finds all tags matching the selector | li, div, h1
|
.class | Finds all elements with a given class name | .content, .external, .dialog-box
|
#id | Finds a single element with the specified ID | #main, #menu, #next
|
parent > child | Finds child elements that are directly inside the parent | body > h1
|
ancestor child | Finds any child elements that are anywhere inside the ancestor element | body a
|
For more information on CSS and CSS selectors, you may want to check out this comprehensive guide from Mozilla.
In the last few chapters, we've been loading scripts using the script tag, either inline or external via the src attribute. Now it's time to open up the developer tools again, using both the console and the HTML views (Chrome currently calls this "Elements"). While still on this page, from the console, type the following code, pressing enter after each line:
var rows = $('#selectors tr');
rows.hide();
rows.fadeIn();
You should see the table of selectors above vanish and then reappear with a fade effect. Our first function call, $('#selectors tr'), told jQuery to find any <tr> tags inside of the #selectors element (the table has an ID of "selectors"). jQuery actually creates two functions that you can use for finding elements, $ and jQuery, but most people use the shorter form, so we will too. Then we called the hide() and fadeIn() methods on the resulting object, causing it to do exactly those actions. If you watch in the HTML/Elements tab, you'll see the inline styles for those parts of the page change (usually this change is highlighted in yellow).
For right now, let's just concentrate on searching for elements on the page using jQuery. Try using the following jQuery selections in your console, and then mouse over them in the resulting output line. You should see the elements highlighted on the page when you hover over each one in the array-like jQuery object that's returned.
$('code'); //finds all code elements
$('code:first'); //finds just the first code element
$('p:odd'); //finds only odd-numbered paragraph tags
$('td, th'); //finds both th and td tags
$('table > td'); //finds td tags that are direct children of the table
You'll notice that not all of these are valid CSS3 selectors. :odd, for example, is not defined in the W3C specification, even though it's very useful. jQuery defines a number of these custom selectors in order to save you some time, and also because that way they can support it in all browsers (the W3C equivalent to :odd, :nth-child, doesn't work in older browsers like IE). If you'd like more information, the jQuery documentation includes a page listing all the selectors (standard or not) supported by jQuery.
That last query tells us something interesting: there are no <td> tags that are direct children of the table tag, because they're all contained in a <tr> tag and then in the table (sometimes there's also a <tbody> between the two as well). In that case, jQuery doesn't throw an error, even if you try to call functions like hide() or fadeIn on the resulting object. Instead, the result is an empty array, and the functions are simply ignored. Since jQuery objects are "array-like," the way to test whether or not you've found something is (just as with arrays) to check the length property.
if ($('table > td').length > 0) {
//do something with the query here
//you can also just test the length by itself--non-zero numbers are truthy
}
Whenever we call the jQuery function (through $ or jQuery) and pass in a selector, what we get back is a special object that contains a huge number of functions for changing the selected elements. It also contains functions for finding other elements, using the current selection as a starting place. For example, you might want to locate a menu, and then find different child elements in order to give them a certain appearance or behavior:
var menu = $('.menu');
//now, starting from the menu, we'll find different items inside
var listItems = menu.find('li');
var links = menu.find('li > a');
var submenus = menu.find('ul');
The complete list of functions for navigating around the document (or traversing it) this way are listed in jQuery's traversal function documentation, but the most useful ones include:
Function | Role |
---|---|
find() | Given a selector, find any elements matching it inside the current selection |
children() | If called with a selector, finds all direct child elements matching it. If called without, returns all child elements. |
parent() | Returns the parent element (there's only one possible parent) |
closest() | Returns the nearest ancestor element matching the selector. This includes the current element, so running $('li').closest('li') will return the same objects you started with. |
next() | If called without a selector, it returns the next element with the same parent. If you provide a selector, it only finds the next element if it matches. |
prev() | Same as next(), but working in the opposite direction (up the page instead of down). |
Manipulation
Finding elements with jQuery is all well and good, but what do we do with them once we have them? The answer comes in the form of the manipulation functions. The simplest form of manipulation is just to replace the contents of an element using html(). For example, there's an element after this paragraph with an ID of "substitute." Let's change its contents by running the following code from the console.
$('#substitute').html('This space intentionally left blank.');
Note that when we change the page using jQuery, for most changes, jQuery will apply our alteration to all of the objects you selected--whether that's just one element (as above) or a long list. For example, $('p').html('replaced') will change the contents of every <p> tag on the page. You don't have to write a loop to affect each selected element, because jQuery will do it for you. Just make sure your selectors are accurate, since it will also happily affect elements that you didn't intend to target.
The html() function is, like many of jQuery's functions, both what we call a getter and a setter. By this, I mean that it can either get the inner HTML of an element, or it can set it. If you call html() without providing any input, as below, you'll get back the contents of the element.
$('#substitute').html(); //should be what you previously inserted here
You can type any string you want into the html() function, including other HTML tags. While that's not the most efficient way to construct a page, it's a useful ability to have. We can already imagine being able to create a large numbered list using a loop and some jQuery:
var list = "";
for (var i = 0; i < 100; i++) {
list = list + "<li> #" + i;
}
$('#substitute').html(list);
You can also feed a jQuery object into html(), which will remove those objects from their current location and insert them into the selected element or elements. For example, we might grab the first code example off this page, and insert it into our substitute element:
var code = $('code:first');
$('#substitute').html(code);
If you want to keep the current contents, but add to them, you might consider using append() instead of html(). The former works the same way, but it supplements an element's inner HTML instead of replacing it. append() makes our list-generation loop quite a bit simpler, because we don't have to generate all the HTML in one big string.
for (var i = 0; i < 100; i++) {
var item = "<li> #" + i;
$('#substitute').append(item);
}
Finally, let's look at a few methods for changing the appearance of an item. If you'd like to alter the inline style for an element, you can use the css() function, which takes as its inputs the name of the CSS property you want to change, followed by the value you'd like to give it. Try it out on the blockquote element that follows:
"I don't have to forgive my enemies. I've had them all killed."
--General Ramon Maria Naraez
var quote = $('blockquote');
quote.css('border', '8px dotted #CCC');
quote.css('color', 'red');
quote.css('background-color', 'green');
Like html(), css() is both a getter and a setter. If you don't provide a value as the second input to the function, it will retrieve the current value of that property. The best part of using this function to get CSS properties is that it's not limited to only inline styles. It will pull whatever the current computed style for the element is--meaning inline, stylesheet, and built-in styles. You can see this if you try to get the text-decoration style for our blockquote, which I have not set in any way on this page.
$('blockquote').css('text-decoration'); // should return "none"
Be careful, though. The format that you get a property back from css() may not match the way you set it, or the way you expect. Asking for the color style of an element in Firefox currently gets formatted as an "rgb(0, 0, 0)"-style string, even if it was set with a regular color string like "black." It can also vary from browser to browser. Be sure to test your code as widely as possible, to make sure you handle differences in CSS implementation.
Stay Classy, jQuery
When you call the css() function as a setter, it sets the styles inline. Of course, we're always taught that inline styles are a bad idea when writing HTML: they're repetitive, break up your code, and if you ever change the look of your site you have to go through each line of HTML to update the style attributes. It's much better practice to use a CSS class, thus separating the appearance of your site from its structure. The same is true of JavaScript, and jQuery's got your back:
var headline = $('h1');
headline.addClass('awesome');
//in addition to classes it already had, now has a class of "awesome"
headline.removeClass('awesome');
//and now just the "awesome" class has been removed, leaving others untouched
By using classes instead of CSS properties, we keep our JavaScript separate from the look and feel of our site. Now, if we decide to give the page a style makeover, we don't have to hunt through many lines of JavaScript to find places where we might be setting styles via code. Whenever possible, use each web language only for the problems it's designed for:
- HTML: structure and content
- CSS: look and feel
- JavaScript: interaction and behavior
Example Code
It's time to practice on the console again. The following lines of code will help you explore the jQuery interface a bit more, including its many handy functions for interacting with elements and their properties. Try typing each line into the console, and see what you get back, or what changes.
//Let's start with some animations
$('p').slideUp();
$('p').slideDown();
//Note that animations stack--by default, they don't overlap.
$('code').slideUp().slideDown();
//Let's mess with our links
$('a');
//Of course, it would be more fun to only mess with the sidebar links
$('aside').find('a');
//You can do that with a selector as well
$('aside a');
//But you can't go the other way: starting from links and finding asides
$('a').closest('aside');
//You can get information about an element's attribute using attr()
//Let's get the href for our links:
$('a').attr('href');
//Note that you only get the value from the first link.
//Setting an attribute, however, affects all elements.
$('a').attr('href', 'http://example.com');
//likewise, we can get the html for the first paragraph...
$('p').html();
//...or we can replace it for all of them.
$('p').html('This space intentionally left blank.');
//Remember to refresh to reset everything!
//Let's practice setting some styles
$('li').css('background', 'green');
//Many functions in jQuery will set multiple properties if you
//provide an object instead of individual arguments.
$('li').css({
background: 'green',
'margin-left': "40px", //you can set hyphenated properties by quoting them
marginTop: '10px' //alternately, you can camelCase them
});
These are some common uses of jQuery, but they're by no means exhaustive. One of the best ways to learn the library better is to decide on something you want to do--hide only links to certain domains! add a class to all elements inside another type of element! find elements that contain a certain word!--and then look through the jQuery Application Programming Interface (API) documentation for functions that will help you accomplish that task. The API also includes information on what arguments are required, and which are optional for any given call. For example, fadeOut() has a default speed, but by calling fadeOut('slow');, we can change how fast the element disappears.