๐ป 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
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.
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.
Youtube: Step-through-prep workshop ๐
๐งญ 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:
There are two times we may want to do this:
- When the page first loads we should show the initial limit.
- Whenever the user adds or removes a character from the textarea, we want to update to show the remaining limit.
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.
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
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.
๐ 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
textarea
elementThe 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:
|
|
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
- On your local machine, set up a new directory with an
index.html
andscript.js
. - Make sure you start with the same static HTML as the example above.
- Double-check your script file is linked to your html file.
- Try querying the DOM and accessing various elements like the
textarea
element. - Try typing in the
textarea
element, and then accessing itsvalue
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:
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:
- To solve Step 4: Update the interface with the number of characters left.
- To make this happen on page load.
- 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:
|
|
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
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 backprintMessage
, pass the input of"Sally"
toprintMessage
.”
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
๐ฌ 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:
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
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:
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
:
|
|
We need to tell the browser to call updateCharacterLimit
whenever a keyup event
addEventListener
:
|
|
Let’s break down the arguments that are passed to addEventListener
.
"keyup"
- this is the type of event we want to be notified aboutupdateCharacterLimit
- 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>
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.
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:
|
|
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);