Session 16: Using JavaScript to Change the Content of a Page

Download this starter code to use throughout the session.

Reviewing Our First Tour of JavaScript

Last time, we began to learn JavaScript. We ended up writing code such as this using these features of the language:

The different kinds of value that an expressions can be, whether primitive or compound, are called data types. We used two data types in our code to compute a circle's area, numbers and strings. Last time, we also saw the boolean data type and the special value undefined. We will learn about a couple of more data types in the coming weeks, including one general category today.

Our code to compute the area of a circle works. We can make it even better by learning about JavaScript objects, which are packages that contain data and code we can use.

Once we know how to work with JavaScript objects, we can learn how to use JavaScript to interact with the HTML on a web page. That is our end goal for the day: to write a script in JavaScript that modifies the content of a web page.

JavaScript Objects and Data Properties

... or: Why Am I Defining Pi?

The first shortcoming of our circle area code is one of correctness: pi is not exactly 3.14159. We can't write the exact value of pi, of course, but we can do better with more digits of precision. Furthermore, we would like for programs to be consistent, using the same value for pi in every program.

JavaScript defines a common, more precise value of pi in the primitive Math.PI:

❯ Math.PI
← 3.141592653589793

The name used in this expression leads us to an important new JavaScript data type: Math is an object. An object is a package containing multiple parts.

Consider a typical US postal address. It consists of a street address, a city, a state, and a zip code. It might also include an apartment number or a second line associated with the street. A postal address is a thing itself, but we can also think of it in terms of its parts.

Or think of a pizza. A pizza has many parts: crust, sauce, cheese, and toppings. The toppings might include sausage, peppers, and mushrooms. Some of the parts are made up of parts themselves, such as the crust and the sauce.

Math is an object that contains a value named PI. Notice the uppercase letters... Recall from last time that JavaScript is case-sensitive.

We say that PI is a data property of the Math object. Math contains several other data properties, as you can see in the online documentation.

We use a period, ., to access a property of an object: object.property.

It turns out that JavaScript strings are objects, too. Among other things, they contain the handy data property length:

❯ let name = 'Eugene';
← undefined
❯ name.length
← 6

We will see several other useful objects in the next few weeks. Most importantly, a web browser will give us a webpage in the form of a JavaScript object!

Update our code file to use Math.PI.

JavaScript Objects and Methods

A second shortcoming of our program is one of functionality: We would like to display the area on a web page somewhere, but the program produces a string value, like any expression.

Objects have a second kind of property: methods. A method is a piece of code that can do something for us.

The Math object provides many methods, including sqrt() and floor(). It also includes pow(), which is short for 'power'. We can use pow() in our code:

> let radius = 4.2;
← undefined
> Math.PI * Math.pow(radius,2);
← 55.41769440932395

Update our code file to use Math.pow().

The String object also provides many methods. String methods are important to us because there are so few operators for working with strings. Among the string methods are toLowerCase(), substr() (short for 'substring'), and trim().

❯ let name = 'Eugene';
← undefined
❯ name.toLowerCase()
← "eugene"
❯ name
← "Eugene"
❯ let text = '      I am here.          ';
← undefined
❯ text.trim()
← "I am here."

All of these methods work like operators: they create a compound expression that has a value.

Other methods work like a statement: they take an action.

For example: The console we have been working with is itself an object, with a log() method.

❯ console.log('Hello, world');
[Log] Hello, world
← undefined
❯ console.log(Math.PI * 2 * 2);
[Log] 12.566370614359172
← undefined

The first line of output is the result of an action: writing to the screen. The second line of output is the value of the expression.

Update our code file to log its output to the console.

The Resulting Program

We have turned our code to compute the area of a circle into a new program:

let radius = 4.2;
let area = Math.PI * Math.pow(radius, 2);
console.log("Area = " + area);

Notice that our editor, VS Code, helps us with JavaScript in all of the same ways as it helps with HTML and CSS, including auto-completion and access to documentation.

Run the program using Node.js.

node.js is a standalone version of the JavaScript engine that drives Google Chrome. When we want to process a data file and create some output, node.js frees us from the browser.

We can also put code like this in an HTML script and execute it as part of a web page. Let's learn how to do that now, but in the context of a page we would like to modify. To do that, let's first learn how to access elements in a web page.

The Web Page as an Object

Why is all of this object talk, with data properties and methods, important to us as web developers? The browser gives us access to every web page as... a JavaScript object!

This image shows us this object for an arbitrary web page, along with the object's components.

a picture of the DOM tree for an arbitrary web page
a picture of the object tree for an arbitrary web page

JavaScript provides us many data properties and methods for working with the parts of the page. One of the most useful methods is document.querySelector(), which accesses an element on a page based on a CSS selector. Once we have an element, we can use its data properties innerText and innerText to see and change the content of the element.

Demonstrate the use of document.querySelector() to access a web page's elements, and the use of innerText and innerText to access and modify the content of an element.

let heading = document.querySelector('h1');
heading                                 // expand
heading.querySelector('a')
heading.innerText = "Tralfamadore";     // check...
[* reload page! -- this is client side!!]
heading.innerHTML = '<a href="https://en.wikipedia.org/">Hijacked!</a>';

This model of the web page is what we mean by the DOM, the domain object model.

Now let's use what we have learned about JavaScript and the DOM to modify a web page.

A Quick Exercise: The Road to St. Ives

St. Ives

Consider this children's rhyme:

As I was going to St. Ives, upon the road I met 7 wives.

Every wife had 7 sacks, and every sack had 7 cats, and every cat had 7 kittens.

Kittens, cats, sacks, and wives, how many were going to St. Ives?

Open a console and write one or more lines of JavaScript to compute the answer.

You can do this with variables or without variables.

Building a Solution in the Console

This is a traditional English-language nursery rhyme with many variations, from the early 1700s. The numbers get big (for children) fast.

One expression will do the job:

7 + (7 * 7) + (7 * 7 * 7) + (7 * 7 * 7 * 7)

If all we need is the answer, once, this code is fine. In general, though, we would like for our code to be more expressive — that is, to "say what it means". That helps readers, including ourselves, understand it.

One way we can do that is to give names to the parts of the expression. There are four numbers in the sum, and the rhyme tells us that each means something particular. We can use variables to name them:

let wives = 7;
let sacks = 7 * 7;
let cats = 7 * 7 * 7;
let kittens = 7 * 7 * 7 * 7;
let total = wives + sacks + cats + kittens;
total

Better! But those * 7s also mean something! There is a relationship between the number of wives and the number of sacks, the number of sacks and the number of cats, and so on. The statements don't say anything about that. We can fix it!

let wives = 7;
let sacks = 7 * wives;
let cats = 7 * sacks;
let kittens = 7 * cats;
let total = wives + sacks + cats + kittens;
total

And we have our answer, in a clean, readable sequence of JavaScript statements.

Turning the Solution into a Script

We can do even better. We can incorporate scripts written in JavaScript directly into our HTML.

This web page presents our rhyme as a riddle for the viewer. Let's use our code to write the answer on the page.

First, we add a script tag to the bottom of the body.

  ...
  <script>
  </script>
</body>

Then we copy the statements that compute the answer from the console and paste them into the script:

<script>
  let wives = 7;
  let sacks = 7 * wives;
  let cats = 7 * sacks;
  let kittens = 7 * cats;
  let total = wives + sacks + cats + kittens;
</script>

But now how do we "see" the answer? Working in the console is like having a conversation with an interpreter. The interpreter reads an expression or statement from the user, evaluates it, and prints the result. We can evaluate total to see the value 2800. Seeing the answer is built into the process. Then the console waits for the user to enter another expression.

A script is a set of explicit instructions, outside the console. To display anything, we have to do so explicitly!

In a traditional programming setting, the program would write its answer to the user's computer screen. For example, in Python, a script might say print(total). But we are working inside a web browser: our computer and screen are the browser. We show the user something within the browser.

JavaScript offers us a number of ways. Earlier in the session we learned how to use console.log() to write values to the console. We could add console.log(total); to our script. Unfortunately, this requires the user to open up the developer tools in order to see the answer, which is not something most users want to do, or even know how to do.

But there is something better. As we also learned earlier in the session, we can use JavaScript to access content on a page and to modify content on a page.

So let's write our answer on the web page itself!

First, add a paragraph to the web page that reports the answer.

<p id="answer">
  ... The answer is
</p>

We give the paragraph an id so that our script can select it.

Then, add code to the script to select the paragraph, gets its text, and append the answer to the text.

// select the paragraph to display the result
let target = document.querySelector("#answer");

// retrieve the current text of that paragraph
let currentText = target.innerText;

// add a space and the result to the current text
// and assign to the paragraph
target.innerText = currentText + " " + total;

Note the use of // to create a comment in JavaScript. The browser ignores all characters on the line after the //. We can also use the /* ... */ form of multi-line comments we used in CSS.

Reload the St. Ives page... Nice.

However, the script is now as long as the content of the web page. That makes it harder for readers to see the content and structure. As with CSS, we can move our code to an external script and link it in to our HTML.

Let's move the JavaScript code in our script to a separate file and modify the HTML page to load it in:

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

This solution is even better, with a cleaner HTML file that is easier to read. The JavaScript code resides in a different file, befitting its different purpose.

Practice for later: Use a details element to hide the answer until our young reader clicks the widget. Style the element to draw in the reader.

One last item: Why did I place the script at the bottom of the body, and not at the top of the body or even in the head?

Move the script element to the top of the body to see what happens.

The browser processes the elements of the page in order. If the script appears at the top of the body, the browser will process the script before it ever sees the paragraph with the id of "answer". The code intended to modify that element has no effect, and the paragraph will be presented as it appears in the document: "... The answer is".

(Later we will see cases where we do load scripts in the head element. These scripts include code that does not refer to the HTML on the web until we invoke it.)

Yes, But.

Here is the final version of the HTML file and the JavaScript file.

All is good... until someone changes the story. The Wikipedia page says that in some variants there are 9 wives, sacks, cats, and kittens. In a nursery rhyme for children, we could use any number of travelers: 6, 7, 8, 9, .... An Egyptian story circa 1650 BCE has a fifth category of traveler, of which there are 7 * kittens many!

Copying and pasting and changing the script becomes unbearable.

Fortunately, we can do better. And learning how will also prepare us to write code in a way that the browser will soon require. Let's pick up here in our next session.

Closing

Homework 7 will give you a chance to practice JavaScript on your own. It will go live tomorrow or early Saturday.

If you have not installed node.js yet, give it a try. Node can be a useful tool for working with JavaScript, including inside VS Code itself.

The midterm went well. Please make sure you know the names of the things we talk about all the time (elements, attributes, properties, ...). Otherwise, every conversation and every reading are a bit more work than they need to be.