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("© "+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:
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:
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
- invokes the function right away (which we don't want to do), and
- 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 thetext()
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