diff --git a/notes/callbacks/callbacks.md b/notes/callbacks/callbacks.md index 1d2ac49649045f7591a900775255a8f5fe29acb9..70dae7e957b19cbd03d16d1c147dba4375f31b70 100644 --- a/notes/callbacks/callbacks.md +++ b/notes/callbacks/callbacks.md @@ -18,11 +18,11 @@ Some examples of things webpages often do in JavaScript: <script> // We use the `id` property of the button to access it from JS const button = document.getElementById('alert-button') - button.onclick = () => { + button.addEventListener('click', () => { // You could put any other code you wanted here // and it would run each time the button is clicked alert('Button was clicked') - } + }) </script> </body> </html> @@ -36,8 +36,8 @@ Each time the button is clicked, it displays a message saying that the button wa Let's tease apart the code responsible for making button clicks display the message. First, note that `() => alert('Button was clicked')` is a *function* in JS. When this function is called, it causes the message `Button was clicked` to appear. -Then `button.onclick = ...` tells the browser "this is the function I want to attach to the click action on `button`. -When a user interacts with the webpage, the browser figures out whether the button was clicked, and if so, it runs the function stored in `button.onclick`. +Then `button.addEventListener('click', ...)` tells the browser "I want the click action on `button` to call this function". +When a user interacts with the webpage, the browser figures out whether the button was clicked, and if so, it runs the "click handler" function. ## How doesn't this work? @@ -65,7 +65,7 @@ So why doesn't JS use an interface like one of these? 2. This approach solves the issue of not being able to respond to multiple events: we could just add an `if (button2.wasClicked())` inside the loop. However, it has a more subtle problem. The loop may run billions of times before the user actually clicks the button. - Since our program can't actually do anything until the user finally clicks the button, this is an incredible waste of the processor's time. + Since our program won't actually do anything until the user finally clicks the button, this is a huge waste of the processor's time. We see that the "event handler" approach used by JS has two main benefits: it can easily set up handlers for thousands of events at once, and no JS code runs while we wait for an event to occur. @@ -79,14 +79,16 @@ In [this example](lock1.html), we make a combination lock with 4 digits and want function makeLockDigit(changeCallback) { // ... (the full code is at the link above) - digit.onchange = () => changeCallback(Number(digit.value)) + digit.addEventListener('change', () => { + changeCallback(Number(digit.value)) + }) } // The super secret combination const COMBO = [1, 2, 3, 4] // Whether each digit is correct const digitCorrect = [false, false, false, false] -for (let i = 0; i < COMBO.length; i++) { +for (let i = 0; i < 4; i++) { makeLockDigit(value => { // Digit i has changed, so store whether it is correct digitCorrect[i] = (value === COMBO[i]) @@ -105,7 +107,7 @@ There are other ways to store the state of the digits; for example, we could ins ```js // The number of correct digits let correctCount = 0 -for (let i = 0; i < COMBO.length; i++) { +for (let i = 0; i < 4; i++) { // Whether this digit was previously correct let wasCorrect = false makeLockDigit(value => { @@ -159,6 +161,7 @@ In general, this requires us to create the next asynchronous event *inside* the As a concrete example, let's simulate a "random walk" on a grid. At each step, we randomly move left, right, up, or down, and we count the total number of times we have visited each grid square. +To run an asynchronous action after another one finshes, we *nest it inside the previous callback*. Here is a [first attempt](walk1.html) that only moves 3 times. ```js // ... (the full code is at the link above) diff --git a/notes/callbacks/click.html b/notes/callbacks/click.html index 8362088d8ac2e79699237caeaab1adfff37ded76..a6c031ea9fdf62dc0d9b2b870bd679cd78dd902d 100644 --- a/notes/callbacks/click.html +++ b/notes/callbacks/click.html @@ -4,11 +4,11 @@ <script> // We use the `id` property of the button to access it from JS const button = document.getElementById('alert-button') - button.onclick = () => { + button.addEventListener('click', () => { // You could put any other code you wanted here // and it would run each time the button is clicked alert('Button was clicked') - } + }) </script> </body> </html> diff --git a/notes/callbacks/lock1.html b/notes/callbacks/lock1.html index c8a2330496234ccc30c85b2bca1eb2bbf6b0794f..192568f58f4e4c740ef6531da1403d82d018bcbc 100644 --- a/notes/callbacks/lock1.html +++ b/notes/callbacks/lock1.html @@ -16,7 +16,9 @@ // When the digit changes, call `changeCallback()`, // passing the new value of the digit. // Inputs' values are strings, so we convert them to numbers. - digit.onchange = () => changeCallback(Number(digit.value)) + digit.addEventListener('change', () => { + changeCallback(Number(digit.value)) + }) // Add the digit to the lock lock.appendChild(digit) @@ -26,7 +28,7 @@ const COMBO = [1, 2, 3, 4] // Whether each digit is correct const digitCorrect = [false, false, false, false] - for (let i = 0; i < COMBO.length; i++) { + for (let i = 0; i < 4; i++) { makeLockDigit(value => { // Digit i has changed, so store whether it is correct digitCorrect[i] = (value === COMBO[i]) diff --git a/notes/callbacks/lock2.html b/notes/callbacks/lock2.html index b3f3f6a33bb277dbaf331cf8664e56fd23ab94f6..5e504a96cdb3148306b333234c7ba685ce20fe37 100644 --- a/notes/callbacks/lock2.html +++ b/notes/callbacks/lock2.html @@ -16,7 +16,9 @@ // When the digit changes, call `changeCallback()`, // passing the new value of the digit. // Inputs' values are strings, so we convert them to numbers. - digit.onchange = () => changeCallback(Number(digit.value)) + digit.addEventListener('change', () => { + changeCallback(Number(digit.value)) + }) // Add the digit to the lock lock.appendChild(digit) @@ -26,7 +28,7 @@ const COMBO = [1, 2, 3, 4] // The number of correct digits let correctCount = 0 - for (let i = 0; i < COMBO.length; i++) { + for (let i = 0; i < 4; i++) { // Whether this digit was previously correct let wasCorrect = false makeLockDigit(value => { diff --git a/notes/js/calculator.html b/notes/js/calculator.html index 17d38bb29d4a06cc86031c11411b14d16d8b6d14..ab1de3e9b6d9dfa730820164ec34b6136c3e9bb6 100644 --- a/notes/js/calculator.html +++ b/notes/js/calculator.html @@ -27,10 +27,10 @@ // Every time `<button id='add'>` is clicked, // add the number in `<input id='number'>` to the sum - document.getElementById('add').onclick = () => { + document.getElementById('add').addEventListener('click', () => { sum += Number(numberInput.value) showSum() - } + }) </script> </body> </html> diff --git a/notes/streams/streams.md b/notes/streams/streams.md index 56a1140f3fc6528bb5dde053ad4ebd57bccbd6e1..a8acf6d20df5b181751da2f326b263dd884ee095 100644 --- a/notes/streams/streams.md +++ b/notes/streams/streams.md @@ -31,6 +31,7 @@ There are two main reasons: In Node.js, an asynchronous task that produces chunks of data is represented as a "readable stream". To process a readable stream, we need to specify the callback function that will be used to process each chunk of data. We can also register a callback to run when the stream finishes emitting data. +In Node.js, the `.on()` method for "event emitters" works similarly to the `.addEventListener()` method for DOM elements in the browser. As we saw above, reading a file is a prime example of a stream. Indeed, the `fs` module provides a function [`fs.createReadStream()`](https://nodejs.org/api/fs.html#fs_fs_createreadstream_path_options) to make a readable stream for a given file. diff --git a/notes/websockets/pixel-art.html b/notes/websockets/pixel-art.html index 9e1fa26e33656cad9ab73449904b314ccebe8f16..492fc02d2bfa469f5df5a605c57bd3d0d2bb7122 100644 --- a/notes/websockets/pixel-art.html +++ b/notes/websockets/pixel-art.html @@ -57,12 +57,12 @@ for (let col = 0; col < GRID_WIDTH; col++) { const cell = document.createElement('td') // Handles the click of a click-and-drag - cell.onmousedown = () => { + cell.addEventListener('mousedown', () => { startRow = row startCol = col - } + }) // Handles the release of a click-and-drag - cell.onmouseup = () => sendRectangle(row, col) + cell.addEventListener('mouseup', () => sendRectangle(row, col)) gridRow.append(cell) } grid.append(gridRow) @@ -84,22 +84,24 @@ // We need to run code that accesses HTML elements after // the rest of the HTML is parsed (signaled by the `load` event) - window.onload = () => { + window.addEventListener('load', () => { // Retrieve HTML elements colorInput = document.getElementById('color') grid = document.getElementById('grid-canvas') - colorInput.onchange = sendColor + colorInput.addEventListener('change', sendColor) makeGrid() // Create a WebSocket connection socket = new WebSocket('ws://localhost') // As soon as socket opens, send the initial color (black) - socket.onopen = sendColor + socket.addEventListener('open', sendColor) // Handles `draw` messages from the server. // `event.data` is the JSON string received. - socket.onmessage = event => draw(JSON.parse(event.data)) - } + socket.addEventListener('message', event => { + draw(JSON.parse(event.data)) + }) + }) </script> </head> <body> diff --git a/notes/websockets/websockets.md b/notes/websockets/websockets.md index d644e8a61593be7a21beeab35174f7c58355ea28..26a9689973aecce30426fa56a337edf9a28e117e 100644 --- a/notes/websockets/websockets.md +++ b/notes/websockets/websockets.md @@ -88,9 +88,9 @@ function sendColor() { } // When the socket opens, call `sendColor()` -socket.onopen = sendColor +socket.addEventListener('open', sendColor) // When `colorInput`'s value changes, call `sendColor()` -colorInput.onchange = sendColor +colorInput.addEventListener('change', sendColor) ``` Rectangles can be drawn by clicking and dragging, and this should send a `draw` message. @@ -104,11 +104,11 @@ let startCol for (let row = 0; row < GRID_HEIGHT; row++) { for (let col = 0; col < GRID_WIDTH; col++) { const cell = document.createElement('td') - cell.onmousedown = () => { + cell.addEventListener('mousedown', () => { startRow = row startCol = col - } - cell.onmouseup = () => sendRectangle(row, col) + }) + cell.addEventListener('mouseup', () => sendRectangle(row, col)) } } @@ -131,7 +131,9 @@ This requires setting the `message` event handler on the socket. ```js // Handles `draw` messages from the server. // `event.data` is the JSON string received. -socket.onmessage = event => draw(JSON.parse(event.data)) +socket.addEventListener('message', event => { + draw(JSON.parse(event.data)) +}) // The curly braces around the arguments are shorthand for // extracting these fields from the message object diff --git a/specs/chat/client.md b/specs/chat/client.md index a549e140370b128fb0891550a0b60a857931af89..a11b8a3b27521db3e61ba1cc7cd504b8473c7343 100644 --- a/specs/chat/client.md +++ b/specs/chat/client.md @@ -34,12 +34,12 @@ There is a small bit of additional CSS in `style.css`. One change is that `client.js` runs *before* the elements are defined, which is typical for webpages. This means, for example, that `document.getElementById('login')` won't work until the rest of the page is loaded. -Any code that depends on page elements should be put inside the `window.onload` callback, e.g.: +Any code that depends on page elements should run as a handler for the window's `load` event, e.g.: ```js -window.onload = () => { +window.addEventListener('load', () => { const loginButton = document.getElementById('login') // ... -} +}) ``` ## Requirements diff --git a/specs/minesweeper/minesweeper.md b/specs/minesweeper/minesweeper.md index 00b02a0cc96899d394853ff032de00876529b7f8..1d497d56823f4b9d1bca1a4d5b2e80bf417493ee 100644 --- a/specs/minesweeper/minesweeper.md +++ b/specs/minesweeper/minesweeper.md @@ -42,6 +42,7 @@ Your game should have the following features, which are shown in the video linke - Clicking on a square without a mine displays the number of mines in the 8 neighboring squares (up, down, left, right, and diagonally) - Clicking on a square with 0 neighboring mines automatically reveals the numbers in the neighboring squares. You should add a small time delay before revealing the neighbors to simulate a ripple effect across large regions of 0s. + To verify that your ripple visits the squares efficiently, make sure it looks smooth even on large grids, e.g. 30 x 30 with 10 mines! - Clicking on a square with a mine ends the game and displays a loss message (you can use `alert()` for this). Clicking and flagging are disabled when the game is over. - Revealing all the non-mine squares ends the game and displays a win message @@ -76,12 +77,20 @@ Here is a minimal subset of APIs you will likely find useful for this project: - `element.appendChild(child)` adds `child` as the last child element of `element` - `element.remove()` removes `element` from its parent - `element.classList` represents the set of classes that `element` has. + (In HTML, classes are strings used to identify groups of elements in JavaScript or CSS. + Each element can belong to any number of classes.) `classList.add(className)`, `classList.remove(className)`, `classList.toggle(className)`, and `classList.contains(className)` do what you expect. - **Event handlers** - - `element.onclick` is the function to call when `element` gets clicked. - You can set a click handler by setting `element.onclick` to a handler function, and you can also simulate a click by calling `element.onclick()`. - - `element.oncontextmenu` is similar, but for right-clicks instead. - You can prevent the normal right-click menu from showing by returning `false` from the event handler. + - `element.addEventListener('click', handler)` registers the function `handler` to be called whenever `element` is clicked. + For example, `element.addEventListener('click', () => alert('clicked'))` would display the message "clicked" when `element` is clicked. + - `element.addEventListener('contextmenu', handler)` is similar, but for right-clicks instead. + You can prevent the normal right-click menu from showing by calling `event.preventDefault()`: + ```js + element.addEventListener('contextmenu', event => { + event.preventDefault() + // Handler code... + }) + ``` It is also possible to attach data to HTML elements using DOM APIs, but they are tricky to use. Instead, it is simpler to store the state of the grid using JavaScript objects and arrays.