The DOM: The Document Object Model

One of the key things that you can do with JavaScript is to modify the contents of the browser: changing the structure of the document, including adding and removing content. You can also alter the style of the elements, dynamically changing the CSS classes of elements or directly altering the CSS rules.

The API or Application Programming Interface by which JavaScript can modify the document is called the Document Object Model, or DOM for short.

The DOM entails a lot, but one thing to know is that your document is a tree of nodes. So, for example, this paragraph is a child of the BODY element, and the em tag earlier is a child of this paragraph. Next in this section is a UL (unordered list), which is the next sibling of this paragraph, and has several child elements, each of which is a LI (list item). And so on.

  • this is a LI in a UL, after the P above
  • this is another LI, next sibling of the previous one
  • this is a third LI, all of which are children of the UL

Note that when we talked about HTML, we talked about elements. For example, we might say that there are three LI elements in that UL above. In CSS, we talked about how many elements a particular selector might match. However, when we talk about the DOM, the code representation of an element is called a node. But you can think of these words as synonyms; don't let the terminology confuse you.

Because jQuery makes manipulating the DOM easier, let's learn jQuery.

jQuery

jQuery is a JavaScript library of useful methods for manipulating the document, by which I mean things like this:

  • Adding structure. For example, suppose the back-end sends some new Facebook content; JQ can add that to the page we are reading. You've probably seen that.
  • Removing structure. You finish composing a Gmail message in a little sub-window of your browser window and you click on send. JQ can remove the sub-window and put the focus back on your main Gmail window.
  • Modifying style. You type some stuff into a Google Doc, and moments later, the phrase "all changes saved" appears at the top of the window, then fades to gray and disappears over the course of several seconds. JQ not only inserts the text, but sets its color and animates its changing color and opacity.

It does a few other things as well, including Ajax. Ajax is the technique where a browser makes asynchronous requests of a back-end service, often a database. For example, those Facebook updates (which come from to a database on Facebook's servers), sending mail from Gmail, and sending the document changes in Google Docs are all done via Ajax.

jQuery has a small footprint, which means it doesn't take a long time to download to your browser and doesn't take up too much memory once it is loaded. It's well-supported and extremely popular. Google, Facebook, and many other tech companies use and support it.

For extreme brevity, everything in the jQuery library is accessed via one function whose name is $ — yes, the dollar sign character. A synonym of the $ variable/function is jQuery, but that's rarely used. After all, it may be clearer, but it's six times as much typing!

Loading JQuery

jQuery isn't built into the browser the way that raw JavaScript is. You have to load it. Many sites host versions of jQuery, and so typically, we load it from one of those.

Skim this page about Google Hosted Libraries

Then, put the following in your page(s):

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>

Note that, as with most programming languages, you have to load jQuery before you use it.

Click Handling Example

The following code implements a click handler for the button below, so that if you click on the button it will change to a random number. Try it! Click as many times as you like!

1234

Below is the code. Don't worry (yet) about what it all means; we'll get to each of those things. For now, notice that jQuery is very terse, which is nice. Still, it's nice to do a lot with just a few lines of code. It's very powerful.


jQuery also tries to work the same in all browsers, hiding their idiosyncracies. It's extremely popular for those reasons.

jQuery Usage

There's a pattern to most jQuery usage:

$(selector).method(arg);

The selector argument uses CSS syntax to select some set of DOM elements. The method then operates on that set in some way. If the method needs to know additional info, that's supplied in the arguments.

Here are some examples:

// change the CSS of all paragraphs (P elements)
// to make the text blue:
$("P").css("color","blue");

// change the CSS of all elements of class "important"
// to have red text:
$(".important").css("color","red");

// change the CSS of the navbar to be red on white.
// Notice the use of an object literal to package up two changes
$("#navbar").css({"color":"red","background-color":"white"});

// add the class "important" to all H2 elements:
$("h2").addClass("important");

// change the text of all H1 elements to "Cool Stuff"
$("h1").text("Cool Stuff");

// change the HTML of the copyright notice to this year
var d = new Date();
$("#copyright").html("&copy; "+d.getFullYear());

// add bananas to the grocery list.
$("ul#groceries").append("<li>bananas</li>");

// delete all H3 elements from the document
$("h3").remove();

// hide (make invisible via CSS display: none)
// all paragraphs in the sidebar
$("#sidebar p").hide();    

We could go on, but you get the idea.

jQuery API

The jQuery API is well documented. Here are some of the methods we used above and a few we'll use later in this page:

  • css modifies the CSS of the selected elements
  • attr modifies an attribute of the selected elements
  • addClass adds a class to the selected elements
  • text changes the text inside the selected elements
  • html changes the HTML inside the selected elements
  • append adds an element as an additional child of the selected elements
  • remove removes the selected elements from the page
  • hide hides the selected elements
  • click makes the argument, which should be a function, into a click handler for the selected elements. That means the function will be invoked if the user clicks on any of the elements.

You can learn a lot just by poking around in their website and reading some of their examples and notes.

Method Chaining

The implementation of jQuery uses a clever trick that can create a great deal of efficiency and brevity. Supplying a selector to the jQuery function and invoking it returns an object that represents the set of matched elements. (This is called the jQuery object or sometimes a wrapped set.)

The jQuery object supports the methods like the ones we looked at above. Furthermore, most of those methods return the same object as their return value, which means that we can keep operating on the same set, just by invoking another method, chaining them together.

That's all very abstract, so let's see some examples.

This first example is how a novice might do a series of things with some selected objects:

$(sel).addClass('important');        // make them important
$(sel).css('color','red');           // make them red
$(sel).append('<em>really!</em>');   // add an exclamation
$(sel).hide();                       // and hide them??

(Note that in real life we wouldn't make something red and then hide it; that's supposed to be amusing.)

That works fine, but the trouble is that jQuery has to keep finding all the DOM objects, so it wastes a lot of work.

A more experienced or efficiency-conscious person might do the following:

var $elts = $(sel);                 // get some DOM objects
$elts.addClass('important');        // make them important
$elts.css('color','red');           // make them red
$elts.append("<em>really!!</em>");  // add an exclamation
$elts.hide();                       // and hide them??

That's efficient, but a bit tedious to type. Note that it's a common convention, but not universal or required, to name variables that hold jQuery objects with a dollar sign. The dollar sign is just another character in the variable name. The example above is equivalent to:

var elts = $(sel);
elts.addClass('important');
...

We still haven't done chaining. In jQuery, most methods return the object (the value of the $elts variable AKA the jQuery object or "wrapped set"), which means that we can just invoke another method on the return value. That allows us to chain several methods together.

Using chaining, an experienced and terse jQuery coder might instead do the following:

$(sel).addClass('important').css('color','red').append('<em>really!</em>').hide();

Of course, that's a little ugly and a little hard to read, all in one line like that. The important point is that each method is just called on the return value of the one to its left. The layout of the code isn't syntactically required, so we are free to lay out the code nicely, maybe with comments. So, the true jQuery expert writes the following (notice the lack of semi-colons, which would interrupt the chain by ending the statement):

$(sel)                           // get some DOM objects
   .addClass('important')        // make them important
   .css('color','red')           // make them red
   .append('<em>really!!</em>')   // add an exclamation
   .hide();                      // and hide them??

The preceding is concise, efficient, and easy to read.

jQuery's Flaw and a Plugin

One of the only things I don't like about jQuery, its Achilles Heel, is the fact that a wrapped set that is empty is okay, generating no error message or warning. That can be useful sometimes, but most of the time, if the wrapped set is empty, it's because I made a mistake in the selector string.

Here's an example, where a typo in the selector string keeps the code from working, yet jQuery doesn't complain:

$("sectoin").css('border','2px solid blue');

So if your jQuery isn't working and there's no error message, scrutinize your selector expressions. In fact, I often end up checking the size of the returned set when I'm debugging. In the following screenshot, you can see that the wrapped set is empty, and that there is no error:

an empty wrapped set

In fact, I got so annoyed with this flaw in jQuery that I wrote a jQuery plugin for checking the size of the wrapped set in an easy way. With the plugin loaded, just do:

$("sectoin").some().css('border','2px solid blue');

Notice the some() inserted into the chain. This method will give you an error if there are zero things in the set. There's also a .one() method, which throws an error if there isn't exactly one match. Here, if I mistype fred as ferd, I get a useful error:

$('#ferd').one().css('color','red');

You can load the bounds plugin like this:

<script src="https://cs.wellesley.edu/~anderson/js/bounds/bounds-plugin.js"></script>

Both jQuery and my bounds plugin are already included in the readings/template-jq.html file. Notice the -jq in the filename, since this template loads jQuery (and my plugin).

DOM Events

In addition to letting us work with the DOM, jQuery lets us work with events. Events are important ways of hooking into user behavior: a user clicking on something or mousing over something is an event, and we can make things happen when that event occurs.

The way that event-handling in the DOM works is that you can say:

when event E occurs to DOM element D, please invoke function F.

Here's a partial list of some common DOM events:

  • click: when you click on something
  • dblclick: when you double-click something
  • mouseover: when your mouse is moved onto an element
  • keypress: when a key on the keyboard is pressed
  • keydown: when a key on the keyboard goes down
  • keyup: when a key on the keyboard goes up
  • load: when the document or an object like an image finishes loading
  • submit: when a form is submitted

We won't use all of those, but we'll use click, keyup, and submit.

Click Example

H3 Events

Let's make that more concrete with an example of using a click handler to change the color of some element. Just above is an H3 header with an ID and the ID is h3events. Let's write some code that would turn that header a random color:

var colors = ['red','orange','green','blue','purple'];
var randIndex = Math.floor(Math.random()*colors.length);
var randColor = colors[randIndex];
console.log('turning it '+randColor);
// jQuery magic to turn it a random color:
$("#h3events").css('color',randColor);

Take a moment to read that code. We create an array of colors; we can add/remove colors to it very easily. We generate a random index into the array: a number from 0 to the length of the array (well, the length - 1). Then we get the corresponding random color from the array. Finally, we use the jQuery css method to change the element with id h3events to that random color.

Okay, very nice, but that's not yet what we want. We'd like the user to be able to turn the header a random color just by clicking on it. So, one step on the way to do that is to package up that code into a function, say turnEventsRandomColor:

function turnEventsRandomColor() {
    var colors = ['red','orange','green','blue','purple'];
    var randIndex = Math.floor(Math.random()*colors.length);
    var randColor = colors[randIndex];
    console.log('turning it '+randColor);
    // jQuery magic to turn it a random color:
    $("#h3events").css('color',randColor);
}

Then, whenever we want to turn that header a random color, we just invoke the function:

turnEventsRandomColor();

The Click Method

However, we want the user to be able to have that function invoked by clicking on the header. More precisely, we want to say that whenever the #h3events element gets a click event, we'd like that function invoked. jQuery provides a very easy way to do this, using the same pattern we've seen many times:

$("#h3events").click(turnEventsRandomColor); // attach event handler

Scroll back and try it! See the jQuery click method for more details.

Now, there are some very important points to make: we are not invoking the turnEventsRandomColor function right now (when we attach the event handler). Instead, we are giving the function to the click method, much like we gave the 'color' string and the value of the randColor variable to the css method above. That is, the function is merely a piece of data that is being passed as an argument. It is not being invoked now.

To invoke a function we give its name (or, equivalently, a variable whose value is the function) followed by parentheses containing any arguments to be passed. Since we are not invoking turnEventsRandomColor now, it's not followed by parentheses.

When does it get invoked? The browser will invoke it when the event happens. The function is an event handler.

The function is also an example of a callback. A callback is a general Computer Science term for a function that is invoked later, when something happens or has happened. They're used in graphics programming, processing data, GUI programming and lots of other situations.

By the way, an experienced jQuery programmer wouldn't bother to devise that cumbersome name (turnEventsRandomColor) for a function that they are never going to refer to again after handing it to the click method. Instead, they use an anonymous function literal, putting all the important code right where they need it:

$("#h3events").click(function () {
    var colors = ['red','orange','green','blue','purple'];
    var randIndex = Math.floor(Math.random()*colors.length);
    var randColor = colors[randIndex];
    // jQuery magic to turn it a random color:
    $("#h3events").css('color',randColor);
});

The whole function literal is the argument of the click method — notice the close paren on the last line, after the closing brace of the function literal.

Functions as Arguments

This is a good time to re-visit the concept of functions as first-class objects. You might review first class functions to refresh your memory.

The first thing to remember is the following:

A function's return value is different from the function itself.

In symbols, we might say:

f != f()

Let's see some examples before we dig into what that means. Let's define a function named five that returns the number 5:

function five() { return 5 }

console.log(five() == five); // prints false
console.log(five() == 5); // prints true

Go ahead and copy/paste that code into the JS console to check. Why is five different from five()? Let's look at the comparison very carefully. This time, we'll save parts of the computation in variables.

five() == five; // the comparison above

val = five();   // the left hand side of the comparison
fun = five;     // the right hand side of the comparison

val == fun;     // the comparison again

When we invoke the function, it evaluates to a number. In other words, val is the return value of the function, which is the number 5. That's the left hand side of the comparison.

What about the right hand side? What is fun? The variable fun contains a function, our five function. When we just give the name of the function, the value is the function itself, not the result of invoking the function.

You can copy/paste the code above and check the values of val and fun in the JS console. Here's what I got:

values of val and fun

So far, this probably seems pretty obvious. You're probably checking your clock and wondering why I'm belaboring this. I'll get to the point very soon.

I think you'll agree that:

turnEventsRandomColor() != turnEventsRandomColor

The left hand side is the return value of the function, while the right hand side is the function itself.

What is the return value of turnEventsRandomColor()? Nothing. Look back at the function definition, and you'll see that it doesn't return anything. So:

$("h3events").click(turnEventsRandomColor());

is the same thing as:

val = turnEventsRandomColor();
$("h3events").click(val);

So, the code above

  1. invokes the function right away (which we don't want to do), and
  2. hands nothing to the click method, instead of a function.

That's why the code above doesn't work. Contrast the two versions of the code:

$("h3events").click(turnEventsRandomColor);    // works
$("h3events").click(turnEventsRandomColor());  // fails

The working version hands the function to the click method, while the mistake hands the return value to the click method.

This mistake is one of the top mistakes in CS 204.

Why do so many smart, careful students this mistake? The trouble is that we have strong habits when we program, things like reflexively

  • indenting if statements,
  • ending a line of code with a semi-colon, and
  • following a function name with parentheses.

It's that last habit that will get you in trouble in CS 204.

Avoiding the Mistake

One way to avoid the mistake is to learn new habits, particularly by being aware when passing a function as an argument. We'll get lots of practice in this course.

Another way is to write your code in a more long-winded way. So, instead of:

$("h3events").click(turnEventsRandomColor);

You can write:

let f = turnEventsRandomColor;
$("h3events").click(f);

By making the argument of .click always be f, without parentheses, you can more quickly build new habits. It also means that if your code doesn't work, you can do:

let f = turnEventsRandomColor;
console.log(f);
$("h3events").click(f);

And it should print a function, not undefined.

You don't have to code in this long-winded way. But it can't hurt, and adding that unnecessary extra line of code might save you hours of scratching your head wondering what's wrong with your event handler.

Challenge

The following challenge is completely optional, just for the interested reader. Can you define a function such that the following is true? This would be an exception to the statement we made above, where the following is almost always false:

f() === f;

Summary

  • jQuery allows you to select DOM elements and operate on them with methods
  • The selector string can be anything in the CSS language
  • We can modify attributes with the attr() method and content with the text() method
  • We can add click handlers with the click() method
  • A common error is to add the result of a function instead of the function itself.
  • I wrote a plugin to remedy jQuery's major flaw (in my opinion): no error for selectors that match no elements.

Building Structure

If you're curious about using jQuery to build structure, which we'll do later the course, you can read about building structure