๐Ÿง‘๐Ÿพโ€๐Ÿ’ป prep

๐Ÿ’ป User interfaces

Learning Objectives

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 web browser ๐Ÿงถ ๐Ÿงถ web browser provides a user interface to interact with web pages. 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.

๐Ÿ›‘ Implenenting a character limit

Learning Objectives

Letโ€™s define a problem.

Suppose we’re working on a website where users will need to comment on articles. In the user interface, they’ll be provided with a textarea element where they can type their comment. However, there is a character limit of 200 characters on their comment. As users type in to the textarea they should get feedback on how many characters they’ve got left for their comment.

Try typing in the character limit box above and observing the behaviour as you type.

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.

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

In the user interface, we will start off with some static html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <section>
      <h3>Example character limit comment component</h3>
      <label for="comment-input">
        Please enter your comment in the text area below
      </label>
      <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>
</html>

To implement the acceptance criterion, we’ll need to interact with the elements on the page. We’ll need a way to access and update elements based off user interactions.

๐Ÿงญ Breaking down the strategy

Learning Objectives

To implement the character limit component, we need to update the interface as the user types in the text area. We can outline a strategy as follows:

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.
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.

๐ŸŒฒ The DOM

Learning Objectives

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

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <section>
      <h3>Character limit</h3>
      <label for="comment-input">
        Please enter your comment in the text area below
      </label>
      <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>
</html>

๐ŸŒณ HTML tree

HTML documents form a tree-like structure. We start at the top html element and from there other html elements are nested inside.

html tree

When we use a web browser, it takes this HTML document, and provides us with an interface - a visual representation of the document, which we can read, and interact with (e.g. with a keyboard and mouse).

Document Object Model

When the browser first renders a web page it also creates the DOM - short for Document Object Model ๐Ÿงถ ๐Ÿงถ Document Object Model The Document Object Model 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. .

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.

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.

๐Ÿ”Ž Querying the DOM

Learning Objectives

Inside the body of the html document, we start with the following html:

<section>
  <h3>Character limit</h3>
  <label for="comment-input">
    Please enter your comment in the text area below
  </label>
  <textarea
    id="comment-input"
    name="comment-input"
    rows="5"
    maxlength="200"
  ></textarea>
  <p id="character-limit-info">You have 200 characters remaining</p>
</section>

querySelector()

๐Ÿ’ก recall

In the plan defined earlier, we had the following step: Step 2: Access the textarea element

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script defer src="script.js"></script>
  </head>
  <body>
    <section>
      <h3>Character limit</h3>
      <label for="comment-input">
        Please enter your comment in the text area below
      </label>
      <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>
</html>

Inside script.js, we can call document.querySelector:

const textarea = document.querySelector("textarea");

document.querySelector takes a single argument a string containing a CSS selector (just like we use when defining what elements a CSS rule should apply to).

document.querySelector returns an element object representing the first element in the page which matches that CSS selector. This element object lets us inspect and modify the element in the page.

Here we have given it the string "textarea", which is the CSS selector used to look up the elements with tag name textarea. The function returns an element object, which represents the first textarea in the web page. Once we can access the textarea object, we can access its properties. In particular we want to access the value a user types into the textarea box. We can do this by accessing the value property:

const textarea = document.querySelector("textarea");
console.log(textarea.value); // evaluates to the value typed by the user
  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.
  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.

๐Ÿงฎ Calculating the remaining characters

Learning Objectives

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

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

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:

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

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

Try typing in your textarea, then running this in the Dev Tools console.

๐Ÿท๏ธ Updating the interface

Learning Objectives

We know we don’t want to always have the number “200” in the text “You have 200 characters remaining”.

We’ve solved Step 3: Calculate the number of characters left. So we know what value we want to show.

All that remains is:

  1. To solve Step 4: Update the interface with the number of characters left.
  2. To make this happen on page load.
  3. To make this also happen when the textarea changes.

Instead of writing that text exactly in our HTML, we can use the DOM to set the contents of our p tag.

We can do this by querying the DOM for the element we want to update, and setting its innerText property. innerText is a property that represents “the text inside the element”.

If we change the value of a property in the DOM, it will update the page we’re viewing.

Try writing adding this to your script.js:

const limitDisplay = document.querySelector("#character-limit-info");
limitDisplay.innerText = "You have loaded the page.";

Even though our HTML said the paragraph should contain “You have 200 characters remaining”, we replaced this text by using the DOM.

Step 4: Update the interface with the number of characters left.

To achieve this goal, we’ll need to access the p element with id "character-limit-info" and then update its text content. As before, we can use document.querySelector to access an element in the DOM using an appropriate CSS selector:

1
2
3
4
5
const textarea = document.querySelector("textarea");
const remainingCharacters = textarea.maxLength - textarea.value.length;

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

And we can remove the initial text from the p tag from our HTML.

We want to do this because we have another way of setting this. If we wanted to change the text (e.g. to “You only have 200 characters remaining”), or change the character limit, we only want to change that one place (in our JavaScript). If we leave the initial value in the HTML, it could get out of date.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <section>
      <h3>Character limit</h3>
      <label for="comment-input">
        Please enter your comment in the text area below
      </label>
      <textarea id="comment-input" name="comment-input" rows="5" maxlength="200"></textarea>
      <p id="character-limit-info"></p>
    </section>
  </body>
</html>

We are now computing and setting the character limit info using the DOM on page load.

โฐ Timers

Learning Objectives

To update the DOM, we’ll need to understand the idea of timers and callbacks ๐Ÿงถ ๐Ÿงถ callbacks A callback function is a function that is passed as an argument to another function and gets executed after the main function has finished its execution. .

function printMessage(name) {
  console.log(`My name is ${name}`);
}

printMessage("Sally");
printMessage("Daniel");

In this example, we have 2 different parts: function declaration and function calls. We define the function printMessage and we call this function twice. However, sometimes we may want to define a function but have it called back at a later point in time. Consider another example:

function printMessage(name) {
  console.log(`My name is ${name}`);
}

setTimeout(printMessage, 3000, "Sally"); // <-- Call printMessage after at least 3000ms
printName("Daniel");

In this example, we define the function and call printMessage just once. However, we’re also using a built-in function called setTimeout. setTimeout allows us to set a minimum amount of time after which a function will be called back.

setTimeout(printMessage, 3000, "Sally");

Let’s break this down this call to setTimeout. It is saying:

“After at least 3000 ms, call the function printMessage, and when you call back printMessage, pass the input of "Sally" to printMessage.”

Notice we’re saying at least 3000 ms because setTimeout guarantees a minimum amount of time: it doesn’t say that printMessage muse be called exactly after 3000 ms. In this example, we say that printMessage is a callback function ๐Ÿงถ ๐Ÿงถ callback function A callback function is a function that is passed as an argument to another function and gets executed after the main function has finished its execution. as it is called back after 3000 miliseconds. In the terminal, we’ll see “Daniel” appear first and then after an at least 3000 ms delay, we’ll see the console log of “Sally”

๐ŸŽฌ DOM events

Learning Objectives

In the case of the textarea element, we want to update the p element text every time the user types inside the textarea. In other words, we want our application to react to the user typing on the keyboard. Currently our plan looks like this:

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;

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

Definition: events

An event is something that occurs in a programming environment that can be observed or responded to.

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 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:

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 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.

๐Ÿ“ค Reacting to events

Learning Objectives

As a user, we interact with the elements on a web page. We click on buttons, input text, submit forms etc.

To react to an event, we can declare a function that we want to run whenever a certain event occurs. We call this function an event handler. In the example below, we name this function updateCharacterLimit:

1
2
3
const textarea = document.querySelector("textarea");

function updateCharacterLimit() {}

We need to tell the browser to call updateCharacterLimit whenever a keyup event fires ๐Ÿงถ ๐Ÿงถ fires “fires” means “an event is triggered” . We do this using addEventListener:

1
2
3
4
5
const textarea = document.querySelector("textarea");

function updateCharacterLimit() {}

textarea.addEventListener("keyup", updateCharacterLimit);

Let’s break down the arguments that are passed to addEventListener.

  • "keyup" - this is the type of event we want to be notified about
  • updateCharacterLimit - the second argument is a function. It is a function that is called when an event occurs.

In JavaScript, we can pass functions as arguments to other functions. In this case, we’re passing a function updateCharacterLimit to addEventListener as an input. We can think of this as saying: whenever a key is released on the textarea element, then the browser will call the function updateCharacterLimit. Any code we want to run in response to the keyup event will need to be inside the updateCharacterLimit function.

We can add a log to updateCharacterLimit to check it is called every time the "keyup" event fires.

// 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(
    "keyup event has fired... The browser called updateCharacterLimit..."
  );
}

textarea.addEventListener("keyup", updateCharacterLimit);
<section>
  <h3>Character limit</h3>
  <label for="comment-input"
    >Please enter your comment in the text area below
  </label>
  <textarea
    id="comment-input"
    name="comment-input"
    rows="5"
    maxlength="200"
  ></textarea>
  <p id="character-limit-info"></p>
</section>
In your local project, define your own event handler and then use addEventListener to register that event handler for a keyup event. Add a console.log to the event handler and check the event handler is being called when the event fires. Check the console tab in dev tools to see the log appear in the console.

๐Ÿ“ Check progress

Learning Objectives

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:

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(
    "keyup event has fired... The browser called updateCharacterLimit..."
  );
}

textarea.addEventListener("keyup", updateCharacterLimit);

We’ve done the following:

  • Step 1: Defined a characterLimit
  • Step 2: Accessed the textarea element
  • Step 3: Registered an event handler updateCharacterLimit
  • Step 5: Calculate the number of characters left
  • On initial page load, update the user interface with the number of characters left

The browser will do the following for us:

  • Step 4: The browser will tell us when a user has pressed a key

We must still complete the following steps:

  • Step 6: Update the user interface with the number of characters left

We can re-use our existing code to update the user interface when the browser tells us the user has pressed a key:

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 remainingCharacters = textarea.maxLength - textarea.value.length;
  const charactersLeftP = document.querySelector("#character-limit-info");
  charactersLeftP.innerText = `You have ${remainingCharacters} characters remaining`;
}

textarea.addEventListener("keyup", updateCharacterLimit);

Typing in to the textarea element, we should see the page get updated to say e.g. “You have 118 characters left”.

๐Ÿงฉ Refactor

Learning Objectives

We have two identical blocks of code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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 remainingCharacters = textarea.maxLength - textarea.value.length;
  const charactersLeftP = document.querySelector("#character-limit-info");
  charactersLeftP.innerText = `You have ${remainingCharacters} characters remaining`;
}

textarea.addEventListener("keyup", updateCharacterLimit);

We know that functions can be used to avoid duplication. We have actually already extracted a function for this functionality for the event handler! Now let’s call it from the other place we do the same thing:

const textarea = document.querySelector("textarea");

updateCharacterLimit();

function updateCharacterLimit() {
  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);

๐Ÿ’ก Remember

When we think we’ve completed a goal or sub-goal, we should look at our code and see if we can improve it before we continue.