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

  • window
    - comes from the days before browser tabs when if a user wanted a new webpage they’d have to create a new window
  • document
    - the
    html
    document, an interface that represents a web page in the browser
  • each node in the html document
    • html
      ,
      body
      ,
      header
      ,
      div
      , etc are all represented by an object of
      HTMLElement
      interface
    • 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
      Element
      object
  • 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
      Element
      that matches the selectors (use
      querySelectorAll()
      to return a collection)

Note:

HTMLCollection
s (live) don’t have all modern array functions like
filter
,
map
,
reduce
, or
forEach

Note: If

document.querySelector()
doesn’t match, it returns
null
and if
document.querySelectorAll()
doesn’t match it returns and empty
NodeList[]
. This is the same case for all single match methods and list returning
Document
methods.

// 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>
at the first visible node in the HTML document.

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
- for loading scripts that need to be executed right away (e.g. analytics, auth, etc). Once downloaded, the browser will halt parsing to execute the script.

defer
- marks scripts for download and execution after the whole document is parsed

Waiting for content load

window.addEventListener(“DOMContentLoaded”)
- event attached to window that fires when the whole DOM is ready to be manipulated, before rendering

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
    ,
    popstate
    in window

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
statements.

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
) and change its contents depending on the URL. There are a couple ways to do this:

  • 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
isn’t fired if the user clicks on an external link or changes the URL manually

Note: Browsers won’t attempt to load the URL after a call to

pushState()
. The
pushState
URL can be relative or absolute, and will be resolved relative to the current URL.

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
object in Javascript allows developers to create an object that can be used in place of the original object.

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’