Vanilla JS: You might not need a framework - Frontend Masters
Maximiliano Firtman, firt.dev
What is vanilla JS?
Usage of core language and browser APIs to create web apps without the use of any additional libraries or frameworks. It’s “plain” javascript without anything added.
Vanilla JS is much faster in most circumstances than using a library.
Retrieve DOM element by ID (Code ops / sec):
- Vanilla JS document.getElementById('test-table');
- 12,137,211
- Dojo dojo.byId('test-table');
- 5,443,343
- Prototype JS $('test-table')
- 2,940,734
- Ext JS delete Ext.elCache['test-table']; Ext.get('test-table');
- 997,562
- jQuery $jq('#test-table');
- 350,557
- YUI YAHOO.util.Dom.get('test-table');
- 326,534
- MooTools document.id('test-table');
- 78,802
Main advantages
- lightweight
- more control and power, more decisions in the hands of the developer
- compatibility
- flexibility
- performance
- no node modules
Biggest fears
- routing for single page apps
- too verbose and time consuming
- state management
- complexity
- templating
- reusable components
- maintenance
- learning curve
- browser compatibility
- reinventing the wheel every time
- scalability
Easy ways to reduce complexity and verbosity in Javascript
// turns $ into jQuery-esqe selector const $ = function(args){ return document.querySelector(args);} // rewrite document.getElementById(“nav”) as: $(“nav”)... // shorthand addEventListener HTMLElement.prototype.on = (a, b, c) => this.addEventListener(a ,b, c); $(‘nav’).on(“click”)...
Core concepts
DOM
The document object model is a representation of the web page’s structure (HTML) in memory
DOM API
Browser API exposed to developers to manipulate the DOM
The API is available on many different objects
- - comes from the days before browser tabs when if a user wanted a new webpage they’d have to create a new window
window
- - the
document
document, an interface that represents a web page in the browserhtml
- each node in the html document
- ,
html
,body
,header
, etc are all represented by an object ofdiv
interfaceHTMLElement
- it’s possible to listen for events on the element and react
- it’s possible to create references to DOM elements or create new elements and add them to the DOM
Selecting DOM elements
- by ID
document.getElementById(“logo”)
- returns an object
Element
- by class name
document.getElementsByClassName(“logo”)
- returns a live
HTMLCollection
- by name
document.getElementByTagName(“p”)
- returns a live
HTMLCollection
- by CSS selector
document.querySelector(“.logo”)
- returns the first that matches the selectors (use
Element
to return a collection)querySelectorAll()
Note:
HTMLCollection
filter
map
reduce
forEach
Note: If
document.querySelector()
null
document.querySelectorAll()
NodeList[]
Document
// to use modern array methods, use Array.from() const elements = Array.from(document.getElementsByClassName(“important”));
Reading and changing attributes of DOM elements
element.hidden = false; element.src = “logo.png”; element.className = “logo”
To change the style of an element
// change CSS' kebab syntax (-) to camelCase syntax element.style.color = “blue”; element.style.fontSize = “1.2em”; element.style.borderRightColor = “#FF0000”;
Listen to and bind a callback function to an event name
element.addEventListener(“click”, showAlert());
Changing document text
- with
textContent
// with textContent const element = document.querySelector(“#message”); // read the element’s current content as a string const content = element.textContent; // change the element’s content element.textContent = “The text has been changed”;
- with
innerHTML
const element = document.querySelector(“#section-6 header”); const content = element.innerHTML; // change the element’s content by writing `HTML` directly element.innerHTML = ` <h1>My app</h1> <p>The best platform</p> `;
- with the DOM API
const element = document.querySelector(“#section-6 header”); const h1 = document.createElement(“h1”); h1.textContent = “My App”; element.appendChild(h1);
Note: The HTML file and the DOM are not the same thing. The DOM is a structure in memory. The HTML file is the source for the DOM structure, but not equal to it.
Note: The DOM will start the
<body>
Where do you put your script?
Before context per file, developers had to use bundlers to reduce network requests—or worse yet—import 15 different javascript files. But now with ES Modules it’s possible to organize code in different files and only import what’s needed per file. Modern async and defer attributes also allow scripts to be placed anywhere in the head and not block rendering of the rest of the document.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> . . . <script src="“app.js”" defer></script> </head> </html>
Script attributes
async
defer
Waiting for content load
window.addEventListener(“DOMContentLoaded”)
Events
Each DOM element has a list of possible events to listen to:
- Basic: ,
load
,click
dblclick
- Value:
change
- Keyboard Events: ,
keyup
,keydown
keypress
- Mouse Events: ,
mouseover
mouseout
- Pointer: ,
touch
,scroll
focus
Some specific objects have special events:
- ,
DOMContentLoaded
in windowpopstate
Note: The spec's naming pattern is to use lowercase with no word separator for event names (e.g.
webkitcurrentplaybacktargetiswirelesschanged
onevent property binding
It’s possible to attach a single function to an event listener
function eventHandler(event) { … } element.onload = eventHandler; element.onload = (event) => { // replaces the first handler since it’s a property on the element }
eventListeners
To attach more than one event or more than one listener, use
addEventListener
function eventHandler(event) { … } element.addEventListener(“load”, eventHandler); element.addEventListener(“load”, (event) => { // all eventListeners will fire on load } // it’s also possible to add options const option = { once: true, passive: true }; element.addEventListener(“load”, eventHandler, options); /* useful for single page applications to cleanup when removing elements */ element.removeEventListener(“load”, eventHandler);
custom events
const event = new Event(“mycustomname”); element.dispatchEvent(event);
Modules
As Javascript web development took over the internet, developers needed a better way to manage large projects. All modern browsers now support Javascript modules via simple
import
export
export default Name = “square”;
import Name from “shapes”;
<script src=“app.js” type=“module”></script>
Routing
In this course, we’re building a single page application, e.g. we’ll serve a single HTML file (
index.html
- remove the previous page and inject the new page into the DOM
- add all pages to the DOM and use visibility
const Router = { init: () => { document.querySelectorAll("a.navlink").forEach((a) => { /* bind click event to all links and prevent default routing */ a.addEventListener("click", (e) => { e.preventDefault(); /* grab URL from the event object and fire the router’s go method using the URL */ const url = e.target.getAttribute("href"); Router.go(url); }); }); /* if the user goes back in the browser */ window.addEventListener("popstate", (e) => { if (e.state) { Router.go(e.state.route, false); } }); Router.go(location.pathname); }, go: (route, addToHistory = true) => { /* add the route and url to the browser’s session history */ if (addToHistory) { history.pushState({ route }, null, route); } let pageElement = null; switch (route) { case "/": pageElement = document.createElement("h1"); pageElement.textContent = "Menu"; break; case "/order": pageElement = document.createElement("h1"); pageElement.textContent = "Order"; break; } if (pageElement) { const cache = document.querySelector(“main”); cache.innerHTML = “”; // or cache.children[0].remove(); cache.appendChild(pageElement); // prevent wild scroll defaults window.scrollX = 0; window.scrollY = 0; } }, }; export default Router;
History API
// pushing a new URL (second argument is unused) history.pushState(state, null, “/route”); // to listen for changes in URL within the same page window.addEventListener(“popstate”, (event) => { let url = location.href; });
Note:
popstate
Note: Browsers won’t attempt to load the URL after a call to
pushState()
pushState
Web components
Essentially a custom HTML tag element. Allows web developers to utilize the component-based architecture.
- compatible with every browser
Custom elements
class MyElement extends HTMLElement { constructor() { super(); this.dataset.language } connectedCallback() {} // element added to document disconnectedCallback() {} // element removed adoptedCallback() {} // element moved to new document } customElements.define(“my-element”, MyElement);
<body> <my-element data-language="“en”"></my-element> </body>
Note: The custom HTML tag must contain a hyphen to assure future compatibility
Template elements
A tag the browser ignores.
<template id="menu-page-template"> <section> <ul id="menu"></ul> </section> </template>
connectedCallback() { const template = document.getElementById(“template”); const content = template.content.cloneNode(true); this.appendChild(content); };
Shadow DOM
A private, isolated DOM tree within a web component that is separate from the main document’s DOM tree
- by default, CSS declared in the main DOM won’t be applied to the shadow DOM
- CSS declared in the shadow DOM applies only there
- shadow DOM can be open or closed to the outer DOM
class MyElement extends HTMLElement { constructor() { super(); this.root = this.attachShadow({ mode: “open” }); } connectedCallback() { this.root.appendChild(…) } }
Proxies
A
Proxy
Note: proxies only work with objects
const original = { name: “John”, age: 30 }; const handler = { get: function(target, prop) { if (property === ‘age’) { return target[prop] + ‘ years old’; } } } const p = new Proxy(original, handler); console.log(p); // ’30 years old’