JavaScript and the browser
Table of Contents
The DOM
- Based on the received HTML document the browser builds up a model of the document structure, the Document Object Model (DOM)
- The DOM has a tree structure and each node of the document is represented by an object
- The browser renders the page based on the DOM and provides an API to read and modify the DOM with JS
- The DOM is live, i.e., DOM modifications are immediately reflected on the page
The DOM Tree
<!doctype html>
<html>
<head>
<title>Todo App</title>
</head>
<body>
<h1>Todo App</h1>
<p>Don't forget anything!</p>
<p>Please <a href="/login">login</a>.</p>
</body>
</html>
the Tree Structure of the html above:
you can traverse the DOM Tree with:
// Root node of the dom tree (html)
const root = document.documentElement;
// The child elements of a node (only element nodes, no text nodes)
// -> HTMLCollection(2) [head, body]
root.children;
// head and body can also be accessed directly
const head = document.head;
const body = document.body;
// All children of a node (including text nodes)
// -> NodeList(7) [\#text, h1, \#text, p, \#text, p, \#text]
body.childNodes;
// Accessing children and node type
body.childNodes[0].nodeType === Node.TEXT_NODE; // \#text
body.childNodes[1].nodeType === Node.ELEMENT_NODE; // h1
body.firstChild; // \#text
body.firstElementChild; // h1
// dito for last: lastChild, lastElementChild
Another example:
const h1 = document.body.children[0];
// Parent node
h1.parentNode;
// Siblings
h1.nextSibling; // \#text
h1.nextElementSibling; // p
// dito for previous: previousSibling, previousElementSibling
// Example of recursively traversing the DOM
function findAnchors(node) {
if (node.tagName === 'A') {
console.log(node.textContent+": "+node.pathname);
}
if (node.nodeType === Node.ELEMENT_NODE) {
for (let i = 0; i < node.children.length; i++) {
findAnchors(node.children[i]);
}
}
}
findAnchors(document.body);
Finding Elements
It is often not a good idea to access a specific node by a fixed path since the document structure may change over time:
// Get the href attribute of the first anchor
document.body.childNodes[5].childNodes[1].href;
There are a number of functions to find DOM elements in a more sophisticated way. The functions can be called on the document or on any element node to reduce the search on the node’s descendants
// Get the href attribute of the first anchor
document.getElementsByTagName('a')[0].href;
more examples of finding elements:
// Get a HTMLCollection of nodes by tag name
document.getElementsByTagName('a');
// Get a HTMLCollection of nodes by class name
document.getElementsByClassName('external-link');
// A single node can be retrieved by id (only via document)
document.getElementById('logo');
// Elements can also be selected by using a CSS selector string
// A static NodeList is returned
document.querySelectorAll('p a.external-link');
// Like querySelectorAll but only the first matching node is returned
document.querySelector('#logo');
Element nodes
- For different HTML elements (body, div, p, a, etc.) different objects with different properties and methods are created
- For example, an anchor object has a href and pathname property, whereas a form object has a submit and reset method
- w3schools provides a nice reference http://www.w3schools.com/jsref/default.asp
const a = document.querySelector('a');
console.log(a.href);
// -> ’https://www.todo-app.com/login’
console.log(a.pathname);
// -> ’/login’
const f = document.getElementById('myForm');
f.reset();
// -> resets all fields of the form
f.submit();
// -> submits the form
Manipulating the DOM
- Almost everything in the DOM can be manipulated with JavaScript:
- Single node properties can be changed
- Nodes can be moved within the tree
- Nodes can be removed
- New nodes can be added
const h1 = document.querySelector('h1');
// Change the color style property to ’#FF0000’
h1.style.color = '#FF0000';
// Change the display style property to ’none’
h1.style.display = 'none';
// Change the text of the h1
h1.childNodes[0].nodeValue = "Hello Earth!";
// Or more comfortable by using the textContent property (all children of h1
// are removed and and a new text node with the respective text is added)
h1.textContent = "Hello World!";
another example:
const myList = document.querySelector('ul');
// Nodes can be moved by simply re-inserting them at a different position
// -> The last node of the list is moved to the beginning of the list
myList.insertBefore(myList.lastElementChild, myList.firstElementChild);
// An element is removed by calling remove() on the node
// -> Removes the last element from the list
myList.lastElementChild.remove();
// New element and text nodes can be created. Newly created nodes must be
// added to the DOM tree explicitly. e.g. using appendChild()
// -> Creates a new list item and appends it to the end of the list
const li = document.createElement('li');
li.appendChild(document.createTextNode("New item"));
myList.appendChild(li);
// Nodes can also be created implicitly by using the innerHTML property
li.innerHTML = "An <b>important</b> item";
Animation
- The setTimeout function waits a given number of milliseconds and then calls a function
- setTimeout can be used to implement an animation
// The background color shall change between red and blue every second
const ball = document.querySelector('#ball');
let toggle = false;
// The animation function
function animate() {
ball.style.backgroundColor = toggle ? '#f00' : '#00f';
toggle = !toggle;
// Keep the animation runnning
setTimeout(animate, 1000);
}
// Start the animation
setTimeout(animate, 1000);
- setInterval is similar to setTimeout but calls the function every interval and not only once
- clearInterval and clearTimeout clear a timer set with setInterval and setTimeout respectively
const ball = document.querySelector('#ball');
let toggle = false;
function animate() {
ball.style.backgroundColor = toggle ? '#f00' : '#00f';
toggle = !toggle;
}
// Start the animation
const id = setInterval(animate, 1000);
// Stop the animation after 10s
setTimeout(function(){
clearInterval(id);
}, 10000);
- For smooth animations use requestAnimationFrame
- The scheduled animation function is called when the browser is ready to repaint the screen (at a rate of about 60 times per second but only when the window/tab is active)
- By using requestAnimationFrame the browser can optimize concurrent animations
const ball = document.querySelector('#ball');
let angle = Math.PI / 2;
function animate(time, lastTime) {
if (lastTime) {
// Compute the next position based on the elapsed time
angle += (time - lastTime) * 0.001;
}
ball.style.left = (Math.cos(angle) * 200) + 'px';
requestAnimationFrame(function(newTime) { animate(newTime, time); });
}
requestAnimationFrame(animate);
Event handling
- Normally, a GUI must not only provide data to the user but also react to user input
- A user interacts with the GUI by key strokes, mouse clicks, mouse moves, etc.
- There are also implicit user interactions like window scrolling, window resizing, etc.
- To react programmatically to user input, one could constantly read the state of the input device or adopt a polling mechanism
- A better approach is a system that actively notifies registered listeners, also called handlers, about user inputs by firing events
- Event handlers are registered for a certain event type using addEventListener and removed using removeEventListener
- Event handlers are always registered in a context and are only called for events in that context
- Event handlers can be registered on the window or on any DOM element
- Multiple handlers can be registered for the same event and the same handler can be registered for multiple events
// An event handler can be registerd for a certain event type
window.addEventListener('click', function() {
console.log("That's amazing!");
});
window.addEventListener('resize', function() {
console.log("Window has been resized");
});
// Event handlers can be registerd on DOM elements to confine the context
const myButton = docuemnt.querySelector('#my-button');
myButton.addEventListener('click', function(){
console.log('What a surprise!');
});
// Event handlers can also be removed again
function once() {
console.log('Done.');
window.removeEventListener('click', once);
}
window.addEventListener('click', once);
Event Object
- Most events are propagated through the DOM tree, thus event handlers registered on an ancestor nodes are called too
- Default propagation behaviour is bubbling (the more specific handler, the earlier it is called)
- Handlers can also be registered in the capturing phase (before the bubbling phase)
- The propagation of an event can be stopped by any handler by calling stopPropagation on the event object
Event Propagation
// Registering a click event handler on the body and a button
document.querySelector('body').addEventListener('click', function(e){
console.log("Handler on body (bubbling)");
});
document.querySelector('button').addEventListener('click', function(e){
console.log("Handler on button (bubbling)");
});
// Clicking on the button results in the following output:
// -> Handler on button (bubbling)
// -> Handler on body (bubbling)
// Register an event handler in the capturing phase
document.querySelector('body').addEventListener('click', function(e){
console.log("Handler on body (capturing)");
}, true);
// Clicking now on the button results in the following output:
// -> Handler on body (capturing)
// -> Handler on button (bubbling)
// -> Handler on body (bubbling)
// The propagation of an event can be stopped by an event handler
document.querySelector('button').addEventListener('click', function(e){
console.log("Handler on button (bubbling)");
e.stopPropagation();
});
// Clicking on the button results in the following ouput:
// -> Handler on body (capturing)
// -> Handler on button (bubbling)
Default action
- Some events have associated default actions:
- clicking on a link loads the link target
- clicking on a submit button submits the form
- pressing the down arrow scrolls the page down
- Event handlers are called before the default behaviour takes place
- If the default action is unwanted, then the event handler can call preventDefault on the event object
// The default action can be prevented by a handler
document.querySelector('a').addEventListener('click', function(e) {
e.preventDefault();
alert("Sorry, but we don't want you to leave!");
});
AJAX
- AJAX (Asynchronous JavaScript and XML) is used to create faster and more interactive web applications
- AJAX enables JavaScript to load data from the server without reloading the page
- On the protocol level, AJAX requests are just a normal HTTP requests
- The XMLHttpRequest (XHR) object is the interface for AJAX requests
- Despite the term XML in AJAX and XHR, any type of data can be loaded from the server
- ES6 introduced the new Promise based Fetch API
// Create a new XMLHttpRequest object
const xhr = new XMLHttpRequest();
// Specify the type of request: method and url
xhr.open('GET', '/ajax/time');
// Register a callback to be notified about state changes
xhr.onreadystatechange = function() {
// Check ready state: 4 means request completed
if (this.readyState === 4) {
// Check HTTP status: 200 OK
if (this.status === 200) {
// Update DOM with the response text
document.getElementById('time').innerHTML = this.responseText;
} else {
// Error handling...
console.log("Something failed!");
}
}
};
// Send the request
xhr.send();
AJAX and JSON
- The property responseText of the XMLHttpRequest object contains the HTTP response body as a string
- If the response is an XML document, the data can be accessed directly by the responseXML property
- If the response is a JSON document, the JSON string must be parsed manually
// Retrieving a JSON document
const xhr = new XMLHttpRequest();
xhr.open('GET', '/ajax/data.json');
xhr.onloadend = function() {
if (this.status === 200) {
// Parse the responseText into an object
const obj = JSON.parse(this.responseText);
console.log(obj.name+" is "+obj.age+" years old.");
} else {
console.log("Error...");
}
};
xhr.send();
Same-Origin Policy
- The same-origin policy is a browser security feature that restricts how documents and scripts from one origin can interact with resources from another origin
- Two URLs have the same origin if the protocol, host, and port are the same
- Generally, embedding a cross-origin resource using tags such as img, link, and script is permitted
- Accessing a cross-origin resource using an AJAX request is blocked by default
- Using CORS headers, a web server can explicitly allow certain hosts to access its resources
jQuery
”jQuery is a fast, small, and feature-rich JavaScript library” / jquery.com
- jQuery simplifies
- element selection
- DOM traversal/manipulation
- event handling
- animation
- AJAX requests
- Provides an easy-to-use API
- Is tested on all major browsers
- Was initially released in 2006 and used by 80% of the top 100k websites in 2022 (builtwith.com)
jQuery usage
jQuery can be downloaded and included by a single script tag:<script src="js/jquery.js"></script>
jQuery can also be loaded directly from jQuery or a CDN:<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://unpkg.com/jquery"></script>
Example:
// On document ready...
$(function() {
// Set the css property color of each div to red
$('div').css('color', 'red');
// Load some content for each selected element
$('.content').each(function(){
$(this).load('/ajax/content', {id: this.id});
});
// Register a click event handler. On each click a paragraph is added
// to the article element
$('#add-content').click(function() {
$('article').append($("<p>More content...</p>"));
});
// Make an AJAX request and insert the received HTML into the DOM
$.get('/ajax/test.html', function(data) {
$('#result').html(data);
});
});
There is no magic!
- $ is an alias for jQuery, which is a function and thus an object
- $(...) is equal to jQuery(...) and is just a function call
- $.abc() calls the jQuery’s static utility function abc
// From jQuery’s source code: jQuery is a function and $ an alias
var jQuery = function(slector, context) {
// ...
};
window.jQuery = window.$ = jQuery;
The jQuery Object
- A jQuery object references one or more DOM nodes
- jQuery objects have methods to manipulate and to access these nodes
- Manipulations are applied to each DOM node referenced by the object
- jQuery object methods are chainable
$('div')
.css('background-color', '#f00')
.addClass('whatever')
.click(function(){ console.log("Clicked!"); })
.first()
.fadeOut(400);
jQuery and AJAX
- jQuery has great support for AJAX
- Supported data types are XML, HTML, Script, JSON, JSONP, and TEXT
- Besides the basic $.ajax() function, there are a number of convenient shorthand functions:
- $.get()
- $.post()
- $.getJSON()
- .load()
- Provides simplified callback handling (success/error)
- In addition, the jqXHR object implements the Promise interface
// AJAX example with success and error callback
$.ajax({
url: "/ajax/time",
type: "GET",
dataType: "text",
success: function(data){ $("#time").html(data); },
error: function(jqXHR, status, msg){ $("#error").html("Error! "+msg); }
});
// GET example with success callback
$.get("/ajax/time", function(data){ $("#time").html(data); });
// Load example
$("#time").load("/ajax/time");
// POST example with success callback and JSON response
$.post(
"/ajax/get_person_data",
$('#form').serialize(), // request data
function(person) { $('#person').html(person.name+" is "+person.age); },
"json");