Digital humanities


Maintained by: David J. Birnbaum (djbpitt@gmail.com) [Creative Commons BY-NC-SA 3.0 Unported License] Last modified: 2015-04-09T18:42:15+0000


JavaScript exercise 1: answers

The task

Here is our test HTML, which utilizes @id attributes to be used in our JavaScript functions.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>JavaScript</title>
        <script type="text/javascript" src="practice.js">/**/</script>
    </head>
    <body>
        <h1>JavaScript is fun!</h1>
        <p id="paragraphToToggle">This is a paragraph.</p>
        <div><button>Show/Hide</button></div>
        <p>This is another paragraph.</p>
    </body>
</html>

Your job is to create a valid script that will make the first paragraph appear or disappear when you click the Show/Hide button. The methods for doing this are similar to that of those we used in class, but rather than having multiple targets, you’ll only have one.

Our solution

Our practice.js file looks like:

window.addEventListener('DomContentLoaded',init,false);

function init() {
    var buttons = document.getElementsByTagName("button");
    buttons[0].addEventListener('click', show_hide, false);
}

function show_hide() {
    var p = document.getElementById("paragraphToToggle");
    if (p.style.display == "none") {
        p.style.display = "block";
    } else {
        p.style.display = "none";
    }
}

Discussion

In the first part of our code we interact with the DOMContentLoaded event, which fires automatically whenever a page (including any linked auxiliary pages, such as CSS and JavaScript) finishes loading. Our first sentence says that when this happens, the init() function should be run (see the discussion of how .addEventListener() works below).

The second part of the JavaScript code is the init() function itself. It rounds up all of the <button> elements in the HTML file and assigns them to an HTML collection (a type of list), which we assign as the value of a variable we’ve decided to call buttons. We happen to know, because we know our document, that there is just one <button> element in the document, and because JavaScript starts numbering from zero, that one element is the zeroeth item in the collection. We find that item and attach to it an event listener, which is to say that we tell it to listen for (respond to) the specified event, which in this case is a click. The .addEventListener() method (a method is like a function can be run on an object, which in this case is the button) takes three arguments: the event to listen for (in this case, a click event, and the event must be specified in single or double quotation marks), the function to fire when the event occurs (in this case show_hide(), specified without parentheses), and the magic word false. The init() function, then, makes the button pay attention to when it gets clicked, and fire the show_hide() function whenever it senses that that event has happened.

The show_hide() function is a toggle, which means that it swaps the state (condition) of the target paragraph between visible and invisible, changing it each time the button is clicked. The paragraph we want to toggle has a unique @id value, paragraphToToggle, so we use that value to find it. We create a variable called p and set it equal to the value of the paragraph that has the specified @id value, using the .getElementById() method of the document object. We then use a conditional test: if that paragraph has block display, we set the value of the display property to none, making it disappear. Otherwise we set it to block, making it (re)appear. We change the value of that property by starting with our variable, accessing its style properties, and then narrowing to the display property. You can read about the CSS display property (block and none are not the only possible values) at http://www.w3schools.com/cssref/pr_class_display.asp.

Testing the value of a variable vs assigning a value to a variable

Note that we use the single equal sign (=) to assign a value to a variable, but the double equal sign (==) in the conditional to test whether a variable is equal to a specific value. Typing a single equal sign when you mean a double is a common JavaScript error because humans don’t think about the difference between asking whether two things are equal and making them equal. If you use a single equal sign, you won’t get an error message because assigning a value to a variable is legal. It isn’t what you really want to do here, though; the assignment will always succeed because you can always set a display property, which means that the if clause will always fire and the else clause never will.

Testing order

To the viewer, it appears as if the paragraph we’re toggling always has one of two possible values for its display property: block when it’s visible and none when it’s invisible. The situation is more complicated than that when you need to interact with the paragraph using JavaScript because there is actually a third value: null. The style of an element, including the way it is displayed (block, none, or something else) can be set in at least three ways: 1) it can be specified on the display property of the element itself (e.g., <p style="display: block;">); 2) it can be specified in an external CSS stylesheet associated with the HTML; or 3) the browser has a default built in for displaying paragraphs as block when no display property has been declared explicitly.

When we test if (p.style.display == "none"), we are testing only the first two of those three options. Meanwhile, when we first load the page, the paragraph looks like a block not because of option #1 or #2, but because of option #3. This means that the test fails (we have not explicitly set the value of display to none), so the else clause fires and we make the paragraph disappear. Doing that causes the <p> to turn into, in memory, <p style="display: none;">. As a result, the next time we push the button the test succeeds.

Suppose we run the test the other way around, though, testing not for whether the value is equal to none, but whether it’s equal to block:

function show_hide() {
    var p = document.getElementById("paragraphToToggle");
    if (p.style.display == "block") {
        p.style.display = "none";
    } else {
        p.style.display = "block";
    }
}

When we first load the page, the paragraph is displayed like a block, but that’s for reason #3, the browser default, and the display property hasn’t been set explicitly. As a result, the first time we click, the test fails even though the paragraph has block styling, the else clause fires, and the display value gets set explicitly to block. This means that the first time we click the button it looks as if nothing happens, but the second time we click, now that we’ve set an explicit vaule, we can begin toggling.

The lesson is that testing a style property tests only an explicit setting. The test is not aware of browser default settings. You need to cater for the default situation when the page first loads, whether by ordering your test or by testing for the third option explicitly, the one where the value of the display property is not set at all. Once you have set a property explicitly though, as we do here, it is an explicit setting and it can subsequently be tested.

Accessing objects

Note that .getElementById() is singular (because @id values are required to be unique, this method returns just a single element object) but .getElementsByTagName() is plural (because there is no limit to the number of <button> elements one can find in a document). It may seem silly to have to return a collection of <button> elements and then index into the collection to get the zeroeth one when you know there is just one in the document, but that’s the way JavaScript works. When you access objects by tag name (element name), you always get a collection, whether it has many, one, or even no content elements. When you access an object by its @id attribute value, though, you always get exactly one object (or zero, if it doesn’t exist; that isn't a JavaScript error, but it probably isn’t what you want).