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
, offline
message
, close
load
, 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!)load
error
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;
)fullscreenchange
resize
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!