DOM & Event
- We will be studying the DOM and Event APIs (Application Programming Interfaces)
- not part of the (Javascript) language (syntax)
- rather, a collection of useful functions for interacting with the environment (browser)
<!DOCTYPE html>
<html>
<head><title>Hello world</title></head>
<body><p>Hello <em>world</em> 👋</p></body>
</html>
Document:
This is the root node, and does not correspond to any HTML element.HTMLElement:
Every HTML element, such as html, body, or em
is of this type. Usually, they merely inherit from HTMLElement,
and are an instance of a more specific type such as
HTMLHtmlElement,
HTMLBodyElement and so on.Text:
Text nodes, such as "Hello ", "world", and "!" in our example.
These never contain any other element, they are always leaves.
Comment:
HTML comments (<!-- like this -->) are represented by objects of this type.
>
$$("h1, h2, h3")
< [h1,
h2,
h3#general-information, ...]
window is a global variable holding an object that represents the windowwindow has properties like document and methods like close()window becomes a global variable!
window: {
innerHeight: /* height of window in pixels */,
close: function () {
/* closes window if invoked */ },
…
document: { /* object representing document */
title: /* title of document */
location: /* url of document */
head: /* HTMLElement representing head*/,
body: /* HTMLElement representing body*/
…
}
}
let selector = "h1, h2, h3, h4, h5, h6";
// Get all headings in the current document
let headings = document.querySelectorAll(selector)
// Get the first heading in the current document
let firstHeading = document.querySelector(selector)
<button id="submit">Submit</button>
console.log(submit, window['submit']);
Classic C-style
Low level, trades readability for power
|
|
|---|---|
Iterate over object properties
|
|
Iterate over items of iterable objects
|
|
Hint: element.remove() removes an element from the DOM
style elements. What remains?
$$() in your scripts
function $$(selector) {
let elements = document.querySelectorAll(selector);
return Array.from(elements);
}
Code that is executed non-linearly when something happens
If the button withid="clickme"gets clicked, change its label to"Clicked"
<button id="clickme">Click me</button>
clickme.addEventListener("click", function handler(event) {
event.target.textContent = "Clicked";
});
element.addEventListener, where element is a reference to your element in the DOM tree.
let handler = function (evt) {
evt.target.textContent = "Clicked";
};
clickme.addEventListener("click", handler);
let handler = evt => evt.target.textContent = "Clicked";
clickme.addEventListener("click", handler);
error, load, unload, …online, offlinemessage, closeload, DOMContentLoaded, unload, …<button id=button>Click me</button>
button.addEventListener("click", evt => {
evt.target.textContent += "😊";
});
button.addEventListener("click", evt => {
evt.target.textContent += "✅";
});
<button id=button>Click me</button>
button.onclick = evt => {
evt.target.textContent += "😊";
};
button.onclick = evt => {
evt.target.textContent += "✅";
};
let handler = evt => evt.target.textContent = "😊";
button.addEventListener("click", handler);
button.removeEventListener("click", handler);
<button id=button>Click me</button>
let handler = evt => {
evt.target.textContent = "💩"
};
button.addEventListener("click", handler);
button.removeEventListener("click", handler);
<button id=button>Click me</button>
button.addEventListener("click", evt => {
evt.target.textContent = "💩"
});
button.removeEventListener("click", evt => {
evt.target.textContent = "💩"
});
generally, presentation should be a function of the current state of the information/system, so something you can describe just with css. if you are editing js to change presentation, that is a code smell that you are doing something wrong.
| Javascript | CSS | Result |
|---|---|---|
|
||
|
|
|
|
document.addEventListener("mousemove", evt => {
let x = 100 * evt.x / innerWidth;
let y = 100 * evt.y / innerHeight;
document.body.style.backgroundImage = `radial-gradient(
at ${x}% ${y}%,
black,
transparent
)`;
});
body {
background-image: radial-gradient(
at calc(var(--mouse-x, .5) * 100%)
calc(var(--mouse-y, .5) * 100%),
transparent, black
);
}
document.addEventListener("mousemove", evt => {
let x = evt.x / innerWidth;
let y = evt.y / innerHeight;
let root = document.documentElement;
root.style.setProperty("--mouse-x", x);
root.style.setProperty("--mouse-y", y);
});
"10px" or "50%"| Input Event | Javascript event |
|---|---|
| Key pressed or released | keydown, keyup |
| Mouse moved | mousemove |
| Mouse button pressed or released | mousedown, mouseup |
| Input Event | Javascript event |
|---|---|
| Clicking | click |
| Double-clicking | dblclick |
| Character held down | keypress |
| Form element value changed | input |
| Entering or exiting an object’s bounding box | mouseenter, mouseleave |
<button id=button1>Button 1</button>
<button id=button2>Button 2</button>
let handler = evt => {
evt.target.textContent += "✅";
};
button1.addEventListener("click", handler);
button2.addEventListener("mousedown", evt => {
evt.target.addEventListener(
"mouseup",
handler,
{once: true}
);
});
<textarea id=tweet></textarea>
<span id="output"></span>
tweet.addEventListener("input", evt => {
output.textContent = evt.target.value.length;
});
document.addEventListener("mousemove", evt => {
document.body.textContent = `${evt.x} ${evt.y}`;
});
let start = {x: 0, y: 0};
element.addEventListener("mousedown", evt=> {
start.x = start.x || evt.x;
start.y = start.y || evt.y;
let mousemove = evt => {
evt.target.style.left = (evt.x - start.x) + "px";
evt.target.style.top = (evt.y - start.y) + "px";
};
evt.target.addEventListener("mousemove", mousemove);
evt.target.addEventListener("mouseup", evt => {
evt.target.removeEventListener("mousemove", mousemove);
});
})
{
let start = {x: 0, y: 0};
dragme.addEventListener("mousedown", evt=> {
start.x = start.x || evt.x;
start.y = start.y || evt.y;
let target = evt.target;
let mousemove = evt => {
target.style.left = (evt.x - start.x) + "px";
target.style.top = (evt.y - start.y) + "px";
};
document.addEventListener("mousemove", mousemove);
document.addEventListener("mouseup", evt => {
document.removeEventListener("mousemove", mousemove);
});
});
}
<button onclick="this.textContent += '😊'">Click me</button>
<button id=button>Click <mark>me</mark>!!</button>
button.addEventListener("click", evt => {
evt.target.innerHTML += "🦄";
});
<button>!If a tree falls in a forest and no one is around to hear it, does it make a sound?
<button id=button>Click <mark>me</mark>!!</button>
button.addEventListener("click", evt => {
evt.currentTarget.innerHTML += "🦄";
});
<ol id="palette" class="items">
<template>
<li class="item">
<input type="color">
<button class="delete">🗑</button>
</li>
</template>
</ol>
<button id="addColor" class="add-item">
Add item
</button>
addColor.addEventListener("click", evt => { let template = palette.querySelector("template"); let item = template.content.cloneNode(true); let del = item.querySelector(".delete"); del.addEventListener("click", e => { e.target.closest(".item").remove(); }); palette.append(item); });document.addEventListener("click", evt => { if (evt.target.matches(".item .delete")) { evt.target.closest(".item").remove(); } else if (evt.target.matches(".items + .add-item")) { let list = evt.target.previousElementSibling; let template = list.querySelector("template"); let item = template.content.cloneNode(true); list.append(item); } });
<button id=button1>Click <em>me</em>!</button>
<button id=button2>No, click <strong>me</strong>!</button>
<span id=output></span>
let over = evt => output.innerHTML = evt.target.innerHTML;
let out = evt => output.innerHTML = "";
button1.addEventListener("mouseover", over);
button2.addEventListener("mouseover", over);
button1.addEventListener("mouseout", out);
button2.addEventListener("mouseout", out);
focus (but focusin does!)blur (but focusout does!)mouseenter (but mouseover does!)mouseleave (but mouseout does!)loaderror
element.addEventListener(eventName, evt => {
if (evt.bubbles) {
evt.stopPropagation();
}
})
document and propagate down to target
element.addEventListener(
eventName,
callback,
{capture: true}
)
element.addEventListener("keyup", evt => {
if (evt.key === "S" and (evt.metaKey or evt.ctrlKey)) {
evt.preventDefault();
myApp.save();
}
})
Name: <input id=textfield>
textfield.addEventListener("keypress", evt => {
if (evt.key < "A" || evt.key > "Z") {
evt.preventDefault();
}
});
unload (but beforeunload can!)input (but its composite raw events can!)scroll (use overflow: hidden;)select (use user-select: none;)fullscreenchangeresize
element.addEventListener(eventName, evt => {
if (!evt.cancelable) {
console.log("This event cannot be prevented!");
}
})
<input id="name" />
name.addEventListener("input", evt => {
console.log(evt.target.value);
});
name.value = "Lea";
name.addEventListener("input", evt => {
console.log(evt.target.value);
});
name.value = "Lea";
let evt = new InputEvent("input");
name.dispatchEvent(evt);
let evt = new CustomEvent("itemadded", {
detail: {index: 2}
});
list.dispatchEvent(evt);
class GithubAPI extends EventTarget {
constructor() {
super();
}
login() {
// ...
let evt = new CustomEvent("login", {name: "Lea"});
this.dispatchEvent(evt);
}
}
let github = new GithubAPI();
github.addEventListener("login", evt => {
greeting.textContent = `Hi ${evt.detail.name}!`; // display user info
});
[Decoupling](https://en.wikipedia.org/wiki/Coupling_(computer_programming)), decoupling, decoupling!