GDI Logo

Intermediate JavaScript

Class 2: Functions, DOM, and Events

View the slides at gdila.org/js201.

Functions

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

add(5, 4);

Review

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

Function Declaration

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

nameImprover('Natalie', 'Sparkly');
  • Defines a named function
  • Occur as standalone constructs
  • Cannot be nested in non-function blocks

Function Expression

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

nameImprover('Natalie', 'Sparkly');
  • Defines a function as part of a larger expression syntax (usually a variable)
  • Functions can be named or anonymous
    • If named, the function name is not available outside of its own scope
  • Cannot start with function

Named Function Expression (NFE)

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

The function name is scoped to the defined function.

Recursion

NFE is handy for recursive functions, or functions that call themselves:

var factorial = function factorialCalc(num) {
	// If the number is less than 0, reject it.
	if (num < 0) {
		return -1;
	}
	// If the number is 0, its factorial is 1.
	else if (num == 0) {
		return 1;
	}
	// Otherwise, call this recursive procedure again.
	else {
		return (num * factorialCalc(num - 1));
	}
}

console.log(factorial(3));

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);
}

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.

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');

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

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

(function sayHello() {
	alert("hello!");
})();

It starts with a ( not with function.

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));

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));

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);
};

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

this inside callback functions

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();

Functions are objects

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.

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

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

btn.onclick = cat.callCat.bind(cat);

call()

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

var cat1 = {name: 'Mabel', age: 6 };
var cat2 = {name: 'Ollie', age: 1 };

function feedCat(food) {
	console.log('Feeding some ' + food + ' to ' + this.name);
}

feedCat.call(cat1, 'salmon');
feedCat.call(cat2, 'tuna');

apply()

Invokes the function, allows you to explicity set the value of this and pass in an array of arguments.

var cat1 = {name: 'Mabel', age: 6 };
var cat2 = {name: 'Ollie', age: 1 };

function feedCat(food) {
	console.log('Feeding some ' + food + ' to ' + this.name);
}

feedCat.apply(cat1, ['salmon']);
feedCat.apply(cat2, ['tuna']);

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.

Questions?

Questions about functions before we move on?

The DOM: 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];

Let's Develop It!

Pair up with a buddy

Open gdila.org in your browser, then open the console.

One person will select a couple different elements on the page, and the other person has to use document methods to select them. You'll know you've got the right one when you hover over it and it highlights on the page (I'll demonstrate).

Take turns.

Creating a new node

Three step-process:

  1. Create and store in a variable
    var myNewElement = document.createElement('div');
  2. Edit the node
    myNewElement.innerHTML = 'I am the html inside of a brand new div tag!';
    myNewElement.className = 'new';
  3. Insert the node
    document.body.appendChild(myNewElement);
    

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);

 

Now, 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);

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);

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);

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.

Questions?

Questions on event bubbling and delgation?

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.

 

If you have questions, post in our Facebook group.