Digital humanities


Maintained by: David J. Birnbaum (djbpitt@gmail.com) [Creative Commons BY-NC-SA 3.0 Unported License] Last modified: 2016-12-09T17:45:32+0000


JavaScript exercise 2: answers

Instructions

Answer all questions below (the challenge questions are optional) in a plain text file (not a word-processor file) and upload your answers to CourseWeb. You can create a plain text file in <oXygen/> (File > New > Text), don’t use a word processor because word processors may convert your straight quotes to curly ones or your hyphens to dashes, and they may introduce hyphenation, all of which can break your code.

HTML questions

  1. The attribute used to hold the text for the definition of each word is @data-definition, which is a new kind of attribute in HTML5. Read more about data attributes at the Mozilla Using data attributes page and explain why we can make up an attribute called @data-definition without rendering our HTML5 invalid, but we can’t make one up called @definition?

    Data attributes are designed to improve the flexibility of HTML5 by enabling users to create their own attributes, with their own names, in way that nonetheless makes it possible to validate the HTML. @data-* attributes (where the rest of the name can be whatever you want [except for certain prohibited characters]) can be associated with any element and can mean whatever you want them to mean. They allow us to add extra information to our code in a way that is convenient for the developer, without having to create ad hoc attributes that will break validation or use attributes in a way that abuses their semantics (a practice called tag abuse). Any attribute name that begins with the string data- is considered valid in HTML5. @definition cannot be used because it is not a valid predefined HTML attribute name and does not begin with the string data-. HTML is strict when it comes to non-standard attributes; if they aren’t part of the HTML spec they make your HTML invalid, unless they begin with the string data-.

  2. Why can’t we use @id here?

    @id attributes must be unique within the document, which is not a problem here since we are not defining the same word twice in different places. However, the rules for @id say that the value of an @id attribute must contain at least one character, begin with a letter (a-z), and may not contain any spaces. Our definitions in this document are multi-word phrases with spaces that separate words, which makes @id an impossible attribute for this situation.

  3. Why can’t we use @class here?

    Similarly to our problems with @id, but for a different reason, spaces are also an issue with @class. Space characters are allowed inside a @class attribute, but they have special meaning, and cannot be part of a meaningful string. Specifically, an element can belong to multiple classes simultaneously, and spaces inside a @class attribute value are the way HTML separates multiple concurrent class assignments for a single element. For example:

    <span class="left important">

    In the code above, the @class attribute contains the values "left" and "important". They are separate classnames, but both apply to the same <span> element. They tell you that this element belongs to two classes, left and important. It is not possible to have a class name that contains a space character, that is, left important cannot be the name of a single class. For this reason, <p class="left important"> and <p class="important left"> are equivalent expressions in HTML.

JavaScript questions

For ease of reference, here is a copy of our javascript2.js with numbered lines:

"use strict";

function init() {
    var spans = document.getElementsByTagName("span");
    for (var i = 0; i < spans.length; i++) {
        spans[i].addEventListener('click', popup, false);
    }
}

function popup(e) {
    if (! this.id) {
        var overlay = document.createElement("div");
        var XMousePos = e.clientX;
        var Ypos = e.clientY + 20;
        var windowWidth = window.innerWidth;
        if (windowWidth - XMousePos > 300) {
            var Xpos = XMousePos;
        } else {
            var Xpos = windowWidth - 330;
        };
        var random = "n" + Math.random();
        
        this.id = random;
        overlay.innerHTML = this.dataset.definition;
        overlay.style.backgroundColor = "pink";
        overlay.style.position = "absolute";
        overlay.style.left = Xpos + "px";
        overlay.style.top = Ypos + "px";
        overlay.style.border = "1px solid black";
        overlay.style.width = "300px";
        overlay.style.margin = "0";
        overlay.style.padding = ".5em";
        overlay.dataset.pointer = random;
        overlay.addEventListener('click', destroy, false);
        document.body.appendChild(overlay);
    }
}

function destroy() {
    var span = document.getElementById(this.dataset.pointer);
    span.removeAttribute("id");
    document.body.removeChild(this);
}

window.onload = init;

Strict mode and initialization

  1. The first line of the JavaScript file contains a "use strict"; directive, which is designed to help developers write more robust JavaScript. Read about this directive on the Mozilla Strict mode page and tell us:

    1. Where can a "use strict"; directive go in a JavaScript file? Where can’t it go?

      The "use strict"; directive can be placed in only two places in a JavaScript file: at the top of the document or inside a specific function. Putting "use strict"; at the very beginning of a script applies to the entire document, unless a function tells it otherwise. Alternatively, include the directive in the body of a function to apply strict mode to only within that function.

    2. What are some of the benefits of including the "use strict"; directive in your script? Are there disadvantages?

      Strict mode enforces more precise coding, and thus protects you from writing statements that are legal in JavaScript (won’t throw an error), but that are unlikely to be what you meant to write. The link to the Mozilla page above leads to a list of specific controls that are active only in strict mode; you don’t need to understand what exactly they do to understand that they convert mistakes to errors, that is, cause validation to fail when you’ve written something that isn’t prohibited by JavaScript syntax, but that you nonetheless should never write. Except for the hastiest, fast-and-dirty development scripts, we always use strict mode in our own work.

  2. Explain the connection between the last line in the JavaScript file, which reads

    window.onload = init; 

    and the init() function on lines 3–8.

    window.onload = init; is the line of code responsible for making the initialization function, which we’ve called init(), fire. The window has its own load event, which is triggered when the content of the document inside it is finished loading and is ready to be presented in the browser. Because it fires automatically whenever the document loads, it can be used to initialize other JavaScript functions. We can assign this event handler to a specific action that we would like to be performed upon the triggering of the load event, which in this case is the init() function. The init() function then retrieves all of the <span>, assigns them to a variable called spans, and then loops over the items in our variable and attach an event listener to each one that says, When a click event happens on one of those elements, the popup() function should fire.

  3. The function init() on lines 3–8 should look familiar to you after reading our JavaScript piece by piece tutorial. On line 4 we create a variable called spans that is used to refer to all of the <span> elements in the HTML document. Lines 5–7 constitute a loop that will apply the same code to each <span> element in our spans variable.

    1. What does each part of the (var i = 0; i < spans.length; i++) in line 5 do?

      The first component inside the for loop declares a range variable, which we call i, and initialize it to zero. In the third component, which reads i++, we tell the script, each time the loop finishes, increase the value of our i variable by one. This means that the variable i will have the value 0 the first time through the loop, then it will be equal to 1, then 2, etc. The middle component is in charge of telling the loop when to stop; otherwise it would cycle forever (or at least until it threw a fatal error that halted the program).

      The second argument compares the value of i on each pass through the loop to the number of <span> elements in our spans variable. In JavaScript, we can find out how many items we have in a variable by using the .length property expression, that is, by tagging a period and the word length after the object whose components we want to count. The loop finishes whenever the test in the second component results false, that is, whenever we’ve looped through all of our <span> elements, incrementing our counter each time, and the value of the counter is equal to the number of items, which means that we’ve already processed them all and can stop.

    2. How does .addEventListener() in line 6 work?

      .addEventListener() is what tells the script that when a certain event happens happens, it must do something. The .addEventListener() method takes three arguments: the first is the name of the event (in single or double quotes), the second is the name of the function to fire when the event occurs (in our case, popup, without parentheses), and the third is the word false. If we set the value of a variable i to a number inside a loop, which we have, the expression will take the i’th member of our group of<span> elements and assign to it an event listener that will cause it to fire the popup() function whenever the user clicks that particular word.

The popup() function

The function popup() on lines 10–37 is responsible for creating and styling the popup windows that appear when a user clicks on one of the underlined words. The argument e in the function definition on line 10 is a JavaScript idiom for referring to the event that triggered the function. The following questions are about specific features of the popup() function.

  1. Line 11 is an if statement with the condition (! this.id); note that in JavaScript conditionals, the condition being tested has to be wrapped in parentheses. Read about the JavaScript bang operator (the term bang is geek-speak for the exclamation mark character; in this use it is also called logical not operator) at jsforallof.us and explain what it is that we’re testing, and how the test works.

    In the conditional on line 11, we’re testing whether the current object being proccessed does not have an @id attribute. The bang operator, or negation operator, is used to invert the test. It means, If the object to which the keyword this is pointing does not have an @id attribute, execute the following tasks. Using this method, we test to see whether the this object has already been assigned an @id value (which we will explain later). If it has been assigned an @id, ignore the body of the function, that is, do nothing in response to the click event. If it hasn’t, do everything in the body of the function.

  2. Why have we wrapped everything inside the popup() function definition inside this if statement? What is the effect of the if statement, and how would the program operate differently if it weren’t there? (Hint: you can remove it to experiment, but if you do, you have to remove both the if statement on line 11 and the matching closing curly brace on line 36.)

    Removing this if statement would still allow the popups to happen on click events, but it would also allow multiple clicks on the same word to result in multiple popups, which we don’t want. As we explain below, clicking to create a popup also assigns an @id to the target, the place where the click happened. When we make the popup disappear, we also take away the @id. This means that checking whether the target has an @id is a sneaky way of checking whether it’s already showing a popup. If it is, we don’t want to create another one, so we tell the function not to do anything.

  3. What is the relationship among lines 12, 24, and 35? You can read about the relationship between the .createElement() and .appendChild() methods at the SitePoint createElement (W3C DOM Core method) page and W3Schools HTML DOM appendChild() Method page, and you can read about accessing data attributes with JavaScript at the Mozilla Using data attributes page. Explain how the .appendChild() method (line 35) works and how the value of @data-definition gets inserted into the <div> (line 24).

    When our click demands that a popup be created, we create a new <div> element to hold it, and line 12 is the line that is responsible for creating that new <div> element. The .createElement() method creates an element node of whatever type is specified inside the parentheses, and so that we can refer to our new <div>, we create a new variable called overlay, which points to it.

    At the moment of its creation you can think of the new <div> as floating aimlessly in space. It exists, and we can refer to it by its variable name, but it isn’t attached to the document tree, so it isn’t visible in the browser window. In order to turn this floating empty <div> into a popup, we need to 1) insert the text of our definition into it and 2) attach it to the tree, so that it will be visible in the browser window.

    Line 24 takes the item referenced by the overlay variable, our new floating <div>, and uses its .innerHTML property to give it content, specifically by copying the definition for that word (the content of the @data-definition attribute in the HTML) into the <div>. The .innerHTML property can be used to read or write the content of any HTML element. HTML5 @data- attributes are special not only because you can create your own names, but also because you access them in a special way. The .dataset property of any element is an object that contains the names of all @data- attributes defined for that element, with the names pointing to the values. Note that we don’t specify the data- part of the attribute name; the expression this.dataset.definition in line 24 means take the target element, the thing that was clicked, look at all of its @data- elements, and get the value of the one that has the string definition after the data- string that begins the names of all @data- attributes.

    In principle, you can write any well-balanced HTML, with markup, into an element by setting its .innerHTML property. In this case, though, we’re copying the value that we want from an HTML attribute (@data-definition), and attributes cannot contain markup, so we’re restricted to plain text. There’s an example of how to create popups with clickable links (that is, with embedded HTML markup) using a variant of this strategy at our Popups that permit markup, including clickable links page. The JavaScript there is pretty poor because we were still learning how to do it (= don’t try this at home, kids), but it works, and it can give you an idea of what’s possible.

    Finally, line 35 appends our floating <div> (which we can reference by using its variable name, overlay) as the new last child element of the HTML <body>. The .appendChild() method appends a node (the one specified inside the parentheses) as the last child of a node (the one before the name of the method, which in this case is the HTML <body> element), so when you click on one of the defined words, the corresponding <div> gets added after all of the other content in the <body>. To see this in action, open up the JavaScript developer window and watch the <div> elements appear and disappear.

  4. You can read about random number generation in Javascript at the W3Schools JavaScript random() Method page. What is the connection between line 23 and line 33 and what do we do with our random variable? For this question in particular you might want to open the JavaScript developer tools in your browser window, expand the elements so that you can see the entire HTML source, and watch what happens when you click on each of the clickable defined words in the main browser display.

    On line 21 we set a variable called random equal to the letter n concatenated with the result of the Math.random() method. Math.random() generates a pseudo-random number (which we can consider a random number) between 0 and 1, expressed to several decimal places. We use this number, appended to the letter n, to create a unique @id value (keep reading). It is theoretically possible to generate the exact same random number twice, and in that case our @id values would not be unique and our HTML would not be valid, but the likelihood is so remote that we are willing to take the risk. An industrial-strength solution would rely on a Universally unique identifier (UUID), which reduces even further the risk of inadvertently generating the same number twice in the same session.

    On line 23 we set the @id attribute of the target (the thing on which we clicked) to equal the value of our random variable (and since the target doesn’t have an @id attribute when we first click it, that attribute is created first so that the value of random can be assigned to it). You can watch this happen in the debugger. On line 33, we assign that same random variable as the value of a new attribute we create called @data-pointer, which is an attribute on the popup <div> that we’ve called overlay. The matching @id and @data-pointer attribute values are what allow the system to know which popup window to open and close on click, that is, which popup window is associated with which clickable word.

The destroy() function

  1. Explain each line of code inside the destroy() function on lines 39-43.

    The destroy() function does the opposite of the popup() function: it closes the popup window that is associated with the clicked word. Line 40 creates a variable called span and points it at the <span> element that contains the @id attribute of the clicked word, that is, the target that was clicked earlier to create the popup. We created the @id so that we could test, inside our popup() function, whether a popup was already open for that particular word, so that we wouldn’t create multiple popups for the same word. When we destroy the open popup, we can use the connection between the value of the @data-pointer attribute on the popup <div> and the @id attribute on the clickable word to find and remove the @id, so that word will once again respond to new clicks.

    We use the keyword this to identify the object where the click event occurs, which in this case is the popup <div>, which we want to destroy when the user clicks on it. We use this in line 40 to get the value of the @data-pointer attribute of the popup and find the @id attribute we want to remove, and we do the removal on line 41 with the help of the .removeAttribute() method. This step is important because if we don’t remove that @id, the next time a user clicks on the word, the existence of the @id would cause the test on line 11 to fail and we would be unable to create a new popup after destroying the old one. Finally, line 41 uses the method .removeChild to eliminate the <div> popup window from the body of the document. JavaScript doesn’t provide a reliable way for an object to destroy itself, but it does provide a way for a parent element to destroy a child element. Because the destroy() function fires when we click on the popup <div>, and we know that that <div> was created as a child of <body>, we can call the .removeChild() method of the <body> element and tell it that the child to remove is the one we clicked one, which we can reference with the keyword this.

  2. How does line 34 enable the destroy() function to do its work when needed?

    Line 34 tells the popup <div> that we are creating, which we can refer to by using the overlay variable name, to listen for click events. When a click event happens on the popup <div>, the destroy() function fires.

Challenge questions

  1. Read about the window.load event at load and about window.DOMContentLoaded at DOMContentLoaded. In line 45 we bootstrap our JavaScript with:

    window.onload = init;

    How is that different from the code we used in earlier examples, which would be:

    window.addEventListener('load',init,false)

    or:

    window.addEventListener('DOMContentLoaded',init,false)

    All three strategies will cause the init() function to fire once the content is loaded, but they all do it differently.

  2. We could have used the HTML @title attribute to create a tool tip (popup) to hold our definition text, and had we done that, we wouldn’t have had to write any JavaScript. Read about the HTML @title attribute at the W3schools HTML title Attribute page and explain how it creates popups without requiring JavaScript. What are the limitations that constrain the HTML @title attribute, and that might therefore persuade us to use JavaScript instead?

    The @title attribute is easier to use than a JavaScript popup, but it has at least two limitations. The first is that because @title is an attribute, it cannot contain markup. That isn’t an issue in this case because our @data-definition is also an attribute, and therefore also cannot contain markup, and we aren’t using markup in our definitions anyway. But as you can see in the Popups that permit markup, including clickable links page that we mentioned earlier, our JavaScript popups can include markup if we need them to, and that isn’t possible with the @title attribute. The second limitation is that @title popups fade away on their own after a predefined amount of time. If the tool tip is just a few words, that isn’t usually a problem, but a long text could disappear before the user finishes reading it. Our JavaScript popups, on the other hand, go away only when the user clicks on them. That isn’t always an advantage, of course; in environments where the developer can predict that the user won’t want to see the tool tip for more than a few seconds, letting it disappear automatically is preferable to making the user click on it explicitly.

  3. In order to make sure that each popup window is linked to the correct word, our script creates matching @id values inside the <span> element and @data-pointer values inside the <div> element each time a word is clicked. We do this by generating a random number with the method Math.random() on line 21, information on which can be found on the W3Schools JavaScript random() Method page. The code on line 21 looks like:

    var random = "n" + Math.random();
    1. What is the plus sign doing here? (Hint: it isn’t arithmetic. Other hint: it does the same thing in lines 27 and 28.)

      The plus sign is a concatenation operator in JavaScript, and concatenates two strings. It is also an addition operator, and adds two numbers. Here we have a string to the left and a number to the right, and JavaScript figures out that we probably want to convert the number to a string and concatenate the two, instead of converting the string to a number and adding them. JavaScript can usually guess what you mean in situations like this, and if it fails (either doing the wrong thing or throwing an error), it’s possible for the developer to convert one datatype to another (that is, coerce a string into being a number or vice versa) explicitly.

    2. Why is there an n before our call to the random number method? We know that @id values must be unique within the document, but the random number generator does that for us. Why do we need to include the n? Insights can be found at the SitePoint id (HTML attribute) page.

      We’re going to use our random number as an @id value, and not only do @id values have to be unique, but they also cannot begin with a digit (they can contain digits, but not as the first character of the value). We work around this limitation by prepending an arbitrary letter to the digits.

  4. Line 35 makes the popup visible by inserting it into the DOM, the internal tree model of the document that the browser constructs. Where in the DOM does it appear and why?

    When the <div> is first created on line 12, it isn’t part of the document DOM tree, which means that it isn’t visible in the browser window and it isn’t a descendant (in the XPath sense) of the root <html> element that’s at the top of the DOM tree. When we insert it into the DOM tree on line 35, we append it as a child of the <body> element, which makes it the last child of <body>, right after the <p> element that was the last child of <body> in the original HTML document.

    And if you’re wondering where the floating <div> was before we attached it to the DOM, the answer is that JavaScript can address lots of things in the browser interface that aren’t part of the document DOM (such as the address bar at the top). These are part of the browser object model (BOM), which, unlike the DOM, is not standardized, and which may be implemented differently in different browsers. Despite the lack of standardization, some features are implemented the same way in all major browsers. For example, if you open the JavaScript console while you’re looking at a page and type

    window.location.href;

    the console will show you the URL of the page you are viewing. And you can change that page by assigning a value to this property; for example, if you type the following into the JavaScript console, you’ll load our main course page as a replacement for whatever you were viewing:

    window.location.href = 'http://dh.obdurodon.org';

    In any case, the floating <div> that you create is part of the window object, even though it is not part of the document object. The reason we create a variable to point to is is so that we don’t have to worry about how to find it; we can always refer to it by its variable name.