Amherst College I.T. I.T. home. Amherst College.
I.T. home.
ATS > Software > Web Programming > Dynamic Web Content

Web Programming

Part 2: Dynamic Web Content

Previous: Static Web Content

Next: Interactive Web Content


The programming language JavaScript lets you manipulate all of the static Web content discussed previously. Many libraries have been built on top of JavaScript to make many tasks easier; jQuery and D3 are two such libraries.

“A well-written program is its own Heaven; a poorly-written program is its own Hell.” — The Tao of Programming

Topics


JavaScript

JavaScript is a programming language that works with the text, HTML, and CSS of a Web page. It was introduced in 1995, long after the first Web servers appeared in 1991.

JavaScript is based on the C programming language, a characteristic it shares with other programming languages such as Java and C’s superset C++, and they will therefore appear similar to each other. Like Java and C++, JavaScript is object-oriented, though it has slightly more restricted capabilities.

“JavaScript” is actually a product name of the Netscape Communications Corporation, whose Navigator Web browser evolved into Mozilla Firefox. It was originally named LiveScript but was renamed because of the popularity of the Java language. Variants include JScript from Microsoft and ActionScript from Adobe. It has been standardized by the European Computer Manufacturers Association (ECMA) and is officially known as ECMAScript.

The Mozilla Foundation provides a good JavaScript reference.


The JavaScript Console

You can start playing around with JavaScript by using the built-in console provided by the different browsers. First open a Web page, e.g. the page doi.html that you were working on previously. Then:

Safari

  1. If you don’t see the Develop menu:
    1. Menu Safari > Preferences…;
    2. In the dialog that appears, click on the tab Advanced;
    3. Check on Show Develop menu in menu bar.
  2. Menu Develop > Show Error Console….

Firefox

  • Menu Tools > Web Developer > Web Console

Chrome

  • Menu View > Developer > JavaScript Console

Internet Explorer

  1. Menu Tools > Developer Tools;
  2. In the new window at the bottom of the page, click on the tab Console.

Note that in all of these, the Web Inspector used previously and the JavaScript Console share the same window, so you can click on their names at the top to switch between them.

Important: Use the console to try out the following bits of JavaScript code! In the examples below the character is used to indicate the console's response.


Numeric Data

JavaScript describes and uses a number of different data types, but the one you are probably most familiar with is numeric data.

Numbers

Javascript numbers are generally written the same way you would write them for any other purpose:

3
3.14159
-5

The one exception is that numbers cannot have commas in them, e.g. “5,280” would be interpreted as a sequence of two numbers, “5” and “280”.

An additional way that you can write numbers is in scientific notation by using the letter e in place of the “times 10”, similar to a calculator. For example, 5280 = 5.280 × 103 , which can be written:

5.280e3

All numbers are stored as double-precision floating point values, meaning they are accurate up to 15 digits.

Numeric Operators

Numbers can be combined using the binary operators with which you are certainly familiar, addition (+), subtraction (-), multiplication (*), and division (/). For example:

2 + 3

5

2 * 3

6

Note the use of the asterisk * to indicate multiplication, a standard substitution in computer languages for the times symbol × that doesn’t appear on normal keyboards.

Just as in written arithmetic, the multiplication and division operators have a higher precedence than the addition and subtraction operators, which means they are applied first:

2 + 3 * 4

14

To change this order, use parentheses:

(2 + 3) * 4

20

Numbers can be negated with the unary operator -:

- 2

-2

The unary operator + also exists but has no effect on numeric values:

+ 2

2

Again as in written arithmetic, the negation operator has a higher precedence than the binary operators:

- 3 + 4

1

-(3 + 4)

-7


Text Data

Because Web pages are all text, and more generally we use text to provide context to numbers, JavaScript lets you easily work with text data.

Character Strings

JavaScript text is generally written the same way you would write it for any other purpose, except that you must surround character sequences with either single or double quotes:

'Samuel Adams'
"John Hancock"

Such sequences are commonly referred to as character strings or just strings.

Warning: if a string begins with one kind of quote, it must also end with the same kind; this allows the use of the other kind in the middle:

"the Laws of Nature and of Nature's God"

It’s a good idea to use single quotes when possible to help distinguish JavaScript from the HTML with which it’s often mixed, as the latter commonly uses double quotes for attribute values.

Note that curly quotes, ‘’ and “”, are not used to define strings, and therefore can always be included within them.

String Operators

Two strings can be concatenated together with the binary operator +:

'<td><span class="signer">' + 'Samuel Adams' + '</span></td>'

'<td><span class="signer">Samuel Adams</span></td>'

The value in the above is that the HTML tags can be boilerplate information, while the signer names can vary and be inserted in-between as the code comes to them.

Using + with two different data types, numbers and text, is known as overloading the operator.

The unary operator + will produce automatic type conversion when applied to the text representation of numbers, converting them to actual numbers:

+'3.14'

3.14

This is also true of the negation operator -.

Warning: For text representations of numbers to convert this way, they must not have any adjacent text other than white space. Anything that cannot be interpreted as a number will result in the value Not-a-Number (NaN), which will propagate through any subsequent calculations.

Automatic type conversion can occur in many operations where it makes sense, e.g. numbers in text format will be converted to actual numbers when appropriate:

'3.14' * 2

6.28

Note, however, that using binary + with strings and numbers will result in string concatenation:

'3.14' + 2

'3.142'

To ensure that numbers in text format are converted to actual numbers, precede them by the unary + or operators:

+'3.14' + 2

5.142

The functions parseInt() and parseFloat()are generally a safer way to convert text of unknown format to either integer or floating point (decimal) values, respectively, as they will correctly handle strings with trailing characters:

parseInt('2.7px')

2

parseFloat('2.7px')

2.7

Keep this in mind when working with data arriving from the Web, as it will always be character strings to begin with, and you generally don’t know where it’s coming from!

Question: How might you quickly convert a numeric value into a character string?


Variables

Computer languages gain much of their power by their abilty to reference and manipulate data using symbolic names, called variables.

Variable Declaration

Any datum can be assigned to a variable with the assignment operator = :

s = 0

signerOpenTag = '<span class="signer">'

signerCloseTag = '</span>'

The last two are examples of good descriptive variables that clearly indicate their purpose. They are written in camel case, which removes spaces from phrases and uses uppercase letters to distinguish the beginning of words. Spaces are also commonly replaced by underscores _ , for example signer_open_tag .

Variables can also be declared using the keyword var, which helps to make these expressions stand out:

var signer = 'Samuel Adams'

In most cases the keyword var is optional, but it does provide one distinguishing feature inside of functions, as described below.

The keyword var also lets you declare a variable without assigning it a value initially, in which case it is undefined:

var kingOfGreatBritain

'undefined'

Variables can be used anywhere that specific values can be used:

signerOpenTag + signer + signerCloseTag

'<span class="signer">Samuel Adams</span>'

JavaScript is dynamically typed, so that an existing variable can be reassigned to any other value of any kind:

signer = 'John Adams'

In many other computer languages one must worry about the consequences of such reassignments because it leaves the previous string stored in memory but (usually) unreferenced, and an accumulation of such data can overwhelm a computer; however, Javascript will automatically remove that data, a process known as garbage collection.

To determine the type of a variable, you can use the operator typeof, which returns a description as a string:

typeof s

'number'

typeof signer

'string'

Assignment Operators

Variables also allow the use of specific operators that both modify the variables and produce new results.

For each of the above binary operators there are assignment operators +=, -=, *=, /= that take a variable, apply the operation, and assign the result back to the variable:

var s = 6

s /= 3

2

This is more efficient than the equivalent operation s = s / 3 , since the variable is only accessed once.

Assignment operators are a convenient way to buildup strings from pieces that are obtained at different times:

var tableCell = '<td>' + signerOpenTag

tableCell += signer

tableCell += signerCloseTag + '</td>'

'<td><span class="signer">Samuel Adams</span></td>'

The basic numeric operators have a higher precedence than assignment operators, including plain assignment, so in these expressions the subtraction and multiplication occur first:

s = 4 - 2

2

s += 3 * 2

8

Increment and Decrement Operators

Because the particular operations s += 1 and s –= 1 are so common, JavaScript also defines the more efficient unary operators increment (++) and decrement (--) that can be applied to numeric variables to change their values by 1:

s = 1

++s

2

Here the variable is incremented, and then its result referenced. When these operators are applied following a variable, their values are referenced before they are changed:

var t = s--

2

s

1

Like negation, these unary operators have a higher precedence than the binary operators:

u = ++t + 3

6

Warning: variable operators can only be applied directly to variables:

u = ++(t + 3)

Reference Error

Question: What do you think would happen if you applied an increment or decrement operator to a variable containing a character string?


JavaScript Statements

The expressions above all produce some result, but those results can be combined together with other operators to produce other results. The order in which operations occur are summarized in this JavaScript Operator Precedence Table. It’s a good idea to double-check this table until you’re completely familiar with it (and maybe even afterward).

The final result of the combination of expressions is a statement, code that completes one particular task.

JavaScript statements are usually placed in an HTML <script> element, which can be anywhere in a document, and is interpreted by the browser as it loads that part of the Web page:

  • Code that does not directly affect the content of a Web page can be placed in the <head> element of the document, e.g. initial variable assignments:
  • <head>
    ....
        <script type="text/JavaScript">
            var signerOpenTag = '<span class="signer">', signerCloseTag = '</span>', signer = 'Samuel Adams';
        </script>
    ....
    </head>

  • Code that modifies Web page content should be placed in the <body> at the appropriate point:
  • <body>
    ....
        <script type="text/JavaScript">
            document.write('<tr><td></td>' + signerOpenTag + signer + signerCloseTag + '</tr>');
        </script>
    ....
    </body>

You can also place JavaScript code in external files with a statement like

<script type="text/JavaScript" src="doi.js"></script>

Inside such a file there should not be a <script> element, it should be purely JavaScript.

With HTML 5 the attribute type="text/JavaScript" is optional; while some other languages such as Visual Basic can be used in some contexts, the default scripting language is now JavaScript.

Statements can be terminated with a semicolon, as above, but they aren’t usually required, since JavaScript checks syntax, the relative positioning of different elements of the language, to determine where a statement ends. However, it’s a good idea to include them due to the occasional ambiguities that can occur, as well as minimizing occasional programmer confusion. (Using semicolons also allows code-sharing with C, C++, and Java, which do require them.)

Semicolons also allow more than one statement on a line, e.g.

var s = 1, t = 3; u = s * t;


Javascript Comments

You will want to put explanatory comments in your JavaScript code even more so than with HTML and CSS! Otherwise you will return weeks or months later and not know what you did. See XKCD for an example.

JavaScript provides two ways to write comments, one like in CSS that can appear anywhere and cross lines, and is commonly used for longer comments:

/* This is a comment. */

The second comment format is unclosed, continuing to the end of the line, and is commonly used for shorter comments:

// This is also a comment


Boolean Data

Comparison Operators

When writing programs you will routinely want to make comparisons of variables with other data, to test for particular conditions in order to determine different courses of action, such as deciding which type of calculation to perform or, more generally, branching to different parts of a program.

The results of these comparisons we usually think of as being “true” or “false”, and Javascript therefore provides the Boolean data type, which can be one of two values, true or false.

Comparison operators test for equality (==), inequality (!=), greater than (>), greater than or equal to (>=), less than (<), and less than or equal to (<=), always returning one of these two values:

s = 2 // This is assignment
s == 2 // This is equality; don’t confuse these two!

true

s > 3

false

signer != 'George Washington'     // signer == 'Samuel Adams' — also compare strings!

true

s != 2 * 3

true

Numeric and string operators have a higher precedence than comparison operators, so in the previous expression the not-equal != is applied only after the multiplication * produces its numeric result (6).

Logical Operators

The logical operators AND (&&), OR (||), and NOT (!) can combine boolean values to produce other boolean values, according to the following “truth tables”:

AND && T F   OR || T F     NOT !
T T F   T T T   T F
F F F   F T F   F T

In plain text, AND is only true if both values are true; OR is true if either value is true; and NOT is always the opposite value.

true && false

false

true || false

true

!false

true

s >= 2 && typeof signer != 'undefined'

true

Note that comparison operators have a higher precedence than logical operators, so in the previous expression the && is applied only after the >= and == produce values of either true or false.

In addition, ! has a higher precedence than &&, which has a higher precedence than ||.

Automatic type conversions frequently occur in logical expressions , because the empty string '', the number 0, and an undefined type all correspond to false, while all other values correspond to true:

true && ''

'' // string version of false

false || 1

1 // numeric version of true

Important: Logical operations proceed from left to right, so in an AND expression (&&) the left-hand argument must be true for the right-hand argument to even be evaluated; if so the latter result is returned, as above. On the other hand, in an OR expression (||) the left-hand argument must be false for the right-hand argument to be evaluated, and the latter is returned. Similarly:

true || ''

true // no need to evaluate second part; first part is returned

1 && false

false // second part must evaluated, and its value is returned


Collections of Data

When working with large amounts of data, it’s often convenient to group them together in structures that can be referenced as a unit, but whose components can also be referenced. JavaScript provides two such structures, simple lists called arrays and sets of property-value pairs called objects.

Arrays

Arrays are lists of data that are most simply constructed with square brackets []. They can hold any data type or expression, uniform or disparate, separated by commas:

sequence = [ 0, 1, 2, 3, 4 ];

signers = [ 'Samuel Adams', 'John Adams', 'Robert Treat Paine', 'Elbridge Gerry' ];

signerStatus = [ signer, 1776 - 1722, signer == kingOfGreatBritain ]     // [ 'Samuel Adams', 54, false ];

Note the difference between signers, above, and signer = 'Samuel Adams'; plural variable names are commonly used for arrays.

Arrays can even include other arrays, which is how you could build a multidimensional array, e.g. this 2 × 3 matrix with two rows and three columns:

matrix = [ [0, 1, 2] , [3, 4, 5] ];

Array Operators

The individual elements of an array can be selected using the index operator [], which requires a numeric index that runs from 0 up to the array’s length – 1:

signers = [ 'Samuel Adams', 'John Adams', 'Robert Treat Paine', 'Elbridge Gerry' ];    // length = 4

signers[0]

'Samuel Adams'

signers[3]

'Elbridge Gerry'

Referencing the elements of a multi-dimensional array requires as many indices as their are dimensions, each in their own pair of brackets:

matrix = [ [0, 1, 2] , [3, 4, 5] ];

matrix[1]

[3, 4, 5]

matrix[1][2]

5

Using the index operator you can assign values to the individual elements:

signers[2] = 'Rob Paine';

And you can append additional values to the array:

signers[4] = 'John Hancock';

The use of square brackets to access the elements of an array can be distinguished from their use to create an array because the former immediately follows an array name; it has a different syntax.

Objects

When your list of items is a disparate set, it might be better to use an object instead, which is most simply constructed with curly braces {} surrounding a comma-separated list of property: value pairs, often called key: value pairs. For example:

state = { name: 'Massachusetts', capital: 'Boston', "population 1770": 266565, governor: 'Hutchinson' };

Values can, again, be any data type.

Property names can also be any data type, but are usually strings, which only need quotes if they include spaces. To make them more compatible with other programming languages (e.g. SQL), double-quotes are recommended.

An array is actually a special type of object with indices that are numeric values:

typeof signers

'object'

Warning: objects look similar to CSS styles, but use commas instead of semicolons, and cannot have a trailing comma.

Object Operators

The individual elements in an object can be selected using the property operator. (commonly called “dot”), which joins the object with the appropriate property:

state = { name: 'Massachusetts', capital: 'Boston', "population 1770": 266565, governor: 'Hutchinson' };

state.name

'Massachusetts'

This won’t work if the property name has spaces in it, but you can also aways use the alternative square bracket notation where the index value is the property name:

state["population 1770"]

266565

You can use this with any property (including the numeric properties used by array objects, e.g. signers[1]), but string properties must always be written with quotes.

As with arrays, you can assign values to the individual elements to replace previous values:

state.governor = 'Baker';

And you can append additional values:

state["population 2010"] = 6.646e6;

Array and Object Length

In addition to using numeric indices, array objects always include a property length, which is automatically updated:

signers.length

5

To get the length of a more general object, you can request its set of properties with the function Object.keys(), which returns an array whose length you can examine:

Object.keys(state)

[ "name", "capital", "population 1770", "governor", "population 2010" ]

Object.keys(state).length

5

Equivalently you could use the function Object.values(), which returns an array of the object’s values:

Object.values(state)

[ "Massachusetts", "Boston", 266565, "Baker", 6646000 ]

JSON

Arrays and objects are commonly used together to represent tables in a format known as JavaScript Object Notation (JSON):

states = [
  { name: 'Massachusetts', capital: 'Boston', "population 1770": 266565, governor: 'Hutchinson' },
  { name: 'New Hampshire', capital: 'Portsmouth', "population 1770": 62396, governor: 'Wentworth' },
  { name: 'Rhode Island', capital: 'Providence', "population 1770": 58196, governor: 'Wanton' }
]

states[2].capital

'Providence'

Rather than a array of objects as above, one might also use an object whose values are also all objects:

states = {
  "Massachusetts": { capital: 'Boston', "population 1770": 266565, governor: 'Hutchinson' },
  "New Hampshire": { capital: 'Portsmouth', "population 1770": 62396, governor: 'Wentworth' },
  "Rhode Island": { capital: 'Providence', "population 1770": 58196, governor: 'Wanton' }
}

states["New Hampshire"].capital

'Portsmouth'


Control Structures

A common task in programming is to sequentially step through a set of values, usually stored in an array or object, and do something to each one.

The for…in Statement

The simplest control structure is the for…in statement, which works with objects to step through their property-value pairs, and for each one execute a statement:

var state = { name: 'Massachusetts', capital: 'Boston', "population 1770": 266565, governor: 'Baker', "population 2010": 6.646e6 };

for (property in state)
    console.log(property + ': ' + state[property]);


'name: Massachusetts'
'capital: Boston'
'population 1770: 266565'
'governor: Baker'
'population 2010: 6646000'

This structure is commonly called a loop because after it executes each statement it “loops back” to the for…in statement to get the next property.

While you can type simple statements into the JavaScript console and it will simply print them back for you, inside of a loop you must wrap them inside the expression console.log(), which “logs” the value to the console, otherwise only the last will be printed.

Warning: there is no guarantee that the order that items are listed in an object are the same order that they’ll come back out.

Instead of a single statement following the for…in statement, there can also be multiple statements, grouped together into a block using curly braces {}:

for (property in state)
{
    var value = state[property];
    console.log(property + ': ' + value + ' (' + typeof(value) + ')');
}


'name: Massachusetts (string)'
'capital: Boston (string)'
'population 1770: 266565 (number)'
'governor: Baker (string)'
'population 2010: 6646000 (number)'

In this example, state[property] is used twice, to print its value and find its type, so it’s best to assign it a name, and this is clearest if it’s written as a separate statement.

The use of curly braces {} to introduce a block of statements can be distinguished from its use to create an object because it immediately follows the for…in statement; it has a different syntax.

The for Statement

You can also loop through a set of values in a sequential fashion using the just-plain for statement:

signers = [ 'Samuel Adams', 'John Adams', 'Robert Treat Paine', 'Elbridge Gerry' ];    // length = 4

for (s = 0; s < signers.length; s++)
    console.log('Declaration Signer: ' + signers[s]);


'Declaration Signer: Samuel Adams'
'Declaration Signer: John Adams'
'Declaration Signer: Robert Treat Paine'
'Declaration Signer: Elbridge Gerry'

The three statements in the parentheses following the keyword for are:

initialization
Here, s = 0. This first statement occurs immediately.
test
Here, s < signers.length. The second statement is evaluated at the beginning of each iteration of the loop, allowing it to proceed only if true.
update
Here, s++. The third statement is evaluated after the iteration completes.

This particular set of initialization, test, and update statements are commonly used for arrays, however the only requirement is that the result of the test is something that can be evaluated as true or false through automatic type conversions, e.g. any non-zero number and non-empty string evalutes to true , while 0, '', and undefined variables are false.

Warning: every for loop should at some point reach a test that is false, but it’s very easy to miss that, in which case the result is an infinite loop that will slow down your browser and possibly make it crash. Carefully think about the logic of your code!

The if…else Statement

Very commonly you will want to test the values of variables and then evaluate a statement or block of statements depending on whether the comparison is true or false. You can structure this using the if keyword, followed by a logical test in parentheses, followed by a statement or block of statements that will be executed if the logical test is true. This can then be followed by an optional else keyword and a statement or block of statements that will be executed if the logical test is false:

signers = [ 'Samuel Adams', 'John Adams', 'Robert Treat Paine', 'Elbridge Gerry', 'Paul Revere' ];
johnHancock = 'John Hancock';

if (signers.length < 5)
    signers[4] = johnHancock;    // Add John Hancock; length now 5
else
    if (signers.length == 5)
    {
        if (signers[4] != johnHancock)
            console.log('Warning: The Massachusetts Delegation is trying to send ' + signers[4] +
                         ' in place of ' + johnHancock + '!');
        signers[4] = johnHancock;
    }
    else  // signers.length > 5; report an error
        console.log('Error: The Massachusetts Delegation is too large!');

'Warning: The Massachusetts Delegation is trying to send Paul Revere in place of John Hancock!'

Note that the statement following the optional else can be another if statement.

Also note that the braces are necessary to associate the second else with the second if statement rather than the third (even if there was only statement).

Also note that logical testing can be very involved sometimes!

When you combine the for loop with the if statement, you can test for conditions that let you skip the remainder of the code block with the continue statement, or exit the loop altogether with the break statement:

for (s = 0; s < signers.length; s++)
{
    if (signers[s] == 'Robert Treat Paine')    // Don’t print out 'Paine' and go on to next
        continue;
    if (signers[s] == 'John Hancock')    // Quit the loop early
        break;
    console.log('Declaration Signer: ' + signers[s]);
}


'Declaration Signer: Samuel Adams'
'Declaration Signer: John Adams'
'Declaration Signer: Elbridge Gerry'


Modifying Web Pages with JavaScript

An important use of loops is to handle the repetitive output of parts of a Web page, for example to print out in a uniform fashion an array of strings such as http://ats.amherst.edu/software/web/dynamic/doiabuses.js:

var abuses = [ "He has refused his Assent to Laws, the most wholesome and necessary for the public good.", "...." ]

If you place such a file in a subfolder named js next to your main document in the doi folder (a common practice), then you can load it with a <script> statement in the <head> of your document:

<script src="doiabuses.js"></script>

Then a list can be constructed by inserting JavaScript at the appropriate point in the Web page using the function document.write():

<ul class="abuse">
    <script>
        for (a = 0; a < abuses.length; a++)
            document.write('<li>' + abuses[a] +'</li>');
    </script>
</ul>


<ul class="abuse">
    <li>He has refused his Assent to Laws,
      the most wholesome and necessary for the public good.</li>
    ....
</ul>

The JavaScript code in the <script> element will be evaluated when the browser reaches that point in its interpretation of the HTML, and it will be replaced by the output of the function document.write(), which will then also be interpreted as HTML.

If you look at the Web console in your browser, you’ll see both the JavaScript statement and the HTML elements it inserts into the Web page:

Web Inspector with JavaScript Code

Multiple Loops in a List

The Declaration’s list of abuses actually contains a sublist of “pretended Legislation”; the variant file http://ats.amherst.edu/software/web/dynamic/doiabuses2.js includes them as an array with an array:

var abuses = [
    "He has refused his Assent to Laws, the most wholesome and necessary for the public good.",
    "....",
    "He has combined with others to subject us to a jurisdiction foreign to our constitution, and unacknowledged by our laws; giving his Assent to their Acts of pretended Legislation:",
    [
        "For Quartering large bodies of armed troops among us:",
        "....",
        "For suspending our own Legislatures, and declaring themselves invested with power to legislate for us in all cases whatsoever."
    ],
    "...."
]

We can rewrite the code we used previously to test for an array instead of a string, and if present include another loop that writes a sub-list instead of just outputting a string:

<ul class="abuse">
    <script>
        for (a = 0; a < abuses.length; a++)
            if (typeof abuses[a] == 'string')
                document.write('<li>' + abuses[a] +'</li>');
            else
                if
(abuses[a].length)  // if not an array, .length is undefined, which is converted to false
                {
                    document.write('<ul class="abuse">');
                    for (b = 0, abuses2 = abuses[a]; b < abuses2.length; b++)
                        document.write('<li>' + abuses2[b] +'</li>');
                    document.write('</ul>');
                }
                // else skip this item
    </script>
</ul>


<ul class="abuse">
    ....
    <li>He has combined with others to subject us to a jurisdiction foreign to our constitution, and unacknowledged by our laws; giving his Assent to their Acts of pretended Legislation:</li>
    <ul class="abuse">
        <li>For Quartering large bodies of armed troops among us</li>
        ....
    </ul>
    <li>He has abdicated Government here, by declaring us out of his Protection and waging War against us.</li>
    ....
</ul>

Lists inside of lists are further indented, and use different symbols to distinguish them (though they have been suppressed in this case).

Note that the attribute class="abuse" must be repeated for the sublist <ul> because, unlike <li> elements, the latter will not inherit the styles of the parent <ul>.

Problem 4: Multiple Loops in a Table

The Declaration’s table of signers can be saved in its own file as a JSON array of arrays:

var signers = [
    [ "Georgia", [ "Button Gwinnett", "...." ] ],
    "....",
    [ "Connecticut", [ "Roger Sherman", "...." ] ]
]

Write code to step through these arrays to create a table in the format shown in the previous chapter.


Functions

Functions are a way to package blocks of statements so that you can hand them a set of values, from which they may return a calculated result or produce some other effect.

We’ve already seen a number of built-in JavaScript functions like parseInt(), but you can also define your own!

Function definitions are commonly placed in the <head> of a document, either directly or in external script files, since they don’t actually do anything until they are called, and at that point their definition must already be known to the browser.

Constructing Functions

Function definitions all begin with the keyword function followed by an optional function name and then a list of arguments inside of parentheses and separated by commas, and then a block of statements enclosed in curly braces.

For example, a function to test if a person is one of the signers of the Declaration can be written with a statement like:

function issigner(person)
{
    var signers = [ 'Adams', 'Paine', 'Gerry', 'Hancock' ];
    for (var s = 0; s < signers.length; s++)
        if (person == signers[s]) return true;
    return false;
}

where person is an argument that is assigned its value when the function is evaluated or called.

The function expression will be replaced in its surrounding statement by its return value, which is the value following the keyword return, if any:

issigner('Hancock')

true

Function arguments such as person are local variables that are hidden from outside the function. They will also take precedence over the same symbols defined outside, which are global variables.

By declaring the variable signers inside the function with the keyword var, it also becomes a local variable, and won’t be confused witht the same variable name used outside the function.

Anonymous Functions

It’s also possible to define anonymous functions using an expression rather than a statement as above, so that the result can be assigned to variables. The above could have been written:

issigner = function(person) { /* Statements here */ }

Anonymous functions can be assigned to properties of objects, when they are commonly called methods of the object, and they are automatically provided with a reference to the object in the variable this:

state = { state: 'Massachusetts', capital: 'Boston', "population 1770": 266565, governor: 'Baker', "population 2010": 6.646e6 };

state.display = function() { return this.capital + ', ' + this.state }

state.display()

"Boston, Massachusetts"

The method above requires no arguments, but must still have an empty set of parentheses if you want to get something out of it (as opposed to just passing it around as the function reference state.display).

Question: We have already been using an object method on a regular basis; can you think of what it is? Also a couple of others occasionally!

Problem 5: Object Methods

Create a method for the following object that prints out the population for a given year:

state = { state: 'Massachusetts', capital: 'Boston', "population 1770": 266565, governor: 'Baker', "population 2010": 6.646e6 };

Callback Functions

You will also often see function references handed as arguments to other functions, when they are known as callbacks. They can be either named functions or anonymous functions.

For example, a useful array method, map, returns an array with transformed elements (the original array is unmodified):

signers.map(function(item) { return item.split(' ').pop(); })


['Adams', 'Adams', 'Paine', 'Gerry', 'Hancock']

This method allows you to hand the array a callback function that is applied to each item in the array in turn. The callback here receives each element of the array of signer names through the argument item, then splits it into an array of individual name strings at the spaces, e.g. [ 'Robert', 'Treat', 'Paine' ], and then pops the last element from the array and returns it for inclusion in the output array.

The full specification of the callback in this case is function(item, index, array) { … }. The second argument index is the position of the item in the array. The third argument array refers to the array itself and could be used to reference other characteristics such as its length. In particular, array[index] == item.

Problem 6: A Programmatic Flag

Rewrite the earlier SVG for the US flag so that its repetitive pieces are generated by JavaScript. Warning: there is a common browser bug that blocks document.write('<text>'), but if you put such statements into a function they will work properly.


The Document Object Model

When a Web browser loads an HTML document, it parses (interprets) the HTML and builds a set of programmable objects for every element and the content they enclose.

These document objects can then be directly read or written using JavaScript.


The Document Hierarchy

As noted previously, an HTML document has a hierarchical structure, with many elements being containers of content, attributes, and other elements, collectively known as nodes:

<!DOCTYPE HTML>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>The Declaration of Independence</title>
        <meta name="signer" content="Thomas Jefferson" />
    </head>
    <body>
        <h2>In <strong>Congress</strong>, July 4, 1776.</h2>
        <h1>the unanimous <span id="declaration">declaration</span> of the</h1>
        <h2>Thirteen United States of <strong>America</strong>.</h2>
        <p><img src="john_hancock_signature.jpg" width="30%" /></p>
        <hr />
    </body>
</html>

In the Document Object Model (DOM) we refer to the relationships between different nodes with familial terms. For example, the <h1> element is the parent of the <span> element and an ancestor of the text declaration, while at the same time being a sibling of the <p> and the <h2> elements, and a child of <body>. All of the nodes except for <!DOCTYPE HTML> are descendants of <html>, and all are descendants of the document itself.

Every element in this hierarchy is an object, with a set of properties and methods that can be read and sometimes set with JavaScript. There are a few standard elements, such as <body> and <title>, that can be simply referenced, e.g.

document.title    // Type this into the console!

"The Declaration of Independence"

Most other elements must be looked up in some way, commonly by letting the document find them for you, from an index it creates as it parses the document:

dec = document.getElementById('declaration')

<span id="declaration">declaration</span>

You can obtain a lot information about an element from the properties of its representative object, for example:

dec.id

"declaration"

dec.nodeType      // 1 for an element, 3 for text, 8 for comments, 9 for document, 10 for <!DOCTYPE>

1

dec.nodeName

"span"

If you know the relative location of two elements, you can also climb up and down the node tree:

h1 = dec.parentNode

<h1>

dectext = dec.firstChild

#text "declaration"

Note that, while in the second case dectext.firstChild the console prints the text in the node, this property is not actually text — it’s a text object in the DOM, which the console is helpfully identifying for you by printing some common details, viz. both the node name and value:

dectext.nodeName

"#text"

dectext.nodeValue

"declaration"

For any element you can request an array of its child nodes:

h1.childNodes     // array of all child nodes of the H1, include dec

[ 'the unanimous ', <span id="declaration">declaration</span>, ' of the' ]

Sometimes you must loop through them and test their contents to find the right one.

In addition to searching for identified elements, you can request arrays of specific element types:

image = document.getElementsByTagName('img')[1];     // array of img elements in document; select second

<img src="john_hancock_signature.jpg" width="30%" />

Attributes can be accessed in a couple of ways:

image.getAttribute('src');

"john_hancock_signature.jpg"

image.attributes[0];     // second item in an array of attributes

src="john_hancock_signature.jpg"

image.attributes[0].value;

"john_hancock_signature.jpg"

There are a number of such properties and methods available, so if you need a capability, look for it, you just might find it!


Modifying the DOM

DOM Node ModificationSome of the document and element properties above are directly modifiable:

dectext = document.getElementById('declaration').firstChild;
dectext.nodeValue = 'announcement';

"announcement"

Attributes can also be set this way (if they already exist; otherwise they must first be created):

image = document.getElementsByTagName('img')[1];
image.attributes[2].value;

"50%"

image.attributes[2].value = '40%';

"40%"

In addition to modifying the values of nodes and attributes, the document model itself can be modified by adding or removing nodes, using a set of predefined methods. As one example:

var newEm = document.createElement('em')

newEm.appendChild(document.createTextNode('John Hancock'))

newEm

"<em>John Hancock</em>"

image.parentNode.appendChild(newEm)     // inserts after last child of the <p> containing image

Again, there are a number of such properties and methods available, so if you need something, look for it. Many of them are non-standard but commonly available in browsers, e.g. innerHTML().

You may wonder, why go to all of the trouble to modify the DOM using these methods when it’s simpler to use document.write()?

document.write('<em>John Hancock</em>')

  • One reason is that a statement like the above must be placed at a specific position in the document; if what you are writing is large, you might want to delay writing it until the page framework has displayed.
  • Creating the content to be placed into document.write() can get pretty messy, and separating the text creation from that of the containing element makes your code a little cleaner when using DOM libraries such as jQuery and D3.
  • Most importantly, you might want to change the page content in response to an interactive request, without reloading the entire page.

Procedure: Multiple Loops in a List Using DOM Methods

We can rewrite the code for the list of abuses to use DOM methods rather than document.write(), as follows:

  1. Assign an id to the <ul> to make it easy to find:
  2. <ul id=abuses class="abuse"></ul>

  3. Look up the element:
  4. <script>
        ulAbuses = document.getElementById('abuses')
    </script>

  5. Create a function for the repetitive task:
  6. <script>
        ....
        function createLi(text, parent)
        {
            var newLi = document.createElement('li')
            newLi.appendChild(document.createTextNode(text))
            parent.appendChild(newLi)
        }
    </script>

  7. Loop through the array of abuses, creating an <li> element for each one and appending it to its target parent <ul> element:
  8. <script>
        ....
        for (a = 0; a < abuses.length; a++)
            if (typeof abuses[a] == 'string')
                createLi(abuses[a], ulAbuses)
            else
                if (abuses[a].length)  // if not an array, .length is undefined, which is converted to false
                {
                    ulAbuses2 = document.createElement('ul')
                    ulAbuses2.setAttribute('class', 'abuse')
                    ulAbuses.appendChild(ulAbuses2)
                    for (b = 0, abuses2 = abuses[a]; b < abuses2.length; b++)
                        createLi(abuses2[b], ulAbuses2)
                }
    </script>


DOM Libraries

The process of modifying the DOM is greatly assisted by the use of libraries of JavaScript functions such as jQuery and D3, which have become extremely popular in recent years. They provide a streamlined interface for manipulating the DOM that is known in general as an Application Programming Interface (API).

jQuery is described as “a fast, small, and feature-rich JavaScript library [that] makes things like HTML document traversal and manipulation … much simpler with an easy-to-use API that works across a multitude of browsers.” The most recent version, designed for more recent browsers, weighs in at 246 KB (86 KB compressed).

D3 or “Data-Driven Documents” is described as “a JavaScript library for manipulating documents based on data [helping bring them] to life using HTML, SVG and CSS. D3’s emphasis on Web standards gives you the full capabilities of modern browsers without tying yourself to a proprietary framework, combining powerful visualization components and a data-driven approach to DOM manipulation”. It is somewhat larger than jQuery at 319 KB (144 KB compressed).

One primary advantage of these libraries is that they can hide some of the inconsistencies of different Web browsers, automatically using work-arounds when necessary. D3 is newer, though, and with the recent convergence on a more standardized Web it provides somewhat less backward compatibility.


The DOM Library jQuery

Installing jQuery

You can obtain jQuery from the Web site http://jquery.com/. Get the latest, compressed, production version and install it in the same doi folder as your main document, but in a subfolder named js (a common place to put such libraries).

The minimal (compressed) version will be faster to load because it shortens variable names and removes all unnecessary space from the library, making it smaller but also avoiding unnecessary #text node creation by your browser. It’s pretty much unreadable by humans, though.

Documentation for jQuery’s API is also available from the same site.

To use jQuery in a Web page, just place this library reference (or something similar if you’re using a different version) somewhere in the page:

<script src="js/jquery-3.7.1.min.js"></script>

It could be placed in the <head>, which would ensure that it’s loaded before it’s used, but because of its size you may prefer to place it at the end of the document (after the closing </body>), which would allow the page to first load and show at least a basic framework before it seems to pause. In any case, it’s important that the library be loaded before jQuery is first referenced.

Examining the DOM Using jQuery

jQuery is provided to your Web page as an object with many methods, and it can be referenced either with jQuery or $, a variable name that is assigned to this object for simplicity.

jQuery uses the notation of CSS selectors to find elements of interest and provides streamlined methods to operate on them. For example, instead of the previous cases where we used document.getElementById('declaration') to select an element by ID, we can use the # to lead the ID and let jQuery determine how to look it up:

declaration = $('#declaration') // Selects the element with this ID

Object [<span id="declaration">] (1)

Similarly we can use the . to lead a class name, as a compact alternative to using document.getElementsByClassName('signer'):

signers = $('.signer') // Selects all elements with the class="signer"

Object (7)
0 <td class="signer">Samuel Adams</td>
1 <td class="signer">John Adams</td>
2 <td class="signer">Robert Treat Paine</td>
3 <td class="signer">Elbridge Gerry.</td>
4 <td class="signer">Stephen Hopkins</td>
5 <td class="signer">William Ellery</td>
6 <h1 class="signer">John Hancock.</h1>

With no leading special character and the name of an element, jQuery will select elements with that name using document.getElementsByTagName('img'):

images = $('img')       // Selects all image elements

Object [<img>] (3)
0 <img src="https://cdn.loc.gov/service/pnp/cph/3a30000/3a30000/3a30000/3a30084_150px.jpg" width="244" title="John Hancock, an Engraving by J.B. Longacre from a Painting by Copley" alt="Portrait of John Hancock, facing right."
1 <img src="john_hancock_signature.jpg" width="23%" title="John Hancock’s Signature" alt="John Hancock’s Signature, from the Original Declaration of Independence">
2 <img src="https://upload.wikimedia.org/wikipedia/commons/4/49/File-Goddard_broadside.jpg" width="30%">

The result of these expressions is a jQuery selection object that contains the matched elements as numbered properties (so that it behaves like an array), along with additional information that jQuery can later reference.

signers[6] // Returns just the seventh element

<h1 class="signer">John Hancock.</h1>

The above result is the DOM element itself; jQuery also provides its own method .eq() to pick out individual items, but with the result remaining as a jQuery selection:

signers.eq(6)

Object (1)
0 <h1 class="signer">John Hancock.</h1>

The advantage of continuing to use a jQuery selection is that you can keep using its methods, e.g. to determine an element’s text content as an alternative to using signers[6].firstChild.nodeValue:

signers.eq(6).text()

"John Hancock."

Or to determine a particular attribute’s value:

sigImg = images.eq(1)
sigImg.attr('title')

"John Hancock’s Signature"

Selecting just the contents of nodes is simplified:

signers.contents()

Object (7)
0 "Samuel Adams"
1 "John Adams"
2 "Robert Treat Paine"
3 "Elbridge Gerry."
4 "Stephen Hopkins"
5 "William Ellery"
6 "John Hancock."

And then you can find particular values using a filter, similar to using Array.forEach() where a callback function that returns either true or false is applied to each item selected:

signers.contents().filter(
    function(index, item) {
        return item.nodeType == 3 && item.nodeValue.indexOf('Adams') != -1;
    }
)

Object (2)
0 "Samuel Adams"
1 "John Adams"

Notice the chaining of methods together here, the result of each method is passed on to the next, which works as long as they produce a jQuery selection object.

Modifying the DOM Using jQuery

To set an attribute, use the attr() method again, but with a second argument, the new value:

sigImg.attr('title', 'John Hancock’s “John Hancock”')

Object (1)
0 <img src="john_hancock_signature.jpg" width="23%" title="John Hancock’s “John Hancock”" alt="John Hancock’s Signature, from the Original Declaration of Independence">

Finally, creating and inserting new elements into the DOM is much easier:

  1. Create an unattached element in the DOM using the notation $('<p>') (which is distinguished from $('p'), which selects all paragraph elements); the result can then be immediately modified with a certain style and content by chaining methods:
  2. trueCopy = $('<span>')
        .css('font-family', 'cursive')
        .css('float', 'left')
        .text('Attest Chas Thomson secy A True Copy.')

    Be aware that the method .css() directly affects an element’s attribute style, so if possible use .attr('class', 'value') or .attr('id', 'value') to reference a pre-defined style and keep your style definitions in one place.

  3. Use another jQuery method to insert the new element in place, for example the methods sibling.before(newElement) or sibling.after(newElement) to place a new element immediately before or after its future sibling, whatever their parent:
  4. sigImg.before(trueCopy)

An alternative to the latter are the methods newElement.insertBefore(sibling) or newElement.insertAfter(sibling), which can be used in chaining to somewhat simplify the code:

trueCopy = $('<span>')
    .css('font-family', 'cursive')
    .css('float', 'left')
    .text('Attest Chas Thomson secy A True Copy.')
    .insertBefore(sigImg)

If a sibling is not known, or if there are none, the parent of the “future child” must be determined and used as the base object, with one of a number of approaches:

  • Use parent.prepend(newElement)or parent.append(newElement) to place a new child element before or after all of its siblings, or
  • Use newElement.prependTo(parent) or newElement.appendTo(parent) to place a new child element before or after all of its siblings.

Using jQuery we can redo the generation of the list of abuses as follows:

<ul class="abuse"></ul>    <!-- Provide an anchoring element for the following script to reference -->
    <script>
        ul = $('ul.abuse').eq(0)
        for (a = 0; a < abuses.length; a++)
            if (typeof abuses[a] == 'string')
                $('<li>').text(abuses[a]).appendTo(ul)
            else if (abuses[a].length) // if not an array, .length is undefined, which is converted to false
            {
                ul2 = $('<ul>').attr('class', "abuse").appendTo(ul)
                for (b = 0, abuses2 = abuses[a]; b < abuses2.length; b++)
                    $('<li>').text(abuses2[b]).appendTo(ul2);
            }
            // else skip this item
    </script>

Warning: because the browser loads the HTML in order and this jQuery code references this particular <ul class="abuse"> element, it must follow the element in the document or it won’t be found by the underlying .getElementById() search.

This JavaScript code affects the visibility of the document text; otherwise, such code is commonly placed at the end of the document between the </body> and </html> tags to ensure it’s loaded last.

If the code doesn’t refer to specific elements, e.g. defining variables and functions, it can also be placed in a separate .js file that’s referenced in the document <head>.

jQuery Summary

jQuery uses CSS selector notation to determine what you are trying to do when you call it:

  • $('element'): select all elements in the document of type element;
  • $('.class'): select all elements in the document with class class;
  • $('element.class'): select all elements in the document of type element with class class;
  • $('#id'): select the one element in the document with id id;
  • $('<element>'): create an element of type element and select it.

jQuery selections are arrays of objects that provide references to the matching elements, along with many methods to query and transform them.

Once you have a jQuery selection of elements, you can “chain” methods to it to narrow the selection or find related elements, for example:

  • selection.eq(n): select the (n + 1)-item in the jQuery selection.
  • selection.filter(callback): select the items in the jQuery selection for which the callback function callback, applied to the elements referenced by them, returns true.
  • selection.parent(): select the parent(s) of the items in the jQuery selection.
  • selection.children(): select the children of the items in the jQuery selection.

You can also “chain” other methods to query or transform them, for example:

  • selection.attr(attribute): get the value of the attribute attribute for the selected nodes.
  • selection.attr(attribute, somevalue): set the value of the attribute attribute to somevalue for the selected nodes.
  • selection.text(): get the text content of the selected nodes as a character string.
  • selection.text(sometext): set the text content of the selected nodes to sometext.
  • selection.text(sometext).css(somestyle, somevalue): set the text content of the selected nodes to sometext and then set their style somestyle to somevalue.

If you have created a new element with $('<element>'), you can insert it into the DOM with a number of methods:

  • parent.prepend(newElement) or newElement.prependTo(parent): insert a new element before all other children of a future parent element.
  • parent.append(newElement) or newElement.appendTo(parent): insert a new element after all other children of a future parent element.
  • sibling.before(newElement) or newElement.insertBefore(sibling): insert a new element immediately before a future sibling element.
  • sibling.after(newElement) or newElement.insertAfter(sibling): insert a new element immediately after a future sibling element.

The second example in each case allows chaining from the original creation expression, e.g. $('<element>').attr(attribute, somevalue).appendTo(parent).

Problem 7J: Multiple Loops in a Table, Reprise

Rewrite Problem 4: Multiple Loops in a Table to use jQuery rather than document.write().


The DOM Library D3

Installing D3

You can obtain D3 from the Web site http://d3js.org/. Get the latest version and install it in the same doi folder as your main document, but in a subfolder named js (a common place to put such libraries), with its pieces grouped together in another folder d3.

To use D3 in a Web page, just place this statement (or something similar if you’re using a different folder) somewhere in the page:

<script src="js/d3/d3.min.js" charset="utf-8"></script>

This could be placed in the <head>, which would ensure that it’s loaded before it’s used, but because of its size you may prefer to place it at the end of the document (after the closing </body>), which would allow the page to first load and show at least a basic framework before it seems to pause.

The minimal (compressed) version will be faster to load because it shortens variable names and removes all unnecessary space from the library, making it smaller but also avoiding unnecessary #text node creation by your browser. It’s pretty much unreadable by humans, though.

Note that D3 requires the UTF-8 character set; if this is established in the header of your document as described earlier, the attribute in the script element above is unnecessary.

Modifying the DOM Using D3

D3 makes it somewhat simpler to modify pieces of a document, by using the notation of CSS selectors to find elements of interest and by providing streamlined methods to operate on them. For example, in the previous cases:

d3.select('#declaration').text('announcement');  // Selects the element with this id

"announcement"

d3.select('img').attr('width', '40%');     // Selects the first <img> element found

"40%"

We can also select existing node references, and easily add elements within them:

image = document.getElementsByTagName('img')[1];
d3.select(image.parentNode).append('em').text('John Hancock');


<p>
    <img src="john_hancock_signature.jpg" width="30%" />
    <em>John Hancock</em>
</p>

The power of D3 can be seen by revisiting the list of King George’s abuses:

<script src="js/doiabuses.js"></script>


var abuses = [ "He has refused his Assent to Laws, the most wholesome and necessary for the public good.", .... ];

which we earlier wrote into the document using straight JavaScript and the CSS class selector li.abuses:

<ul>
    <script>
        for (a = 0; a < abuses.length; a++)
            document.write('<li class="abuses">' + abuses[a] +'</li>');
    </script>
</ul>

If we uniquely identify this <ul>:

<ul id="abuses"></ul>

then we can use D3 to replace the above script with a single expression, using a set of methods chained together:

<script>
    d3.select('ul#abuses')          // Step 1a: Select <ul> element with id #abuses
        .selectAll('li')            // Step 1b: Select all descendent <li> elements, if any
        .data(abuses)               // Step 2: Attach data
        .enter().append('li')       // Step 3: Create additional elements, if necessary
        .attr('class','abuse')      // Step 4: Apply attributes, if desired
        .text(String)               // Step 5: Insert data
</script>

This statement should be placed somewhere after the loading of the D3 library.

The result of each D3 method is a set of element references called a D3 selection, which is then operated on by subsequent methods in the chain, as follows:

  1. Select the containing elements in the document where the data will be inserted, e.g. this particular ul element and then its child li elements (if any);
  2. Attach a list of data to this selection, e.g. abuses;
  3. Create and append additional containing elements as necessary to produce a one-to-one correspondence with the data;
  4. Apply a set of attributes to the containing elements, possibly in a way that depends on the data;
  5. Process the data in some way and insert it into the containing elements as text nodes.

The result is a simple and powerful procedure for building parts of Web pages.

Procedure: Inserting Data into the DOM Using D3

  1. To select the data container elements, you can use CSS notation for element, class, and ID selectors, for example:
  2. selection = d3.select('ul#abuses')

    [ Array[1] ]

    The select() method searches the document tree for the first element <ul id="abuses"> that it comes across. In this case there should be just one such element, since id attributes should be unique.

    To select all elements matching a selector, use selectAll() instead.

    The found elements are returned, along with references to their parents, as a D3 selection, which is an array of arrays of child elements, one array per parent element.

    In the example above, the one array returned corresponds to <body> and it has one sub-array, which references <ul id="abuses"> .

    Once the location in the document is selected, we can narrow the search to descendant elements that are of the particular type we want to insert our data into:

    selection = d3.select('ul#abuses').selectAll('li')

    [ Array[0] ]

    If no matching elements are found, the D3 selection will consist of the previous set of child elements, now reselected as parent elements, ready to have child elements added to the sub-array.

    In the example above, the one parent element returned is <ul>, and since it has no children the sub-array has length 0.

  3. Once the D3 selection is set, additional containing elements can be appended to the parents as necessary. To determine how many containing elements are actually required, we attach the list of data to the selection, e.g. the 18 abuses:
  4. data = selection.data(abuses)

    [ Array[18] ]


  5. To now create just enough containing elements, we apply the methods enter() followed by append():
  6. containers = data.enter().append('li')

    [ Array[18] ]

    The data is now associated, one-to-one, with the containing elements, but is still just a part of the D3 selection, and is not yet visible on the page.

  7. If there are attributes that we’d like to assign to the containing elements, we apply the method attr():
  8. containers.attr('class', 'abuse')

    [ Array[18] ]

  9. Finally, we create #text nodes in the containing elements, one for each piece of data:
  10. containers.text(String)


    <ul id="abuses">
        <li class="abuse">He has refused his Assent to Laws, the most wholesome and necessary for the public good.</li>
        ....
    </ul>

    The method text() requires a function as an argument that will take the corresponding piece of data and process it; the function String is really the string object constructor, and if given text will simply return it; the same effect would be provided by function(d) { return d; }.

Problem 7D: Multiple Loops in a Table, Reprise

Write the table of Declaration signers using D3. The data is in http://ats.amherst.edu/software/web/dynamic/doisigners.js and is stored as an array of arrays of states and their signers (another JSON format), which are also stored in an array:

[
    "....",
    [
      "Massachusetts Bay",
      [ 'Sam Adams', 'John Adams', 'Robert Treat Paine', 'Elbridge Gerry', 'John Hancock' ]
    ],
    "...."
]

Hint: D3’s data() method can take either an array of values as in the example above, or a function to process data — in which case it passes in data that it already knows about, such as the second-level arrays that may have been distributed to <tr> elements with a first call to data(). In the second call you’ll just want to hand it back with .data(function(datum) { return datum; }). In the .text() method that processes each of these arrays, you’ll need to distinguish between the first and second items, which you can do using the operator typeof, returning 'string' and 'object', respectively.

Another Hint: There’s also a JavaScript method to join an array of strings into a string!

Challenge: Sometimes you need to do more than just return some text, in which case the D3 method html() will interpret HTML and insert it into the DOM. Can you use that to format the signers’ names as in the original document?

Making a Diagram Using D3

We can express the same set of data described in the previous problem using objects, stored in http://ats.amherst.edu/software/web/dynamic/doisigners2.js , which provide labels that could be used in a graphic tree structure:

var signers = {
    name: "Continental Congress",
    content: [
        "....",
        {
            name: "Massachusetts Bay",
            content: [
                { name: "Samuel Adams" },
                { name: "John Adams" },
                { name: "Robert Treat Paine" },
                { name: "Elbridge Gerry" }
            ]
        },
        "....",
    ]
}

This has a clear hierarchical structure using an object with two properties, name and content, and the latter is an array of objects with the same properties. This could continue to an arbitrary depth until the content property is not included, indicating a “leaf” of the tree.

The basic procedure for creating trees is described in this set of D3 documentation, but it’s generally helpful to start with some example code such as this. Transformed for our purposes:

Problem 8: A D3 Sunburst

An alternative tree display format is the Sunburst:

It has the same data format as the tree above, but with the keyword children instead of content:

var signers = {
    name: "Continental Congress",
    children: [
        "....",
        {
            name: "Massachusetts Bay",
            children: [
                { name: "Samuel Adams" },
                { name: "John Adams" },
                { name: "Robert Treat Paine" },
                { name: "Elbridge Gerry" }
            ]
        },
        "....",
    ]
}

  1. The data format provided for Problem 10 that is stored in doisigners.js is simpler than that provided in the previous example (which matches the above), doisigners2.js, though less descriptive. It is probably a more likely format to find your data. Write code to transform the former into the latter.

    Hint: Write a recursive function that calls itself to write the key-value pairs name and children, returning when it reaches array values that are not arrays. The testing is similar to that done for Multiple Loops in a List above, but more general in that it allows for multiple levels instead of a predetermined depth.
  2. Use the sample code provided for the Sunburst with this data, and include labels for each sector. What would an appropriate weight be for each? Hint: Look at the code for the previous tree to see how they write the labels.

Previous:
Static Web Content

Dynamic Web Content

Next:
Interactive Web Content

Top of Page  | Using IT DocumentationIT Home  |  IT Site Map  |  Search IT