GDI Logo

Intermediate JavaScript

Class 2: DOM, Functions, and Events

View the slides at github.com/gdiseattle/gdi-intermediate-js.

Functions

function add(a, b) {
return a + b;
};

add(5, 4);

Review

  • Declaring and defining
  • Arguments or parameters
  • Function name
  • Function body
  • Invocation

ES6 Functions: http://bit.ly/2phTmIi

In JavaScript functions can be created either through a function declaration or a function expression. A function declaration is the "normal" way of creating a named function.

Named Function Declaration

// Named function declaration
function myFunction () { /* logic here */ }
  • Defines a function and gives it a name
  • Standalone
  • Cannot be nested in non-function blocks (inside your other code)

Function Expression

// Assignment of a function expression to a variable
var myFunction = function () { /* logic here */ };

// Assignment of a function expression to a property
var myObj = {
	myFunction: function () { /* logic here */ }
};
  • Defines a function as a variable or property of an object
  • Functions can be named or anonymous
  • Cannot start with function

Named Function Expression

Yes, we combined them!

var nameImprover = function huzzah(name, adj) {
return 'Admiral ' + name + ' Mc' + adj + 'pants';
};

The function name is scoped to the defined function.

Hoisting

Let's say you have a function defined with a function declaration, and you try to call it in your code before it's defined in the code:

saySomething('hello');

function saySomething(text) {
console.log(text);
}

ES6: https://codepen.io/niccipeeps/pen/ZxWpeQ

It works! The function gets hoisted, or moved to the top of its scope.

This is both good and bad. Good because it keeps our code from throwing errors. Bad because it allows us to develop bad habits in our code.

Hoisting

Function Expressions

Function expressions do not get hoisted. So this won't work:

saySomething('hello');

var saySomething = function(text) {
console.log(text);
}

You must define the function expression before it's called.

var saySomething = function(text) {
console.log(text);
}

saySomething('hello');
ES5: http://bit.ly/intermediate-js-function-expressions
ES6: http://bit.ly/2DrkTfo

Declaration vs Expression

Generally, best practices favor using expressions over declarations for defining functions.

  • Expressions help us to enforce good coding habits.
  • Plus, you can use expressions in places in your code where you can't use declarations - like inside an if statement.

Immediately-Invoked Function Expression

Yo dawg, I heard you liked functions...

We can also use a function expression to create a function that is invoked immediately - an IIFE.

// Anything within the parentheses is part of an expression
(function () { /* logic here */ }());
or
(function () { /* logic here */ })();

It starts with a ( not with function.

Immediately-Invoked Function Expression

Why?

The primary reason to use an IIFE is to obtain data privacy. But other reasons include:

  • Saves space in the global namespace (frees up a variable name)
  • Self-documenting style choice
  • Avoids accidentally be invoked more than once

Wikipedia has some great examples - https://en.wikipedia.org/wiki/Immediately-invoked_function_expression

Arguments and IIFEs

We can use the parentheses to pass arguments to an IIFE:

(function sayHello(name) {
alert("hello, " + name + "!");
})('Natalie');

Function Arguments

Inside a function, we can use the arguments keyword to fetch an array of all the arguments passed to a function.

var addTwo = function(a, b) {
console.log(arguments); // logs [3,10]
return a + b;
};

console.log(addTwo(3, 10, 11, 14, 16));

http://bit.ly/intermediate-js-function-arguments

arguments

var addMany = function() {
console.log(arguments);
var sum = 0;
for (var i=0; i < arguments.length; i++) {
	sum += arguments[i];
}
return sum;
};

console.log(addMany(3, 10, 57, 24));
console.log(addMany(3, 10, 57, 24, 200, 300));

ES6: http://bit.ly/2FIyXTI

Setting default values for arguments

var nameLogger = function(name, adj) {
if(adj === undefined) adj = "Fancy";
var newName = 'Admiral ' + name + ' Mc' + adj + 'pants';
console.log(newName);
};

ES6: http://bit.ly/2tOaSJn

Let's Develop It!

Exercise instructions

this

Each time we create a function, JavaScript creates a keyword called this.

We use this similarly to a variable, but we can't change the value.

The keyword this refers to the object inside which the function is created.

Huh?

Let's look at some examples.

this inside methods of objects

var natalie = {
living: true,
age: 42,
getAge: function(){
	return this.age;
}
};

console.log(natalie.getAge());

Here our function is a method inside an object - so this refers to the object, natalie


http://bit.ly/intermediate-js-this

this inside callback functions

HTML DOM Events

var btn = document.getElementById('myBtn');

btn.onclick = function(){
var text = this.innerHTML;

console.log(text);
};

A callback function is connected to a DOM element through an event handler - so this refers to the DOM element that fired the event - in this case, the button that was clicked.

But wait...what's the object here?

function trySomething(){
console.log(this);
}

trySomething();

The window object

A window object is created in the browser as soon as the JavaScript interpreter starts.

All globally-scoped functions and variables are actually properties and methods of the window() object.

The window object

var num = 7;
var name = "Natalie";

console.log(window.num);
console.log(window.name);

function sayHello() {
console.log("Hello there!");
}

window.sayHello();
http://bit.ly/intermediate-js-window-object

Functions are objects

Function on Function Action

Don't forget! Functions in JavaScript are objects.

They have properties and methods just like any other object.

Let's take a look at a few methods.

bind()

Use the bind() method to explicity set the value of this without invoking the function immediately.

this.name = 'Ollie'
var cat = {
name: 'Artie',
callCat: function() {
	console.log('Come here, ' + this.name);
}
};
cat.callCat(); //Come here, Artie

var catCall = cat.callCat;
catCall();
// Come here, Ollie  - The function gets invoked at the global scope

// global var name with module's property name
var catCall = cat.callCat.bind(cat);
catCall(); //Come here, Artie
http://bit.ly/2GtTxIG

call()

Invokes the function, allows you to explicity set the value of this and pass in arguments one by one

function Animal(name, age) {
this.name = name;
this.age = age;
}

function Cat(name, age) {
Animal.call(this, name, age);
this.category = 'cat';
}

function Dog(name, age) {
Animal.call(this, name, age);
this.category = 'dog';
}

var cat = new Cat('Artie', 5);
var dog = new Dog('Roscoe', 40);
http://bit.ly/intermediate-js-call

Other Functions

Check them out here

  • apply()
  • isGenerator()
  • toSource()
  • toString()

Inner functions

Functions can be nested inside one another:

function showRelationship(personName, catName) {
var intro = "Please meet ";
function makeSentence() {
	return intro + catName
	+ ' and his human, ' + personName;
}
console.log(makeSentence());
}
showRelationship('Natalie', 'Ollie');

An inner function has access to three scopes: its own scope, the enclosing function scope, and the global scope.

http://bit.ly/intermediate-js-inner-functions

Questions?

Questions about functions before we move on?

The DOM: Review

Sample HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Test Page</title>
<style>
	h1 {
		color: red;
	}
</style>
</head>
<body>
<h1>My Page</h1>
<p>Hello World!</p>
<img src="http://placekitten.com/200/300" alt="cat"/>
</body>
</html>

The DOM: Sample Model

Simple DOM Tree

DOM

  • The DOM is a data structure called a tree
  • Each point of data is called a node
  • Each node can have a parent, child, and/or sibling nodes
  • The DOM is accessed by a global variable (object) called document
  • We can also use dot notation to access methods and properties

Identify

Simple DOM Tree
  • Parent/child
  • Siblings

How JavaScript sees the DOM

document = {
head: {
	children: [ ... ] // contains all child nodes
},
body: {
	children: [ ... ] // contains all child nodes
	hasChildNodes: function() {
		//returns true or false
	}
},
getElementById: function(arg) {
	//returns matching node list
}
...
}

Note: This is not complete! But gives an idea...

Let's Develop It!

In the brower console, try these...

document.body
document.body.children
document.querySelectorAll('p');

 

What other properties and methods do we know?

Selecting an array of nodes

By CSS selector:

document.querySelectorAll('section h2');

By tag name:

document.getElementsByTagName('div');

By class name:

document.getElementsByClassName('upper');

Selecting a single node

By ID:

document.getElementById('kittenPic');

By CSS selector:

document.querySelectorAll('section h2')[0];
document.querySelector('section h2');

By tag name:

document.getElementsByTagName('div')[2];

By class name:

document.getElementsByClassName('upper')[1];

Questions?

Any questions on selecting or creating DOM nodes?

Events

Event
Something that happens
Callback
Function that executes after the event happens
Event Listener
A method that binds a callback to an event

Event Listeners

So far, we have only used specific event handler properties to associate a function with an event.

These properties have a limitation: you can only assign one callback per event.

var btn = document.getElementById('testButton');
btn.onclick = function() { console.log('did stuff #1'); };
btn.onclick = function() { console.log('did stuff #2'); };

If you click the button, you'll see that only did stuff #2 is logged to the console. We're assigning properties to an object, and the second one simply overwrites the first.

The addEventListener() method

If we want to get fancy (or just build more stable and flexible code), we can use the addEventListener() method instead.

element.addEventListener(event, callback);

addEventListener() with an anonymous function

var myButton = document.getElementById('testButton');

myButton.addEventListener('click', function() {
console.log('button was clicked!');
});

 

This works, but we can't remove this callback later on because we don't have a way to call it - it's anonymous and has no name.

addEventListener() with a named function

var myButton = document.getElementById('testButton');

//store and define the function
var myCallback = function() {
console.log('button clicked!');
};

myButton.addEventListener('click', myCallback);

 

ES5: http://bit.ly/intermediate-js-addEventListener
ES6: http://bit.ly/2FVLGWe

We can remove that event listener later on:

myButton.removeEventListener('click', myCallback);

What about arguments?

Arguments get passed to the callback:

GDI
var myLink = document.getElementById('testLink');

//store and define the function
var myCallback = function(e) {
e.preventDefault();
console.log('Not going there!');
};

//no argument needed for binding the event
myLink.addEventListener('click', myCallback);
http://bit.ly/intermediate-js-arguments-and-callbacks

Let's Develop It!

Add this button to your HTML page:

<button id="messageButton">Show a message</button>

 

Now add an event listener to the button that will log a message to the console when the button is clicked.

Bonus: If you finish early, try updating the callback so it logs the number of times the button has been clicked to the console.

Event Bubbling

The DOM is a series of objects, nested inside one another: a paragraph inside a div inside the body inside the document, for example.

When an event is triggered on a nested element, it's also triggered on the parent(s), all the way up to the document object.

Really?

Event Bubbling

This is a paragraph

var div = document.getElementById('parent');

var showMessage = function(e) {
console.log('The div was clicked!');
}

div.addEventListener('click', showMessage);
http://bit.ly/intermediate-js-bubbling

The order is called a bubbling order, because an event bubbles from the innermost element up through parents, like a bubble of air in the water.

Event Target

The deepest element that triggered the event is called the target. And this is the element the event has bubbled to - the one that triggers the event handler.

The target is stored as a property of the event object.

var div = document.getElementById('parent');

var showMessage = function(e) {
console.log('You clicked on ' + e.target.nodeName);
console.log('The event was handled on ' + this.nodeName);
}

div.addEventListener('click', showMessage);

stopPropagation()

You can stop an event from bubbling up the DOM with the stopPropagation method.

var div = document.getElementById('parent');
var btn = document.querySelector('#parent button');

var showMessage = function(e) {
console.log('You clicked on ' + e.target.nodeName);
console.log('The event was handled on ' + this.nodeName);
}

var showAnotherMessage = function(e) {
e.stopPropagation();
console.log('The ' + e.target.nodeName + ' was clicked and the event didn\'t bubble.');
};

div.addEventListener('click', showMessage);
btn.addEventListener('click', showAnotherMessage);

Event Delegation

We can take advantage of event bubbling in our code.

Let's start with this HTML:

  • Maru - he likes boxes!
  • Hana - she love Maru
  • Lil Bub - she's a trooper
  • Grumpy Cat - she's the grumpiest

We want to toggle a highlight on each list item when it's clicked.

Event Delegation

We could add an event listener to each list item...or we could just listen for an event on the list:

var list = document.getElementById('awesomeCats');
var listClicking = function(e) {
var el = e.target;
while (el != this) {
	if (el && el.nodeName == 'LI') {
		toggleHighlight(el);
	}
	el = el.parentNode;
}
};
var toggleHighlight = function(el) {
var color = el.style.backgroundColor;
if (color) { el.style.backgroundColor = ''; }
else { el.style.backgroundColor = 'pink'; }
};
list.addEventListener('click', listClicking);
http://bit.ly/intermediate-js-event-delegation

Advantages of using event delegation

The addEventListener() method only fires when the page is first loaded.

If we change the page later - e.g., we dynamically add more items to the list - those new items won't have event listeners. We would have to re-add event listeners to any new items created on the page.

Or we can use event delegation instead - the container element stays on the page no matter what, and we can listen for events there.

Things to be aware of

Not all events bubble - here's a nice list of events that shows which ones bubble and which do not.

If you're not careful, you can put a lot of load on the browser - be sure to think about how often an event might be fired vs how often you'd want to trigger a callback function in response.

Let's Develop It

Given this HTML:

You can learn about about lions, tigers, or bears.

Or you could just relax and watch a video.

Use event delegation to display an alert each time a user clicks a link that asks them if they're sure they want to navigate from this page.

Questions?

Questions on event bubbling and delegation?

Homework

Two things for you to do before next week's class:

  1. Learn how to set up a local server.
  2. Work through the homework problems.