calling javascript
the mechanics of invoking from the browser
Review:
We saw an example of javascript that was run by the browser;
we defined the javascript functions in the head
(or, in a separate file which was included via a
<script src=… >
tag in the head).
Then, in various tags we had onclick attributes,
whose value was a bit of javascript code, which the browser then evaluated.
(e.g.Whatever you do don't click <span onclick="explode();">here</span>.).
Other events that can invoke javascript:
onchange, onclick, onfocus, onblur.
See
w3.org’s
definition.
validating on the client-side, via javascript
Note: (to future self):
attr 'pattern' doesn't get evaluated onchange;
add some solutions lectures:
The straightforward way to validate:
When the user submits, call a function you've written, which validates all the inputs.
The onsubmit attribute of the form tag is the place to call it:
<form onsubmit='return validateAll();'>,
where
your function validateAll should return a boolean;
returning false tells the browser not to actually submit.
The browser will submit a form when:
the submit input-button is pressed,
and
all input-elements meet their validation-specified-via-html-attributes
(maxlength, required, etc.).
and
the form’s onsubmit javascript (if any) returns true.
(If you don't have an onsubmit attribute,
then it's like saying onsubmit='return true;'.)
Alternately: the browser will also submit if your javascript
explicitly calls the form’s submit(·) method.
This bypasses steps (a)…(c) above (!).
Calling submit directly is something one might do on a login-page:
as soon as the user types "enter" at the end of typing their username/password
your javascript might trigger the submit
(instead of forcing the user to click on an additional input type='submit' button).
<form id="form4-demo" method="get" action="">
<input type="number" name="stars" min="1" max="10" required="required" placeholder="a number in 1..10" style="width:9em;"/>
<label><input type='checkbox' onclick='document.getElementById("form4-demo").submit();'/>click to force-submit</label>
<span class="additional-info">note how this checkbox bypasses the browser’s validation</span>
<input type="submit" value="submit to nowhere"/>
</form>
Be aware that it's arguably bad style to have all the javascript (which is more abouty
presentation)
commingled with the html content.
Just like we should separate out the css,
we might separate out the javascript event-handling code by using
“en.wikipedia.org/wiki/Unobtrusive_JavaScript”:
Instead of
<someTag id="foo" onclick="someFunc('whee');">Real Data</someTag>,
you can instead just have HTML
<someTag id="foo">Real Data</someTag>,
and then in separate javascript:
document.getElementById('foo').onclick = function() { someFunc('whee'); }.
For this course, you do not need to do this (but you are welcome to).
Recall from in-class example:
js-bingo.html is another example
of adding our own properties (fields) to a node of the DOM.
Adding error messages dynamically
Suppose you have an a field named "age",
which should be between at least 21, but not 40 or more1.
We've already seen that we want to validate this both
by the input tag's onchange,
as well as
by the form tag's onsubmit.
So we definitely want to avoid the repeated code.
function validateAge() {
var ageNode = document.getElementById('age');
var age = ageNode.value;
var isValid = (21 <= age && age < 40);
var ageErrMsgNode = document.getElementById('ageErrMsg');
if (!isValid) {
ageErrMsgNode.innerHTML = "Must be between 21 (inclusive) and 40 (non-inclusive)";
// Hmm, might be nice to include the offending value in the error msg?
}
return isValid;
}
Things to consider:
What if in the HTML, we forgot to have an (initially empty) paragraph
with id="minLevelErrorMsg"?
What javascript error would ensue?
How would the user see that error message?
How can we improve our code?
Checking the numeric range of an input is a pretty danged common task.
Let's make this a general function validateInRange,
that we'll keep in a file of utility javascript functions, utils.js!
What arguments need to be passed in to the code?
Right now, the numbers 21 and 40 are repeated several times,
including in the original HTML's additional-info for the user.
The numbers should be defined just once, probably in a
php file (site-constants.php)
that is used to generate this file's HTML and it's javascript.
Improved version:
Have a validation-function “validateAgeErrMsg”
which just returns the error-message-string
(perhaps the empty-string, if no error);
have a separate function “insertErrMsg”
that takes a node and a message, and splices that message-text into
the DOM for that node, with a style that indicates an error message2 (perhaps small but angry red font).
For each input, have a function to validate it.
Call that function from onchange.
The validation-function should return true if the input is valid;
if not, the function should return
falseand
it should cause an error-message to be displayed.
A function validateAll, which is called onsubmit.
This function should return true if all inputs are valid;
if not, the function should return
falseand
it should cause all error-messages to be displayed.
…This is easy to write: simply call each input's specific-validation above,
and && the results.
validating on the client-side, via html5 attributes
A lot of validation can be done with html5 attributes.
This can shorten — or even eliminate — the need for individual validation functions.
And if no individual validation functions are needed, then a validateAll isn't either!
You don't need to validate the checkbox-values, like we do server-side.
You don't need to validate drop-downs (beyond checking if they're required).
Note:
If a dropdown is required,
and you don't want the first option to be used as a (default) value,
then the first option must include value="".
<form id='demo-form'>
<select name="select-demo" required="required">
<option value="">choose one</option>
<option>a</option> <!-- 'a' will be the value, if this option is chosen. -->
<option value="bee" formaction="." formmethod="get">b</option>
</select>
<input type="submit" value="submit to nowhere"/>
</form>
It is useful in conjunction with the title attribute
(the tooltip-text to use, if validation fails),
and also the placeholder attribute.
It should be clear why our server-side validate-a-particular-input function
had to double-check the maxlength/required/etc. status,
but our corresponding client-side function doesn't:
If an attacker takes the form (on the client-side) an modifies the html, they're
equally able to modify — or turn off — the javascript-validation3.
And that's why the server-side validation is critical!
You should prefer using the built-in validation, over rolling-your-own javascript to do the same.
Even if you have a nicer way of presenting the information,
there is strength in having the user see error messages always presented in the same way
across all their sites.
It also enables users to configure browser shortcut/hotkeys, plug-ins, etc. to improve
their experience on your site.4
The one thing I miss (as of Chrome v56.0, 2017-Mar) is
that browser-validation-errors tends to only get revealed one-by-one, as the
submit button is pressed,
whereas I'd prefer them to be revealed instantly onchange.
But, a future version of the browser might well fix this.
Gotchas about validating regular-expressions in html
The pattern attribute is ignored
if the input is (a) left empty and (b) not required='required'.
(Kinda makes sense-ish:
if it's not required, then the empty string is valid, regardless of any pattern.)
The pattern applies to the entire string;
no need to anchor the pattern with ^ or $.
(Use .* on either side, to effect “the string contains a pattern-match”.)
No need to: quote back-slashes: pattern="\w+" is fine, you don't need
pattern="\\w+".
(After all, our html file is a string; backslash has no special meaning inside an html
file,
except for embedding single-quotes within a double-quoted string or vice-versa).
No need to: use starting/ending slashes, for the pattern attribute. (We're not writing php or javascript.)
So pattern="so\w+in." will pass, when the input is “somestring”.
(On the other hand, if/when we are inside a script tag, then javascript rules apply:
<script type="text/javascript"> …"somestring".match(/^so\w+in.$/); … </script>.5)
The regexp support may be a bit wonky, and might (in reality) differ from one browser to another.
In particular,
when using Chrome (v56.0…, 2017-Mar-19)
the regex pattern='.{3}+' did not prohibit “abcd”,
but pattern='(.{3})+' did:
1
That is, the half-open range [21,40)
—
half-open ranges are the right way to deal with integer ranges.
↩
2
Or even better: a “insertMsg” which includes a boolean -- should
the message be inserted with a style of “error-occurred” or a style
of “all-okay” (which might render in small happy green font).
↩
3
Well, it's not clear to me that if you're very clever, and use some sort
of “cryptographically-obfuscated” javascript and submitted-info, you might be able to keep
an attacker from bypassing the validation and submitting doctored values.
↩
4 An ibarland pet-peeve: login pages that use
javascript instead of standard input forms; my password-manager can't easily auto-fill customized
javascript,
making those pages noticeably more painful to use.
↩
5 Just to make sure you're confused:
Inside javascript, where regexp literals start/end with a slash,
you don't need to quote the backslash like you do in (say) a java string-literal.
So the /so\w+in./ is fine javascript, and is the equivalent of new
java.util.regex.Pattern("^so\\win.$").
↩