6.S082 Lecture 16 Events

What are events?

Code that is executed non-linearly when something happens

DOM Events


			<button>Click me</button>
		

			let button = document.querySelector("button");
			button.addEventListener("click", function handler(event) {
				event.target.textContent = "Thank you 😊";
			});
		

Not only element-related

Two ways to add events


			button.addEventListener("click", evt => {
				evt.target.textContent += "😊";
			});

			button.addEventListener("click", evt => {
				evt.target.textContent += "βœ…";
			});
		

			button.onclick = evt => {
				evt.target.textContent += "😊";
			};

			button.onclick = evt => {
				evt.target.textContent += "βœ…";
			};
		

Context in event listeners


		button.addEventListener("button", function(event) {
			console.log(this);
		});
	
  1. The button
  2. The event parameter
  3. Window

What happens?


		<!DOCTYPE html>
		<title>Event caveats</title>
		<meta charset="UTF-8"/>
		<button>Click me</button>
		<script>
			let button = document.querySelector("button");
			button.addEventListener("click", evt => {
				evt.target.textContent = "Thanks 😊";
			});
		</script>
	

What happens?


		<!DOCTYPE html>
		<title>Event caveats</title>
		<meta charset="UTF-8"/>
		<script>
			let button = document.querySelector("button");
			button.addEventListener("click", evt => {
				evt.target.textContent = "Thanks 😊";
			});
		</script>
		<button>Click me</button>
	

		<!DOCTYPE html>
		<title>Event caveats</title>
		<meta charset="UTF-8"/>
		<script>
			document.addEventListener("DOMContentLoaded", () => {
				let button = document.querySelector("button");
				button.addEventListener("click", evt => {
					evt.target.textContent = "Thanks 😊";
				});
			});
		</script>
		<button>Click me</button>
	

DOMContentLoaded

Removing events


		let handler = evt => evt.target.textContent = "😊";
		button.addEventListener("click", handler);
		button.removeEventListener("click", handler);
	

What happens when I click the button?


		button.addEventListener("click", evt => {
			evt.target.textContent = "πŸ’©"
		});
		button.removeEventListener("click", evt => {
			evt.target.textContent = "πŸ’©"
		});
	
  1. Its text is changed to πŸ’©
  2. Nothing
To remove a function, you need to store a reference to it. Just specifying the same anonymous function doesn't work, because these are different objects.

Three ways to provide feedback

Javascript CSS Result

						button.addEventListener("mouseover", evt => {
							evt.target.style.background = "hsl(330, 100%, 50%)";

							button.addEventListener("mouseout", evt => {
								evt.target.style.background = "";
							}, {once: true});
						});
					

						button.addEventListener("mouseover", evt => {
							evt.target.classList.add("hovered");

							button.addEventListener("mouseout", evt => {
								evt.target.classList.remove("hovered");
							}, {once: true});
						});
					

						button.hovered {
							background: hsl(330, 100%, 50%);
						}
					

						button:hover {
							background: hsl(330, 100%, 50%);
						}
					

What about this?

First attempt


		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
			)`;
		});
	

CSS for presentation, JS for computation


		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);
		});
	

Separation of concerns

Raw input events

state transition in the input hardware

Input Event Javascript event
Key pressed or released keydown, keyup
Mouse moved mousemove
Mouse button pressed or released mousedown, mouseup

Translated events

Higher level events from raw events

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

Click = mousedown + mouseup?


		let handler = evt => {
			evt.target.textContent += "βœ…";
		};

		button1.addEventListener("click", handler);
		button2.addEventListener("mousedown", evt => {
			evt.target.addEventListener(
				"mouseup",
				handler,
				{once: true}
			);
		});
	

Input event: Which events?


			<textarea id=tweet></textarea>
			<span id="output"></span>
		

			tweet.addEventListener("input", evt => {
				output.textContent = evt.target.value.length;
			});
		

Translated events are usually more complex than they appear
Use them instead of rolling your own!

Event object


		document.addEventListener("mousemove", evt => {
			document.body.textContent = `${evt.x} ${evt.y}`;
		});
	

Event object

Metadata about the event

NaΓ―ve dragging


		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);
			});
		})
	

Event coalescing

Dragging, revisited

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);
	});
});

What do I get when I click on "me"?


			<button id=button>Click <mark>me</mark>!!</button>
		

			button.addEventListener("click", evt => {
				evt.target.innerHTML += "πŸ¦„";
			});
		

But… we only had a listener on <button>!

If a tree falls in a forest and no one is around to hear it, does it make a sound?

Events occur whether we listen to them or not

Event bubbling Events start off at a target, and propagate up the DOM tree

What do I get when I click on "me"?


			<button id=button>Click <mark>me</mark>!!</button>
		

			button.addEventListener("click", function(evt) {
				this.innerHTML += "πŸ¦„";
			});
		

this points to the element the listener was attached to, not the event target

Event bubbling

Mostly helpful

Event delegation


			<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);
	}
});

Event delegation

When bubbling is a problem…


			<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);
		

Not all events bubble
Bubbling is just another heuristic!


		element.addEventListener(eventName, evt => {
			if (evt.bubbles) {
				evt.stopPropagation();
			}
		})
	

Event capturing Events start off at document and propagate down to target


			element.addEventListener(
				eventName,
				callback,
				{capture: true}
			)
		

What happens when you press ⌘+S (or Ctrl + S) in Google Docs?

Let's write a script that scrolls local links smoothly instead of abruptly jumping to the elements they target!

Preventing default actions
You can, but should you?

Don't be annoying


		textfield.addEventListener("keypress", evt => {
			if (evt.key < "A" || evt.key > "Z") {
				evt.preventDefault();
			}
		});
	

Some events cannot be prevented


		element.addEventListener(eventName, evt => {
			if (evt.cancelable) {
				evt.preventDefault();
			}
		})
	

What happens?

<input id="name" />

			name.addEventListener("input", evt => {
				console.log(evt.target.value);
			});

			name.value = "Lea";
		

Synthetic events


		name.addEventListener("input", evt => {
			console.log(evt.target.value);
		});

		name.value = "Lea";

		let evt = new InputEvent("input");
		name.dispatchEvent(evt);
	

We can also make our own events!


		let evt = new CustomEvent("itemadded", {
			detail: {item: item}
		});
		list.dispatchEvent(evt);
	

Custom events on custom objects!


		class GithubAPI extends EventTarget {
			constructor() {
				super();
			}
			login() {
				// ...
				let evt = new CustomEvent("login");
				this.dispatchEvent(evt);
			}
		}

		let github = new GithubAPI();
		github.addEventListener("login", evt => {
			// display user info
		});