The HTMbrowserification arrived in 2008. HTML5, however, was so technologically advanced in 2008 that it was predicted that it would not be ready till at least 2022! However, that turned out to be incorrect, and here we are, with fully supported HTML5 and ES6/ES7/ES8-supported browsers.
A lot of APIs used by HTML5 go hand in hand with JavaScript. Before looking at those APIs, let us understand a little about how JavaScript sees the web. This'll eventually put us in a strong position to understand various interesting, JavaScript-related things such as the Web Workers API, etc.
In this article, we will introduce you to the most popular web languages HTML and JavaScript and how they came together to become the default platform for building modern front-end web applications.
This is an excerpt from the book, Learn ECMAScript - Second Edition, written by Mehul Mohan and Narayan Prusty.
The HTML DOM
The HTML DOM is a tree version of how the document looks. Here is a very simple example of an HTML document:
<!doctype HTML>
<html>
<head>
<title>Cool Stuff!</title>
</head>
<body>
<p>Awesome!</p>
</body>
</html>
Here's how its tree version will look:
The previous diagram is just a rough representation of the DOM tree. HTML tags consist of head and body; furthermore, the <body> tag consists of a <p> tag, whereas the <head> tag consists of the <title> tag. Simple!
JavaScript has access to the DOM directly, and can modify the connections between these nodes, add nodes, remove nodes, change contents, attach event listeners, and so on.
What is the Document Object Model (DOM)?
Simply put, the DOM is a way to represent HTML or XML documents as nodes. This makes it easier for other programming languages to connect to a DOM-following page and modify it accordingly.
To be clear, DOM is not a programming language. DOM provides JavaScript with a way to interact with web pages. You can think of it as a standard. Every element is part of the DOM tree, which can be accessed and modified with APIs exposed to JavaScript.
DOM is not restricted to being accessed only by JavaScript. It is language-independent and there are several modules available in various languages to parse DOM (just like JavaScript) including PHP, Python, Java, and so on.
As said previously, DOM provides JavaScript with a way to interact with it. How? Well, accessing DOM is as easy as accessing predefined objects in JavaScript: document. The DOM API specifies what you'll find inside the document object. The document object essentially gives JavaScript access to the DOM tree formed by your HTML document. If you notice, you cannot access any element at all without actually accessing the document object first.
DOM methods/properties
All HTML elements are objects in JavaScript. The most commonly used object is the document object. It has the whole DOM tree attached to it. You can query for elements on that. Let's look at some very common examples of these methods:
getElementById method
getElementsByTagName method
getElementsByClassName method
querySelector method
querySelectorAll method
By no means is this an exhaustive list of all methods available. However, this list should at least get you started with DOM manipulation. Use MDN as your reference for various other methods. Here's the link: https://developer.mozilla.org/en-US/docs/Web/API/Document#Methods.
Modern JavaScript browser APIs
HTML5 brought a lot of support for some awesome APIs in JavaScript, right from the start. Although some APIs were released with HTML5 itself (such as the Canvas API), some were added later (such as the Fetch API).
Let's see some of these APIs and how to use them with some code examples.
Page Visibility API - is the user still on the page?
The Page Visibility API allows developers to run specific code whenever the page user is on goes in focus or out of foucs. Imagine you run a game-hosting site and want to pause the game whenever the user loses focus on your tab. This is the way to go!
function pageChanged() {
if (document.hidden) {
console.log('User is on some other tab/out of focus') // line #1
} else {
console.log('Hurray! User returned') // line #2
}
}
document.addEventListener("visibilitychange", pageChanged);
We're adding an event listener to the document; it fires whenever the page is changed. Sure, the pageChanged function gets an event object as well in the argument, but we can simply use the document.hidden property, which returns a Boolean value depending on the page's visibility at the time the code was called.
You'll add your pause game code at line #1 and your resume game code at line #2.
navigator.onLine API – the user's network status
The navigator.onLine API tells you if the user is online or not. Imagine building a multiplayer game and you want the game to automatically pause if the user loses their internet connection. This is the way to go here!
function state(e) {
if(navigator.onLine) {
console.log('Cool we\'re up');
} else {
console.log('Uh! we\'re down!');
}
}
window.addEventListener('offline', state);
window.addEventListener('online', state);
Here, we're attaching two event listeners to window global. We want to call the state function whenever the user goes offline or online.
The browser will call the state function every time the user goes offline or online. We can access it if the user is offline or online with navigator.onLine, which returns a Boolean value of true if there's an internet connection, and false if there's not.
Clipboard API - programmatically manipulating the clipboard
The Clipboard API finally allows developers to copy to a user's clipboard without those nasty Adobe Flash plugin hacks that were not cross-browser/cross-device-friendly. Here's how you'll copy a selection to a user's clipboard:
<script>
function copy2Clipboard(text) {
const textarea = document.createElement('textarea');
textarea.value = text;
document.body.appendChild(textarea);
textarea.focus();
textarea.setSelectionRange(0, text.length);
document.execCommand('copy');
document.body.removeChild(textarea);
}
</script>
<button onclick="copy2Clipboard('Something good!')">Click me!</button>
First of all, we need the user to actually click the button. Once the user clicks the button, we call a function that creates a textarea in the background using the document.createElement method. The script then sets the value of the textarea to the passed text (this is pretty good!) We then focus on that textarea and select all the contents inside it.
Once the contents are selected, we execute a copy with document.execCommand('copy'); this copies the current selection in the document to the clipboard. Since, right now, the value inside the textarea is selected, it gets copied to the clipboard. Finally, we remove the textarea from the document so that it doesn't disrupt the document layout.
You cannot trigger copy2Clipboard without user interaction. I mean, obviously you can, but document.execCommand('copy') will not work if the event does not come from the user (click, double-click, and so on). This is a security implementation so that a user's clipboard is not messed around with by every website that they visit.
The Canvas API - the web's drawing board
HTML5 finally brought in support for <canvas>, a standard way to draw graphics on the web! Canvas can be used pretty much for everything related to graphics you can think of; from digitally signing with a pen, to creating 3D games on the web (3D games require WebGL knowledge, interested? - visit http://bit.ly/webgl-101).
Let's look at the basics of the Canvas API with a simple example:
<canvas id="canvas" width="100" height="100"></canvas>
<script>
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
ctx.moveTo(0,0);
ctx.lineTo(100, 100);
ctx.stroke();
</script>
This renders the following:
How does it do this?
Firstly, document.getElementById('canvas') gives us the reference to the canvas on the document.
Then we get the context of the canvas. This is a way to say what I want to do with the canvas. You could put a 3D value there, of course! That is indeed the case when you're doing 3D rendering with WebGL and canvas.
Once we have a reference to our context, we can do a bunch of things and add methods provided by the API out-of-the-box. Here we moved the cursor to the (0, 0) coordinates.
Then we drew a line till (100,100) (which is basically a diagonal on the square canvas).
Then we called stroke to actually draw that on our canvas. Easy!
Canvas is a wide topic and deserves a book of its own! If you're interested in developing awesome games and apps with Canvas, I recommend you start off with MDN docs: http://bit.ly/canvas-html5.
The Fetch API - promise-based HTTP requests
One of the coolest async APIs introduced in browsers is the Fetch API, which is the modern replacement for the XMLHttpRequest API. Have you ever found yourself using jQuery just for simplifying AJAX requests with $.ajax? If you have, then this is surely a golden API for you, as it is natively easier to code and read!
However, fetch comes natively, hence, there are performance benefits. Let's see how it works:
fetch(link)
.then(data => {
// do something with data
})
.catch(err => {
// do something with error
});
Awesome! So fetch uses promises! If that's the case, we can combine it with async/await to make it look completely synchronous and easy to read!
<img id="img1" alt="Mozilla logo" />
<img id="img2" alt="Google logo" />
const get2Images = async () => {
const image1 = await fetch('https://cdn.mdn.mozilla.net/static/img/web-docs-sprite.22a6a085cf14.svg');
const image2 = await fetch('https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_150x54dp.png');
console.log(image1); // gives us response as an object
const blob1 = await image1.blob();
const blob2 = await image2.blob();
const url1 = URL.createObjectURL(blob1);
const url2 = URL.createObjectURL(blob2);
document.getElementById('img1').src = url1;
document.getElementById('img2').src = url2;
return 'complete';
}
get2Images().then(status => console.log(status));
The line console.log(image1) will print the following:
You can see the image1 response provides tons of information about the request. It has an interesting field body, which is actually a ReadableStream, and a byte stream of data that can be cast to a Binary Large Object (BLOB) in our case.
A blob object represents a file-like object of immutable and raw data.
After getting the Response, we convert it into a blob object so that we can actually use it as an image. Here, fetch is actually fetching us the image directly so we can serve it to the user as a blob (without hot-linking it to the main website).
Thus, this could be done on the server side, and blob data could be passed down a WebSocket or something similar.
Fetch API customization
The Fetch API is highly customizable. You can even include your own headers in the request. Suppose you've got a site where only authenticated users with a valid token can access an image. Here's how you'll add a custom header to your request:
const headers = new Headers();
headers.append("Allow-Secret-Access", "yeah-because-my-token-is-1337");
const config = { method: 'POST', headers };
const req = new Request('http://myawesomewebsite.awesometld/secretimage.jpg', config);
fetch(req)
.then(img => img.blob())
.then(blob => myImageTag.src = URL.createObjectURL(blob));
Here, we added a custom header to our Request and then created something called a Request object (an object that has information about our Request). The first parameter, that is, http://myawesomewebsite.awesometld/secretimage.jpg, is the URL and the second is the configuration. Here are some other configuration options:
Credentials: Used to pass cookies in a Cross-Origin Resource Sharing (CORS)-enabled server on cross-domain requests.
Method: Specifies request methods (GET, POST, HEAD, and so on).
Headers: Headers associated with the request.
Integrity: A security feature that consists of a (possibly) SHA-256 representation of the file you're requesting, in order to verify whether the request has been tampered with (data is modified) or not. Probably not a lot to worry about unless you're building something on a very large scale and not on HTTPS.
Redirect: Redirect can have three values:
Follow: Will follow the URL redirects
Error: Will throw an error if the URL redirects
Manual: Doesn't follow redirect but returns a filtered response that wraps the redirect response
Referrer: the URL that appears as a referrer header in the HTTP request.
Accessing and modifying history with the history API
You can access a user's history to some level and modify it according to your needs using the history API. It consists of the length and state properties:
console.log(history, history.length, history.state);
The output is as follows:
{length: 4, scrollRestoration: "auto", state: null}
4
null
In your case, the length could obviously be different depending on how many pages you've visited from that particular tab.
history.state can contain anything you like (we'll come to its use case soon). Before looking at some handy history methods, let us take a look at the window.onpopstate event.
Handling window.onpopstate events
The window.onpopstate event is fired automatically by the browser when a user navigates between history states that a developer has set. This event is important to handle when you push to history object and then later retrieve information whenever the user presses the back/forward button of the browser.
Here's how we'll program a simple popstate event:
window.addEventListener('popstate', e => {
console.log(e.state); // state data of history (remember history.state ?)
})
Now we'll discuss some methods associated with the history object.
Modifying history - the history.go(distance) method
history.go(x) is equivalent to the user clicking his forward button x times in the browser. However, you can specify the distance to move, that is history.go(5); . This equivalent to the user hitting the forward button in the browser five times.
Similarly, you can specify negative values as well to make it move backward. Specifying 0 or no value will simply refresh the page:
history.go(5); // forwards the browser 5 times
history.go(-1); // similar effect of clicking back button
history.go(0); // refreshes page
history.go(); // refreshes page
Jumping ahead - the history.forward() method
This method is simply the equivalent of history.go(1).
This is handy when you want to just push the user to the page he/she is coming from. One use case of this is when you can create a full-screen immersive web application and on your screen there are some minimal controls that play with the history behind the scenes:
if(awesomeButtonClicked && userWantsToMoveForward()) {
history.forward()
}
Going back - the history.back() method
This method is simply the equivalent of history.go(-1). A negative number, makes the history go backwards. Again, this is just a simple (and numberless) way to go back to a page the user came from. Its application could be similar to a forward button, that is, creating a full-screen web app and providing the user with an interface to navigate by.
Pushing on the history - history.pushState()
This is really fun. You can change the browser URL without hitting the server with an HTTP request. If you run the following JS in your browser, your browser will change the path from whatever it is (domain.com/abc/egh) to /i_am_awesome (domain.com/i_am_awesome) without actually navigating to any page:
history.pushState({myName: "Mehul"}, "This is title of page", "/i_am_awesome");
history.pushState({page2: "Packt"}, "This is page2", "/page2_packt"); // <-- state is currently here
The History API doesn't care whether the page actually exists on the server or not. It'll just replace the URL as it is instructed.
The popstate event when triggered with the browser's back/forward button, will fire the function below and we can program it like this:
window.onpopstate = e => { // when this is called, state is already updated.
// e.state is the new state. It is null if it is the root state.
if(e.state !== null) {
console.log(e.state);
} else {
console.log("Root state");
}
}
To run this code, run the onpopstate event first, then the two lines of history.pushState previously. Then press your browser's back button. You should see something like:
{myName: "Mehul"}
which is the information related to the parent state. Press back button one more time and you'll see the message Root State.
pushState does not fire onpopstate event. Only browsers' back/forward buttons do.
Pushing on the history stack - history.replaceState()
The history.replaceState() method is exactly like history.pushState(), the only difference is that it replaces the current page with another, that is, if you use history.pushState() and press the back button, you'll be directed to the page you came from.
However, when you use history.replaceState() and you press the back button, you are not directed to the page you came from because it is replaced with the new one on the stack. Here's an example of working with the replaceState method:
history.replaceState({myName: "Mehul"}, "This is title of page", "/i_am_awesome");
This replaces (instead of pushing) the current state with the new state.
Although using the History API directly in your code may not be beneficial to you right now, many frameworks and libraries such as React, under the hood, use the History API to create a seamless, reload-less, smooth experience for the end user.
If you found this article useful, do check out the book Learn ECMAScript, Second Edition to learn the ECMAScript standards for designing quality web applications.
What's new in ECMAScript 2018 (ES9)?
8 recipes to master Promises in ECMAScript 2018
Build a foodie bot with JavaScript
Read more