Welcome!
Everything is fine.

Form Validation and Regular Expressions

Form validation is an important filter to make sure

  1. Users know when they've made an incorrect request and
  2. The main functionality (back-end or front-end) doesn't have to check for (as many) errors

Plan

  1. Announcements
  2. Recap Form Validation
  3. Answer your questions

Recap Form Validation

We have several levels of validation:

  1. Make an input required. This is pretty basic, but catches simple omissions.
  2. Use HTML5 types like email or url. These are essentially pre-defined patterns
  3. Use patterns (regular expressions)
  4. Run arbitrary JavaScript code using the Constraint Validation API

Required

This couldn't be easier. Just add the required attribute, and you're done. You can submit the following form using the "enter" key:

<form>
   <p><label>
         <span class="label-body">zip code</span>
         <input type="text" name="zip" required>
      </label></p>
</form>

HTML5 types

HTML5 added a bunch of types like email, date, datetime, month, range, tel, url and others. See INPUT element

<form>
   <p><label>
         <span class="label-body">email</span>
         <input type="email" name="email" required>
      </label></p>
</form>

You can submit the form by pressing enter. If it succeeds, you'll see no error message, but you'll see your data in the URL.

This is nice, but not very smart. Note that a@b passes the validation check.

Patterns

We'll learn more about regular expressions in a minute. For now, let's look for wellesley.edu email addresses:

<form>
   <p><label>
         <span class="label-body">email</span>
         <input type="email" name="email" required
                pattern="[\w\.]+@wellesley\.edu$">
      </label></p>
</form>

Try examples like:

  • a@wellesley
  • $@wellesley.edu
  • #@wellesley.edu
  • ab&@wellesley.edu
  • a_b@wellesley.edu

Now we're getting somewhere!

Constraint API

For each form input (control), there is a setCustomValidity() method, taking a string as its argument.

If that string is the empty string, it clears the error, allowing the form to submit.

Otherwise, the string will be reported to the user when they attempt to submit the form.

The reportValidity() method updates the browser. (Not sure why this isn't still the default.)

Our authors suggest that the event to attach the processing to is the input event, which fires when the value changes.

Here, let's use the constraint API to check for a value that is not a regular expression or anything else that is easy to check for. Instead, it checks that the number is divisible by 3.

<form>
   <p><label>
         <span class="label-body">Triple</span>
         <input type="number" name="num" id="num1">
      </label></p>
</form>

Here's the code:

$("#num1").on('input',
   function(event) {
     var n = parseInt(event.target.value,10);
     // arbitrary processing of the value
     if( n % 3 === 0 ) {
          event.target.setCustomValidity('');
     } else {
          event.target.setCustomValidity
              ('Sorry, that is not divisible by 3');
     }
     event.target.reportValidity();
});

Comparing Fields

Loosely based on an example of custom validity by Jason Knight

Here's the HTML. The second input needs to match the first; we specify the id of the thing it should match by using a data-match-for attribute.

<form action="/~cs304/cgi-bin/dump.cgi">
<label>
  Password:<br>
  <input
    type="password"
    id="test1"
    required
  ><br>
</label><label>
  Repeat Password<br>
  <input
    type="password"
    data-match-for="test1"
    required
  >
</label>
</form>

Here's the JS. It's attached to the input event on the second input, the one with the data-match-for attribute.

$("[data-match-for]").on('input', function (evt) {
    let otherId = $(evt.target).attr('data-match-for');
    let otherVal = $("#"+otherId).val();
    let myVal = $(evt.target).val();
    console.log('otherVal', otherVal, 'myVal', myVal);
    if(myVal != otherVal) {
        evt.target.setCustomValidity('no match');
    } else {
        evt.target.setCustomValidity('');
    }
    evt.target.reportValidity();
});

Regular Expressions

Regular expressions (Regex to their friends) are very cool and powerful, but they are a whole different language.

MDN regular expression

  • most characters match themselves, so /hi/ matches strings that contain hi
  • special characters allow for greater expressivity
  • [a-z] matches lowercase letters
  • [^a-z] matches the opposite
  • . matches 1 character (any kind)
  • \d matches 1 digit
  • \D matches 1 non-digit
  • \w matches 1 "word" characters: [A-Za-z0-9_]
  • \W matches 1 non-word characters
  • \s matches 1 space character (including tabs, etc)
  • \S matches 1 non-space character
  • get repetitions with quantifiers
  • * means zero or more repetitions
  • + means 1 or more repetitions
  • ^ matches the beginning of the string
  • $ matches the end of the string

Let's start simple, and look at a regexp that tests for a string containing "by" (like "good-bye"):

String and result:

The method .test() is a method on regular expressions, which are delimited by slashes:

/by/.test("bye")

Here is the full code:

$("#by1").on('input',
  function (event) {
    var sp = ' ';
    var str = event.target.value;
    $("#out1").text(str+sp+/by/.test(str));
    if( /by/.test(str) ) {
         event.target.setCustomValidity('');
    } else {
         event.target.setCustomValidity
             ("doesn't match pattern");
    }         
    evt.target.reportValidity();
});

Alternatively, you can use .match() which is a method on strings. Try copy/pasting these into a JS console:

/by/.test("gbye");
"gbye".match(/by/);

The .match() method returns a bit more information. There are many other methods we could look at, but we won't. Instead, let's learn more about the regexp language

Regular Expression Language

From now on, we'll use the following form to test our regular expressions. Here is the regular expression tester in a separate page

String and result:

<form id="regexp-string">
   <p><label>
         <span class="label-body">regexp</span>
         <input type="text" name="pattern" id="pattern">
      </label></p>
   <p><label>
         <span class="label-body">string</span>
         <input type="text" name="string" id="string">
      </label></p>
   <p>String and result:
       <output aria-live="polite" id="result"></output>
       </p>
</form>

Here's the code. Because the regular expression is dynamic, we can't use the /literal/ notation, and we have to use the more long-winded new RegExp() notation:

$("#regexp-string").on('input', // event
                       'input', // selector
    function (event) {
        var sp = ' ';
        var pat = $("#pattern").val();
        var str = $("#string").val();
        var regexp = new RegExp(pat);
        var result = regexp.test(str);
        $("#result").text(str+sp+result);
    });
$("#regexp-string").on('submit',
   function(event) {
      event.preventDefault();
   });

First, try "by" against the strings "goodbye" and "hello".

Suppose we want to allow 1 letter to separate the b and y. We can match it with a category, like [a-z]:

b[a-z]y

Suppose we want zero or more such characters:

b[a-z]*y

Suppose we want 1 or more such characters:

b[a-z]+y

Suppose we want to allow letters (upper and lower) and digits in there:

b[A-Za-z0-9]+y

If we include underscore in the set, we have characters that we could use for variables and properties, and there's a shorthand: \w

b\w+y

What if we want any character between the b and y, including punctuation and spaces and stuff? The dot (period) matches any single character:

b.+y

What if we want to match a dot and only a dot?

b\.+y

Suppose we want to make sure the string starts with the b. We can match the beginning of the string with a caret.

^b\.+y

Suppose we also want the string to end at the y. Use $

^b\.+y$

Exercise:

Match a Wellesley Unit number (four digits)

[0-9][0-9][0-9][0-9]
\d\d\d\d
[0-9]{4}
\d{4}

Balancing Validation and Permissiveness

  • Regular expressions are cool, but it's easy to get too restrictive.
  • The world is a complex place
  • People's names are not all in [a-z]+. E.g. Michael Kölling or Jennifer 8. Lee
  • Find a good balance between protecting your application and being permissive

Summary

We learned about form validation:

  • the require attribute
  • the HTML5 input types
  • the pattern attribute
  • the .setCustomValidity() method on form controls
  • we learned to add an event handler to the input event to do validation

We also learned about regular expressions. Regular expressions can be used to parse simple languages and in general can be very powerful.