Session 25: Cats and Dogs
A more serious title for this session might be More Events on Dynamic Web Pages.
Download this starter code to use during the session.
Cats: Practice
We have been learning about event listeners and recently
learned the addEventListener()
method. For
example:
function respondToClick(e) { document.body.style.backgroundColor = "lightblue"; document.body.innerHTML = "<h1>I am a little blue today</h1>"; } let button = document.querySelector('button'); button.addEventListener('click', respondToClick);
Listeners are not limited to active HTML elements like buttons. As we saw in Session 19, any element can have a listener.
Open up
the 'cats' index page
in VS Code. The rendered page has an h1
heading,
a paragraph with a poem, a paragraph with the author's name,
and a hidden cat image. It also loads
a nearly empty script file
for us to program in.
Open the developer tools see the cats!
message
printed in the log.
- show the image
-
set the background color of the body to
lightgreen
Hints:
-
The double-click event is named
dblclick
. -
You can use
querySelector()
to get the poem and the image. -
You can remove the 'hidden' attribute from the image using,
you guessed it,
removeAttribute()
.
Any element can have a listener. There are many kinds of events.
Cats: More Kinds of Events and Listeners
Let's use the cats page to learn more about events, and to practice working with HTML, images, URLs, and the DOM. With a little JavaScript, we can create an interactive web page.
Load events
TL;DR: A web page can load images dynamically, rather than by hardcoding a URL in its HTML.
Modify the page's img
tag: remove the
src
and hidden
attributes, and
add an id:
<img id="cat-picture" alt="a kitten sitting on a stone walkway" width="300" >
Add modify the script to load a cat picture at the time the page loads:
let url = 'https://upload.wikimedia.org/wikipedia/commons/c/c1/Six_weeks_old_cat_%28aka%29.jpg'; window.onload = function(e) { let img = document.querySelector('#cat-picture'); img.src = url; img.width="300"; };
This code moves the URL from the HTML file to the script file, where it is under program control. We can make the page even more dynamic by loading an image from a web service.
cataas.com site is "Cat as a service". It provides cat pictures as a service via URLs and parameters.
// move this inside the function let url = 'https://cataas.com/cat';
We can make our code more flexible by moving the code that load the image code to a named function we can call elsewhere.
function loadCatPicture(e) { let url = 'https://cataas.com/cat'; let img = document.querySelector('#cat-picture'); img.src = url; img.alt="a cat image loaded randomly from CATAAS, 'Cat as a service'" img.width="300"; } window.onload = loadCatPicture;
Timer events
TL;DR: A web page can schedule events to occur at a later time.
Modify the script to update the picture after five seconds:
window.onload = function(e) { loadCatPicture(e); // Call the loadCatPicture() function again in 5 sec. // note: setTimeout() takes milliseconds -- 5000 msec === 5 sec. setTimeout(loadCatPicture, 5000); }
Our browser is trying to be efficient, but it's getting in our way! It notices that the new URL is the same as the previous URL, so it uses the same image it loaded before. The URL is in its cache.
Let's add something unique (and meaningless) to the URL so
that the browser won't cache the URL and so will always load
the image again. In loadCatPicture()
, add some
code to tweak the URL before setting the img.src
:
let timestamp = new Date().getTime(); url = url + '?t=' + timestamp;
This strategy works because the CATAAS server ignores the extra information on the URL. (We will learn about this kind of name/value pair in URLs, called URL parameters, in a little bit.)
We can also set timer events that run repeatedly after a delay.
Modify the script to update the picture every five seconds:
// remember: setInterval() also takes milliseconds -- 5000 msec === 5 sec. setInterval(loadCatPicture, 5000);
Click events
TL;DR: A web page can respond to the mouse clicks in the window and on arbitrary elements.
Let's return to events caused by the user. We have seen that the page can update the picture after the user clicks anywhere in the window:
window.onload = function(e) { loadCatPicture(e); } window.onclick = loadCatPicture;
We have also seen that the page can update the picture after the user clicks on a specific element, say, the poem.
In the HTML file, add an id to the poem paragraph:
<p id="poem-text">
Then replace the window's click event handler with one for the poem's text:
let poemText = document.querySelector('#poem-text'); poemText.addEventListener('click', loadCatPicture);
Key press events
TL;DR: A web page can respond to the user pressing arbitrary keys.
At the bottom of the script, add code that changes the image whenever the user presses a key:
function onKeyPressDo(e) { let keyName = e.key; console.log('Key Press event', keyName); loadCatPicture(e); } window.addEventListener('keypress', onKeyPressDo);
Now let's modify the code to respond only when the user presses 'm' or 'n':
function onKeyPressDo(e) { let keyName = e.key; if ((keyName === 'm') || (keyName === 'n')) { loadCatPicture(e); } else { console.log('Ignoring keypress event: ', keyName); } }
URL parameters
TL;DR: A web server can accept information in the form of parameter strings at the end of a URL.
The cataas.com service
supports image filters through the use of URL parameters.
Two of its filters are ?filter=mono
, which
returns a grayscale version of the image, and
?filter=negate
, which returns a negative of
the image.
Let's modify the script so that it responds to 'm' or 'n' key presses by appending a filter to the URL.
First, let's change the keypress event handler to pass a
second argument to loadCatPicture()
:
function onKeyPressDo(e) { let keyName = e.key; if (keyName === 'm') { loadCatPicture(e, 'mono'); } else if (keyName === 'n') { loadCatPicture(e, 'negate'); } else { console.log('Ignoring keypress event: ', keyName); } }
Next, let's change the loadCatPicture()
function
to use its second argument to append a fliter to the URL
string. Notice the use of a template string for the filter.
function loadCatPicture(e, filter) { let url = 'https://cataas.com/cat'; if (filter) { console.log('Using picture filter', filter); url = url + `?filter=${filter}`; } else { // the cache-busting code from before let timestamp = new Date().getTime(); url = url + '?t=' + timestamp; } let img = document.querySelector('#cat-picture'); img.src = url; img.alt="a random cat from CatAAS" img.width="300"; }
This approach works because of how JavaScript treats empty
strings. If the caller does not pass a second argument,
the filter
parameter will be the empty string,
which JavaScript interprets as false. It interprets
non-empty strings as true.
The final result is a nifty little page that illustrates several kinds of events that happen in a web page.
Dogs: Practice
We spent the first part of the session with cats, so it's only fair we spend the rest of the session with dogs.
Open up
the 'dogs' index page
in VS Code. The rendered page has an h1
heading,
a paragraph, and a dog image. It also loads two scripts:
one
that defines a JavaScript array of URLs for dog images and
another empty script file
for us to program in. The text on the page says nothing about
dogs...
- change the text of the heading to "Hounds, Hounds, Hounds!"
-
remove the
id
attribute from the heading - change the text of the paragraph to "Afghan, basset, and blood hounds"
Hints:
-
The load event is named
load
. -
You can use
querySelector()
to get the heading. -
You can remove the 'hidden' attribute from the image using
removeAttribute()
.
The source is the view of the static DOM as loaded. The element is the view of the dynamic DOM as executed.
We can fix or expand on the HTML of a web page at load time, or any other time, using JavaScript.
Dogs: More Kinds of Dynamic Behavior
Let's use the dogs page to see a couple of more examples of how we can use JavaScript to create dynamic behavior on a web page.
Window events
TL;DR: A web page can respond to load events at different levels of the DOM.
You may recall from
a slide in Session 23
that there is an event named DOMContentLoaded
.
We can define an event listener for it:
window.addEventListener('DOMContentLoaded', function(e) { console.log('window DOMContentLoaded'); });
We can also define an event listener for the dog image's load event:
let dog = document.querySelector('img'); dog.onload = function(e) { console.log('dog.onload'); };
Notice the order of the messages in the log. The window's
DOMContentLoaded
event is triggered before the
image's load
event.
Events with State
In the in-class lab exercise for Session 23, we saw that events can have state. We can add state to our dog page, too.
Dog CEO offers a dog images as a web service. It returns image URLs as part of a data object. The hounds.js script defines a long list of Afghan hound URLs as a JavaScript array. Let's enable our user to click on the dog image to retrieve the next image in line.
First, let's define a function that returns the next image URL in the list:
let pos = 0; function getNextDogURL() { pos++; if (pos === hounds.length) { pos = 0; }; return hounds[pos]; }
Then let's define a click event listener that calls the function:
function getNextDog(e) { dog.src = getNextDogURL(); } dog.addEventListener('click', getNextDog);
Scripts That Add Elements to a Page
TL;DR: A script can define HTML elements, too.
The hounds
array is really long. Perhaps our
users would like to see links to all the images so that they
can browse randomly.
We did something just like this in Session 24 when we generated a list of IDS bundles and generated a table of city data. Let's do it again for the dogs.
First, let's add a button to the web page along with a place for the list:
<div id="dog-url-list"></div> <!-- empty to start! --> <button id="more-dogs">show more dogs!</button>
Next, let's define a function, makeImageList()
,
that builds an HTML ul
element. To do that, we
will want to:
- make a new
ol
element -
for each URL in the array:
- make a new
a
element for the URL - make a new
li
element for the anchor -
add the
li
element to theol
- make a new
-
add the
ol
element to thediv
That translates into this JavaScript:
function makeImageList(e) { let ol = document.createElement('ol'); for (let i = 0; i < hounds.length; i++) { url = hounds[i]; let a = document.createElement('a'); a.href = url; a.innerText = url; let li = document.createElement('li'); li.appendChild(a); ol.appendChild(li); } let listDiv = document.querySelector('#dog-url-list'); listDiv.appendChild(ol); }
Finally, add this function as a click
event
listener on the button:
let moreDogsButton = document.querySelector('#more-dogs'); moreDogsButton.addEventListener('click', makeImageList);
There are a few possible improvements we could make to our web page:
-
The button is still there, all the way at the end of the
list. Let's remove it after we generate the list:
... e.target.remove();
-
Clicking on a link open the image in the current tab.
Set the anchor to open in a new tab:
... a.target = '_blank';
-
Note: this one is more advanced.
Feel free to skip it.
Displaying the entire URL as the anchor's text is repetitive. Let's pull the image name from the URL and use that as the anchor's text:let urlParts = url.split('/'); // string → array a.innerText = urlParts[urlParts.length - 1]; // use last element in array
The final result is a deceptively simple page that illustrates two kinds of dynamic behavior that can happen in a web page.
Bonus Challenge: When the user clicks on an item in the list, make that the image displayed at the top, a lá the 'featured image' from our lab exercise.
Bonus Challenge: Use JavaScript to create
the button and the div
and to add them to the
page, rather than editing the HTML of the page directly.
Closing
Here are the final versions of the files:
- cats: HTML | JavaScript
- dogs: HTML | JavaScript
The final project is ready for your attention. It is due in a few weeks, but don't let the time get away from you. You will want to have some time to create something you like. Feel free to use the examples from today and last week to inspire the dynamic behavior on your pages. Also feel free to ask for guidance or assistance in implementing your code, whether HTML, CSS, or JavaScript.
Midterm Exam 2 is next session: Thursday, November 21.