Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework DOM to better separate elements from events #1074

Merged
merged 9 commits into from
Oct 29, 2024
2 changes: 1 addition & 1 deletion common-content/en/module/js2/browser/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ emoji= '🧩'
User interfaces provide the gateway between a user and a complex application.
When navigating the internet, we continually interact with web pages to access data and interact with complex web applications.

A {{<tooltip title="web browser">}} provides a user interface to interact with web pages.{{</tooltip>}} is capable of fetching HTML documents from a server, and then rendering the document to create a user interface. If a user visits a website and gets a plain HTML document back, we say this content is static.
A {{<tooltip title="web browser">}} provides a user interface to interact with web pages.{{</tooltip>}} is capable of fetching HTML documents from a server, and then rendering the document to create a user interface. If every time a user visits a website, they get the same plain HTML document back, we say this content is static.

By static, we mean that the server's job was just to hand over the HTML document, and then the browser takes over. A user may interact with the browser's interface, e.g. to scroll, type in a text field, or drag-and-drop an image around, but this is done purely by interacting with the browser - the browser won't talk to the server about this.
49 changes: 49 additions & 0 deletions common-content/en/module/js2/calculating/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
+++
title = '🧮 Calculating the remaining characters'

time = 10
facilitation = false
emoji= '🧩'
hide_from_overview = true
[objectives]
1='Access properties representing HTML attributes'
[build]
render = 'never'
list = 'local'
publishResources = false

+++

We want to implement Step 3: Calculate the number of characters left.

Let's break down Step 3 into sub-goals:

```mermaid
flowchart TD
A[Step 3.1: Get the character limit] --> B[Step 3.2: Get the number of characters already typed] --> C[Step 3.3: Subtract already typed from the limit]
```

## Getting information from the DOM

We have seen that the DOM exposes live information about HTML elements in the page via properties on the objects it returns.

We wrote `textarea.value` to get the characters already typed. This solves Step 3.2 - we can write `textarea.value.length`.

We can also access the character limit, because it's defined as the `maxlength` attribute of the HTML textarea.

In the Dev Tools console, if you type `textarea.max` you should see autocomplete for a property called `maxLength`.

Most HTML attributes are exposed in the DOM as a property with the same name (but in camelCase). Let's try:

```js
console.log(textarea.maxLength)
```

Now that we have the character limit (from `textarea.maxLength`), and the number of characters already typed (from `textarea.value.length`):

```js
const remainingCharacters = textarea.maxLength - textarea.value.length;
console.log(remainingCharacters);
```

Try typing in your textarea, then running this in the Dev Tools console.
13 changes: 9 additions & 4 deletions common-content/en/module/js2/character-limit/index.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
+++
title = '🛑 Character limit'
title = '🛑 Implenenting a character limit'

time = 20
facilitation = false
Expand Down Expand Up @@ -33,7 +33,12 @@ We can define _acceptance criteria_ for this component:

> _Given_ an textarea and a character limit of 200
> _When_ a user types characters into the textarea
> _Then_ the interface should update with how many characters they've got left
> _Then_ the interface should update with how many characters they've got left.

> _Given_ an textarea and a character limit of 200
> _When_ a user has already typed 200 characters into the textarea
> _And_ the user tries to type another character
> _Then_ the extra character should not get added to the textarea.

### 🏁 Starting point

Expand All @@ -50,8 +55,8 @@ In the user interface, we will start off with some static html:
<body>
<section>
<h3>Example character limit comment component</h3>
<label for="comment-input"
>Please enter your comment in the text area below
<label for="comment-input">
Please enter your comment in the text area below
</label>
<textarea
id="comment-input"
Expand Down
53 changes: 20 additions & 33 deletions common-content/en/module/js2/check-progress/index.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
+++
title = '📈 Check progress'
title = '📝 Check progress'

time = 30
time = 15
facilitation = false
emoji= '🧩'
emoji= '📝'
hide_from_overview = true
[objectives]
1='Use a plan to check progress in solving a problem'
[build]
Expand All @@ -19,14 +20,18 @@ Let's use the plan from earlier to check our progress.
flowchart TD
A[Step 1: Define the character limit] --> B[Step 2: Access the textarea element] --> C["`**Step 3: Ask to be notified when a user presses a key**`"]

Initial[On initial page load] --> E
D["`**Step 4: When the browser tells us a user has pressed a key**`"] --> E[Step 5: Calculate the number of characters left] --> F[Step 6: Update the interface with the number of characters left]
```

Let's consider our code at the moment:

```js
const characterLimit = 200;
const textarea = document.querySelector("textarea");
const remainingCharacters = textarea.maxLength - textarea.value.length;

const charactersLeftP = document.querySelector("#character-limit-info");
charactersLeftP.innerText = `You have ${remainingCharacters} characters remaining`;

function updateCharacterLimit() {
console.log(
Expand All @@ -42,51 +47,33 @@ We've done the following:
- [x] Step 1: Defined a `characterLimit`
- [x] Step 2: Accessed the `textarea` element
- [x] Step 3: Registered an event handler `updateCharacterLimit`
- [x] Step 5: Calculate the number of characters left
- [x] On initial page load, update the user interface with the number of characters left

The browser will do the following for us:

- [x] Step 4: The browser will tell us when a user has pressed a key

We must still complete the following steps:

- [ ] Step 5: Calculate the number of characters left
- [ ] Step 6: Update the user interface with the number of characters left

To obtain the characters left, we can calculate the difference between `characterLimit` and the number of characters in the `textarea` element:

{{<tabs>}}

{{<tab name="javascript">}}
We can re-use our existing code to update the user interface when the browser tells us the user has pressed a key:

```js
const characterLimit = 200;
const textarea = document.querySelector("textarea");
const remainingCharacters = textarea.maxLength - textarea.value.length;

const charactersLeftP = document.querySelector("#character-limit-info");
charactersLeftP.innerText = `You have ${remainingCharacters} characters remaining`;

function updateCharacterLimit() {
const charactersLeft = characterLimit - textarea.value.length;
console.log(`You have ${charactersLeft} characters remaining`);
const remainingCharacters = textarea.maxLength - textarea.value.length;
const charactersLeftP = document.querySelector("#character-limit-info");
charactersLeftP.innerText = `You have ${remainingCharacters} characters remaining`;
}

textarea.addEventListener("keyup", updateCharacterLimit);
```

{{</tab>}}

{{<tab name="html-css">}}

```html
<section>
<h1>Character limit</h1>
<textarea id="comment-input" rows="5" maxlength="200"></textarea>
<label for="comment-input"
>Please enter a comment in fewer than 200 characters
</label>
<p id="character-limit-info">You have 200 characters remaining</p>
</section>
```

{{</tab>}}

{{</tabs>}}

Typing in to the `textarea` element, we should see a string like `"You have 118 characters left"` printed to the console each time a key is released. However, we have one final step: we must now **update** the DOM label with the information about how many characters are left.
Typing in to the `textarea` element, we should see the page get updated to say e.g. "You have 118 characters left".
14 changes: 7 additions & 7 deletions common-content/en/module/js2/dom/index.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
+++
title = '🌲 Interacting with the page'
title = '🌲 The DOM'

time = 20
facilitation = false
Expand All @@ -13,7 +13,7 @@ emoji= '🧩'

+++

Let's consider the starting html. We need a way of interacting with the elements of this page once it is rendered.
Let's consider the starting HTML. We need a way of interacting with the elements of this page once it is rendered.

```html
<!DOCTYPE html>
Expand All @@ -26,10 +26,10 @@ Let's consider the starting html. We need a way of interacting with the elements
<body>
<section>
<h3>Character limit</h3>
<label for="comment-input"
>Please enter your comment in the text area below
<label for="comment-input">
Please enter your comment in the text area below
</label>
<textarea id="comment-input" name="comment-input" rows="5"></textarea>
<textarea id="comment-input" name="comment-input" rows="5" maxlength="200"></textarea>
<p id="character-limit-info">You have 200 characters remaining</p>
</section>
</body>
Expand All @@ -46,13 +46,13 @@ When we use a web browser, it takes this HTML document, and provides us with an

### Document Object Model

When the browser first renders a web page it also creates the DOM - short for {{<tooltip title="Document Object Model">}}The **D**ocument **O**bject **M**odel is a data representation of the content in a web page. All html elements are represented as objects that can be accessed, modified and deleted.{{</tooltip>}}.
When the browser first renders a web page it also creates the DOM - short for {{<tooltip title="Document Object Model">}}The **D**ocument **O**bject **M**odel is a data representation of the content in a web page. All HTML elements are represented as objects that can be accessed, modified and deleted.{{</tooltip>}}.

Just like a web browser provides us a visual interface, the DOM is an interface. But it is not an interface for humans to see and interact with, it is an interface for JavaScript to interact with. We can write JavaScript programs to interact with the Document Object Model so we can make the page interactive.

{{<tabs name="activity">}}

{{<tab name="🔎 inspect with dev tools">}}
{{<tab name="🔎 Inspect with dev tools">}}

We can use Dev Tools to inspect the DOM and look at the elements on the page.
Use Dev Tools to inspect the character limit component from earlier.
Expand Down
24 changes: 18 additions & 6 deletions common-content/en/module/js2/events/index.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
+++
title = '🎬 Events'
title = '🎬 DOM events'

time = 60
facilitation = false
Expand All @@ -18,27 +18,39 @@ In the case of the ` textarea` element, we want to update the `p` element text *

```mermaid
flowchart TD
A[Step 1: Define the character limit] --> B[Step 2: Access the textarea element] --> C[Step 3: Calculate the number of characters left] --> D[Step 4: Update the interface with the number of characters left]
A[Step 1: Define the character limit of 200]
Initial[On first load] --> B
Event[When the content changes] --> B
B[Step 2: Access the textarea element] --> C[Step 3: Calculate the number of characters left] --> D[Step 4: Update the interface with the number of characters left]

classDef hidden display: none;
```

However, we're missing a step in our plan. We need to find a way of running some code in response to an **event**.

{{<note type="definition" title="Definition: events">}}
An [event](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events) is something that occurs in a programming environment.
An [event](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events) is something that occurs in a programming environment that can be observed or responded to.
{{</note>}}

Events are things that happen in the browser, which your code can ask to be told about, so that your code can react to them. In a browser context, an event could be a user clicking on a button, a user typing something into a textarea box, a user submitting a form etc. Not all events are in response to user actions, for instance there is an event for the browser finished initially rendering the page. You can find a [complete reference all the different event types](https://developer.mozilla.org/en-US/docs/Web/Events) on MDN.
Events are things that happen in the browser, which your code can ask to be told about, so that your code can react to them. In a browser context, an event could be:
- a user clicking on a button
- a user typing something into a textarea box
- a user submitting a form
- and so on.

Not all events are in response to user actions. You can think of events as "interesting changes". For instance, there is an event for the browser completing its initial render of the page. You can find a [complete reference all the different event types](https://developer.mozilla.org/en-US/docs/Web/Events) on MDN.

When a user presses a key in our `textarea`, the browser will create an event. If we asked the browser to tell us about it, we can respond to it. So we can update our plan as follows:

```mermaid
flowchart TD
A[Step 1: Define the character limit] --> B[Step 2: Access the textarea element] --> C["`**Step 3: Ask to be notified when a user presses a key**`"]

Initial[On initial page load] --> E
D["`**Step 4: When the browser tells us a user has pressed a key**`"] --> E[Step 5: Calculate the number of characters left] --> F[Step 6: Update the interface with the number of characters left]
```

Notice a few things here:

- There's no arrow between Step 3 and Step 4. The trigger for Step 4 is _a user doing something_ - if the user doesn't type anything in the textarea, Step 4 will never run (and neither will Step 5 and Step 6).
- _We_ don't run Step 4. The browser runs Step 4. In Step 3 we asked the browser to do something for us in the future. This is something new - up until now, _we_ have always been the ones telling JavaScript what to do next.
- There's no arrow between Step 3 and Step 4. The trigger for Step 4 is _a user doing something_. If the user doesn't type anything in the textarea, Step 4 will not run after the first load (and neither will Step 5 and Step 6).
- _We_ don't run Step 4. The _browser_ runs Step 4. In Step 3 we asked the browser to do something for us in the future. **This is something new.** Up until now, _we_ have always been the ones telling JavaScript what to do next.
17 changes: 17 additions & 0 deletions common-content/en/module/js2/plan/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ title = '🧭 Breaking down the strategy'
time = 20
facilitation = false
emoji= '🧩'
hide_from_overview = true
[objectives]
1='Break down a problem into a series of steps'
[build]
Expand All @@ -20,4 +21,20 @@ flowchart TD
A[Step 1: Define the character limit of 200] --> B[Step 2: Access the textarea element] --> C[Step 3: Calculate the number of characters left] --> D[Step 4: Update the interface with the number of characters left]
```

There are two times we may want to do this:
1. When the page first loads we should show the _initial_ limit.
2. Whenever the user adds or removes a character from the textarea, we want to _update_ to show the remaining limit.

```mermaid
flowchart TD
A[Step 1: Define the character limit of 200]
Initial[On first load] --> B
Event[When the content changes] --> B
B[Step 2: Access the textarea element] --> C[Step 3: Calculate the number of characters left] --> D[Step 4: Update the interface with the number of characters left]

classDef hidden display: none;
```

Steps 2-4 will be the same, whether we're doing this for the _initial_ load or a subsequent _update_.

This strategy gives us a rough guide for the road ahead. However, as we learn more about this problem, we may need to update our strategy.
19 changes: 10 additions & 9 deletions common-content/en/module/js2/querying/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ Inside the `body` of the html document, we start with the following html:
```html
<section>
<h3>Character limit</h3>
<label for="comment-input"
>Please enter your comment in the text area below
<label for="comment-input">
Please enter your comment in the text area below
</label>
<textarea
id="comment-input"
Expand All @@ -40,9 +40,9 @@ Step 2: Access the `textarea` element

{{</note>}}

The DOM is an interface. It represents html elements as objects and provides functions to access these objects. Let’s start by accessing the `textarea` element and its value. To access DOM elements, we can use a method on the DOM API - [`document.querySelector`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector)
The DOM is an interface. It represents HTML elements as objects and provides functions to access these objects. Let’s start by accessing the `textarea` element and its value. To access DOM elements, we can use a method on the DOM API - [`document.querySelector`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector)

We can create a javascript file, `script.js`, and link it to the html document using a `script` element:
We can create a Javascript file, `script.js`, and link it to the HTML document using a `script` element:

```html {linenos=table,linenostart=1 hl_lines=["7"]}
<!DOCTYPE html>
Expand All @@ -56,10 +56,10 @@ We can create a javascript file, `script.js`, and link it to the html document u
<body>
<section>
<h3>Character limit</h3>
<label for="comment-input"
>Please enter your comment in the text area below
<label for="comment-input">
Please enter your comment in the text area below
</label>
<textarea id="comment-input" name="comment-input" rows="5"></textarea>
<textarea id="comment-input" name="comment-input" rows="5" maxlength="200"></textarea>
<p id="character-limit-info">You have 200 characters remaining</p>
</section>
</body>
Expand Down Expand Up @@ -88,9 +88,10 @@ console.log(textarea.value); // evaluates to the value typed by the user
{{<tab name="🖲️ Follow along">}}

1. On your local machine, set up a new directory with an `index.html` and `script.js`.
2. Make sure you start with the same static html as the example above.
3. Double check your script file is linked to your html file.
2. Make sure you start with the same static HTML as the example above.
3. Double-check your script file is linked to your html file.
4. Try querying the DOM and accessing various elements like the `textarea` element.
5. Try typing in the `textarea` element, and then accessing its `value` property in Dev Tools.

{{</tab>}}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,15 @@ In JavaScript, we can pass functions as arguments to other functions. In this ca
We can add a log to `updateCharacterLimit` to check it is called every time the `"keyup"` event fires.

```js
const characterLimit = 200;
// We already had the top part of this code before.

const textarea = document.querySelector("textarea");
const remainingCharacters = textarea.maxLength - textarea.value.length;

const charactersLeftP = document.querySelector("#character-limit-info");
charactersLeftP.innerText = `You have ${remainingCharacters} characters remaining`;

// From here down is new.

function updateCharacterLimit() {
console.log(
Expand All @@ -76,7 +83,7 @@ textarea.addEventListener("keyup", updateCharacterLimit);
rows="5"
maxlength="200"
></textarea>
<p id="character-limit-info">You have 200 characters remaining</p>
<p id="character-limit-info"></p>
</section>
```

Expand Down
Loading
Loading