Welcome!
Everything is fine.
Form Validation and Regular Expressions
Form validation is an important filter to make sure
- Users know when they've made an incorrect request and
- The main functionality (back-end or front-end) doesn't have to check for (as many) errors
Plan¶
- Announcements
- Recap Form Validation
- Answer your questions
Recap Form Validation¶
We have several levels of validation:
- Make an input required. This is pretty basic, but catches simple omissions.
- Use HTML5 types like email or url. These are essentially pre-defined patterns
- Use patterns (regular expressions)
- 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.
- most characters match themselves, so
/hi/
matches strings that containhi
- 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"):
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
<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.