Working with Forms

Overview

A form is an HTML element that allows the user to input data of one kind or another. The form usually contains some number of input fields that allow the user to enter alphanumeric data using the keyboard. It may also contain radio buttons, checkboxes, or drop-down lists that allow the user to select one or more options using a pointing device such as a mouse. One of the most common use cases for HTML forms is the collection of user data, which can subsequently be sent to a server for processing.

HTML forms were in use for several years before the advent of JavaScript. In those days there was no client-side form validation. In a typical scenario, whatever data was entered into a form by the user would be submitted to a server when the user clicked on the "Submit" button. The server would typically respond by serving a new web page, either acknowledging receipt of the submitted form data or advising the user of errors or omissions in the data received, in which case the user would need to correct any mistakes and re-submit the data.

Each form submission thus involved a two-way exchange of information in which all of the processing, including the validation of the form data, was carried out by the server. If an error occurred, the whole process would have to be repeated, increasing the workload on the server and using additional bandwidth at a time when both server capacity and Internet bandwidth were struggling to keep pace with the demands being placed on them by a rapidly expanding Internet community.

It soon became apparent that at least some of the processing being carried out by the server, including the validation of form data, could be carried out on the client computer before any data was sent to the server, reducing both the workload on the server and the use of network bandwidth, since the data would only need to be sent to the server once. Enter JavaScript, developed by U.S. software engineer Brendan Eich and rolled out by the Netscape Communications Corporation in 1995.

JavaScript is today used together with HTML and CSS in order to make web pages more interactive. In particular, JavaScript can be used to check all of the data entered by the user in the form's input fields in order to ensure that it is in the correct format before it is sent to the server, and that data has been entered in all mandatory fields. We can of course create a form using HTML alone, and HTML5 has introduced a number of rudimentary form validation features. In order to create a professional looking form, however, we must use CSS. And in order to carry out comprehensive form validation or make the form truly interactive, we need to use JavaScript.

We have already described in some detail how to set up a form using HTML in the article "Creating and Formatting Forms with HTML" in the section "HTML Basics", so we will not be providing lengthy explanations here as to how the provided HTML and CSS code works. We will be creating a number of different forms using HTML and CSS, but the emphasis will be on how we can use JavaScript to access the form's input fields, validate user input and, if necessary, interact with the user in order to correct errors and omissions.

Accessing form elements

In order to access the data entered by a user in a specific input field in a form, we need to be able to somehow unambiguously reference that field. It probably won't surprise you to learn that there are several different ways in which we can reference a particular input field in an HTML document using JavaScript.

The HTML code below creates a form that requires the user to enter their first and last names. The accompanying JavaScript adds an event listener to the form's submit event that calls the displayName() event handler function, which displays the user's name in an Alert message box.

<h1>Personal Information</h1>

<p>Please enter your details:</p>

<form>
  <table>
    <tr><td>First name: <input type="text"></td></tr>
    <tr><td>Last name: <input type="text"></td></tr>
    <tr><td><input type="submit" value="Submit"></td></tr>
  </table>
</form>
.
.
.
<script>
  document.forms[0].addEventListener("submit", displayName);

  function displayName() {
    let fname = document.forms[0].elements[0].value;
    let lname = document.forms[0].elements[1].value;
    alert(fname + " " + lname);
  };
</script>

This code works, but there are a few issues we need to think about. The first thing we need to consider is the HTML code. We should really be using the <label> element with each input field in order to provide screen readers and other assistive technologies to retrieve information about the structure and content of the form. One option is to place each <input> element inside the corresponding <label> element. Alternately, we can give each <input> element an id attribute and set the for attribute of the corresponding <label> element to the same value.

We are going to use the former method and place the <input> element inside the <label> element, since this will reduce the complexity of our code somewhat. We still need to assign a name attribute to each <input> element if we want the data they contain to be sent to a server when the "Submit" button is clicked. Values for form input fields without a name attribute are not submitted. We also need to give the <form> element an id attribute, for reasons that will become apparent in due course. Here is the revised HTML code for the form:

<form id="frm1">
  <table>
    <tr>
      <td>
        <label>
          First name: <input type="text" name="fname">
          </label>
      </td>
    </tr>
    <tr>
      <td>
        <label>
        Last name: <input type="text" name="lname">
        </label>
      <td>
    </tr>
    <tr>
      <td>
        <input type="submit" value="Submit">
      </td>
    </tr>
  </table>
</form>

The JavaScript code continues to work despite the changes we have made to the HTML code. We only have one <form> element, so we can access it using document.forms[0], which references the first <form> element found in the document (forms is a reference to an object of type HTMLCollection, in this case a collection containing all of the document's <form> elements). Similarly, we can access the elements within the form using the form's elements collection. The following line of code, for example, gets the value of the first element in the form:

document.forms[0].elements[0].value;

This works perfectly well, but what will happen if we later add more forms to the web page, or add more elements to the existing form? Referencing elements using subscripts is relatively straightforward when we only have a single form with a small number of elements, but can quickly become unmanageable if we have multiple forms on the same page, each of which can have a significant number of input fields, especially if we later want to re-order them or add additional input fields.

Having revised our HTML code, we now have several options to choose from when it comes to accessing the form's input fields. In addition to using the form's elements collection, we can access an input field using its name attribute, which is preferable to using an index number which could change at some time in the future. We could also add an id attribute to each <input> element, which would allow us to access that element without having to reference the form to which it belongs.

Keep in mind, however, that each id attribute in a document must be unique - they are effectively global variables. If we have multiple forms, or forms with a large number of input fields, the global namespace can become somewhat cluttered. Remember also that each <input> element now has a name attribute we can use. This has the advantage that the name attribute does not have to be unique within a document, so we can use it to group elements like radio buttons and checkboxes. We can also reuse name attributes in different forms within the same document. The only caveat is that when we use a name attribute to access an input field, we must also identify the form to which it belongs. Consider the following JavaScript code:

<script>
  let frm1 = document.getElementById("frm1");

  frm1.addEventListener("submit", displayName);

  function displayName() {
    let fname = this.fname.value;
    let lname = this.lname.value;
    alert(fname + " " + lname);
  };
</script>

The first line of code creates the variable frm1 and assigns an object to it, namely the <form> element whose id attribute is set to "frm1". The next line of code adds an event listener to the submit event for frm1 which calls the displayName() event handler (note that the displayName() function uses the this keyword to reference the form).

In the rest of this article, we will use the id attribute to reference <form> elements, and the name attribute to reference the input fields within a form. Wherever practical, we will place each input field inside a <label> element, as we have in our revised HTML code. There is still some debate in developer circles over the best way to access forms and their input fields, but we feel the approach we have adopted here covers all of the necessary bases whilst allowing for reasonably compact and maintainable code.

The code below demonstrates the principles we have described above. It creates a web page with two almost identical forms, both of which allow the user to enter the first and last names of a person. The only difference between the forms is that they have different id attribute values. Here is the code:

<!doctype html>
<html lang="en">

  <head>
    <meta charset="utf-8">
    <title>JavaScript Demo 53</title>
    <style>
      h1, p { text-align: center; }
      form {
        max-width: max-content;
        margin: auto;
        border: solid 1px;
        padding: 0.5em;
        margin-bottom: 2em;
      }
      td {
        padding: 0.5em 0.5em;
      }
    </style>
  </head>

  <body>

    <h1>Personal Information</h1>

    <p>Enter details for person #1:</p>

    <form id="frm1">
      <table>
        <tr>
          <td>
            <label>
              First name: <input type="text" name="fname">
            </label>
          </td>
        </tr>
        <tr>
          <td>
            <label>
              Last name: <input type="text" name="lname">
            </label>
          <td>
        </tr>
        <tr>
          <td>
            <input type="submit" value="Submit">
          </td>
        </tr>
      </table>
    </form>

    <p>Enter details for person #2:</p>

    <form id="frm2">
      <table>
        <tr>
          <td>
            <label>
              First name: <input type="text" name="fname">
            </label>
          </td>
        </tr>
        <tr>
          <td>
            <label>
              Last name: <input type="text" name="lname">
            </label>
          <td>
        </tr>
        <tr>
          <td>
            <input type="submit" value="Submit">
          </td>
        </tr>
      </table>
    </form>

    <script>
      let frm1 = document.getElementById("frm1");
      let frm2 = document.getElementById("frm2");

      frm1.addEventListener("submit", displayName);
      frm2.addEventListener("submit", displayName);

      function displayName() {
        event.preventDefault();
        let fname = this.fname.value;
        let lname = this.lname.value;
        alert(fname + " " + lname);
      };
    </script>

  </body>
</html>

Copy and paste this code into a new file in your HTML editor, save the file as javascript-demo-53.html, and open the file in a web browser. You should see something like the illustration below. Try entering different names into each form and click on the submit button. You should see that the event handler correctly retrieves the user input from both forms.


The forms on this page are identical except for the form id attributes

The forms on this page are identical except for the form id attributes


Whenever a user clicks the "Submit" button on either of the forms, the displayName() function is called. The first line of code in this function disables the browser's default behaviour for the submit event to prevent the form data from being submitted, which it otherwise would be because we have not yet created any validation constraints - if the input fields are left blank, an Alert box is still generated but nothing is displayed).

Form validation

Form validation is the process of systematically examining each input field in a form to check whether data has been entered by the user, and if so, whether it has been entered in the correct format. When an attempt is made to submit the form data, the form's validation routine will be executed. If the validation routine encounters errors or omissions, it should prevent any data from being submitted to the server and display a message advising the user of the problem.

Typically, if a mandatory input field has been left blank the user will be asked to enter the missing data. If some data has been entered but is in the wrong format, the user will be asked to provide input in the correct format. Typically, the focus is returned to the input field in which the problem occurred.

Invalid data can either be highlighted or removed from the field altogether, depending on the precise nature of the error. For example, if the user has typed in an email address but has typed "2" instead of "@" (this can happen on a UK keyboard if you forget to use the shift key), it would be annoying if they had to type in the entire email address again. A better approach might be to display a message that includes an example of a valid email address (e.g. "johndoe@somedomain.com"), and return the cursor to the beginning or end of the (invalid) input.

A form's validation routine is usually triggered by the submit event, although validation may also be carried out "on the fly" for individual input fields before the user clicks on the form's "Submit" button. This could happen, for example, when a mandatory input field gains the focus and then loses it without the user having entered any data. The same principle applies for input fields that contain data that is not in the correct format.

Before we proceed, we should perhaps clarify what is and what is not valid input. Keep in mind that input can be perfectly valid, even if it is obvious at a glance that the data submitted is complete nonsense. For example, if someone completing an online contact form enters their name as Micky Mouse or George Armstrong Custer, it will not be flagged as a validation error. Here is a list of some of the things input errors your validation routine should be able to catch:

If an error or omission is encountered by our validation routine, we need to let the user know, specify the nature of the problem, and prompt them to resolve the issue in an appropriate manner. We could of course generate an Alert message box for this purpose, but this is perhaps not the best option for several reasons.

Alert boxes are modal by nature, which means that a user cannot continue to interact with a web page until they have dismissed the Alert box. Thanks in part to the overuse of Alert boxes in the past, many users have developed an automatic response to them, which is to get rid of them as quickly as possible, often without having read the message they display.

Perhaps more problematical is the fact that some browsers automatically offer the user an option to disable Alert boxes if they have popped up more than once or twice in a page. A user who has chosen to chosen to accept this option may subsequently be left totally unaware of the fact that an error has occurred if your validation routine relies solely on Alert boxes to let them know that something has gone wrong. Form submission will fail, and the user will be left wondering why.

In the examples provided throughout this article, our JavaScript form validation routines will respond to errors and omission, inform the user that a problem has occurred, and suggest appropriate remedial action, but without invoking JavaScript Alert boxes. This usually involves dynamically generating error messages within the page itself rather than in a pop-up box - something that is easy enough to do with JavaScript, as we shall see.

We also need to look in some detail at HTML's built in form validation, first introduced with HTML 5 in 2008. Whilst it certainly doesn't replace JavaScript in terms of form validation, it does offer an alternative approach in certain situations, depending on how sophisticated our form validation needs to be. It is often possible to use JavaScript in tandem with the form validation tools provided by HTML, which eliminates the need to write all of the validation code ourselves but allows us to retain the flexibility and degree of control over validation procedures that JavaScript provides.

One last thing we should point out is that even if the client-side script does not encounter any errors or omissions, that doesn't mean that the server will accept the form data without further scrutiny. A server-side script will have its own, probably very different set of criteria for checking the veracity of received data. It can check, for example, whether the credentials provided by an existing user match those held in a back-end database - something a client-side script cannot do without reference to the server.

HTML's built-in form validation

HTML 5 introduced a number of new features, including some new types of for the <input> element, and a number built-in form validation features. HTML's validation does not require JavaScript in order to function, but because it is necessarily generic in its approach to validation, it is also inherently somewhat limited. The trade-off is essentially between not having to write JavaScript code on the one hand and surrendering a measure of control over the validation process on the other.

Fortunately, we do not need to restrict ourselves to using only HTML or only JavaScript. As we shall see, we can use JavaScript with HTML's built in validation and get the best of both worlds. Before we see how we can achieve this, however, we will briefly review the features introduced by HTML 5 that allow us to validate form input.

We highlighted these new features in some detail in the article "Creating and Formatting Forms with HTML" (see the "HTML Basics" section), although we have avoided a more targeted discussion of form validation until now because form validation has traditionally been handled using JavaScript, and even though HTML now offers automatic validation, JavaScript still has an important role to play, especially if we wish to exercise more control over the validation process. We will start by summarising the validation attributes that HTML has introduced:



HTML Validation-related Attributes
AttributeFor input typesDescription
max range, number, date, month, week, datetime-local, time A valid number (range, number), date (date, month, week) or date and time (datetime-local, time). The value must be less than or equal to the value, or a rangeOverflow constraint violation is generated.
maxlength text, search, url, tel, email, password Specifies the maximum number of characters allowed. If this value is exceeded, a tooLong constraint violation is generated. Also applies to the <textarea> element.
min range, number, date, month, week, datetime-local, time A valid number (range, number), date (date, month, week) or date and time (datetime-local, time). The value must be greater than or equal to the value, otherwise a rangeUnderflow constraint violation is generated.
minlength text, search, url, tel, email, password Specifies the minimum number of characters allowed. If the input provided has less than the required number of characters, a tooShort constraint violation is generated. Also applies to the <textarea> element.
pattern text, search, url, tel, email, password A JavaScript regular expression. The input must match the pattern, otherwise a patternMismatch constraint violation is generated.
required text, search, url, tel, email, password, date, datetime-local, month, week, time, number, checkbox, radio, file If this attribute is present, the input field must contain a value or a valueMissing constraint violation is generated. Also applies to the <select> and <textarea> elements.
step date, month, week, datetime-local, time, range, number An integer number of days (date), months (month), weeks (week), seconds (datetime-local, time), or just an integer value (range, number). If the value is not set to an integral multiple of the step value, a stepMismatch constraint violation is generated.


The new type attributes introduced by HTML 5 for the <input> element include tel, url, email, date, month, week, time, datetime-local, range and color. These new type attributes, together with existing type attributes such as text, number and password, can be cross-referenced with the above table to determine which validation attributes can be used with them.

Of the new input types, only email and url will automatically be checked by the browser against standard regular expressions. Note that, for both of these input types, multiple values may be entered as a comma-separated list if the multiple attribute is used. All entries in the list must satisfy the automatic validation's pattern-matching criteria, or a typeMismatch constraint violation will be generated.

Automatic validation is not really possible for other input types due to the wide variation in input formats that could be considered valid. The format expected for a telephone number, for example, will depend on whether or not country and area codes are required, and whether or not it is allowed to contain spaces or the "+" character.

The introduction of new input types in HTML 5 was accompanied by the introduction of a number of new pseudo classes in CSS, including the :valid and :invalid classes, which match input elements whose values pass or fail validation, respectively. These pseudo classes enable us to style input fields in order to provide visual feedback to the user based on the validity or otherwise of their input.

The code below creates a web page that demonstrates some of the new features introduced with HTML 5 in terms of form validation. The form uses only HTML and CSS - no JavaScript is used for form validation. Here is the code:

<!doctype html>
<html lang="en">

  <head>
    <meta charset="utf-8">
    <title>JavaScript Demo 54</title>
    <style>
      h1, p { text-align: center; }
      form {
        max-width: max-content;
        margin: auto;
        border: solid 1px;
        padding: 0.5em;
        margin-bottom: 2em;
      }
      td { padding: 0.5em 0.5em; }
      input:invalid { box-shadow: 0 0 5px 1px yellow; }
      input:focus:invalid { box-shadow: none; }
      textarea { width: 100%; }
    </style>
  </head>

  <body>

    <h1>Contact Form</h1>

    <p>Please enter your details:</p>

    <form>
      <table>
  <tr>
    <td colspan="2">Required fields are marked with an asterisk.</td>
        </tr>
        <tr>
          <td><label for="fname">*First name: </label></td>
          <td><input type="text" id="fname" name="fname" required></td>
        </tr>
        <tr>
          <td><label for="lname">*Last name: </label></td>
          <td><input type="text" id="lname" name="lname" required></td>
        </tr>
        <tr>
          <td><label for="email">*Email: </label></td>
          <td><input type="email" id="mail" name="mail" required></td>
        </tr>
        <tr>
          <td><label for="tel">Tel: </label></td>
          <td><input type="tel" id="tel" name="tel"></td>
        </tr>
        <tr>
          <td><label for="fax">Fax: </label></td>
          <td><input type="tel" id="fax" name="fax"></td>
        </tr>
        <tr>
          <td><label for="mob">Mob: </label></td>
          <td><input type="tel" id="mob" name="mob"></td>
        </tr>
        <tr>
          <td><label for="dob">*Date of Birth: </label></td>
          <td><input type="date" id="dob" name="dob" required></td>
        </tr>
        <tr>
          <td colspan="2">
            <fieldset>
              <legend>Gender*</legend>
              <label for="m"><input type="radio" name="gender" id="m" value="m" required> Male</label><br>
              <label for="f"><input type="radio" name="gender" id="f" value="f" required> Female</label><br>
              <label for="nb"><input type="radio" name="gender" id="nb" value="nb" required> Non-binary</label><br>
            </fieldset>
          </td>
        </tr>
        <tr>
          <td colspan="2">
            <label for="comment">Leave a comment:</label>
            <textarea id="comment" name="comment" maxlength="100" rows="5"></textarea>
          </td>
        </tr>
        <tr>
          <td colspan="2">
            <input type="submit" value="Submit">
          </td>
        </tr>
      </table>
    </form>

  </body>
</html>

Copy and paste this code into a new file in your HTML editor, save the file as javascript-demo-54.html, and open the file in a web browser. You should see something like the illustration below. Play around with the form to see what happens when you enter invalid input or leave required fields empty (we have used the CSS pseudo-class :invalid to highlight required input fields that have not been completed).


The form demonstrates some of HTML's built in validation features

The form demonstrates some of HTML's built in validation features


The First Name and Last Name fields will validate if they contain input of any kind, including numbers, or even just white space characters. If you leave either or both of these fields blank and attempt to submit the form, the focus returns to the first empty field and the message "Please fill out this field." is displayed (the exact wording of the message may vary, depending on the browser).

The Email field will behave in the same way if left blank, but entering an email address that is not valid results in the browser displaying a more specific message, although there is significant variation in wording from one browser to another. At the time of writing, Mozilla Firefox displays the message "Please enter an email address.", whereas both Google Chrome and Microsoft Edge display a more detailed message. The screenshot below was created using Microsoft Edge.


Browser-supplied error messages can vary considerably

Browser-supplied error messages can vary considerably


Like the First Name and Last Name fields, the Tel, Fax and Mob fields will accept any kind of input, despite each having their type attribute set to tel, because there is no standard format against which the browser can validate telephone numbers. Leaving these fields empty will also not generate a validation constraint violation because none of them are required fields.

The Date of Birth input field is a required field, and has its type attribute set to date. In all the browsers we have tried, this field only accepts numerical input, and dates can either be entered manually or by using the calendar widget. Again, however, the error message displayed for missing or invalid input varies significantly between browsers.

The Gender input field is also a required field, so one of the radio buttons must be selected in order for the form to be submitted. In all the browsers we have tried, failure to select an option results in the browser displaying the error message "Please select one of these options."

We have limited the number of characters that the user can enter in the Leave a comment section ( a <textarea> element) by setting the value of the element's maxlength attribute to 100. Once the user has typed 100 characters, the default behaviour for most browsers is simply not to accept any further input. A more user-friendly approach might be to display a message informing the user how many characters they have left, which would give them the opportunity of editing their comment accordingly, although this would require the use of JavaScript.

Note that we have indicated at the top of the form that fields marked with an asterisk are required. You should always indicate to the user whether or not an input field is required (i.e. mandatory) in order make your form more user friendly, and to comply with the Web Content Accessibility Guidelines (WCAG) published by the World Wide Web Consortium (W3C). You should decide which fields are required on the basis of what information you absolutely must have in a given situation. Sometimes, just a name and email address are all that's really needed.

You have probably surmised from the above discussion that the validation feedback supplied to users can vary from one browser to another, and in many cases is not as informative as it could be. It is of course still perfectly possible, and in some cases perhaps preferable, to achieve complete control over the validation process by writing your own custom validation routines using JavaScript. HTML's automatic validation features can be turned off by adding the novalidate attribute to your <form> element:

<form novalidate>
.
.
.
</form>

Bear in mind, however, that taking complete control of form validation using JavaScript alone invariably comes at the cost of having to write additional code. It is usually advisable for us to use JavaScript in tandem with HTML, reducing the amount of code we need to write but retaining control over the validation process. HTML 5 has even provided the means by which we can achieve this goal in the form of the Constraint Validation API - a set of methods and properties available on HTML form element DOM interfaces.

Validating forms using JavaScript

Given the obvious limitations of HTML's native form validation features, it is reasonable to assume that we will need to use JavaScript if we wish to achieve something more sophisticated in terms of form validation. As we have already mentioned, HTML's Constraint Validation API provides a set of DOM interfaces that allow us to interact with HTML form elements. Using these interfaces, we can determine the validity state of user input and, if necessary, generate a custom error message. The following DOM interfaces are available:

Each of the above objects has the following properties that can be accessed via the Constraint Validation API:

The Constraint Validation API also provides the following methods, which are available on both the form elements and the form itself:

Even when we have turned off HTML's automatic validation using the novalidate attribute, the validation-related CSS pseudo-classes still work. We can now write our own validation routines using the constraint validation API to detect invalid input and display custom error messages. We can also choose when to carry out validation. Whereas HTML's automated form validation only takes place when the user attempts to submit the form, we can now write validation code that is triggered by events on individual form elements prior to form submission.

Required fields

Not all of the fields in an online form are mandatory. Those fields that are mandatory are typically denoted by an asterisk next to the field, with a note just above or next to the form stating that any input field with an asterisk next to it is a required (i.e. mandatory) field. Most contact forms, for example, require the user to at least enter a first and last name and an email address. Other details, such as a telephone or mobile number may be optional.

Checking to see whether or not an input field is empty is quite straightforward, but there is an added complication in that the user might enter one or more white space characters, either inadvertently or in a deliberate attempt to break your script (unfortunately this can and does happen). One way of dealing with this kind of situation is to remove leading and trailing white space from all input fields in the form prior to validating individual fields. This would eliminate the possibility of mandatory fields containing only white space characters from slipping through the net. The following code creates a web page that allows us to try this out:

<!doctype html>
<html lang="en">

  <head>
    <meta charset="utf-8">
    <title>JavaScript Demo 55</title>
    <style>
      h1, p { text-align: center; }
      form {
        max-width: max-content;
        margin: auto;
        border: solid 1px;
        padding: 0.5em;
      }
      td { padding: 0.5em 0.5em; }
      label span {
        min-width: 100px;
        display: inline-block;
      }

    </style>
  </head>

  <body>

    <h1>Personal Information</h1>

    <p>Enter your details:</p>

    <form id="frm">
      <table>
        <tr>
          <td>
            <label><span>First name: </span><input type="text" name="fname" required></label>
          </td>
        </tr>
        <tr>
          <td>
            <label><span>Last name: </span><input type="text" name="lname" required></label>
          <td>
        </tr>
        <tr>
          <td>
            <label><span>Email: </span><input type="email" name="email" size="50" required></label>
          <td>
        </tr>
        <tr>
          <td>
            <input type="submit" value="Submit">
          </td>
        </tr>
      </table>
    </form>

    <script>
      const frm = document.getElementById("frm");
      frm.addEventListener("submit", removeWhiteSpace);

      function removeWhiteSpace() {
        event.preventDefault();
        const elements = this.elements;
        for ( let i = 0; i < elements.length; i++ ) {
          if (typeof elements[i].value === "string") {
            elements[i].value = elements[i].value.trim();
          }
        }
      }
    </script>

  </body>
</html>

Copy and paste this code into a new file in your HTML editor, save the file as javascript-demo-55.html, and open the file in a web browser. Enter some white space characters in each field (but nothing else), then click on the "Submit button". You should see something like the illustration below.


All three fields contain white space characters only

All three fields contain white space characters only


This is not quite the behaviour we were expecting. All three of our input fields have the required attribute, which is satisfied for all three fields because there are white space characters in all three fields. Automatic HTML validation is not triggered for the First name or Last name input fields, but it is triggered for the Email field because, in addition to having the required attribute, this input field has its type attribute set to email.

When the user attempts to submit the form, the automatic HTML form validation procedures are called before any JavaScript event handlers associated with the form's submit event. In this case, we have satisfied the required attribute in all three fields by entering white space characters, but the white space in the Email field triggers an error because it does contain data (the white space characters), but the data it contains not constitute a valid email address, so we see the message "Please enter an email address."

The main purpose of the removeWhiteSpace() function is to remove unwanted leading and trailing white space characters so that the code that checks whether or not a required input field contains data - whether provided by HTML's automatic form validation or a JavaScript validation routine - will work correctly.

Unfortunately, because HTML's automated validation runs before the removeWhiteSpace() function, the fact that the First name and Last name input fields contain white space characters means that HTML is effectively fooled into thinking that the required attribute has been satisfied for these fields because they are not empty.

If we subsequently enter a valid email address into the Email input field and click on the "Submit" button once more, nothing appears to happen. In fact, because there is now nothing to cause HTML's automatic validation to prevent the submit event from occurring, the removeWhiteSpace() function is called, and removes the white space from the First name and Last name input fields. Because these fields are now empty, clicking on the "Submit" button once more will cause HTML's automatic form validation to register them as empty.


The First name and Last name fields are now empty

The First name and Last name fields are now empty


One way of preventing this unexpected behaviour would be to call the removeWhiteSpace() function when a field loses the focus, so that if the user has entered only whitespace characters, or alternatively has entered input that has leading or trailing whitespace characters, the whitespace will be removed. Open the file javascript-demo-55.html once more in your HTML editor if you have closed it, delete all of the code between the <script> . . . </script> tags, and replace it with the following code:

const frm = document.getElementById("frm");
const inputs = frm.getElementsByTagName("input");

for ( let i = 0; i < inputs.length; i++ ) {
  if(typeof inputs[i].value === "string") {
    inputs[i].addEventListener("blur", removeWhiteSpace);
  }
}

function removeWhiteSpace() {
  this.value = this.value.trim();
}

Open the file in a browser once more and try entering some whitespace characters, or text with leading and/or trailing whitespace characters, in the input fields. You will find that when the field currently being edited loses the focus, the resulting blur event will result in the removeWhiteSpace() function being called on that input field, and the whitespace characters will be removed.

Bear in mind that once we have removed any spurious whitespace characters, we are still relying on HTML's automated form validation features to detect whether the user has entered any data, and if not, notify the user accordingly. The default message provided by HTML can vary slightly from one browser to another, but they are usually fairly generic. If we need to tailor error messages to individual fields, we're going to have to write more code.

The following code creates a web page that looks identical to the previous example. This time we have disabled HTML's default validation by including the novalidate attribute on the form element. We will instead use JavaScript together with the Constraint Validation API to determine whether or not all required inputs are present. If a value is entered in the email address field, we will also check whether or not it is a valid email address. Here is the code:

<!doctype html>
<html lang="en">

  <head>
    <meta charset="utf-8">
    <title>JavaScript Demo 56</title>
    <style>
      h1, p { text-align: center; }
      form {
        max-width: max-content;
        margin: auto;
        border: solid 1px;
        padding: 0.5em;
      }
      td { padding: 0.5em 0.5em; }
      label span {
        min-width: 100px;
        display: inline-block;
      }

    </style>
  </head>

  <body>

    <h1>Personal Information</h1>

    <p>Enter your details:</p>

    <form id="frm" novalidate>
      <table>
        <tr>
          <td>
            <label><span>First name: </span><input type="text" name="fname" required></label>
          </td>
        </tr>
        <tr>
          <td>
            <label><span>Last name: </span><input type="text" name="lname" required></label>
          <td>
        </tr>
        <tr>
          <td>
            <label><span>Email: </span><input type="email" name="email" size="50" required></label>
          <td>
        </tr>
        <tr>
          <td>
            <input type="submit" value="Submit">
          </td>
        </tr>
      </table>
    </form>

    <script>
      const frm = document.getElementById("frm");
      const inputs = frm.getElementsByTagName("input");

      frm.addEventListener("submit", formCheck);

      for ( let i = 0; i < inputs.length; i++ ) {
        if(typeof inputs[i].value === "string") {
          inputs[i].addEventListener("blur", removeWhiteSpace);
        }
      }

      function removeWhiteSpace() {
        this.value = this.value.trim();
      }

      function formCheck() {
        event.preventDefault();
        let fname = this.fname;
        let lname = this.lname;
        let email = this.email;
        if( fname.validity.valueMissing ) {
          fname.setCustomValidity("Please enter your first name.");
        }
        else {
          fname.setCustomValidity("");
        }
        if( lname.validity.valueMissing ) {
          lname.setCustomValidity("Please enter your last name.");
        }
        else {
          lname.setCustomValidity("");
        }
        if( email.validity.valueMissing ) {
          email.setCustomValidity("Please enter an email address.");
        }
        else {
          if( email.validity.typeMismatch ) {
            email.setCustomValidity("Please enter a valid email address (e.g. 'johndoe@somedomain.com'");
          }
          else {
            email.setCustomValidity("");
          }
        }
        if( this.reportValidity() == true ) {
          this.submit();
        };
      }

    </script>

  </body>
</html>

Copy and paste this code into a new file in your HTML editor, save the file as javascript-demo-56.html, and open the file in a web browser. Test the form to see what happens when you click on the "Submit" button, (a) when you leave a field blank and (b) when you enter an invalid email address. You will find that if all fields contain input, and if the email address field contains a correctly formatted email address, the form data will be submitted. Otherwise, a custom error message will be displayed, as per the example screenshot below.


A customised error message is displayed when input constraints are violated

A customised error message is displayed when input constraints are violated


The formCheck() function checks the validity of each form input individually, and generates a custom error message for each input that does not comply with the validation constraints specified by the HTML code. Once all inputs have been tested, the reportValidity() method is called on the form itself. If all inputs are valid, the form will be submitted. If an invalid input is encountered, the user will be asked to take appropriate remedial action, and form submission is cancelled.

Note that the constraints imposed on an input field with its type attribute set to email appear to fall short of requiring strict adherence to a valid email address format, which should include both a domain name and a top-level domain name after the "at" sign (@), separated by a period. We will look at a more rigorous way of ensuring the correct format is used later in this article.

Text and text areas

If an <input> element has its type attribute set to text, and in the absence of any additional constraints being imposed, the user can enter just about anything without generating a constraint violation. We usually specify that a field should accept text input when we are not expecting the user to enter data in a specific format. Having said that, we can of course define what constitutes valid input for a text field using other HTML constraints, specifically the maxlength, minlength, pattern and required attributes.

A <textarea> element also allows the user to enter just about any printable characters. It doesn't need a type attribute, since it is by definition a text input field. It can however have the same validation constraints as an <input> element of type text, except for the pattern attribute. A <textarea> element is typically used when the user is required to input information that will not fit on one line, such as a comment, a description, or a narrative of some kind.

We have already dealt with the subject of mandatory (required) input fields, so we'll start by looking at the maxlength and minlength attributes. The maxlength attribute specifies the maximum number of characters that that can be entered by the user. If no value is specified, there is no official limit to the number of characters that may be entered in a text or text area, although the browser may set its own limit. At the time of writing, for example, Google Chrome appears to impose an upper limit of 524,288 characters.

If the minlength attribute is specified for an input field, the user must enter at least the number of characters specified, or the validity.tooShort property will return true and form submission will fail. In the browsers we have tested so far, minlength works as expected when the data is entered manually, but has no effect if the form input is generated programmatically.

The same applies to the maxlength attribute, although if the user enters data manually, they will be unable to enter more than the specified number of characters and the validity.tooLong property will always return false. This applies even if the user tries to insert more then the specified maximum number of characters using copy and paste.

We run into a potential problem, however if the input value is generated programmatically (as opposed to being typed in by the user). Let's suppose for a moment that, for whatever reason, we use code to create the input for a text field. If the string generated has less than the minimum number of characters required, or more than the maximum number of characters allowed, a constraint violation should be generated. Unfortunately, this doesn't happen. The code below generates a web page that illustrates the point.

<!doctype html>
<html lang="en">

  <head>
    <meta charset="utf-8">
    <title>JavaScript Demo 57</title>
    <style>
      h1, p { text-align: center; }
      form {
        max-width: max-content;
        margin: auto;
        border: solid 1px;
        padding: 0.5em;
      }
      td { padding: 0.5em 0.5em; }v       label span {
        min-width: 220px;
        display: inline-block;
        vertical-align: top;
      }
    </style>
  </head>v
  <body>

    <h1>Text and text area validation</h1>

    <form id="frm" novalidate>
      <table>
        <tr>
          <td>
            <label><span>Enter 3 or more characters:</span><input type="text" name="minText" minlength="3" required></label>
          </td>
        </tr>
        <tr>
          <td>
            <label><span>Enter from 1 to 15 characters:</span><input type="text" name="maxText" maxlength="15" required></label>
          </td>
        </tr>
        <tr>
          <td>
            <label><span>Enter from 10 to 40 characters:</span></label><textarea name="txtArea" minlength="10" maxlength="40" rows="4" required></textarea>
          </td>
        </tr>
        <tr>
          <td>
            <input type="button" name="rndBtn" value="Random input">
          </td>
        </tr>
        <tr>
          <td>
            <input type="submit" value="Submit">
          </td>
        </tr>
      </table>
    </form>

    <script>
      const frm = document.getElementById("frm");
      const alphaNumChars ="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

      frm.addEventListener("submit", formCheck);
      frm.rndBtn.addEventListener("click", generateInput);

      function formCheck() {
        event.preventDefault();

        let minText = this.minText;
        let maxText = this.maxText;
        let textArea = this.txtArea;

        if( minText.validity.valueMissing ) {
          minText.setCustomValidity("Please enter three or more characters.");
        }
        else if( minText.validity.tooShort) {
          minText.setCustomValidity("You are required to enter at least 3 characters.");
        }
        else {
          minText.setCustomValidity("");
        }

        if( maxText.validity.valueMissing ) {
          maxText.setCustomValidity("Please enter up to 15 characters.");
        }
        else if( maxText.validity.tooLong) {
          maxText.setCustomValidity("Your input should contain no more than 15 characters.");v         }
        else {
          maxText.setCustomValidity("");
        }

        if( textArea.validity.valueMissing ) {
          textArea.setCustomValidity("Please enter between 10 and 40 characters.");
        }
        else if( textArea.validity.tooLong || textArea.validity.tooShort) {
          textArea.setCustomValidity("Your input should contain between 10 and 40 characters.");
        }
        else {
          textArea.setCustomValidity("");
        }

        if( this.reportValidity() == true ) {
          this.submit();
        };
      }

      function generateString(len) {
        let rndStr = "";
        const charsLen = alphaNumChars.length;
        for ( let i=0; i<len; i++ ) {
          rndStr += alphaNumChars.charAt(Math.floor(Math.random() * charsLen));
        }
        return rndStr;
      }

      function generateInput() {
        let txtMin = Math.floor(Math.random()*6) + 1;
        let txtMax = Math.floor(Math.random()*25) + 1;
        let txtArea = Math.floor(Math.random()*50) + 1;
        frm.minText.value = generateString(txtMin);
        frm.maxText.value = generateString(txtMax);
        frm.txtArea.value = generateString(txtArea);
      }
    </script>

  </body>
</html>

Copy and paste this code into a new file in your HTML editor, save the file as javascript-demo-57.html, and open the file in a web browser. Test the form to see what happens when you click on the "Submit" button, (a) when you leave a field blank and (b) when you type in less than the required number of characters. You should find that typing in too many characters is simply not possible, even if you try to cheat using copy and paste). When you click on the "Submit" button, any constraint violations will cause form submission to fail and you will see a custom error message, as per the example screenshot below.


A customised error message is displayed when input constraints are violated

A customised error message is displayed when input constraints are violated


You will find the form constraints are upheld for manual input. Now try using the "Random" button to generate random-length character sequences for each input field before you submit the form. The generateInput() function generates input for each field programmatically, with no guarantee that the input it creates will satisfy the validation constraints applied to a particular field, either in terms of the minimum number of characters required or the maximum number of characters allowed. Form data will be submitted regardless of whether or not it satisfies the validation constraints.

If we are creating a form for which input will only ever be created by the user and never generated programmatically, we don't need to worry about validation constraints being bypassed in this way. Otherwise, we might need to take an alternative approach. Below, we offer a revised version of the formCheck() function that deals with this issue.

Instead of relying on the validity.tooShort or validity.tooLong properties, the function now retrieves the values of any minlength and maxlength attributes that have been set for each field, and compares them with the length of the field's contents, regardless of whether that input has been randomly generated or entered by the user. Here is the revised code:

function formCheck() {
  event.preventDefault();

  let minText = this.minText;
  let maxText = this.maxText;
  let textArea = this.txtArea;

  if( minText.validity.valueMissing ) {
    minText.setCustomValidity("Please enter three or more characters.");
  }
  else if( minText.value.length < minText.getAttribute("minlength")) {
  //else if( minText.validity.tooShort) {
    minText.setCustomValidity("You are required to enter at least 3 characters.");
  }
  else {
    minText.setCustomValidity("");
  }

  if( maxText.validity.valueMissing ) {
    maxText.setCustomValidity("Please enter up to 15 characters.");
  }
  else if( maxText.value.length > maxText.getAttribute("maxlength")) {
  //else if( maxText.validity.tooLong) {
    maxText.setCustomValidity("Your input should contain no more than 15 characters.");
  }
  else {
    maxText.setCustomValidity("");
  }

  if( textArea.validity.valueMissing ) {
    textArea.setCustomValidity("Please enter between 10 and 40 characters.");
  }
  else if( textArea.value.length > textArea.getAttribute("maxlength") && textArea.value.length < textArea.getAttribute("minlength")) {
  //else if( textArea.validity.tooLong || textArea.validity.tooShort) {
    textArea.setCustomValidity("Your input should contain between 10 and 40 characters.");
  }
  else {
    textArea.setCustomValidity("");
  }

  if( this.reportValidity() == true ) {
    this.submit();
  };
}

For clarity, we have highlighted the revised code, and have commented out the code it has replaced (we have also greyed this code out). The end result is that the function should now work for both user input and programmatically generated input.

Using regular expressions

For some input fields, we may want the user to enter data in a very specific format. We saw earlier, for example, that if we set the type attribute of an input field to email, the browser will examine the input to determine whether or not it is a valid email address (although the validation carried out is somewhat rudimentary). On the other hand, if an input field's type attribute is set to tel, the browser will not attempt to validate user input because telephone numbers can take a variety of formats, all of which are valid.

We usually require things like email addresses, telephone numbers, URLs and passwords to be entered in a specific format, but we can't always rely on the browser to validate user input of this nature because, as we have already mentioned, formatting requirements can vary significantly. We can however specify the exact format in which we require such data items to be entered using regular expressions.

A regular expression (or rational expression) is a sequence of characters that specifies a pattern that can be used to search for a matching string or substring. In terms of form validation, we can compare the contents of an input field with a regular expression to see whether or not it matches the pattern specified by the regular expression, and thus determine whether or not it is in the required format.

Regular expressions are a useful way of validating input strings, but they can be difficult to write and interpret. For the purposes of this article, we will not be providing a tutorial on how to write and use regular expressions as such. We will, however, provide specific examples of regular expressions that can be used to validate form input. We will also explain how each of these examples work. A more detailed article on the use of regular expressions in JavaScript will appear in this section as soon as I get around to writing it!

Regular expressions are created in JavaScript in one of two ways. You can use a regular expression literal, which consists of the pattern you want to match enclosed between two forward slashes:

const re = /ab+c/;

Alternatively, you can use the constructor function of the RegExp object:

const re = new RegExp("ab+c");

Both of these methods create the same regular expression. The main difference is that regular expression literals are compiled when a script is loaded, whereas regular expressions created using the constructor function are compiled at runtime. If the regular expression is not going to change, using a regular expression literal is preferable from a performance point of view. If the regular expression is likely to change - depending on user input, for example - then you should use the constructor function. For the purposes of this article, we will only be using regular expression literals.

In order to demonstrate the use of regular expressions in form validation, we will revisit the contact form we created earlier in this article. You should have this saved as javascript-demo-54.html. If not, don't worry - we will provide all of the code for the new version. Unlike the previous version, which relied solely on HTML's automated form validation features, this version will use the Constraint Validation API to implement form validation. Here is the code:

<!doctype html>
<html lang="en">

  <head>
    <meta charset="utf-8">
    <title>JavaScript Demo 55</title>
    <style>
      h1, p { text-align: center; }
      form {
        max-width: max-content;
        margin: auto;
        border: solid 1px;
        padding: 0.5em;
        margin-bottom: 2em;
      }
      td { padding: 0.5em 0.5em; }
      textarea { width: 100%; }
    </style>
  </head>

  <body>

    <h1>Contact Form</h1>

    <p>Please enter your details:</p>

    <form id="frm" novalidate>
      <table>
        <tr>
          <td colspan="2">Required fields are marked with an asterisk.</td>
        </tr>
        <tr>
          <td><label for="fname">*First name: </label></td>
          <td><input type="text" id="fname" name="fname" required maxlength="50"></td>
        </tr>
        <tr>
          <td><label for="lname">*Last name: </label></td>
          <td><input type="text" id="lname" name="lname" required maxlength="50"></td>
        </tr>
        <tr>
          <td><label for="email">*Email: </label></td>
          <td><input type="email" id="mail" name="mail" maxlength="65" required pattern=\S+@\S+\.\S+></td>
        </tr>
        <tr>
          <td><label for="tel">Tel: </label></td>
          <td><input type="tel" id="tel" name="tel" maxlength="20"></td>
        </tr>
        <tr>
          <td><label for="fax">Fax: </label></td>
          <td><input type="tel" id="fax" name="fax" maxlength="20"></td>
        </tr>
        <tr>
          <td><label for="mob">*Mob: </label></td>
          <td><input type="tel" id="mob" name="mob" maxlength="20" required></td>
        </tr>
        <tr>
          <td><label for="dob">*Date of Birth: </label></td>
          <td><input type="date" id="dob" name="dob" required></td>
        </tr>
        <tr>
          <td colspan="2">
            <fieldset name="genderSelect">
              <legend>Gender*</legend>
              <label for="m"><input type="radio" name="gender" id="m" value="m" required> Male</label><br>
              <label for="f"><input type="radio" name="gender" id="f" value="f" > Female</label><br>
              <label for="f"><input type="radio" name="gender" id="nb" value="nb" > Non-binary</label><br>
            </fieldset>
          </td>
        </tr>
        <tr>
          <td colspan="2">
            <label for="comment">Leave a comment:</label>
            <textarea id="comment" name="comment" maxlength="100" rows="5"></textarea>
          </td>
        </tr>
        <tr>
          <td colspan="2">
            <input type="submit" value="Submit">
          </td>
        </tr>
      </table>
    </form>

    <script>
      const frm = document.getElementById("frm");
      const inputs = frm.getElementsByTagName("input");

      frm.addEventListener("submit", formCheck);
      frm.comment.addEventListener("blur", removeWhiteSpace);

      for ( let i = 0; i < inputs.length; i++ ) {
        if(typeof inputs[i].value === "string") {
          inputs[i].addEventListener("blur", removeWhiteSpace);
        }
      }

      function removeWhiteSpace() {
        this.value = this.value.trim();
      }

      function formCheck() {
        event.preventDefault();

        let fname = this.fname;
        let lname = this.lname;
        let email = this.mail;
        let tel = this.tel;
        let fax = this.fax;
        let mob = this.mob;
        let dob = this.dob;

        const mobRegex = /^\+?[1-9][0-9]{7,14}$/;
              
        if( fname.validity.valueMissing ) {
          fname.setCustomValidity("Please enter a first name.");
        }
        else if( fname.value.length > fname.getAttribute("maxlength")) {
          fname.setCustomValidity("You have exceeded the maximum length for this field.");
        }
        else {
          fname.setCustomValidity("");
        }

        if( lname.validity.valueMissing ) {
          lname.setCustomValidity("Please enter a last name.");
        }
        else if( lname.value.length > lname.getAttribute("maxlength")) {
          lname.setCustomValidity("You have exceeded the maximum length for this field.");
        }
        else {
          lname.setCustomValidity("");
        }

        if( email.validity.valueMissing ) {
          email.setCustomValidity("Please enter an email address.");
        }
        else if( email.value.length > email.getAttribute("maxlength")) {
          email.setCustomValidity("You have exceeded the maximum length for this field.");
        }
        else if ( email.validity.patternMismatch ) {
          email.setCustomValidity("Please enter a valid email address (e.g. jdoe@somedomain.com.");
        }
        else {
          email.setCustomValidity("");
        }

        if( mob.validity.valueMissing ) {
          mob.setCustomValidity("Please enter a mobile phone number.");
        }
        else if( mob.value.length > mob.getAttribute("maxlength")) {
          mob.setCustomValidity("You have exceeded the maximum length for this field.");
        }
        else if(!mobRegex.test(mob.value)) {
          mob.setCustomValidity("Please enter a valid mobile phone number. The number must have between 8 and 15 digits and no spaces.");
        }
        else {
          mob.setCustomValidity("");
        }

        if( this.reportValidity() == true ) {
          this.submit();
        };
      }
    </script>

  </body>
</html>

Copy and paste this code into a new file in your HTML editor, save the file as javascript-demo-58.html, and open the file in a web browser. Fill in all the required fields with valid data except for the Email and Mob fields. Try entering some invalid email addresses and mobile telephone numbers, and see what happens when you subsequently click on the "Submit" button. Invalid data in either of these fields will generate a validation constraint violation and cause form submission to fail. You will see an error message, as per the example screenshot below.


A regular expression is used to check the validity of the  mobile number

A regular expression is used to check the validity of the mobile number


If you study the code that handles the validation of the Email and Mob input fields, you will see that we have used two different approaches. For the Email field, we have set the pattern attribute of the field to the regular expression we want to test the email address against:

pattern=\S+@\S+\.\S+

When the user attempts to submit the form, the email address is checked for a constraint violation using the following code in the formCheck() function:

else if ( email.validity.patternMismatch ) {
  email.setCustomValidity("Please enter a valid email address (e.g. jdoe@somedomain.com.");
}

For the Mob input field, we have omitted the pattern attribute. Instead, the regular expression we will use to validate the mobile phone number against is declared as a constant at the top of the formCheck() function:

const mobRegex = /^\+?[1-9][0-9]{7,14}$/;

The formCheck() function checks that the mobile phone number is in the required format with the following code:

else if(!mobRegex.test(mob.value)) {
  mob.setCustomValidity("Please enter a valid mobile phone number. The number must have between 8 and 15 digits and contain no spaces.");
}

The regular expressions we have used here are not particularly sophisticated, and certainly won't catch all invalid email addresses or telephone numbers. There are many significantly longer examples of regular expressions for telephone numbers, email addresses and URLs. To some extent, the degree to which you need to constrain the format of an input field will determine how far you need to go in formulating a suitable regular expression.

Let's break down the regular expressions used in our code, starting with the regex for the Email field:

\S+@\S+\.\S+

The \S matches any non-white-space character, while the plus sign (+) that follows it indicates that there can be any number of such characters. The "at" sign (@) is a literal - it means the test algorithm expects to see the "@" character next. We then have a further sequence of non-white-space characters, signified by \S+. Next, we have \. which means we are expecting to find a period (if a backslash character is followed by a character that is not recognised as an escaped character, the input must match that character). Last but not least, we have yet another sequence of non-white-space characters, signified by \S.

We'll repeat the analysis for the Mob field's regular expression:

/^\+?[1-9][0-9]{7,14}$/

The first thing we should say here is that the forward slashes surrounding this regular expression are not part of the regular expression itself. We don't need to use them when assigning a regular expression to the pattern attribute of an input field, only when assigning one to a JavaScript variable. Essentially, they tell JavaScript that the character sequence they enclose is a regular expression.

The caret (^) at the beginning of the regular expression and the dollar sign ($) at the end signify the positions immediately before the start of the string to be tested, and the position immediately after the end of it, respectively. They tell JavaScript that the entire string (as opposed to a substring thereof) must match the regular expression.

The next thing we see is \+? which tells us we could see a plus sign (remember, a backslash followed by a character other than a known escaped character matches that character). The question mark indicates that there could be either zero or one such character. In other words, there can be a plus sign here, but there doesn't have to be.

We then see [1-9], which signifies that the next character must be a decimal digit in the range 1 to 9. This is followed by [0-9], denoting that the next character can be any decimal digit in the range 0 to 9. Following this, we have {7,14}, which means we have a further sequence of from seven up to fourteen decimal digits, each of which must be in the range 0 to 9. To summarise, we require a string that may or may not start with "+", followed by a single non-zero decimal digit, followed by a sequence of at least seven but not more than fourteen decimal digits that can include one or more zeros.

The regular expression is a powerful tool that can be used to validate the contents of any input field where the user is required to enter a string value of some kind. The complexity of the regular expression needed depends on the nature of the input, and the degree to which we need to constrain it. The good news is that, when it comes to regular expressions for things like email addresses, telephone numbers and URLs, there are plenty of examples available online, so you don't have re-invent the wheel. That said, it's probably a good idea to develop a working knowledge of how regular expressions work so that you can choose one that meets your requirements.

Numeric input

If we set the type attribute of an <input> element to number or range, the expected input value will be numerical. If the type attribute is set to number, the user must type a value into the input field in the same way they would enter text input, except that the characters they type in should all be either decimal digits or special characters such as a period (the decimal separator), e or E (representing an exponent), or the plus and minus signs (+ and -) that indicate whether the numerical value is positive or negative. Generally speaking, there are two schools of thought about how this should work.

Some people argue that a numeric input field should only accept numerical data as input, and that all non-numeric input should be ignored. At the time of writing, this is the approach taken by Google Chrome and Microsoft Edge. Others maintain that this approach effectively "breaks" the user's keyboard because something should happen when a key is pressed. They maintain that the user should be allowed to enter non-numeric characters, but must subsequently be informed that their input cannot be accepted. Mozilla Firefox currently accepts non-numeric data in numeric input fields.

For the purposes of this article, we will assume that users can enter non-numeric characters in a numeric input field, either inadvertently, or deliberately in an attempt to break our code. We will therefore take measures to validate all numeric input before it is either submitted to a server or used in a calculation. The following code generates a web page featuring a form that allows the user to enter a temperature in Fahrenheit and then calculates the equivalent temperature in both Celsius and Kelvins:

<!doctype html>
<html lang="en">

  <head>
    <meta charset="utf-8">
    <title>JavaScript Demo 59</title>
    <style>
      h1, p { text-align: center; }
      form {
        max-width: max-content;
        margin: auto;
        border: solid 1px;
        padding: 0.5em;
        margin-bottom: 2em;
      }
      td { padding: 0.5em; }
      td.scale, td.out { text-align: right; }
      input {
        width: 75px;
        text-align: center;
      }
    </style>
  </head>

  <body>

    <h1>Fahrenheit Conversion</h1>

    <p>Please enter the temperature in Fahrenheit<br>(up to 3 decimal places).</p>

    <form id="frm" novalidate>
      <table>
        <tr>
          <td class="scale">Fahrenheit: </td>
          <td><input type="number" id="fahrIn" name="fahrIn" autofocus required step="0.001"></td>
        </tr>
        <tr>
          <td class="scale">Celsius: </td>
          <td class="out"><output name="celsOut" tabIndex="-1"></output></td>
        </tr>
        <tr>
          <td class="scale">Kelvin: </td>
          <td class="out"><output name="kelvOut" tabIndex="-1"></output></td>
        </tr>
        <tr>
          <td colspan="2">
            <input type="submit" value="Convert" name="submit">
          </td>
        </tr>
      </table>
    </form>

    <script>
      const frm = document.getElementById("frm");
      frm.addEventListener("submit", convert);

      function convert() {
        event.preventDefault();

        let fahrIn = this.fahrIn;
                 if( fahrIn.validity.valueMissing ) {
          fahrIn.setCustomValidity("Please enter a temperature in fahrenheit.");
        }
        else if( fahrIn.validity.badInput ) {
          fahrIn.setCustomValidity("The data you have entered is not a number.");
        }
        else {
          fahrIn.setCustomValidity("");
        }
        if( this.reportValidity() == true ) {
          calculateValues(fahrIn.value);
        };
      }

      function isNumber(value) {
        return typeof value === "number";
      }

      function calculateValues(tempF) {
        let tempC = (tempF - 32) * 5/9;
        let tempK = tempC + 273.15;
        frm.celsOut.innerHTML = tempC.toFixed(3);
        frm.kelvOut.innerHTML = tempK.toFixed(3);
      }
    </script>

  </body>
</html>

Copy and paste this code into a new file in your HTML editor, save the file as javascript-demo-59.html, and open the file in a web browser. Enter some temperature values into the input field and click on the "Convert" button to see the equivalent temperature values in Celsius and kelvins. You should see something like the screenshot below.


A form to convert temperatures in Fahrenheit to Celsius and kelvins

A form to convert temperatures in Fahrenheit to Celsius and kelvins


Copy and paste this code into a new file in your HTML editor, save the file as javascript-demo-59.html, and open the file in a web browser. Enter some temperature values into the input field and click on the "Convert" button to see the equivalent temperature values in Celsius and kelvins. You should see something like the screenshot below.

The form itself has a single input field, into which the user can type a number representing the temperature in Fahrenheit, with up to three decimal places. Most current browsers incorporate a number picker that the user can use to step the number up or down, as an alternative to typing in a value. Both positive and negative values can be entered, and there does not appear to be any constraints on the size of the number entered, although it is possible to set minimum and maximum values for the range using the min and max attributes.

Notice that we have set the step attribute to 0.001, which allows us to enter a numeric value with up to three decimal places. It also means that, if we use the number picker to select a value, it will increase or reduce the value by intervals of 0.001, which is probably not particularly practical for this particular application, although it does allow for some fine tuning!

The validation we have implemented first checks to see if a value has been entered, and if so, checks whether the input supplied is a valid number. Note that we use the validity.badInput property to determine whether or not the user has entered a valid numerical value. Once a valid number has been entered and the user has clicked on the "Convert" button, the number value is passed to the calculateValues() function, which carries out the necessary calculations and displays the results in the read-only Celsius and Kelvin output fields.

The code for the calculateValues() function is fairly self-explanatory. It uses standard formulae to calculate the equivalent temperature values in Celsius and kelvins, and sets the innerHTML values for the Celsius and Kelvin output fields accordingly. We have not imposed any limits on the range of values that can be entered, although if this was a real-world application we would need to set a minimum input value of -459.67 °F (absolute zero). There is no need for a maximum value, because there is no such thing as an absolute maximum temperature.

We tend to use <input> elements with their type attribute set to number when we want the user to enter precise numeric values. If precision is not so much of an issue, we might instead opt to set the type attribute to range. Most browsers implement this as a slider or dial control which the user can drag up and down using the mouse to adjust the input value. The code below generates a web page that carries out the same temperature conversion as the previous example, requires the user to adjust the input value using a slider or dial control, depending on how the browser implements such controls. Here is the code:

lt;!doctype html>
<html lang="en">

  <head>
    <meta charset="utf-8">
    <title>JavaScript Demo 60</title>
    <style>
      h1, p { text-align: center; }
      form {
        max-width: max-content;
        margin: auto;
        border: solid 1px;
        padding: 0.5em;
        margin-bottom: 2em;
      }
      td { padding: 0.5em; }
      td.scale, td.out { text-align: right; }
      td.scale {
        width: 100px;
        text-align: right;
      }
      td.out {
        width: 75px;
        text-align: right;
      }
      td.button { text-align: center; }
      input[type="button"] { width: 100px; }
    </style>
  </head>

  <body>

    <h1>Temperature Conversion</h1>

    <p>
      Please use the slider to adust the temperature<br>
      (Range: -273.15 - +300 °C)     </p>

    <form id="frm">
      <table>
        <tr>
          <td colspan="2"><input type="range" name="tempCIn" min="-273.15" max="300.00" step="0.01"></td>
        </tr>
        <tr>
          <td class="scale">Fahrenheit: </td>
          <td class="out"><output name="fahrOut" tabIndex="-1">32.00</output></td>
        </tr>
        <tr>
          <td class="scale">Celsius: </td>
          <td class="out"><output name="celsOut" tabIndex="-1">0.00</output></td>
        </tr>
        <tr>
          <td class="scale">Kelvin: </td>
          <td class="out"><output name="kelvOut" tabIndex="-1">273.15</output></td>
        </tr>
        <tr>
          <td class="button" colspan="2"><input type="button" value="Increment" name="increment"></td>
        </tr>
        <tr>
          <td class="button" colspan="2"><input type="button" value="Decrement" name="decrement"></td>
        </tr>
        <tr>
          <td class="button" colspan="2"><input type="button" value="Reset" name="reset"></td>
        </tr>
      </table>
    </form>

    <script>
      const frm = document.getElementById("frm");
      frm.tempCIn.addEventListener("change", updateValues);
      frm.increment.addEventListener("click", increment);
      frm.decrement.addEventListener("click", decrement);
      frm.reset.addEventListener("click", reset);
      addEventListener("load", reset);

      function updateValues() {
        temp = parseFloat(frm.tempCIn.value);
        let tempF = temp * 9/5 + 32;
        let tempC = temp;
        let tempK = temp + 273.15;
        frm.fahrOut.innerHTML = tempF.toFixed(2);
        frm.celsOut.innerHTML = tempC.toFixed(2);
        frm.kelvOut.innerHTML = tempK.toFixed(2);
      }

      function increment() {
        frm.tempCIn.value = Math.floor(frm.tempCIn.value);
        if( frm.tempCIn.value < -273.00) {
          frm.tempCIn.value = -273.00;
        }
        else {
          frm.tempCIn.stepUp(100);
        }
        updateValues();
      }
      function decrement() {
        frm.tempCIn.value = Math.ceil(frm.tempCIn.value);
        if( frm.tempCIn.value == -273.00) {
          frm.tempCIn.stepDown(15);
        }
        else {
          frm.tempCIn.stepDown(100);
        }
        updateValues();
      }
      function reset() {
        frm.tempCIn.value = 0;
        updateValues();
      }
    </script>

  </body>
</html>

Copy and paste this code into a new file in your HTML editor, save the file as javascript-demo-60.html, and open the file in a web browser. You should see something like the screenshot below. Use the slider to move the temperature up and down. You will notice that fine-grained control over the input using the slider alone is not really possible, which is why we have included the "Increment" and "Decrement" buttons.


This form uses a slider control to change the temperature

This form uses a slider control to change the temperature


The temperature is set to 0 C° when the page is loaded or reloaded, and when the user clicks on the "Reset" button, invoking the reset() function which resets a form to its initial state. The maximum and minimum temperature values (Celsius) are set to -273.15 and +100 using the max and min attributes of the <input> element.

The step attribute is set to 0.01 so that, in theory, we should be able to adjust the temperature up or down in intervals of 0.01 degrees Celsius. In practice, the slider does not provide enough control for "fine tuning". The "Increment" and "Decrement" buttons are programmed to increase or decrease the temperature, respectively, by one degree per click.

Handling numeric values in JavaScript can be tricky, because the value entered in an input field, even if we have set the input type to number, is stored as a string. If we subsequently assign that value to a numeric variable, we need to somehow tell JavaScript to store that variable as a number (in JavaScript, all numeric values are stored as 64-bit floating-point numbers). Consider the following code snippet, which is taken from the updateValues() function:

temp = parseFloat(frm.tempCIn.value);
let tempF = temp * 9/5 + 32;
let tempC = temp;
let tempK = temp + 273.15;

The first line of code here gets the input from the slider as a string value, and uses the parseFloat() function to extract the floating-point value represented by the string. This value is then assigned to the variable temp. Consequently, the next three lines of code see that temp is a floating-point numeric variable, and assign floating-point values to the variables tempF, tempC and tempK, after having carried out the necessary calculations. Now let's look at the next three lines of code:

frm.fahrOut.innerHTML = tempF.toFixed(2);
frm.celsOut.innerHTML = tempC.toFixed(2);
frm.kelvOut.innerHTML = tempK.toFixed(2);

Because all numeric values in JavaScript are stores as 64-bit floating-point values, the temperatures displayed in the output fields could have up to sixteen digits. We want to display exactly two digits after the decimal point, so we use the toFixed() method of the Number object to restrict the output to two decimal places by rounding the values up or down.

The code for the increment() and decrement() functions is fairly self-explanatory. Each user click increases or decreases the value of the slider, which represents the temperature in degrees Celsius, by one degree, after which the updateValues() function is called. Even though the temperature in Celsius is displayed as a fixed decimal value, the code is designed to step the temperature up or down to the next integer value.

The increment() and decrement() functions therefore call the Math.ceil() and Math.floor() static methods to round the input value up or down to the nearest integer value respectively, before stepping up or down by 100 intervals (each interval represents 0.01 degree Celsius). Absolute zero (-273.15 °C) is dealt with as a special case because it has a fractional component.

Checkboxes and radio buttons

Check boxes and radio buttons are two different ways of obtaining input from the user whereby the user is presented with a number of options, each represented by a radio button or checkbox. We typically group some number of checkboxes together using a <fieldset> element to enable the user to choose zero, one, or more than one option. Radio buttons are also usually grouped together using a <fieldset> element, but only one item from the group can be selected at any given time.

Radio buttons are typically used to represent a group of mutually exclusive options. When a user selects a particular radio button, any previously selected radio button in that group is automatically deselected. All of the radio buttons in a group are given the same name, but each represents a different value. The value retrieved by JavaScript will be whatever value is associated with the radio button selected by the user (assuming one has been selected).

Checkboxes represent stand-alone choices that are not usually dependent on whether or not other options have been selected. Even though we tend to group check boxes together for convenience, the user can select any or all of the checkboxes in the group - or none at all. Each checkbox must be given a unique name so that JavaScript can assign its value (true or false) to the correct variable. The code below generates a web page that allows a user to order one or more pizzas for home delivery, and features both radio buttons and checkboxes, as well as some of the form validation features we have already seen. Here is the code:

<!doctype html>
<html lang="en">

  <head>
    <meta charset="utf-8">
    <title>JavaScript Demo 61</title>
    <style>
      h1, h2 {
        text-align: center;
        margin: 0.25em;
      }
      form {
        width: max-content;
        margin: auto;
        padding: 0.25em;
        border: 1px solid black;
      }
      table { padding: 0.5em; }
      td { padding: 0.25em; }

      fieldset { min-height: 100px; }
      .right { text-align: right; }
      .center { text-align: center; }
      div { padding: 0.25em; }
      div.fieldset {
        float: left;
        margin: 0.5em;
      }
      div.menu { text-align: left; }
      div.menu {
        clear: left;
        padding: 0.25em;
        border: 1px solid black;
        min-height: 100px;
      }
      textarea { width: 95%; }

      button {
        width: 100px;
        margin: 0.5em;
      }

      .hidden {
        opacity: 0;
        width: 0;
        float: left;
      }
    </style>
  </head>

  <body>

    <h1>Click-a-Pizza</h1>
    <h2>Online Order Form</h2>
    <p class="center">* required fields</p>

    <form id="frm" novalidate>
      <table>
        <tr>
          <td class="right"><label for="customer">*Name: </label></td>
          <td><input type="text" name="customer" id="customer" required></td>
          <td class="right"><label for="tel">*Tel: </label></td>
          <td><input type="tel" name="tel" id="tel" maxlength="20" required></td>
        </tr>
        <tr>
          <td class="right"><label for="addr">*Address: </label></td>
          <td><input type="text" name="addr" id="addr" required></td>
          <td class="right"><label for="mail">*Email: </label></td>
          <td><input type="email" name="mail" id="mail" maxlength="65" required pattern=\S+@\S+\.\S+></td>
        </tr>
        <tr>
          <td class="right"><label for="town">*Town/city: </label></td>
          <td><input type="text" name="town" id="town" required></td>
        </tr>
        <tr>
          <td class="right"><label for="pcode">*Postcode: </label></td>
          <td><input type="text" name="pcode" id="pcode" required></td>
        </tr>
      </table>

      <div class="center">
        <h3>Please choose a pizza:</h3>
        <div class="menu">
          <div class="fieldset">
            <fieldset>
              <legend>*Topping</legend>
              <label><input type=radio name="topping" value="marg"> Margherita</label><br>
              <label><input type=radio name="topping" value="four"> Four Seasons</label><br>
              <label><input type=radio name="topping" value="meat"> Meat Feast</label><br>
            </fieldset>
          </div>
          <div class="fieldset">
            <fieldset name="base">
              <legend>*Size</legend>
              <label><input type=radio name="base" value="s"> Small</label><br>
              <label><input type=radio name="base" value="m"> Medium</label><br>
              <label><input type=radio name="base" value="l"> Large</label><br>
            </fieldset>
          </div>
          <div class="fieldset">
            <fieldset>
              <legend>Extras</legend>
              <label><input type=checkbox name="xa"> Mushrooms</label><br>
              <label><input type=checkbox name="xb"> Green peppers</label><br>
              <label><input type=checkbox name="xc"> Anchovies</label><br>
              <label><input type=checkbox name="xd"> Extra cheese</label><br>
            </fieldset>
          </div>

          <div class="center">
            <button type="button" name="addItem">Add to order</button>
            <button type="button" name="clearItem">Clear selection</button>
          </div>
        </div>

        <input type="number" class="hidden" name="numItems" required>

        <h3>Order details:</h3>
        <textarea rows="5" name="details" readonly></textarea>
        <div class="center">
          <button type="submit">Confirm order</button>
          <button type="button" name="cancel">Cancel</button>
        </div>
      </div>
    </form>

    <script>
      const frm = document.getElementById("frm");
      const inputs = frm.getElementsByTagName("input");

      let itemCount = 0;

      frm.addEventListener("submit", placeOrder);
      frm.addItem.addEventListener("click", addItem);
      frm.clearItem.addEventListener("click", clearItem);
      frm.cancel.addEventListener("click", clearAll);
      window.addEventListener("load", clearAll);

      for ( let i = 0; i < inputs.length; i++ ) {
        if(typeof inputs[i].value === "string") {
          inputs[i].addEventListener("blur", removeWhiteSpace);
        }
      }

      function removeWhiteSpace() {
        this.value = this.value.trim();
      }

      function placeOrder() {
        event.preventDefault();
        let customer = this.customer;
        let addr = this.addr;
        let town = this.town;
        let pcode = this.pcode;
        let tel = this.tel;
        let mail = this.mail;
        let details = this.details;
        let numItems = this.numItems;

        const telRegex = /^[0-9]{8,15}$/;

        if( customer.validity.valueMissing ) {
          customer.setCustomValidity("Please enter your name.");
        }
        else {
          customer.setCustomValidity("");
        }

        if( addr.validity.valueMissing ) {
          addr.setCustomValidity("Please enter your address.");
        }
        else {
          addr.setCustomValidity("");
        }

        if( town.validity.valueMissing ) {
          town.setCustomValidity("Please enter a town or city.");
        }
        else {
          town.setCustomValidity("");
        }

        if( pcode.validity.valueMissing ) {
          pcode.setCustomValidity("Please enter your postcode.");
        }
        else {
          pcode.setCustomValidity("");
        }

        if( tel.validity.valueMissing ) {
          tel.setCustomValidity("Please enter a telephone number.");
        }
        else if( tel.value.length > tel.getAttribute("maxlength")) {
          tel.setCustomValidity("You have exceeded the maximum length for this field.");
        }
        else if(!telRegex.test(tel.value)) {
          tel.setCustomValidity("Please enter a valid telephone number. The number must have between 8 and 15 digits and contain no spaces.");
        }
        else {
          tel.setCustomValidity("");
        }

        if( mail.validity.valueMissing ) {
          mail.setCustomValidity("Please enter an email address.");
        }
        else if( mail.value.length > mail.getAttribute("maxlength")) {
          mail.setCustomValidity("You have exceeded the maximum length for this field.");
        }
        else if ( mail.validity.patternMismatch ) {
          mail.setCustomValidity("Please enter a valid email address (e.g. jdoe@somedomain.com.");
        }
        else {
          mail.setCustomValidity("");
        }

        if( numItems.validity.valueMissing ) {
          numItems.setCustomValidity("You have not added any items to your order.");
        }
        else {
          numItems.setCustomValidity("");
        }

        if( this.reportValidity() == true ) {
            this.submit();
        };
      }

      function addItem () {
        let numItems = frm.numItems;
        let topping = frm.elements["topping"].value;
        let base = frm.elements["base"].value;
        let xa = frm.xa.checked;
        let xb = frm.xb.checked;
        let xc = frm.xc.checked;
        let xd = frm.xd.checked;
        let details = frm.details;
        if( topping == "" || base == "") { return; }

        itemCount += 1;
        numItems.value = itemCount;

        details.value += itemCount + ". ";

        switch (topping) {
          case "marg":
            details.value += "Margherita, ";
            break;
          case "four":
            details.value += "Four Seasons, ";
            break;
          case "meat":
            details.value += "Meat Feast, ";
            break;
          default:
            break;
        }
        switch (base) {
          case "s":
            details.value += "Small";
            break;
          case "m":
            details.value += "Medium";
            break;
          case "l":
            details.value += "Large";
            break;
          default:
            break;
        }
        if(xa == true) { details.value += " + Mushrooms"; }
        if(xb == true) { details.value += " + Green peppers"; }
        if(xc == true) { details.value += " + Anchovies"; }
        if(xd == true) { details.value += " + Extra cheese"; }
        details.value += "\n";
        clearItem();
      }

      function clearItem() {
        for ( let i = 0; i < inputs.length; i++ ) {
          if(inputs[i].name === "topping" || inputs[i].name === "base" ) {
            inputs[i].checked = false;
          }
        }
        frm.xa.checked = false;
        frm.xb.checked = false;
        frm.xc.checked = false;
        frm.xd.checked = false;
      }

      function clearAll() {
        frm.reset();
        itemCount = 0;
      }

    </script>

  </body>
</html>

Copy and paste this code into a new file in your HTML editor, save the file as javascript-demo-61.html, and open the file in a web browser. You should see something like the screenshot below. Use the radio buttons and check boxes to select a pizza, then click on the "Add to order" button. You will see the details of any pizzas you have selected appear in the read-only "Order details" field.


Radio buttons and checkboxes are used to select a pizza

Radio buttons and checkboxes are used to select a pizza


The form requires the user to enter their contact details and select at least one pizza before the form data can be submitted. Once the user has chosen a pizza and added it to the order, they have the option of selecting another pizza or confirming (or cancelling) their order. Details of the items ordered are displayed in the <textarea> element at the bottom of the order form. Details of each pizza ordered are also stored in an orderItem object, which is then added to the orderItems array.

Most of the code relating to the validation of customer details should look to familiar to you, since we have already looked at examples of how JavaScript can be used to validate a typical contact form. The code we are interested in here is the code that retrieves the values input by the user via the radio buttons and checkboxes. The addItem() function declares variables topping  and base  to hold the topping and base selections respectively, as well as the xa , xb , xc  and xd  variables to represent whether or not the extras they represent have been selected:

let topping = frm.elements["topping"].value;
let base = frm.elements["base"].value;
let xa = frm.xa.checked;
let xb = frm.xb.checked;
let xc = frm.xc.checked;
let xd = frm.xd.checked;

The value assigned to the topping and base variables will be the values associated with the selected radio button in each case. The variables representing the extras will be assigned a Boolean value of either true or false, depending on whether or not the user has checked the checkbox. The user must select a topping and a base in order for the item to be added to the order when the user clicks on the "Add to order" button, or the function will simply return without taking any further action (the extras are optional).

The user can also choose to clear the pizza selection if they make a mistake, or change their mind about their order, by clicking on the "Clear selection" button. This calls the clearItem() function (see below). When the user adds the current selection to the order by clicking on the "Add to order" button which calls the addItem() function, which also calls clearItem() once it has added the current selection to the order.

The clearItem() function cycles through the form's <input> elements. When it finds an input field with the names "topping" or "base", it sets the checked property of that input to false. It then sets the checked property of the four checkbox inputs to false.

function clearItem() {
  for ( let i = 0; i < inputs.length; i++ ) {
    if(inputs[i].name === "topping" || inputs[i].name === "base" ) {
      inputs[i].checked = false;
    }
  }
  frm.xa.checked = false;
  frm.xb.checked = false;
  frm.xc.checked = false;
  frm.xd.checked = false;
}

The rest of the code is concerned with keeping track of the items ordered, and displaying details of the order so that the user can see what they have selected. Most of this code is self-explanatory. There is admittedly rather a lot of code in this example, but we wanted to provide a fully working and realistic example of an online order form. We suggest you play around with the form and study the relevant sections of JavaScript code in order to get a feel for how everything works.

Drop-down lists and list boxes

HTML's <select> element enables us to implement drop-down lists and list boxes. Conceptually, these elements can offer the same kind of functionality as radio buttons and checkboxes in the sense that they allow the user to choose one or more options from a set of options. The main difference is the way in which the choices are presented to the user.

Radio buttons and checkboxes tend to be grouped together on the page in an almost tabular layout, and all options are visible at the same time. In a drop-down list, the options are hidden from the user until the expand the list by clicking on the drop-down control. A list box has a fixed size and will typically show some of the options available, but the user may have to scroll up or down in order to see all of the options, depending on how many there are.

As for checkboxes, we can select more than one item in a list box if the <select> tag has the multiple attribute. The down side is that once the user has already selected an option, they must hold down the Shift key in order to select further options, or the original selection will be deselected (in that respect, checkboxes are more user-friendly). A user can select any or all of the options in a list box, or none at all. Each <option> element in the list must be given a unique identifier so that JavaScript can assign the value of its selected property (true or false) to the correct variable.

The code below generates a web page that is very similar to the previous example, except that this time we have created an order form that allows the user to choose a pizza using drop-down lists and list boxes instead of groups of radio buttons and checkboxes. Here is the code:

<!doctype html>
<html lang="en">

  <head>
    <meta charset="utf-8">
    <title>JavaScript Demo 62</title>
    <style>
      h1, h2 {
        text-align: center;
        margin: 0.25em;
      }
      form {
        width: max-content;
        margin: auto;
        padding: 0.25em;
        border: 1px solid black;
      }
      table { padding: 0.5em; }
      td { padding: 0.25em; }

      .right { text-align: right; }
      .center { text-align: center; }
      div { padding: 0.25em 0; }
      div.selection {
        float: left;
        margin: 0 0 2em;
        width: 33.33%;
      }
      div.menu {
        text-align: center;
        clear: left;
        padding: 0.25em 0;
        border: 1px solid black;
        min-height: 100px;
        width: 95%;
        margin: auto;
      }
      textarea { width: 95%; }

      button {
        width: 100px;
        margin: 0.5em;
      }

      .hidden {
        opacity: 0;
        width: 0;
        float: left;
      }
    </style>
  </head>

  <body>

    <h1>Click-a-Pizza</h1>
    <h2>Online Order Form</h2>
    <p class="center">* required fields</p>

    <form id="frm" novalidate>
      <table>
        <tr>
          <td class="right"><label for="customer">*Name: </label></td>
          <td><input type="text" name="customer" id="customer" required></td>
          <td class="right"><label for="tel">*Tel: </label></td>
          <td><input type="tel" name="tel" id="tel" maxlength="20" required></td>
        </tr>
        <tr>
          <td class="right"><label for="addr">*Address: </label></td>
          <td><input type="text" name="addr" id="addr" required></td>
          <td class="right"><label for="mail">*Email: </label></td>
          <td><input type="email" name="mail" id="mail" maxlength="65" required pattern=\S+@\S+\.\S+></td>
        </tr>
        <tr>
          <td class="right"><label for="town">*Town/city: </label></td>
          <td><input type="text" name="town" id="town" required></td>
        </tr>
        <tr>
          <td class="right"><label for="pcode">*Postcode: </label></td>
          <td><input type="text" name="pcode" id="pcode" required></td>
        </tr>
      </table>

      <div class="center">
        <h3>Please choose a pizza:</h3>
        <div class="menu">

          <div class="selection">
            <p>*Choose a topping:</p>
            <Select id="topping" required>
              <option value="" selected disabled>Topping</option>
              <option value="marg" id="marg">Margherita</option>
              <option value="four" id="four">Four Seasons</option>
              <option value="meat" id="meat">Meat Feast</option>
            </select>
          </div>
          <div class="selection">
            <p>*Choose a base:</p>
            <Select id="base" required>
              <option value="" selected disabled>Base</option>
              <option value="s">Small</option>
              <option value="m">Medium</option>
              <option value="l">Large</option>
            </select>
          </div>
          <div class="selection">
            <p>Choose extras:</p>
            <select multiple size="4">
              <option id="xa">Mushrooms</option>
              <option id="xb">Green peppers</option>
              <option id="xc">Anchovies</option>
              <option id="xd">Extra cheese</option>
            </select>
          </div>

          <div class="center">
            <button type="button" name="addItem">Add to order</button>
            <button type="button" name="clearItem">Clear selection</button>
          </div>
        </div>

        <input type="number" class="hidden" name="numItems" required>

        <h3>Order details:</h3>
        <textarea rows="5" name="details" readonly></textarea>
        <div class="center">
          <button type="submit">Confirm order</button>
          <button type="button" name="cancel">Cancel</button>
        </div>
      </div>
    </form>

    <script>
      const frm = document.getElementById("frm");
      const inputs = frm.getElementsByTagName("input");
      const orderItems = [];

      let itemCount = 0;

      frm.addEventListener("submit", placeOrder);
      frm.addItem.addEventListener("click", addItem);
      frm.clearItem.addEventListener("click", clearItem);
      frm.cancel.addEventListener("click", clearAll);
      window.addEventListener("load", clearAll);

      for ( let i = 0; i < inputs.length; i++ ) {
        if(typeof inputs[i].value === "string") {
          inputs[i].addEventListener("blur", removeWhiteSpace);
        }
      }

      function removeWhiteSpace() {
        this.value = this.value.trim();
      }

      function placeOrder() {
        event.preventDefault();
        let customer = this.customer;
        let addr = this.addr;
        let town = this.town;
        let pcode = this.pcode;
        let tel = this.tel;
        let mail = this.mail;
        let details = this.details;
        let numItems = this.numItems;

        const telRegex = /^[0-9]{8,15}$/;

        if( customer.validity.valueMissing ) {
          customer.setCustomValidity("Please enter your name.");
        }
        else { customer.setCustomValidity(""); }

        if( addr.validity.valueMissing ) {
          addr.setCustomValidity("Please enter your address.");
        }
        else { addr.setCustomValidity(""); }

        if( town.validity.valueMissing ) {
          town.setCustomValidity("Please enter a town or city.");
        }
        else { town.setCustomValidity(""); }

        if( pcode.validity.valueMissing ) {
          pcode.setCustomValidity("Please enter your postcode.");
        }
        else { pcode.setCustomValidity(""); }

        if( tel.validity.valueMissing ) {
          tel.setCustomValidity("Please enter a telephone number.");
        }
        else if( tel.value.length > tel.getAttribute("maxlength")) {
          tel.setCustomValidity("You have exceeded the maximum length for this field.");
        }
        else if(!telRegex.test(tel.value)) {
          tel.setCustomValidity("Please enter a valid telephone number. The number must have between 8 and 15 digits and contain no spaces.");
        }
        else { tel.setCustomValidity(""); }

        if( mail.validity.valueMissing ) {
          mail.setCustomValidity("Please enter an email address.");
        }
        else if( mail.value.length > mail.getAttribute("maxlength")) {
          mail.setCustomValidity("You have exceeded the maximum length for this field.");
        }
        else if ( mail.validity.patternMismatch ) {
          mail.setCustomValidity("Please enter a valid email address (e.g. jdoe@somedomain.com.");
        }
        else { mail.setCustomValidity(""); }

        if( numItems.validity.valueMissing ) {
          numItems.setCustomValidity("You have not added any items to your order.");
        }
        else { numItems.setCustomValidity(""); }

        if( this.reportValidity() == true ) { this.submit(); };
      }

      // constructor       function orderItem(topping, base, xa, xb, xc, xd) {
        this.topping = topping;
        this.base = base;
        this.xa = xa;
        this.xb = xb;
        this.xc = xc;
        this.xd = xd;
      }

      function addItem () {
        let numItems = frm.numItems;
        let topping = frm.elements["topping"].value;
        let base = frm.elements["base"].value;
        let xa = document.getElementById("xa").selected;
        let xb = document.getElementById("xb").selected;
        let xc = document.getElementById("xc").selected;
        let xd = document.getElementById("xd").selected;
        let details = frm.details;
        if( topping == "" || base == "") { return; }

        let item = new orderItem(topping, base, xa, xb, xc, xd);
        itemCount = orderItems.push(item);
        numItems.value = itemCount;

        details.value += itemCount + ". ";

        switch (topping) {
          case "marg":
            details.value += "Margherita, "
            break;
          case "four":
            details.value += "Four Seasons, "
            break;
          case "meat":
            details.value += "Meat Feast, "
            break;
          default:
            break;
        }
        switch (base) {
          case "s":
            details.value += "Small"
            break;
          case "m":
            details.value += "Medium"
            break;
          case "l":
            details.value += "Large"
            break;
          default:
            break;
        }
        if(xa == true) { details.value += " + Mushrooms"; }
        if(xb == true) { details.value += " + Green peppers"; }
        if(xc == true) { details.value += " + Anchovies"; }
        if(xd == true) { details.value += " + Extra cheese"; }
        details.value += "\n";
        clearItem();
        console.log(orderItems);
      }

      function clearItem() {
        document.getElementById("topping")[0].selected = true;
        document.getElementById("base")[0].selected = true;
        document.getElementById("xa").selected = false;
        document.getElementById("xb").selected = false;
        document.getElementById("xc").selected = false;
        document.getElementById("xd").selected = false;
      }

      function clearAll() {
        frm.reset();
        orderItems.length = 0;
        itemCount = 0;
      }

    </script>

  </body>
</html>

Copy and paste this code into a new file in your HTML editor, save the file as javascript-demo-62.html, and open the file in a web browser. You should see something like the screenshot below.


Drop-down lists and a list box are used to select a pizza

Drop-down lists and a list box are used to select a pizza


The code used here is not vastly different from the code we used for the radio buttons and checkboxes in the previous example. The code that retrieves the values input by the user via the drop-down lists and list boxes is shown below. As before, the addItem() function declares variables topping and base to hold the topping and base selections respectively, as well as the xa, xb, xc and xd variables to represent whether or not the extras they represent have been selected:

let topping = frm.elements["topping"].value;
let base = frm.elements["base"].value;
let xa = document.getElementById("xa").selected;
let xb = document.getElementById("xb").selected;
let xc = document.getElementById("xc").selected;
let xd = document.getElementById("xd").selected;

The main difference here is that we use the getElementById() method to retrieve the status of the options in the extras list box. If selected, an <option> element's selected property will be set to true. If not, it will be set to false.

The clearItem() function is shown below. It's very similar to the version we used to clear the radio buttons and check boxes in the previous example, except that we use getElementById() to access each <option> element in the list box.

function clearItem() {
  document.getElementById("topping")[0].selected = true;
  document.getElementById("base")[0].selected = true;
  document.getElementById("xa").selected = false;
  document.getElementById("xb").selected = false;
  document.getElementById("xc").selected = false;
  document.getElementById("xd").selected = false;
}

File input

Many online forms these days allow the user to upload a file. When applying online for a new UK passport, for example, you will be asked to upload a digital passport photo in a specific format. File input fields were originally only intended to enable the uploading of files to a server via form submission. Simply allowing JavaScript to read files on the client computer would constitute a potential security risk. On the other hand, if the user chooses to allow a file to be read, JavaScript should be able to do so.

Consider a scenario in which we are asked to create an account for a social media site (or whatever). We will typically be required to provide a username and password, an email address, and (optionally) a profile picture. If we include an <input> control on our form with the type attribute set to file, the user can select an image file to upload by clicking on the "Browse..." button provided.

Let's suppose, however, that we want the user to be able to see a thumbnail preview of the image to be uploaded before they click on the form's "Submit" button. If we add an <img> element to the form and set its name attribute to "preview", we could maybe try something like this:

<form id="frm">
  <input type="file" name="fileInput" accept="image/png, image/jpeg" />
  <img name="preview" height="150">
</form>

<script>
  const frm = document.getElementById("frm");
  const fileInput = frm.fileInput;
  const preview = frm.preview;

  fileInput.addEventListener("change", loadPic);
  function loadPic() { preview.src = fileInput.value; }

  console.log( fileInput.value ); // C:\fakepath\Chris.jpg
</script>

As you can see, the value passed to the src attribute of the <img> element (C:\fakepath\Chris.jpg) contains a placeholder instead of the actual file path, so the image will not be displayed, and we will see something like this:


The selected image file is not displayed

The selected image file is not displayed


The browser is essentially hiding the file path information for security reasons, but there is a way to retrieve contents of the image file so that we can display it. An <input> element whose type attribute is set to file has a files property - an array-like object that contains the file (or files, since a file input field also supports a multiple attribute) selected by the user.

We can access the files held in the files object using the readAsData() method of the FileReader object (defined in the File System Access API), which allows web pages and applications to read the contents of those files asynchronously (i.e. as a background process). Because the files are loaded into the FileReader object asynchronously, we cannot access them until the loading process is complete. We therefore need to wait for the load event to occur before we can access the file (or files) stored in the FileReader object.

The code below demonstrates how this works. It generates a web page containing a form that allows a user to apply for an online account. The user is required to provide a username, a password and an email address, and may optionally upload a profile picture. Here is the code:

<!doctype html>
<html lang="en">

  <head>
    <meta charset="utf-8">
    <title>JavaScript Demo 63</title>
    <style>
      h1, p { text-align: center; }
      form {
        max-width: max-content;
        margin: auto;
        border: solid 1px;
        padding: 0.5em;
      }
      td { padding: 0.5em; }
      label span {
        min-width: 140px;
        display: inline-block;
        vertical-align: top;
      }
    </style>
  </head>

  <body>

    <h1>Create your account</h1>

    <p><strong>Account details:</strong><br><br>(fields marked with an asterisk are required)</p>

    <form id="frm">
      <table>
        <tr>
          <td><label><span>*User name: </span><input type="text" name="uname" required></label></td>
        </tr>
        <tr>
          <td><label><span>*Email address: </span><input type="email" name="email" size="50" required></label><td>
        </tr>
        <tr>
          <td><label><span>*Password: </span><input type="password" name="pword1" required></label><td>
        </tr>
        <tr>
          <td><label><span>*Confirm password: </span><input type="password" name="pword2" required></label><td>
        </tr>
        <tr>
          <td><label><span>Profile picture: </span><input type="file" name="fileInput" accept="image/png, image/jpeg" /></label><td>
        </tr>
        <tr>
          <td><label><span>Preview: </span><img name="preview" height="150"></label><td>
        </tr>
        <tr>
          <td><input type="submit" value="Submit"></td>
        </tr>
      </table>
    </form>

    <script>
      const frm = document.getElementById("frm");
      const fileInput = frm.fileInput;
      const preview = frm.preview;
      const reader = new FileReader();

      fileInput.addEventListener("change", getPic);
      reader.addEventListener("load", loadPic);

      function getPic () { reader.readAsDataURL(fileInput.files[0]); }
      function loadPic() { preview.src = reader.result; }
    </script>

  </body>
</html>

Copy and paste this code into a new file in your HTML editor, save the file as javascript-demo-63.html, and open the file in a web browser. Complete the username, email and password fields and select an image to display as a profile picture. Depending on the data you entered and the file you chose, you should see something like the screenshot below.


The preview image is loaded into the form using the image file data

The preview image is loaded into the form using the image file data


Because JavaScript must read the file from disk (or whatever kind of storage media the file resides on), the loading process is carried out asynchronously in order to prevent the web page becoming unresponsive to user input, which could happen with large input files. The <img> tag does not receive a file name. Instead, its src attribute receives the contents of the file, which is stored in the FileReader object's result property.

Submitting a form

By default, form data is submitted when the user clicks on an <input> element whose type attribute is set to submit - providing all required fields contain input in the correct format. But how is the data sent, and where is it sent to? The short answer to that it depends on several factors. Let's assume for the moment that we are not invoking JavaScript and are using only HTML to determine what happens when the user submits the form. Form data is submitted using an HTTP request.

If all of the form data is valid (or the form's novalidate attribute has been used), the form data will be submitted. The two most important form attributes to consider here are the method attribute and the action attribute. The method attribute specifies the HTTP method - post or get - used to submit form data.

The default method for form submission is get, but if we are submitting form data to a server for processing, it is better to use the post method. This method instructs the browser to encode the form data into the message body of the HTTP request that it sends to the server. The get method is typically used when we want to retrieve data from a server based on user input.

It is possible to submit form data to a sever using the get method, but there are good reasons not to, such as the fact that the form data is encoded as key-value pairs to create what is known as a query string, which is then appended to the URL specified by the form's action attribute. Because the information in the query string forms part of the URL, and because it will also be stored in the browser's history, it is highly visible and therefore represents a potential security risk.

Another problem is that data sent using get can only contain ascii characters, because URLs can only contain ascii characters. Ascii characters that have special meaning within a URL must be urlencoded to ensure they are treated as form data rather than part of the URL syntax. Practical limits on the maximum length of a query string imposed by browser and server software also limit the amount of data that can be sent using get.

If the post method is used for submitting form data, the type of encoding used can be set using the form's enctype attribute, which can take one of three possible values. By default, enctype has the value application/x-www-form-urlencoded - the data is encoded as key-value pairs within the message body using URL encoding. If this is what we want to happen, we don't need to explicitly set the enctype attribute.

In most cases, URL encoding is sufficient for our purposes. The exception is when we have an <input> element whose type attribute is set to file. A file contains binary data and must be sent to the server without its contents being altered, so we don't want it to be URL encoded. We therefore use enctype with its value set to multipart/form-data, which allows files to be included with the submitted data without changing them in any way. The third alternative for enctype is text/plain, which is used only for debugging purposes.

In several of the examples we have seen above, we have disabled a form's default behaviours with respect to HTML's built-in form validation and response to the submit event. This allows us to implement our own validation routines using JavaScript, and only allow the form data to be submitted once we have ascertained that required input fields have been completed and all input provided is the correct format. Once these conditions have been met, we can submit the form data using the submit() method.

The action attribute usually specifies the URL of a program or server-side script to which the form data will be sent, and which will process the form data in some way. By default, the response generated by that program or script will be a web document that will replace the page from which the form data has been submitted. This might be what we want to happen, but there are times when we want to submit form data and stay on the page from which it was sent.

One common scenario is for data to be sent to a server for processing asynchronously (i.e., as a background process - we have already described a situation in which files are loaded asynchronously) while the web page from which the data is sent remains active in the browser. The page will typically receive a response from the server which is then used to update the current page in some way rather than replacing it completely. This can be achieved in various ways, including the use of Asynchronous JavaScript and XML (AJAX).

The asynchronous exchange of data between a web server and a client browser does not always involve HTML forms. In fact, many modern interactive web applications use JavaScript to generate behind the scenes calls to the server in order to update the information on a page without replacing it, often in response to a user action that doesn't involve the submission of form data. We will therefore be dealing with the somewhat broader topic of asynchronous data exchange - including the asynchronous submission of form data - in a separate article.