Lab exercises are designed to help you gain practical experience with web technologies. You should elaborate on these exercises to confirm you understand and deploy the learning you find here in your own code.
Working with user input
User input is handled mostly by the <form>
and <input>
elements.
In this exercise, we will explore how these work by building a few basic user interfaces.
Set up a basic document
Create a standard document template, include a link to a css file.
Add a script as the last element within the <body>
element.
Insert an <input>
element into your <body>
element.
You should have something like this:
|
|
<input>
elements are void elements and should not have closing tags.Also notice that I have added my script into the
<head>
of the page and I have given it atype
attribute set to"module"
. This is usually a good idea and will ensure your content is loaded before the script executes. However, it will only work if you are serving the file using e.g. the live server extension for VSCode.
Notice how it appears in your page, this is not a very clear user interface.
The <input>
element represents a box into which text can be added.
The default
type="text"
variant will accept any text.
Add a <span>
element before your <input>
and add the text “enter your name: " to indicate to the user what to do.
Accessibility
For users with screen readers, there is no connection between the text and the input. This is valid code but its not accessible and so would lose marks if submitted in your assignment.
Label elements
Instead we should use a <label>
element and connect it to the <input>
using a for
attribute.
|
|
This looks the same but, in addition to being more accessible to screen readers, it has slightly different behaviour. Notice that if you hover over the label, you get a different mouse cursor. If you click on the label, you will activate the input. It also allows us to locate the labels and the inputs differently but retain the connection.
Add a
<header>
and a<main>
element to begin the structure of your site. Give your site a visible title using a top-level heading<h1>
element within the<header>
.
The aria-label attribute
Input elements should always be labelled.
An alternative to a <label>
element is to use the aria-label
attribute on the <input>
element.
Add the following search tool into your <header>
.
|
|
Your site should look a bit like this.
Here’s my code.
|
|
I’ve added a <section>
around each example to keep them isolated and, of course, each section will have an <h2>
.
This all helps with accessibility.
Notice, I’ve also added a unicode magnifying glass into the search input placeholder.
Remember: For accessibility, always add a
<label>
element or anaria-label
attribute to<input>
elements.
Some style
Add the following simple styles to your page.
Notice the imported font, simple media
queries and use of hsl
colors.
|
|
The default styles place the search bar under the title.
When space is available, the search bar moves to the right.
By default<input>
elements are generic text fields but they can be modified with the type
attribute.
Add type="search"
to your search input and see what difference it makes.
You should see some minor changes to the field.
When text is entered, a cross appears and the field can be cleared with a click.
Additionally, to help users of screen readers and assistive technology, the entire search section can be given role="search"
to help make it clear.
|
|
More input types
There are many input field types available. Some of these were introduced with HTML5:
- color
- date
- number
- range
- search
- tel
- time
- url
Setting the type
attribute of an <input>
element to any of the above values will produce a different type of input field.
Here are some examples of the syntax:
|
|
The browser will provide an interface.
The <input>
element is one of the most powerful and complex elements in HTML.
Some input field types have a default value, others don’t.
Some require certain attributes, others don’t.
We won’t cover everything in this module, please do your own research.
For most input types, setting the value
attribute will initialise the input field with data.
|
|
The elements have default values entered.
Numbers
Some input types will only accept specific input so are good for preventing user errors.
Add a new <section>
with a second level heading “Numbers” and an input with the type number
.
|
|
The new section has our styles and the input looks only slightly different.
Notice that the input will not accept non-numeric input. It can be controlled via the keyboard arrow keys and with the mouse clicking on the tiny buttons.
But, nobody is 10,000 years old. What if we need to restrict it to a (generous) maximum value of 150?
Easy! Just add a max="150"
attribute.
Notice that it doesn’t prevent you from entering a larger number directly, but it does prevent using the arrow keys or mouse controls to increase the value beyond the provided maximum.
A value greater than 150 also sets the pseudo-class :invalid
which we can target with a css ruleset.
Add the following to your CSS:
|
|
Now notice that an invalid value will trigger the styles.
Experiment by setting the min
and step
attributes also.
Notice that values are invalid if they fall between steps.
Ranges
Inputs of type range
provide a graphical input element that behaves similarly to the number
type.
Add a new <section>
with a second level heading “Ranges” and an input with the type range
.
|
|
We will use the default min
and max
values (0-100) to control the height of an element in %
units.
Notice we have initialised the value of the <input>
to “50”, we also added a class
on the <section>
so we can style it and we have an extra <div>
element which will be controlled via JavaScript.
Add the following additional styles.
First, at the top of your css file, add a css custom property called --height
to the :root
element.
This is essentially a css variable and can be easily set using JavaScript.
We can name custom properties however we like.
Adding a property to the :root
element allows us to use it throughout our css rules.
|
|
Then, add a position: relative
to the new .relative
class.
This will act as a container for any children positioned absolutely.
|
|
Now we can style the #heightTarget
element to be position: absolute
.
The bottom
and right
properties will fix its position within the relatively positioned parent element.
We also give it some width and set its height
property to be equal to our customised --height
property using var(--height)
.
|
|
The result should look like this:
This setup will allow us to vary the height of the #heightTarget
element by setting the value of the custom property.
We will do this with a simple event listener on the input
event of our <input>
element.
The input
event fires whenever the value is updated.
Add the following to your script.
|
|
Here document.documentElement
refers to the top level element (:root
in css terms) and style.setProperty
allows us to set the value of any custom property.
We pass in the name of the property and the value we wish to assign.
In this case, the value is the value
of the myHeight
element plus a %
symbol.
We used string interpolation using backticks and the
${}
syntax to inject a variable into our string.
Notice that now, when the slider is adjusted, the height of the <div>
element responds.
We have a transition
on the element so the change is animated.
Try setting the step
attribute of the <input>
to a large value such as “25”.
Every time the <input>
element steps to the next value, the input
event is triggered, our custom --height
property is updated which changes the <div>
css height
property and the transition
property animates the change.
Colours
The colour <input>
works exactly the same.
In the following example, notice how the code is almost identical to the above example.
First, we add some new css custom properties for foreground colour and background colour.
|
|
Then we create styles for a new #colours
element which will use the custom properties.
So if we change the custom properties, we change the element colours.
|
|
We are also using grid to layout the section.
Now, add a new <section>
into your document.
|
|
Finally, add the necessary JavaScript event listeners to activate the new <input>
elements.
|
|
The result should be as expected.
We can select a foreground or background colour with the new inputs. As we change the input values, the section changes colour.
Using forms
Forms allow us to aggregate inputs together into groups. They also control the communication of user input to a server (which will become very important for us).
Create a simple login form as follows.
|
|
We will use display: grid
to format the form.
This particular approach requires that all elements (except <h2>
and inputs with type="submit"
elements) come as <label>
, <input>
pairs.
|
|
Try to understand what is going on.
The grid has two columns (set by grid-template-columns
), one is max-content
and the other is 1fr
.
This ensures the first column never wraps the “confirm password” label (it is always given the space it needs) and the second column is given all additional space.
We also customised the behaviour of the labels to make them line up on the right and the input elements, to allow them to shrink smaller then the default.
We allowed the <h2>
to span both columns and the type="submit"
<input>
to align right within the second column.
Validation styles
Notice that our input elements are invalid (because they are required
).
The invalid styles are pretty bad, lets improve them.
Update the :invalid
styles like this.
|
|
Now the form gently suggests more work is needed and the individual elements indicate where the problem is.
Submit behaviour
We have specified some validation criteria for the individual input elements. This causes the form to prevent the submit action unless these basic criteria are met.
Try clicking the login button. You should get an error message.
Fill in a username and click again. You should get another error message.
Try a short password, less than eight characters. You should get a different error message.
Custom validation
Using JavaScript, we can add extra validation criteria. Currently, the form will submit even if the password confirmation field doesn’t match the password field. This is not OK. Our server doesn’t need the confirmation password, this is purely for the benefit of the user to help prevent an accidental typo in the password.
We need custom logic to do the test.
We can use the setCustomValidity
method on the confirmation field to set the field to be invalid.
If we pass an empty string to this method, the field is considered valid. So the logic in the code below will set the confirmation field to invalid (with a custom message) when the confirmation doesn’t match the password. We apply this logic when either of the password fields are updated.
|
|
Now, whenever either of the password fields are changed, we are checking for a match.
If they don’t match then the confirmation field is set as invalid with a clear message.
When they do match, the custom validity setting is replaced with an empty string which sets the field to valid
.
Try to submit with a mismatched password confirmation. You should see our customised validation message.
Also notice that if you begin with matching and valid fields, changing the password will impact the validity of the confirmation field.
Handling the data
By default, forms will issue an HTTP GET
request to the server when they are submitted.
Submit the form and you should see that the page reloads.
Now notice that the data you submitted in the form has been added to the url as a so-called query string. For example, if you entered “Something” as the username and “data_breach!” as the password, then something like this would be requested from the server.
http://127.0.0.1:5500/index.html?username=Something&password=data_breach%21
A query string is a set of parameters appended to the url following a question mark (
?
) and separated by ampersand symbols (&
). The query string is url encoded so the exclamation mark in the above example is converted to%21
.
Notice that our password confirmation isn’t included in the data.
This is because only those input elements with a name
attribute are included in the submitted data.
If we had a back-end, it could read these data in and return a modified version of the page. This is one of the main ways in which we will communicate between the web browser and the web server. The other way is simple links to load files.
Links can also have query parameters hard-coded into their href attributes.
<a href="http://127.0.0.1:5500/users.html?id=3">User 3</a>
Query parameters are excellent ways to create links to specific content (e.g. using an id parameter such as user.php?id=3) or configuration (e.g. products.php?query=yellow&sort_order=price for filtering or sorting).
A php script can read in the provided data using
$_GET
.
|
|
Note that sending sensitive data in a GET request like this is insecure, even if HTTPS is used (which it MUST be for a secure application deployed on the web) the users password would be displayed as part of the url.
Redirecting
Forms can load any page we want.
We can configure a form to request a different document by setting the action
attribute.
This is how a simple php web application might work, each form pointing to a specific php script which interacts with the database and constructs an HTML response.
<form action="search.html"> <input type="search" name="query" placeholder="search"> </form>
We have seen the default setting issues an HTTP GET
request with data encoded directly into the URL as query parameters.
Setting method="POST"
on the form will issue a POST
request with the data inside the HTTP request body.
This will fail when using live server in VSCode but if you are running PHP (e.g. via PHP server in VSCode) then the data can be extracted from the request by a php script.
Capturing data with JavaScript
Alternatively, we can capture the form data on the submit event as follows.
This approach would not make much sense for a login form and is being included for completeness. It should probably not be used without a compelling reason to do so. Allowing the HTML form to do the job in a simple way as above is usually the correct approach.
|
|
The above changes get a reference to the login form (login
) and register an event listener on it, listening for the 'submit'
event.
Inside the function, we can see this line:
|
|
This line prevents the default action of the submit event from being triggered. This means the page will not reload, the form submit action is effectively cancelled. If you comment this line out you will see the page reload and the form data (including the password) will appear in the url.
Instead of submitting the form, we grab the data, log it to the console and clear the form.
We extract the data by creating a FormData object from our login form.
|
|
FormData
objects are convenient ways to capture data from forms using JavaScript.
They will automatically extract all the named values for us and provide a convenient API for us to use.
We then loop over the iterator returned by the FormData.keys() method and log the value extracted using the FormData.get() method.
|
|
Notice that only the fields with the
name
attribute set are being extracted. See the FormData documentation for more details.
Finally, we clear the form using the HTMLFormElement.reset() method.
|
|
Additional exercise
If you have got this far, look up HTML data attributes and think about how you could implement a simple search function using the existing search input. Try to add data attributes to each section and use the search input to filter the visible sections.
Lab learning outcomes
- Use
<input>
fields to collect user input - handle user input with JavaScript
- Use CSS custom properties
- Reflect user input directly on the page without reloading
- Use
<form>
fields and form validation
Input fields are the primary method for getting user data, so it is essential to choose the correct type, depending on what you want the user to enter. Handling these on the page with JavaScript and providing feedback without (or before) calling any server scripts or reloading the page is one of the key elements of modern web development.
Manipulating the Document Object Model (DOM) in this way avoids excessive to-and-fro messages between the client (browser) and the web server, making user interaction more immediate and intuitive. If you use local storage (in a future lab) to retain user preferences between browser sessions, their choices can be made to persist in that user’s browser.