JavaScript for Websites

JavaScript is used since Web 2.0 to add interactivity to web pages:

  • ๐Ÿ‘‰ Validate a form (errors, autocompletion, ...)
  • ๐Ÿ‘‰ Handling events (click, visibility change, ...)
  • ๐Ÿ‘‰ Connect to an API (fetch...)
  • ๐Ÿฅ‚ For a programmer, it can be used on someone else's website, to automate a task, bypass a restriction...
  • ...

Some browsers may not support some newly added features.

  • ๐Ÿงช See caniuse to see which browsers support a specific feature
  • ๐Ÿš€ If there is a heavy need for a feature, you can use polyfill to load missing features. You can generate one here.

JavaScript (JS) is based on ECMAScript. ECMAScript2016 (ES6) is fully supported since late 2016/early 2017, while for ES6+, providers/vendors are implementing the features they want.


Getting started

There are 3 ways to use JavaScript for a website.

Inline JS ๐ŸคฎSCRIPT tag ๐ŸคขExternal JavaScript file ๐Ÿ˜

You can use attributes such as onmouseover, onclick, onsubmit etc. A list can be found here (W3Schools).

<p onmouseover="console.log('Hello World')">
    ...
</p>

You can use <script> tags.

<script>
// your JavaScript code
console.log("Hello World")
</script>

You can link an external file. This is the proper way to do it (CSP policy+caching, CDNs, SoC...).

<script src="/path/to/file.js"></script>

โš ๏ธ Note: Modern browsers usually cache JavaScript files. If you changed a file, and don't see any changes, reload the page while ignoring the cache: CTRL+SHIFT+R or SHIFT+F5 or CTRL+F5.


Document Object Model

There is one big difference with usual JavaScript: there is a variable window for the tab in which the page is displayed.

The Document Object Model (DOM) is a tree of nodes (HTML tags). The root is window.document or the shortcut window.document.

document.head // access the head tag
document.body // access the body tag
// collection with every
document.forms // form
document.images // image
document.links // link
document.scripts // scripts
// ๐Ÿฅ‚ tip: collection to Array
Array.from(document.links)

To create a node, we use these. Note that by editing the innerHTML of an existing tag manually, you can avoid doing that.

const node = document.createTextNode("Some text")
const node = document.createElement("div")

To find a node, you can use

const xxx = document // you can put any Node here
// ex: only match <node id="id" />
const node = xxx.getElementById('id');
// ex: match <node class="a b c" />
const nodeList = xxx.getElementsByClassName('c');
// ex: match every link
const nodeList = xxx.getElementsByTagName('a');
// ๐Ÿฅ‚ You can use CSS selectors
const node = xxx.querySelector('#id');
const nodeList = xxx.querySelectorAll('table tr > .xxx');
// Navigate from one node to another
xxx.childNodes ; xxx.children
xxx.firstChild ; xxx.firstElementChild
xxx.lastChild ; xxx.lastElementChild
xxx.parentNode ; xxx.parentElement
// edit HTML
xxx.replaceChildren() // remove children
xxx.replaceChildren(node1, node2) // replace with ...
xxx.appendChild(node)
// edit attributes
xxx.hasAttribute('class')
xxx.getAttribute('class')
xxx.removeAttribute("hidden")
xxx.setAttribute("hidden", "")
xxx.classList.xxx("yyy") // add, remove, replace...
// edit content
xxx.innerHTML = `<p>You can write HTML</p>`
xxx.innerText = "<p>HTML won't be rendered</p>"
xxx.textContent = "<p>HTML won't be rendered</p>"
// other
xxx.outerHTML
xxx.nodeName // "HEAD"...
xxx.click() // fire a "click" event

You can also manually edit the style

xxx.style.width = "5px";
xxx.style.display = "none";
xxx.style.color = "red";
xxx.style.backgroundColor = "yellow";
// get effective style
const style = window.getComputedStyle(xxx)
if (style.visibility === 'hidden') { /* ..; */ }

๐Ÿš€ Events ๐Ÿš€

Listen to events

<!-- โŒ inside the HTML. Use this to get the button. -->
<button onclick="console.log(event, this)">xxx</button>
// โœ… inside the JavaScript
const btn = document.querySelector("...")
// only to add one listener
btn.onclick = (event) => { console.log(event, btn) }
// add as many listeners as you want
btn.addEventListener('click', (event) => { console.log(event, btn) })

Add on before the name, like click would become onclick to get the HTML attribute from a JSEvent.

  • click: called when we clicked on the component
  • load: called when the component was loaded
  • error: called on loading error (ex: loading image failed)
  • mouseover: trigger function on mouse entering
  • mouseout: trigger function on mouse exiting
  • See: HTML events and Global attributes

Useful attributes/methods to call on an event are

  • event.preventDefault(): don't further handle the event (ex: stop a link from opening another page, a form from being submitted...)
  • event.target: the element on which we clicked/...

Examples

Execute code when the visibility (hidden, shown back) of the page is changed (ex: tab changed, minified...)

// โžก๏ธ  See "document.hidden" (boolean)
document.addEventListener('visibilitychange', () => {});

Execute some code when a key is pressed

document.onkeydown = e => {
    switch (e.code) {
        case 'KeyR': 
            e.preventDefault();
            // ...
            break;
    }
}

๐Ÿ”Ž Validating Forms ๐Ÿ”Ž

You can listen to a form submission with submit/onsubmit. This event is particular, as true must be returned if we can send the form, false otherwise. It's used to validate a form, before sending it.

const form = [...]
form.onsubmit = (event) => {
    const form = event.target // if not stored
    // ๐Ÿค” using elements (array)
    const value = form.elements['name']['value'] // get

    // โœ… Using FormData
    let formData  = new FormData(form);
    if (formData.has('name')) {}
    formData.append('name', 'value') // add
    formData.set('name', 'value') // set
    const value = formData.get('name') // get
    for(const [name, value] of formData) {} // iterate
    // ...
    return false // false <=> don't send
}

You may know that you can validate an HTML form with attributes such as minlength... You can show custom errors, or further handle the verification in JavaScript.

<form novalidate></form> <!-- disable HTML errors -->
form.onsubmit = (event) => {
    const form = document.forms[0] // alternative
    const xxx = form.querySelector('#xxx')
    if (xxx.validity.badInput) {}
    if (xxx.validity.tooShort) {}
    if (xxx.validity.tooLong) {}
    if (xxx.validity.typeMismatch) {}
    if (xxx.validity.valueMissing) {}
    // ...
}

โœˆ Request an API โœˆ

*See also, APIs, and Async, await, and Promises

The Fetch API is the easiest, and most used to do API requests.

// you can use a try-catch to catch errors
const res = await fetch("URL" , params)
// or
fetch('URL', params)
    .then(res => res.xxx())
    .then(xxx => /* ... */)
    .catch(err => /* ... */)

Note that res is a pending request, you need to ask for an output.

const json = await res.json() // as a json object
const text = await res.text() // as a string
// other
const value = res.headers.get('header-name')

GET is the default if no parameters are provided.

const params = { method: "GET", }

POST/PUT/PATCH are usually taking a body.

โžก๏ธ Note that the API may not require you to add headers.

const params = {
    method: "POST",
    body: JSON.stringify({ username: "xxx" }),
    headers: { 'Content-type': 'application/json; charset=UTF-8' }
}
const params = {
    method: "POST",
    body: 'xxx=value&yyy=value',
    headers: { 'Content-type': 'application/x-www-form-urlencoded' }
}
Prior to 2015: XMLHTTPRequest

To code a simplified fetch, you could use the code below.

function fetchXXX(URL, params = undefined) {
    return new Promise((resolve, reject) => {
        const ajax = new XMLHttpRequest();
        ajax.onreadystatechange = () => {
            // we the loading is done
            if(ajax.readyState === XMLHttpRequest.DONE) {
                // 200, 201, ... are OK
                if (ajax.status >= 200 && ajax.status < 300)
                    resolve(ajax.responseText);
                else
                    reject(ajax.responseText);
            }
        }
        // use the provided method, or GET by default
        ajax.open(params?.method ?? 'GET', URL, true);
        // add headers
        for(const header in params?.headers) {
            ajax.setRequestHeader(header, params?.headers[header]);
        }
        // send the body, if any
        ajax.send(params?.body);
    })
}

// tested with GET+POST, 
// but the code is using a somewhat recent syntax
fetchXXX('https://jsonplaceholder.typicode.com/posts')
    .then((text) => console.log(text));

โšก Special features โšก

Notifications

See NotificationsAPI

if (Notification?.permission === "granted") {
    // send a notification
    const img = 'xxx.png';
    const notification = new Notification(title, { body: body, icon: img });
    // done
} else {
    // you may request permission first
    Notification.requestPermission().then()
}

LocalStorage

The localStorage is a storage are inside the client browser. What's stored inside can be edited inside the devtools, in the "Application" tab.

// store
localStorage.setItem('key', 'value')
// get
const value = localStorage.getItem("key")
if (value != null) { /* ... */ }
// remove
localStorage.removeItem("key")
// clear
localStorage.clear()

It's useful to store/cache some data, but you must take into account that the user can add/edit/remove data.

โžก๏ธTo store an Object, use JSON.stringify and JSON.parse.

Popups

  • โžก๏ธ Show a popup with some message
alert("XXX?")
  • โžก๏ธ Show a popup asking the user for input
let value = prompt("XXX?", "defaultValueOfTheInputField")
if (value != null) {
    // ...
}

Libraries

jQuery

A long time ago, jQuery was a popular library shortening/simplifying DOM manipulations. Nowadays, many features were added to JavaScript, and with technologies with less manipulation of the DOM like React, or Vue.js, the library is nowhere near what it used to be.

โžก๏ธ Bootstrap removed its jQuery dependencies in its 5th version, to make the library more lightweight.

โžก๏ธ jQuery.terminal (2.8k โญ) | ๏ธ jQuery Migrate (1.9k โญ)


Snippets

  • Scroll to the bottom of an element
const scrollToTheBottomOfElement = (e) => {
    let previous = e.scrollHeight;
    while (1) {
        e.scrollTo(0, e.scrollHeight);
        if (e.scrollHeight === previous) break;
        previous = e.scrollHeight;
    }
}

๐Ÿ‘ป To-do ๐Ÿ‘ป

Stuff that I found, but never read/used yet.

print()
debugger; // only resuming when closed
document.write(" Dont open Developer Tools. ");
window.confirm('Are you sure?')

xxx:
for (;;) {
    break xxx;
}