Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
0% found this document useful (0 votes)
9 views

Lect03 JavaScript 2

Uploaded by

deonhov22
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
9 views

Lect03 JavaScript 2

Uploaded by

deonhov22
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 62

61FIT3WPR - Fall 2024

Lecture 3
JavaScript (2)
DOM Manipulation & JS Timers
Today’s Contents

● More about event handling


● JS File Skeleton
● DOM Manipulation
○ Classes
○ Nodes
● Anonymous functions, callbacks, and this
● Debugging JS
● JS Timers
JS File Skeleton ● JavaScript “strict” mode
● The “module pattern”
● Visualization of how a DOM tree is
parsed and built by the browser
● The window “load” event
JavaScript “strict” mode

"use strict";
// your code

Writing "use strict"; at the very top of your JS file turns on strict syntax
checking:
● Shows an error if you try to assign to an undeclared variable
● Stops you from overwriting key JS system libraries
● Forbids some unsafe or error-prone language features
You should always turn on strict mode for your code!
The “module pattern”

(function() {
// statements;
})();

Wraps all of your file's code in an anonymous function that is declared and
immediately called.

0 global symbols will be introduced!

The variables and functions defined by your code cannot be


accessed/modified externally (i.e. by other JS scripts).
You should use this pattern for all of your JS files.
How the browser builds a DOM Tree
<html>
<head>
<title>My Fancy Title</title>
<meta charset="UTF-8">
<link rel="stylesheet" href="styles.css">
<script src="script.js"></script>
</head>
<body>
<header>...</header>
<nav>...</nav>
<article>
<section>
<p>Hello world: <a href="...">here</a></p>
<p>Welcome!</p>
<img src="...">
<a href="...">citation</a>
</section>
</article>
<footer>
<a href="..."></a>
</footer>
</body>
</html>
How the browser builds a DOM Tree
<html>
<head>
<title>My Fancy Title</title>
<meta charset="UTF-8">
<link rel="stylesheet" href="styles.css">
<script src="script.js"></script>
</head>
<body>
<header>...</header>
<nav>...</nav>
<article>
<section>
<p>Hello world: <a href="...">here</a></p>
<p>Welcome!</p>
<img src="...">
<a href="...">citation</a>
</section>
</article>
<footer>
<a href="..."></a>
</footer>
</body>
</html>
How the browser builds a DOM Tree
<html>
<head>
<title>My Fancy Title</title>
<meta charset="UTF-8">
<link rel="stylesheet" href="styles.css">
<script src="script.js"></script>
</head>
<body>
<header>...</header>
<nav>...</nav>
<article>
<section>
<p>Hello world: <a href="...">here</a></p>
<p>Welcome!</p>
<img src="...">
<a href="...">citation</a>
</section>
</article>
<footer>
<a href="..."></a>
</footer>
</body>
</html>
How the browser builds a DOM Tree
<html>
<head>
<title>My Fancy Title</title>
<meta charset="UTF-8">
<link rel="stylesheet" href="styles.css">
<script src="script.js"></script>
</head>
<body>
<header>...</header>
<nav>...</nav>
<article>
<section>
<p>Hello world: <a href="...">here</a></p>
<p>Welcome!</p>
<img src="...">
<a href="...">citation</a>
</section>
</article>
<footer>
<a href="..."></a>
</footer>
</body>
</html>
How the browser builds a DOM Tree
<html>
<head>
<title>My Fancy Title</title>
<meta charset="UTF-8">
<link rel="stylesheet" href="styles.css">
<script src="script.js"></script>
</head>
<body>
<header>...</header>
<nav>...</nav>
<article>
<section>
<p>Hello world: <a href="...">here</a></p>
<p>Welcome!</p>
<img src="...">
<a href="...">citation</a>
</section>
</article>
<footer>
<a href="..."></a>
</footer>
</body>
</html>
How the browser builds a DOM Tree
<html>
<head>
<title>My Fancy Title</title>
<meta charset="UTF-8">
<link rel="stylesheet" href="styles.css">
<script src="script.js"></script>
</head>
<body>
<header>...</header>
<nav>...</nav>
<article>
<section>
<p>Hello world: <a href="...">here</a></p>
<p>Welcome!</p>
<img src="...">
<a href="...">citation</a>
</section>
</article>
<footer>
<a href="..."></a>
</footer>
</body>
</html>
How the browser builds a DOM Tree
<html>
<head>
<title>My Fancy Title</title>
<meta charset="UTF-8">
<link rel="stylesheet" href="styles.css">
<script src="script.js"></script>
</head>
<body>
<header>...</header>
<nav>...</nav>
<article>
<section>
<p>Hello world: <a href="...">here</a></p>
<p>Welcome!</p>
<img src="...">
<a href="...">citation</a>
</section>
</article>
<footer>
<a href="..."></a>
</footer>
</body>
</html>
Full DOM
Each node in the tree has:
● Name of element
● Attributes, and their values
● Content (if any)
Listening to the window “load” event

You can only access HTML elements after the “load” event has fired
"use strict";
(function() {
window.addEventListener("load", init);
// no access to the document here

function init() {
// we now have access to the DOM tree!
// set up your initial document event handlers here.
}
})();
DOM Manipulation: Classes
What if I want to change the styles of an element on the page?
Hiding/Showing Elements

How can we hide an HTML element?

.hidden {
display: none;
}

In JS, it’s possible to modify the style properties of an element directly

id("my-img").style.display = "none";

● What’s wrong with the method above?


Modifying the classList
You can manipulate the DOM element’s classList with the following
methods:
Name Description

add(classname) Adds the specified class(es) to the list of classes on this


element. Any that are already in the classList are ignored.

remove(classname) Removes the specified class(es) to the list of classes from


this element. Any that are already not in the classList are
ignored without an error

toggle(classname) Removes a class that is in the list, adds a class that is not
in the list.

contains(classname) Returns true if the class is in the DOM element's classList,


false if not.

replace(oldclass, newclass) Replaces the old class with the new class.
Example: adding a class to classList

Add the hidden class to the element when you want to hide it:

id("my-img").classList.add("hidden");

Remove the class to restore the element’s styles to the state before:

id("my-img").classList.remove("hidden");

A more convenient option is to toggle the class in both cases:

id("my-img").classList.toggle("hidden");
More about event ● Multiple event handlers
● Removing event handlers
handling ● Event objects
addEventListener with multiple events
removeEventListener
As opposed to adding event listeners to an element, you can also remove them:
Event Objects!

Recall that the event handler function can be attached to objects (window,
DOM elements, etc.)

source.addEventListener("click", responseFunction);

function responseFunction(e) {
// we can access the click Event object here!
}

When the event occurs, an Event object is created and passed to the
event listener. You can "catch" this event object as an optional first
parameter to get more information about the event.
DOM Manipulation: Nodes
What if I want to add or remove elements on my page?
<body> Visualization of a DOM Tree

<header> <main>

<p> <section> <section>

Hello
<ol> <p>
World

dolor sit
<html> <span>
<html> amet ...
<li> ...

Lorem
ipsum
Creating New Elements

Name Description

document.createElement("tag" creates and returns a new empty DOM node


) representing an element of that type

// create a new <h2> node


let newHeading = document.createElement("h2");
newHeading.textContent = "This is a new heading!";

Note: Merely creating an element does not add it to the page


You must add the new element as a child of an existing element on the page...
An alias function

When creating new DOM elements using JS, you may use
document.createElement often.
You are allowed to copy this shortcut function to your JavaScript code. Just be
sure to use comments to describe it so that you and others understand what
this function does.

function gen(tagName) {
return document.createElement(tagName);
}
Other handy alias functions

It's handy to create shortcut functions to code faster as we use these


methods a lot!

function id(id) {
return document.getElementById(id);
}

function qs(selector) {
return document.querySelector(selector);
}

function qsa(selector) {
return document.querySelectorAll(selector);
}
Adding and Moving Nodes on the DOM
When you have a parent DOM node, you can add or remove a child
DOM node using the following functions:

Name Description

parent.appendChild(node) Places the given node at the end of this


node’s child list

parent.insertBefore(new, old) Places the given node in this node’s child


list just before old child

parent.replaceChild(new, old) Replaces given child with new nodes

parent.insertAdjacentElement(location, Inserts new element at given location


newElement) relative to parent

let li = document.createElement("li");
li.textContent = "A list item!";
id("my-list").appendChild(li);
Removing Nodes from the DOM
When you have a parent DOM node, you can remove a child DOM node using
the following functions:

Name Description

parent.removeChild(node) Removes the given node from this node’s child list

node.remove() Removes the node from the page

qs("#my-list li:last-child").remove();
/* or */
let li = qs("#my-list li:last-child");
li.parentElement.removeChild(li);
Removing all Nodes using innerHTML

// before js
<section id="fyi">
<p>hi</p>
<p>bye</p>
<section> let el = document.getElementById("fyi");
el.innerHTML = "";
// after js
<section id="fyi">
</section>
DOM Traversal Methods
We can use the DOM tree to traverse parent/children/sibling relationships (e.g. to
remove an element from its parent node). Every node's DOM object has the
following (read-only) properties to access other DOM nodes in the tree:

Name Description

firstElementChild, start/end of this node’s list of children elements


lastElementChild

children Array of all of this node’s children (not the same as


childNodes, which includes text)

nextElementSibling, Neighboring element nodes with the same parent, skipping


previousElementSibling whitespace nodes

parentNode The element that contains the node


parentElement (These properties are mostly the same, see differences)

These are the common traversal properties we’ll see, but you can find a complete list here
DOM Tree Traversal Example
Write JS code to get these elements using DOM Tree Traversal methods:

<div id="container"> // [#column1, #column2]


<div id="column1">
<div>1</div> // all three ways to get <div>1</div>
<div id="box2">2</div>
<div>3</div>
</div>
<div id="column2"> // <div>3</div>
<div>4</div>
<div>5</div> // <div>6</div>
<div>6</div>
</div> // #container
</div>
DOM Tree Traversal Example
Write JS code to get these elements using DOM Tree Traversal methods:

<div id="container"> // [#column1, #column2]


<div id="column1"> id("container").children;
<div>1</div> // all three ways to get <div>1</div>
<div id="box2">2</div> id("column1").firstElementChild;
<div>3</div> id("container").firstElementChild.firstElementChild;
</div> id("box2").previousElementSibling;
<div id="column2"> // <div>3</div>
<div>4</div> id("box2").nextElementSibling;
<div>5</div> // <div>6</div>
<div>6</div> id("column2").lastElementChild;
</div> // #container
</div> id("box2").parentNode.parentNode;
Recall: Event handler syntax

This doesn't, work, right?

addEventListener("click", openBox());

What if I wanted to pass a parameter into this function?

addEventListener("click", openBox(param));
Anonymous Functions

/* named function with one parameter that logs to the console */


function sayHello(name) {
console.log("Hello " + name);
}
/* Nameless functions which are assigned to variables */
let sayHello = function(name) {
console.log("Hello " + name);
}
let sayHello = (name) => { // arrow function
console.log("Hello " + name);
}
/* Equivalent function with no parens because there is only 1 parameter */
let sayHello = name => { console.log("Hello " + name); }
/* This arrow function has 0 parameter */
let sayHello = () => { console.log("Hello!"); };
Parameter passing to Event Handlers

let defaultReply = "Hello World";


button.addEventListener("click", function() {
draftReply(defaultReply);
});

How else could we do this?

let defaultReply = "Hello World";


// with an arrow function
button.addEventListener("click", () => {
draftReply(defaultReply);
});
Named Functions vs Anonymous Functions

function addElement() { ● Both 1. and 2. work


// assume ‘element’ has been successfully defined. equivalently

// example 1: named callback function ● Do not overuse annonymous


element.addEventListener("dblclick", removeElement); functions! Breaking down
your code into named
// example 2: anonymous function functions can be useful to
element.addEventListener("dblclick", function() {
reduce redundancy and keep
this.parentNode.removeChild(this);
code understandable.
});
} ● If you have more than 3 lines
of code, it should be a named
// Removes an element when dblclicked
function.
function removeElement() {
this.parentNode.removeChild(this); // or this.remove();
}
The keyword this

function init() { ● All JavaScript code actually runs


// this === window inside of "an object" that we can
id("btn1").addEventListener("click", namedFunction); access with the keyword this
● By default, code runs in the global
id("btn2").addEventListener("click", function() { window object (so this ===
console.log("this === " + this); // this === #btn2
window)
});
} ● Event handlers attached in an
event listener are bound to the
function namedFunction() { element
console.log("this === " + this); // this === #btn1
● Inside the handler, that element
}
becomes this
this in other languages

class Test { class Test { class This {


public: public void hello() { void hello() {
void hello() { System.out.println(this); print(this);
cout << this << endl; } }
} } }
}; C++ Java Dart

class Test:
def hello(self): What do these all have in common?
print(self)
Python ● They all refer to the object that owns the method.
What happens to this in JavaScript?

let defaultReply = "Hello World";


button.addEventListener("click", function() {
draftReply(defaultReply);
});

vs.

let defaultReply = "Hello World";


button.addEventListener("click", () => {
draftReply(defaultReply);
});

function draftReply(startingText) {
this.parentNode.appendNode( /*....*/ );
// ...
}
Arrow functions DO NOT bind this
We've seen how this refers to the bound element in an event handler.
However, arrow functions do not bind this the same way.

element.addEventListener("dblclick", function() {
// All good! this === element that was clicked
this.parentNode.removeChild(this);
id("result").textContent = "the element has been removed";
});

element.addEventListener("dblclick", () => {
// error! this === window
this.parentNode.removeChild(this);
id("result").textContent = "the element has been removed";
});
Comparing this in different callback functions

function init() {
id("btn1").addEventListener("click", namedFunction);

id("btn2").addEventListener("click", function() {
console.log("this === " + this); // this === #btn2
});

id("btn3").addEventListener("click", () => {
console.log("this === " + this); // this === window
});
}

function namedFunction() {
console.log("this === " + this); // this === #btn1
}
Debugging JS - ● Strategies
● JS Debugger
Tips & Tricks
Strategies

● Check if your JS file has been loaded


● Use console.log
● Read the error messages in the console
● Use the JS debugger in Chrome Dev Tools
Did Your JS File Load?

● Use the "Network" tab in Chrome Dev Tools


● Did you spell your JS filename correctly?
● Are you listening for the "load" event
● Sometimes, your browser caches the old script file
○ Press Ctrl + F5 to force refresh (download latest version from server)
console.log everything
After adding an event listener
myBtn.addEventListener("click", launchRocket);

function launchRocket() {
console.log('Launching!!!');
}

After accessing an element


let myBtn = qs('button');
console.log(myBtn);

After a complex calculation


const COLORS = ['red', 'blue', 'green'];
let randIndex = Math.floor(Math.random() * COLORS.length);
console.log(randIndex);
Read the Messages in the Console

Errors in your code are printed to the Chrome Dev Tools console

● Error message
● File the error originated from
● Line number

The console error messages even link you to the line in your code where the
error occurred!
Use the JS Debugger

Under the "Sources" tab in Chrome Dev Tools

● Set breakpoints to pause execution of your code at a desired point


● View variable values at that point
● Execute code step-by-step
● Step in and out of functions
JS Timers
Delaying and/or repeating functions with setTimeout and
setInterval
Counting Down - A Classic Loop Problem

function startCountDown() {
let count = 10;
for (let i = count; i > 0; i--) {
console.log(i + "...");
}
console.log("0!");
}

This prints a countdown to the console as soon as it's called. But what if we
want to delay each line printed by 1 second?
Setting a Timer

Function Description

setTimeout(responseFn, delayMS) Arranges to call given function after given


delayMS, returns timer id

setInterval(responseFn, delayMS) Arranges to call function repeatedly every


delayMS milliseconds, returns timer id

clearTimeout(timerID) Stop the given timer


clearInterval(timerID)

● Both setTimeout and setInterval return an ID representing the timer. A


unique identifier the window has access to in order to manage the page timers.
● If you have access to the id, you can tell the window to stop that particular timer by
passing it to clearTimeout/Interval later
setTimeout Example

<button id="demo-btn">Click me!</button>


<p id="output-text"></p>

function init() {
id("demo-btn").addEventListener("click", delayedMessage);
}

function delayedMessage() {
id("output-text").textContent = "Wait for it...";
setTimeout(sayHello, 3000);
}

function sayHello() { // called when the timer goes off


id("output-text").textContent = "Hello!";
}
setInterval Example

<button id="demo-btn">Click me!</button>


<p id="output-text"></p>

let timerId = null; // stores ID of interval timer


function repeatedMessage() {
timerId = setInterval(sayHello, 1000);
}

function sayHello() {
id("output-text").textContent += "Hello!";
}
Motivating the timerId variable

● We sometimes need to keep track of our timer(s) when managing them


between functions so we can use clearInterval/clearTimeout or
know if we have a timer already running on our page.

● When we can't keep track of them as local variables, it is good practice to


store them as module-global variables (within the scope of the module
pattern, but accessible to all functions in your program).

● These examples will assume we are writing inside a module pattern for
brevity, but you can refer to the full examples (linked on slides).
“Toggling” animation with clearInterval
<button id="toggle-btn">Start/Stop<button>

let timerId = null; // stores ID of interval timer


function init() {
id("toggle-btn").addEventListener("click", toggleMessageInterval);
}
function toggleMessageInterval() {
if (timerId === null) {
timerId = setInterval(sayHello, 1000);
} else {
clearInterval(timerId);
timerId = null; // 2. Why is this line important?
// 3. What happens if you swap the two lines above?
}
}
function sayHello() {
id("output-text").textContent += "Hello!";
}
Passing Additional Parameters to setTimeout/setInterval

function delayedMultiply() {
// 6 and 7 are passed to multiply when timer goes off
setTimeout(multiply, 2000, 6, 7);
}

function multiply(a, b) {
console.log(a * b);
}

Any parameters after the delay are eventually passed to the timer function
● Doesn't work in IE; must create an intermediate (anonymous) function to pass the
parameters
Why not just write this?

setTimeout(multiply(6, 7), 2000);


Common Timer Errors

Many programmers mistakenly write () when passing the function

setTimeout(sayHello(), 2000);
setTimeout(sayHello, 2000);

setTimeout(multiply(num1, num2), 2000);


setTimeout(multiply, 2000, num1, num2);

What does it actually do if you have the ()?


 It calls the function immediately, rather than waiting the 2000 ms
Back to our Countdown Example

Recall that this function prints each


line immediately (in order). If we want
to output each line every 1 second
(1000ms), what kind of timer should
we use?
Timed Countdown: An Initial Attempt

function startCountDown() {
let i = 10;
setInterval(function() {
console.log(i + "...");
i--;
}, 1000);
console.log("0!");
}

● What's wrong here? (remember we want a 10 second countdown printed to the


console)
● Note that we could also replace function() { ... } with () => { ... }
A Better Attempt
function startCountDown() {
let i = 10;
setInterval(function() {
if (i === 0) {
console.log("0!");
} else {
console.log(i + "...");
i--;
}
}, 1000);
}

● This is closer! But there's still something wrong...


 Our timer won't stop when we reach 0!
A working solution...
function startCountDown() {
let i = 10;
let timerId = setInterval(function() {
if (i === 0) {
clearInterval(timerId);
console.log("0!");
} else {
console.log(i + "...");
i--;
}
}, 1000);
}

When startCountDown is called, we assign a new interval to our timer and start a one
second countdown at 10.
When we reach 0, we need to clear the interval from the window's tasks
Timers Summary

● When you want to call a function after a specified delay in time, use
setTimeout.
● When you want to call a function repeatedly every X seconds, use
setInterval (though you can also use setTimeout recursively!)
● For both types of timers, if you want to stop the delay/interval you'll need a
variable to keep track of the timer id (returned by both functions) to pass to
clearTimeout/clearInterval

You might also like