Learn JavaScript: Novice to Ninja

Chapter 8: Forms

Forms are a very common method of interacting with a web page. A form is the main component of Google’s home page, and most of us use forms every day to log in to our favorite sites. In this chapter, we will look at how forms can be used to interact with a JavaScript program.

In this chapter, we’ll cover these topics:

  • Form controls
  • Accessing form elements
  • Form properties and methods
  • Form events
  • Submitting a form
  • Retrieving and changing values from a form
  • Form validation
  • Our project ― add a form for answering the questions.

Forms

Forms are made up of a <form> element that contains form controls such as input fields, select menus and buttons. These input fields can be populated with information that is processed once the form has been submitted.

Traditionally, when a form was submitted, it would be sent to a server where the information would be processed using a ‘back end’ language such as PHP or Ruby. It’s possible, and becoming more and more common, to process the information in a form on the ‘front end’beforeit is sent to the server using JavaScript, which is what we’ll be focusing on in this chapter.

Each form control has an initial value that can be specified in the HTML code. This value can be changed by a user entering information or interacting with the form’s interface (such as using a slider to increase or decrease a value). The value can also be changed dynamically using JavaScript.

Forms UX

When it comes to forms, there are plenty of usability and accessibility considerations to keep in mind, such as using correct and semantic markup, making forms keyboard accessible and using WAI-AIRA labels. Most of these fall outside the scope of this book, as we’ll be keeping the focus on how to use JavaScript to interact with the forms.

If you’d like to learn more about how to design forms that are accessible and enhance the user experience thenDesigning UX: Formsby Jessica Enders is well worth a read.

A Searching Example

We’ll start off with a simple example of a form that contains one input field, and a button to submit a search query, not unlike the one used by Google. This example doesn’t use any styles; you just need to create a file called search.html that contains the following code:

<!doctype html><html lang='en'><head>    <meta charset='utf-8'>    <title>Search</title></head><body>    <form name='search' action='/search'>        <input name='searchInput'>        <button type='submit'>Search</button>    </form><script src='main.js'></script></body></html>

This form has a name attribute of search , and contains two controls: an input field where a user can enter a search phrase, and a button to submit the form. The form can also be submitted by pressing Enter.

The action attribute is the URL that the form will be submitted to so it can be processed on the server side. The input field also has a name attribute of searchInput that is used to access the information inside it.

You should also create a file called main.js to put the JavaScript in. This can be saved in the same directory as search.html .

Accessing Form Elements

The legacy DOM had a useful property called document.forms that returns an HTML collection of all the forms in the document in the order they appear in the markup. Even though there is only one form in our example, a collection will still be returned, so we have to use index notation to return the first (and only) form object, like so:

const form = document.forms[0];

This is the equivalent of using the following method that we learned in chapter 6:

const form = document.getElementsByTagname('form')[0];

Instead of using a numerical index, we can use the name attribute to identify a form:

const form = document.forms.search;

Be careful referencing elements in this way, however. If the form had the same name as any properties or methods of the document.forms object, such as ‘submit’, for example, that property or method would be referenced instead of the <form> element. This is unlikely to happen, as the document.form object doesn’t have many properties or methods, but it is something to be aware of. To avoid this, square bracket notation can be used (this is also required if the form’s name attribute contains any invalid characters, such as spaces or dashes):

const form = document.forms['search'];

A form object also has a method called elements that returns an HTML collection of all the elements contained in the form. In this case the form contains two controls: an input element and a button element:

const [input,button] = form.elements;

We can also access the form controls using their ‘name’ attributes as if it was a property of the form object. So, for example, the input field has a name attribute of searchInput and can be accessed using this code:

const input = form.searchInput

The square bracket notation can also be used instead (again, this is useful if there are any naming clashes with existing property and method names, or if the name is an invalid variable name):

const input = form['searchInput']

Form Properties and Methods

Form objects have a number of useful properties and methods that can be used to interact with the form.

The form.submit() method will submit the form automatically. Note that submitting a form using this method won’t trigger the form submit event that’s covered in the next section.

A form can be submitted manually by the user employing a button or input element with a type attribute of submit , or even an input element with a type attribute of image :

<button type='submit'>Submit</button><input type='submit' value='Submit'><input type='image' src='button.png'>

The form.reset() method will reset all the form controls back to their initial values specified in the HTML.

A button with a type attribute of reset can also be used to do this without the need for additional scripting:

<button type='reset'>Reset</button>

Reset Buttons

Reset buttons are generally considered poor for usability, as they are too easy to click and then wipe out all the data that’s been entered. So think very carefully before using one in a form.

The form.action property can be used to set the action attribute of a form, so it’s sent to a different URL to be processed on the server:

form.action = '/an/other.url'

Form Events

Forms trigger a number of events like those discussed in the last chapter. Some of these events are exclusive to forms.

The focus event occurs when an element is focused on. In the case of an <input> element, this is when the cursor is placed inside the element (either by clicking or tapping on it or navigating to it using the keyboard). To see an example, add the following code to main.js :

const input = form.elements.searchInput;input.addEventListener('focus', () => alert('focused'), false);

Open search.html in your browser and place the cursor inside the input field. You should see an alert dialog similar to the one in the screenshot below.

Our alert dialog

The blur event occurs when the user moves the focus away from the form element. Add the following to main.js , reload the page, and then move the cursor away from the search box:

input.addEventListener('blur', () => alert('blurred'), false);

The change event occurs when the user moves the focus away from the form elementafter changing it. So if a user clicks in an input field and makes no changes, and then clicks elsewhere, the change event won’t fire, but the blur event will.

Add the following code to main.js and reload the page. You’ll notice the alert message ‘changed’ only appears if you actually change the value inside the search box, then move the cursor away from it:

input.addEventListener('change', () => alert('changed'), false);

Note that the blur event will also fire, but after the change event.

Submitting a Form

Possibly the most important form event is the submit event, occurring when the form is submitted. Usually this will send the content of the form to the server to be processed, but we can use JavaScript to intercept the form before it’s sent by adding a submit event listener. Add the following code to the main.js file:

const form = document.forms['search'];form.addEventListener ('submit', search, false);function search() {    alert(' Form Submitted');}

Now reload the page and click on theSubmitbutton. You should see an alert dialog sayingForm Submitted. After you clickOK, the browser tries to load a nonexistent page (the URL should end in something similar to ‘…/search?searchInput=hello’). This is because when the event fired, our search() function was invoked, displaying the alert dialog. Then the form was submitted to the URL provided in the ‘action’ attribute for processing, but in this case, the URL isn’t a real URL, so it doesn’t go anywhere. Back-end processing isn’t covered in this book, so we’ll keep this as a ‘dummy’ URL and focus on using JavaScript to process the information instead.

We can actually stop the form from being submitted to that URL altogether by using the preventDefault() method that we saw in the last chapter. Add the following line to the search function:

function search(event) {    alert('Form Submitted');    event.preventDefault();}

Now reload search.html and try submitting the form. You’ll see that the alert dialog still appears, but after you clickOK, the form doesn’t try to submit itself to the dummy URL.

Retrieving and Changing Values From a Form

Text input element objects have a value property that can be used to retrieve the text inside the field.

We can use this to report back what the user has searched for. Edit the search() function to the following:

function search(event) {    alert(`You Searched for: ${input.value}`);    event.preventDefault();}

Note that in this example, input is the variable that we defined at the start of the main.js file that points to the input element in our form, but it could have been called anything.

Now refresh the page, enter some text in the search box, and you should see a similar sight to the screenshot shown below:

Reporting what the user searched for

It’s also possible to set the value using JavaScript. Add the following line of code to the main.js file:

input.value = 'Search Here';

Now refresh the page and you should see that the string ‘Search Here’ is displayed in the input field.

The problem with this is that the text remains in the field when the user clicks inside it, so it has to be deleted before the user can enter their own text. This is easily remedied using the focus and blur event handlers. Add the following to main.js :

input.addEventListener('focus', function(){    if (input.value==='Search Here') {        input.value = ''     }}, false);input.addEventListener('blur', function(){    if(input.value === '') {        input.value = 'Search Here';    } }, false);

Now the default text will disappear when the user clicks inside the input field (the focus event) and reappear if the user leaves the field blank and clicks away from it (the blur event).

The placeholder Attribute

Similar functionality can be produced in modern browsers using the placeholder attribute in the HTML markup. Simply change the input field to the following in search.html :

<input type='text' name='search-box' placeholder='Search Here'>

This has slightly different behavior in that the placeholder text is not actually a value of the input field, so it won’t be submitted as the field’s value if the user fails to fill it in.

Form Controls

In our previous search example, we only used the input and button form controls. But there are others that can help to make our web pages more interactive.

Some common types of form control are:

  •  <input> fields, including text, passwords, check boxes, radio buttons, and file uploads
  •  <select> menus for drop-down lists of options
  •  <textarea> elements for longer text entry
  •  <button> elements for submitting and resetting forms

To demonstrate all these HTML form controls, we’ll create another form that contains all these elements. Back in Chapter 5, we created a superman object that had lots of properties associated with the Man of Steel. We’re going to design a form that allows a user to enter all these details into a browser, so we’ll create a similar hero object that describes a superhero (or villain).

Create a new project folder that contains the following code in a file called hero.html :

<!doctype html><html lang='en'><head>    <meta charset='utf-8'>    <title>Hero Form</title></head><body>    <form id='hero'>        <label for='heroName'>Name:            <input type='text' id='heroName' name='heroName' autofocus placeholder='Your Super Hero Name' maxlength=32>        </label>        <button type='submit'>Submit</button>    </form><script src='main.js'></script></body></html>

We’ll start with a basic form that’s fairly similar to our previous search example, containing a text input field and button to submit the form.

New Attributes in HTML5

The input element includes some of the new attributes introduced in HTML5.

The autofocus attribute give focus to this element when a page loads. It is the equivalent to putting the following line of JavaScript in main.js :

document.forms.hero.heroName.focus();

The placeholder attribute will insert the value provided in the input field until the user enters some text. This can be useful to place hints about how to fill in the form.

The maxlength attribute will limit the number of characters that can be entered in the field to the value given (in this case 32).

There are many new attributes that can be employed to make forms more user-friendly. A good roundup of all the new form elements can be foundin this article on the SitePoint website.

We’ll also need a file called main.js that is saved in the same folder as the hero.html file. In this file, let’s start off by assigning the form to a variable and then adding an event listener for when the form is submitted:

const form = document.forms['hero'];form.addEventListener('submit', makeHero, false);

The event listener will invoke the makeHero() function when the form is submitted. This function will return an object based on the information provided in the form. Let’s implement that function by adding this code to main.js :

function makeHero(event) {    event.preventDefault(); // prevent the form from being submitted    const hero = {}; // create an empty object    hero.name = form.heroName.value; // create a name property based on the input field's value    alert(JSON.stringify(hero)); // convert object to JSON string and display in alert dialog    return hero;}

This function uses the event.preventDefault() method to stop the form from being submitted. We then create a local variable called hero and assign it to an empty object literal. We’ll then augment this object with properties from the form, although we only have the name property at the moment.

Once the hero object is created, it would probably be returned by the function then used elsewhere in the rest of the program. Since this is just for demonstration purposes, we simple use the JSON.stringify() method to convert the hero object into a JSON string and display it in an alert dialog.

Open up hero.html in a browser and enter the name of a superhero and you should see a screenshot similar the below.

Entering our hero’s name

Now we know our code is working, let’s look at some of the other types of form controls.

Input Fields

Input fields are the most common types of form control, but there are several categories of input field as you’ll soon see:

Text Input Fields

The default type of input field is text , which is used for entering a short piece of text, such as a username. In our example, we use a text input field to enter the name of the superhero. The type='text' attribute isn’t required (we didn’t use it in the search example as text is the default), but it is advisable to use it as it makes the intended purpose of the field explicit, helping with maintenance, readability and future-proofing.

The initial value of this field can be set in the HTML using the value attribute. For example, you could pre-fill the ‘recommended’ donation on a charity page like so:

<label for='donation-amount'>Enter amount to donate:     <input type='text' id ='donation-amount' name='donationAmount' value='10'></label>

Password Input Fields

 input type='password' is used to enter passwords or secret information. This works in the same way as an input field with type='text' , except the characters are concealed as they are entered so they’re unable to be read on the screen.

To see this in action, we will add a realName property to our hero object. Obviously the real name of a superhero is secret information, so it needs to be hidden from prying eyes when it is being entered. Add the following line to the form in hero.html (just before the submit button):

<label for='realName'>Real Name:<input type='password' name='realName' id='realName'></label>

To process this information, we add the following line to the makeHero() function in main.js :

hero.realName = form.realName.value;

As you can see, values from a password input field are accessed in exactly the same way as text input fields using the value property.

Checkbox Input Fields

Check boxes are created using input fields with type='checkbox' . They are used to select different options that can be checked (true) or left unchecked (false). The user can selectmore than one checkboxfrom a list.

We’ll use checkboxes to add a list of powers that the superhero can have. Add the following lines of code to the form in hero.html :

<p>Super Powers:</p><label for='flight'>Flight:    <input type='checkbox' id='flight' value='Flight' name='powers'></label><label for='strength'>Super Strength:    <input type='checkbox' id='strength' value='Strength' name='powers'></label><label for='speed'>Super Speed:    <input type='checkbox' id='speed' value='Super Speed' name='powers'></label><label for='energy'>Energy Blasts:    <input type='checkbox' id='energy' value='Energy Blasts' name='powers'>    </label><label for='telekinesis'>Telekinesis:    <input type='checkbox' id='telekinesis' value='Telekinesis' name='powers'></label>

Notice that all the checkbox elements have the same ‘name’ property of ‘powers’. This means they can be accessed as an HTML collection, like so:

form.powers;

We can then iterate over this collection using a for loop to see if each checkbox was checked. Checkbox objects have a checked property that tells us if it has been checked or not. It is a boolean property, so can only have the values true or false . The value property is used to set the name of the power that can be used if the checkbox has been checked. Add the following code to the makeHero() function in main.js :

hero.powers = [];for (let i=0; i < form.powers.length; i++) {    if (form.powers[i].checked) {        hero.powers.push(form.powers[i].value);    }}

This creates a powers property for our hero object that starts as an empty array. We then iterate over each checkbox to see if it was checked in the form. If it was, we add the ‘value’ property of the checkbox to the powers array using the push method.

We can refactor this code to be much more succinct by using the array iterators we saw in Chapter 4. The following code will achieve the same result:

hero.powers = [...form.powers].filter(box => box.checked).map(box => box.value);

This uses the spread operator to turn the node list into an array. This then allows us to use the filter() method that returns an array containing only the check boxes that were checked (this is because their ‘checked’ property will be truthy). We then chain the map() method to the end, which replaces each checkbox in the array with its ‘value’ property. This array is then returned and stored in the hero.powers variable.

Note that a checkbox can be set to true using JavaScript by setting its ‘checked’ property to true . For example, we could make the first checkbox in the list of powers appear checked with this line of code:

document.forms.hero.powers[0].checked = true;

Checkboxes can also be checked initially using the ‘checked’ attribute in the HTML:

<input type='checkbox' value='Flight' name='powers' checked>

Radio Button Input Fields

Radio buttons are created using input fields with type='radio' . Like checkboxes they allow users to check an option as true , but they provide an exclusive choice of options, soonly one optioncan be selected.

This type of mutually exclusive option could be whether a superhero is a hero or a villain… or even an antihero (you know, those who are unable to decide whether to be good or bad!). Add this line of code to the form in hero.html :

<p>What type of hero are you?</p><label for='hero'>Hero:    <input type='radio' name='category' value='Hero' id='hero'></label><label for='villain'>Villain:    <input type='radio' name='category' value='Villain' id='villain'></label><label for='anti-hero'>Anti-Hero:    <input type='radio' name='category' value='Antihero' id='anti-hero'></label>

All these radio buttons have the same ‘name’ attribute of ‘category’. This is used to group them together ― only one radio button can be checked in a group that has the same name attribute. It also means we can access an HTML collection of all the radio buttons in that group using the property of the same name ― as can be seen in this line of code:

form.category;

If you examine this array after the form has been submitted, it will look similar to the example below:

[input, input, input, value: "Antihero"]

The value of the radio button that was selected is stored in form.category.value (in this case it is “Antithero”). This means we can assign a category property to our hero object by adding the following code to the makeHero() function in main.js :

hero.category = form.category.value;

Each radio button has a ‘checked’ property that returns the boolean values true and false , depending on if it has been selected or not. It’s possible to change the ‘checked’ property to true using JavaScript, but because only one radio button can be checked at once, all the others with the same ‘name’ property will change to false . So the following line of code would check the ‘antihero’ radio button, but the ‘hero’ and ‘villain’ radio buttons would then be unchecked:

form.type[2].checked = true;

Radio buttons can also be checked initially using the ‘checked’ attribute in the HTML:

<input type='radio' name='type' value='Villain' checked>

Hidden Input Fields

Hidden fields can be created using input fields with type='hidden' . These are not displayed by the browser, but have a ‘value’ attribute that can contain information that is submitted with the form. They are often used to send information such as settings or information that the user has already provided. Note that the information in these fields is in no way secret, as it’s visible in the HTML, so shouldn’t be used for sensitive data. The value of a hidden input field can be changed using JavaScript in the same was as any other input field.

File Input Fields

A file input field can be created using input fields with type='file' . These are used to upload files, and most browsers will provide a browse button or similar that lets users select a file from their file system.

Other Input Types

There are lots of new input types included in HTML5, such as numbertel and color . As browsers start to support these, they will implement different user-interface elements depending on the input type. So a number field might use a slider, whereas a date field will show a calendar. They will also validate automatically, so an email input field will show an error message if there’s no valid email address.

Let’s add an input type of ‘number’ to our form so we can enter the age of our hero. Add the following to hero.html :

<label for='age'>Age:<input type='number' id='age' name='age' min=0 step=1></label>

Number input fields also have optional ‘min’ and ‘max’ attributes that can be used to limit the input given. The ‘step’ attribute is used to specify how much the value changes by each click. Most modern browsers will add controls at the side of the input field so the value can be increased or decreased, as shown below.

Using the number input field to specify our hero’s age

We’ll also need some JavaScript to process the age information. Add the following line to the makeHero() function in main.js :

hero.age = form.age.value;

These new input types are yet to be supported, but the good news is that you can start using them now because they will still work; the browser will just display a normal text input field if it doesn’t support a particular type. A good roundup of all the new form elements can befound in this article on SitePoint.

Select Drop-Down List

Select drop-down lists can be used to select one or more options from a list of values. The ‘multiple’ attribute is required if more than one option is to be selected. We’ll use one in our example to choose the city where our hero operates. Add the following line of code to the form in hero.html :

<label for='City'>Base of Operations:    <select name='city' id='city'>        <option value='' selected>Choose a City</option>        <option value='Metropolis'>Metropolis</option>        <option value='Gotham City'>Gotham City</option>        <option value='Keystone City'>Keystone City</option>        <option value='Coast City'>Coast City</option>        <option value='Star City'>Star City</option>    </select></label>

Note that the ‘selected’ attribute can be used to set the initial value in the HTML. In this example, the blank option that provides the instructional message ‘Choose a City’ has this attribute, so it’s shown when the page loads.

The ‘name’ attribute of the <select> element is used to access it in JavaScript as a property of the form object:

form.city;

If only one item was selected, this will return a reference to that selection; otherwise a collection will be returned containing each selection.

Each selection object has a value property that’s equal to the ‘value’ attribute of the <option> tag that was selected. Add the following code to the makeHero() function to set the city property:

hero.city = form.city.value;

It is also possible to find out the index of the option that has been selected, using the selectedIndex property. For example, if a user selected ‘Gotham City’ from the menu, form.city.selectedIndex would return 2 because it’s the third option in the list. This can then be used to access the actual text contained in the selected option:

form.city.options[form.city.selectedIndex].text

From the example above, it should be clear that you can access the text of any option using index notation. For example, the following code returns the text from the first option:

form.city.options[0].text<< "Choose a City"

Text Areas

<textarea> element is used to enter long pieces of text over multiple lines such as a comment or blog post. They work in much the same way as input fields. We access them using the ‘name’ attribute, and use the value property to see what text was entered.

For example, we can add a text area to our form so the origin story of our superhero can be entered. Add the following lines of code to the form in hero.html :

<label for='origin'>Origin Story:    <textarea id='origin' name='origin' rows='20' cols='60'></textarea></label>

The text entered into this text area can now be added as a property of the hero object by placing the following line of code to the makeHero() function in main.js :

hero.origin = form.origin.value;

It is also possible to change the value in the form directly:

form.origin.value = 'Born as Kal-El on the planet Krypton...';

The initial value of a text area can be set in the HTML by placing the text between the opening and closing tags:

<textarea name='origin' rows='20' cols='60'>Born as Kal-El on the planet Krypton...</textarea>

Buttons

We’ve already used a button to submit a form, but there are different types of buttons. The default type is ‘submit’, which is why we didn’t have to specify the type in the search example at the start of the chapter. Another type we’ve already seen is ‘reset’, which will reset all the form fields to their initial settings. Let’s add a reset button to our example by adding the following line to hero.html , just before the submit button:

<button type='reset'>Reset</button>

Now have a go at filling in part of the form and pressing the reset button; all the form fields should clear. Remember: this isnotrecommended good practice for usability reasons!

The other type is simply ‘button’. This doesn’t need to be inside a form element and has no default behavior. It simply creates a clickable button that can have an event listener attached to it:

<button type='button'>Click Me</button>

There is also a type of ‘menu’ that can be combined with <menu><menuitem> and <li> tags to create a dropdown menu when it’s clicked on, although support for this is fairly patchy at present.

I Need a Hero!

Now that our example form is complete, have a go at filling it in and pressing the Submit button. You should see something similar to the screenshot below.

Hero JSON

We’ve successfully created a JavaScript object from form inputs that could then be used in the rest of our program. In this example we’ve used the JSON.stringify() method to convert the object into a JSON representation of the object, which could then be stored in a database or exported to an external web service.

Form Validation

Form validation is the process of checking whether a user has entered the information into a form correctly. Examples of the types of validation that occur include ensuring that:

  • A required field is completed
  • An email address is valid
  • A number is entered when numerical data is required
  • A password is at least a minimum number of characters

Validation can occur on the client side using JavaScript, and on the server side. It is advisable to use both client-side and server-side validation. JavaScript should not be relied upon to validate any data before it’s saved to a database. This is because it’s possible for a user to modify the JavaScript code and bypass the validation rules. It’s also very easy to bypass the front-end completely and send arbitrary data to the application’s backend. For these reasons, JavaScript validation should be used to enhance the user experience when filling in a form by giving feedback about any errors before it’s submitted. This should then be backed up with more validation performed on the server before the data is eventually saved to a database. Having said that, it’s still useful to validate on the client side even if the data will be validated again on the server side. This is because it will ensure that more valid data is sent to the server, which helps to cut down the number of HTTP requests required to send the form back and forward from the server to be corrected.

HTML5 has its own validation API that can be used, although it lacks the full support from all browsers at the moment. The error messages that it produces can look inconsistent across browsers and are difficult to style.

The API works by simply adding relevant attributes to the form fields. For example, if a field is a required field that must be filled in, all you need to do is add a ‘required’ attribute to that field and the browser will take care of the rest.

To see an example of this in action, add a required attribute to the heroName field in our hero form:

<input type='text' id='heroName' name='heroName' autofocus placeholder='Your Super Hero Name' maxlength=32 required>

Now refresh the page and leave the name field blank. As you click in another field, you’ll notice that the blank name field is highlighted because it’s a required field.

You can find more information about the HTML5 validation API inthis article by Craig Buckler on SitePoint.

It is also possible to implement custom form validation using JavaScript. For example, say we wanted to exclude any superhero names that begin with an ‘X’. This is not a standard form of validation, so we’d have to write our own. Add this code to main.js to see an example of custom validation:

form.addEventListener('submit',validate,false);function validate(event) {    const firstLetter = form.heroName.value[0];    if (firstLetter.toUpperCase() === 'X') {        event.preventDefault();        alert('Your name is not allowed to start with X!');    }}

We start by finding the first letter of the value entered in the name field using the index notation (remember that an index of 0 represents the first letter in a string). It then checks to see if the first letter is equal to the string literal ‘X’, and alerts the user if this is the case. It also uses the preventDefault() method to stop the form from being submitted. Otherwise it returns true , which means the form is submitted as normal.

If you refresh the page and enter a name beginning with ‘X’ in the name field, then try submitting the form, you should receive an error alert dialog as in the screenshot shown below.

Validation error alert dialog

We can improve the usability of the form further by giving instant feedback, instead of waiting for the form to be submitted. This can be achieved by adding the event listener directly to the input field that will fire when the user presses a key (using the keyup event). The feedback can then be inserted inside the label element of the input field, along with a class of error for more direct feedback. Add the following code to main.js :

const label = form.querySelector('label');const error = document.createElement('div');error.classList.add('error');error.textContent = '! Your name is not allowed to start with X.';label.append(error);function validateInline() {    const heroName = this.value.toUpperCase();    if(heroName.startsWith('X')){    error.style.display = 'block';    } else {    error.style.display = 'none';    }}

For this technique to work, we actually add the error message to the HTML in the JavaScript file, regardless of whether the error has been made or not. This is done in the first five lines above, using the DOM to create a <div> element that contains the error message and has a class of ‘error’. It’s then added to the <label> element using the append() method. The trick here is that the element will not be visible as it will start with a style declaration of display: none; . This will be updated dynamically as the keyup event fires.

The validateInline() function is called every time the event is triggered. We start by assigning the variable heroName to the value entered in the input field, but we also apply the toUpperCase() method to it. This will allow us to check if it begins with an ‘x’ or ‘X’ without having to check both separately.

We then use an if-else block to check if the error has been made using the startsWith() method, which will return the first letter of a string. If it starts with an ‘X’ then we change the style of the error element to display: block , which will make it visible.

The code inside the else block is run if there is no error, so it resets the style of the error element to display: none , making the error disappear.

To make this technique work, we need to add some custom styling to the error element to make sure it isn’t visible initially, and to make the message stand out. Add the following <style> block inside the <head> section of hero.html :

<style>    .error{        background: #f99;        border: #900 1px solid;        display: none;    }</style>

We’re Not Using an External CSS File for Simplicity

It would be better to place any styles in an external CSS file, but for the purposes of this example, it’s easier to put it straight into the HTML file.

Now if you refresh the page and try to enter a name beginning with ‘X’, you should see an error message above the input field as soon as you try to move to another field. This can be seen in the screenshot below.

Showing an inline error message

This is a Specific and Perhaps Unrealistic Example

This was a very specific example of inline form validation – checking to see if an input field began with the letter ‘x’, and it was only applied to one element.

In a real application, you might end up having to validate many different elements according to various different rules. If this is the case, it would make sense to write some more generic addError() and removeError() functions to deal with the different types of validation you might want to apply to the various elements in a form.

Disabling the Submit Button

Another useful technique that can aid usability is to disable the submit button if there are errors on the form. If the submit button is disabled then no action is taken when it’s clicked. Most browsers will also display it in a lighter color to indicate that it cannot be clicked on. This prevents users from submitting a form containing any errors.

A submit button can be disable by added the disabled attribute to the <input> element:

<button type='submit' id='submit' disabled>Submit</button>

This can be changed programmatically using the disabled property of the <button> element. The following function will disable the button if an input field is empty:

function disableSubmit(event) {    if(event.target.value === ''){        document.getElementById('submit').disabled = true;    } else {        document.getElementById('submit').disabled = false;    }}

We can apply this to the heroName field by adding the following event handler that will fire every time a key is pressed:

form.heroName.addEventListener('keyup',disableSubmit,false);

Quiz Ninja Project

Now we’re going to use forms in our Quiz Ninja game so that players can enter their answers without using prompt dialogs. Our first task is to add a form element with an ID of response in the HTML. This goes in between the question and result  <div> elements in the index.html file:

<form id='response'>    <input name='answer' type='text'>    <button type='submit'>Submit Answer</button></form>

Now we add a reference to the form in our JavaScript. Add the following line of code as a property of the view object in main.js :

response: document.querySelector('#response')

The next task is to remove the for-of loop we’ve been using to loop through each question. This is because the prompt dialogs pause the execution of the program and wait until the player has entered the answer. This won’t happen if we use a form to enter the answers, so the program would just loop through each question without giving the player a chance to answer!

Instead, we’re going to use use the pop() method to remove each question, one at a time, from the this.questions array. Remove the main game loop code from the game.start() method in main.js , so it looks like this:

start(quiz){        this.score = 0;        this.questions = [...quiz];        this.ask();}

This sets up the quiz as it did before, but it also calls the game.ask() method, which results in the first question being asked.

Next, we need to change the game.ask() method, so it looks like the following:

ask(name){    if(this.questions.length > 0) {        this.question = this.questions.pop();        const question = `What is ${this.question.name}'s real name?`;        view.render(view.question,question);    }    else {        this.gameOver();    }}

This checks the length property of the this.questions array, to see if there are any questions left to ask. If there are, the pop() method is used to remove the last element of the array and assign it to this.question . We use the same method as before to render the question in the HTML.

Next, we need to add an event handler that fires when the form is submitted. Add the following line of code to the bottom of main.js :

view.response.addEventListener('submit', (event) => game.check(event), false);view.hide(view.response);

This will call the game.check() method that’s used to check if the answer submitted by the player is correct. We need to update this method so it has an event object as a parameter. We can then use the event.preventDefault() method to stop the form from actually being submitted:

check(event){    event.preventDefault();    const response = view.response.answer.value;    const answer = this.question.realName;    if(response === answer){        view.render(view.result,'Correct!',{'class':'correct'});        this.score++;        view.render(view.score,this.score);    } else {        view.render(view.result,`Wrong! The correct answer was ${answer}`,{'class':'wrong'});    }    this.ask();},

We can grab the answer that was submitted by querying view.response.answer.value , which is the value stored in the <input> field. We then assign this to the variable response and use exactly the same code as before to deal with the outcome of the player’s answer being right or wrong.

We also need to call the game.ask() function at the end of the method so the next question is asked after the current question has been answered and checked.

Players can now use the form instead of prompt dialogs to enter their answers, but a lot of the elements are displayed when they are unnecessary. For example, when the page loads, the form is displayed, even though there is no question to answer. To remedy this, we can create a couple of helper functions to update the view at the start and end of the game.

The first helper function is view.setup() , which will be used to set up the view when the game starts. Add the following method to the view object:

setup(){    this.show(this.question);    this.show(this.response);    this.show(this.result);    this.hide(this.start);    this.render(this.score,game.score);    this.render(this.result,'');    this.render(this.info,'');    this.resetForm();}

This function makes use of the view.show() and view.hide() methods we created in the last chapter to make the questionresponse and result  <div> elements visible and hide the start button. It also uses the view.render() method to reset any HTML content in the result and info elements back to an empty string. This will stop them displaying any messages from the previous game. It also calls a view.resetForm() method. This also needs adding to the view object:

resetForm(){    this.response.answer.value = '';    this.response.answer.focus();}

This method resets the input field to an empty field and gives it focus, which improves usability as it means the player is left to concentrate on just answering the next question.

This will be useful to do after every question, so add a call to this method at the end of the game.check() method:

check(event){    event.preventDefault();    const response = view.response.answer.value;    const answer = this.question.realName;    if(response === answer){        view.render(view.result,'Correct!',{'class':'correct'});        this.score++;        view.render(view.score,this.score);    } else {        view.render(view.result,`Wrong! The correct answer was ${answer}`,{'class':'wrong'});    }    view.resetForm();    this.ask();}

The view.setup() method needs calling at the beginning of every game, so it needs adding to the game.start() method:

start(quiz){    this.score = 0;    this.questions = [...quiz];    view.setup();    this.ask();}

The other helper method is view.teardown() . This is called at the end of the game, and is responsible for hiding any elements that aren’t required and making the start button visible again. Add the following method to the view object:

teardown(){    this.hide(this.question);    this.hide(this.response);    this.show(this.start);}

The method needs calling at the end of the game, so we need to place it in the game.gameOver() method:

gameOver(){    view.render(view.info,`Game Over, you scored ${this.score} point${this.score !== 1 ? 's' : ''}`);    view.teardown();}

This should make the game look a lot more polished, so only the elements that are required as part of the game are on display at the relevant time. Let’s see what it looks like by opening up index.html and trying it out. If everything has gone to plan, it should look similar to the screenshot below.

Playing Quiz ninja-skills

You can see a live example onCodePen.

Our quiz is now shaping up nicely, and looking much more professional without all the alert and prompt dialogs.

Chapter Summary

  • Forms are the primary method used for entering data into a browser.
  • Forms have a variety of controls that are used for entering different types of information.
  • HTML5 has a large number of new input types that are beginning to be implemented in modern browsers.
  •  document.forms will return an HTML collection of all the forms on a page.
  •  form.elements will return an HTML collection of all the elements contained within a form.
  • Forms have focusblur , and change events that fire as a user interacts with the form.
  • Forms also have a submit event that can be used to intercept a form before it’s been submitted.
  • The information entered into a form can be read or updated using the value property of the form controls.
  • The HTML5 form validation API can be used to automatically validate a form, but only at a basic level, so a custom validation script may be required.

In the next chapter, we’ll be taking a look at the window object.

Pages: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16