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 child of this paragraph. Next in this section is a UL (unordered list), which is the nextSibling of this paragraph, and has several child elements, each of which is a LI (list item). And so on.

DOM References

Many of the following are excellent introductions to the DOM, but they will use the native JavaScript API. The raw DOM is actually not that hard to use from JS, but the jQuery library makes it even easier, so rather than learn two ways to modify the DOM, we'll skip over this and modify the DOM via JQ.

The following is a list of useful but optional references, but you don't need to learn the raw JS API to the DOM, so feel free to ignore those parts.

The DOM in raw JavaScript

Your book starts by using the JavaScript DOM API that is built into the browser and only later introduces jQuery. We will use jQuery from the beginning, but let's take a minute to look at the built-in API first. Specifically, we'll look at these:

  • document.querySelector() takes a CSS-style selector as a string argument, and returns the selected node. (If more than one matches, it returns the first.)
  • document.querySelectorAll() is just like the previous method but it returns an array-like list of selected nodes.
  • node.textContent is an property that corresponds to the content of the selected node. You can set it to replace whatever is there. You can only set it to text, not arbitrary HTML.
  • node.addEventListener(type,function) takes an event type (such as "click") and a function as its arguments. When the specified event type happens to the node (e.g. someone clicks on the node), the function is invoked. That function can then handle the event in any way it wants to.

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


By the way, did you notice the anonymous function literal that is the second argument of addEventListener? That's the code that runs when the button is clicked.

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, the back-end sends some new Facebook content; JQ can add that to the page.
  • 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. For example, those Facebook updates, 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 by 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):

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

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

Click Handling Example

As a point of comparison, let's compare jQuery code equivalent to the example we saw using the raw API.

As before, 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


You can see that jQuery is a bit more terse than the raw API, which is nice, but not decisive. 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 nodes in your document to operate on. 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:

You can learn a lot just by poking around in there 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 a object that represents the set of matched elements. That object supports the methods like the ones we looked 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??

That works fine, but the trouble is that jQuery has to keep finding all the 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 required, to name variables that hold jQuery objects with a dollar sign).

We still haven't done chaining. In jQuery, most methods return the 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 really ugly and 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 important, 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 Plug-in

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:

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');

And you'll get 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 plug-in like this:

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

Both jQuery and my bounds plug-in are already included in the template file.

Building Structure

Before we get to events, we should take few minutes to look at operating on the structure of the document. We'll use jQuery to do this. We'll start with adding a list of prime numbers to the web page. First, we need to have a destination for them:

<div id="prime-container">
  <p>Our primes:</p>
  <ul id="prime-list">
  </ul>
</div>

Now the code to add some primes to that list:

function addPrimes( primes ) {
    primes.forEach(function(p) {
             $('<li>').text(p).appendTo('#prime-list')
         });
}

addPrimes( [2, 3, 5, 7, 11, 13, 17] );

Here it is:

Our primes:

You might wonder what the '<li>' does as the argument of the jQuery function, since it's not a CSS selector. What happens is that jQuery creates the given element, but it is not (yet) attached to the document. Here, we attach it to the document with the jQuery appendTo method.

An alternative way to do this is to build the entire list up and only attach it to the document at the end. This is more efficient, since the document only has to be re-rendered once for the list, as opposed to once for each element.

  <div id="prime-container2">
    <p>Our primes:</p>
  </div>

Here's the variant JS/JQ code:

function addPrimes2(primes) {
    var $ul = $('<ul>');
    primes.forEach(function (p) {
        $('<li>').text(p).appendTo($ul);
    });
    $ul.appendTo('#prime-container2');
}
addPrimes2( [2, 3, 5, 7, 11, 13, 17] );

Here it is:

Another list of our primes:

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
  • load: when the document or an object like an image finishes loading
  • submit: when a form is submitted

H3 Events

Let's make that more concrete. 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);

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();

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 do to this, using the same pattern we've seen many times:

:::javascript
$("#h3events").click(turnEventsRandomColor);

Scroll back and try it!

Now, there are some very important points to make: we are not invoking the turnEventsRandomColor function right now. Instead, we are giving it 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 she is never going to refer to again after handing it to the click method. Instead, she uses an anonymous function literal, putting all the important code right where she needs 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.