Dalke Scientific Software: More science. Less time. Products

Javascript for validation

Suppose you have a form where you give it the taxon identifier and it display the record.

Taxon id:
Taxon identifiers are only integers. It would be nice to verify the input is an integer before having the browser connect to the server. NOTE: you must still validate the input on the server. People don't need to use your web page to send data to a server, and can send arbitrary values.

Browsers include a programming language called Javascript and an "event model" whereby Javascript can be called when certain events occur, like a form submission. Here's an example

Taxon id:
and the HTML which defines it.
<form method="GET" action="lookup_taxid"
    onsubmit="javascript:alert('sending form')">
 Taxon id: <input type="text" size="7" name="tax_id"></input>
</form>
When the 'onsubmit' event occurs, it calls the javascript code "alert('sending form'). The alert function pops up the alert box.

The javascript can abort the submission call by having the 'onsubmit' handler return 'false'. (The default returns true.)

Taxon id:
<form method="GET" action="lookup_taxid"
    onsubmit="javascript:alert('access denied'); return false;">
 Taxon id: <input type="text" size="7" name="tax_id"></input>
</form>

Instead of an alert box I can ask a simple question with a yes (returns true) or no (returns false) answer.

Taxon id:
<form method="GET" action="lookup_taxid"
    onsubmit="javascript:return confirm('Send the query?');">
 Taxon id: <input type="text" size="7" name="tax_id"></input>
</form>

Instead of putting the code in the onsubmit string I can have it call a Javascript function which I'll define inside of a "script" element:

Taxon id:
<script type="text/javascript">
var deny = function() {
  alert("I'm not going to let you do that.");
  return false;
}
</script>
<form method="GET" action="lookup_taxid"
    onsubmit="javascript:return deny();">
 Taxon id: <input type="text" size="7" name="tax_id"></input>
</form>

Suppose I want to deny the request and in the alert box show the tax id being denied. The browser loads the HTML is into a DOM tree. The tree is available to Javascript through the variable 'document'. It has a method named 'getElementById' which returns the node in the tree where the HTML has the matching 'id' attribute. Each element in your document must have a unique identifier.

What I'll do is give the 'id' of "tax" to the input element in the HTML and in my function get that element as an object named "tax". HTML Input objects in the DOM have a property called "value" which is the current value of the input

Taxon id:
<script type="text/javascript">
var deny_taxid = function() {
  var tax = document.getElementById("tax");
  alert("Not going to let you see tax_id " + tax.value);
  return false;
}
</script>
<form method="GET" action="lookup_taxid"
    onsubmit="javascript:return deny_taxid();">
 Taxon id: <input type="text" size="7" name="tax_id" id="tax"></input>
</form>

I'll allow fetching 9606 but nothing else.

Taxon id:
<script type="text/javascript">
var human_only = function() {
  var tax = document.getElementById("tax2");
  if (tax.value == "9606") {
    alert("I'll let you see the human record.");
    return true;
  } else {
    alert("Not going to let you view that one.");
    return false;
  }
}
</script>
<form method="GET" action="lookup_taxid"
    onsubmit="javascript:return human_only();">
 Taxon id: <input type="text" size="7" name="tax_id" id="tax2"></input>
</form>

The input elements's value can be changed in Javascript so I'll always change it to 9606 then allow the form submission to continue

Taxon id:
<script type="text/javascript">
var only_submit_human = function() {
  var tax = document.getElementById("tax3");
  if (tax.value != "9606") {
    alert("Changing taxon to 9606");
    tax.value = "9606";
  }
  return true;
}
</script>
<form method="GET" action="lookup_taxid"
    onsubmit="javascript:return only_submit_human();">
 Taxon id: <input type="text" size="7" name="tax_id" id="tax3"></input>
</form>

What I really want to do is make sure the input is an integer. I can use a Javascript function for that called 'parseInt', which turns a string into an integer. If the first part of the string isn't an integer then it returns a special value to indicate "not a number".

Taxon id:
<script type="text/javascript">
var validate_int_taxid = function() {
  var tax = document.getElementById("tax4");
  var tax_id = parseInt(tax.value);
  if (isNaN(tax_id)) {
    alert("taxon id must be an integer");
    return false;
  }
  return true;
}
</script>

<form method="GET" action="lookup_taxid"
    onsubmit="javascript:return validate_int_taxid();">
 Taxon id: <input type="text" size="7" name="tax_id" id="tax4"></input>
</form>

A problem with parseString is it only looks for leading digits. An input like "2xyz" will be converted into 2, and the "xyz" omitted. As a fix I'll convert the value into an integer then back to a string. I'll set the original element's value to the newly created string.

Taxon id:
<script type="text/javascript">
var validate_int_taxid2 = function() {
  var tax = document.getElementById("tax5");
  var tax_id = parseInt(tax.value);
  if (isNaN(tax_id)) {
    alert("taxon id must be an integer");
    return false;
  }
  /* parseInt ignores anything after the leading number */
  /* Convert back to guarantee the input contains only digits */
  tax.value = tax_id.toString();
  return true;
}
</script>

<form method="GET" action="lookup_taxid"
    onsubmit="javascript:return validate_int_taxid2();">
Taxon id: <input type="text" size="7" name="tax_id" id="tax5"></input>
</form>

If I want to I can make the function a bit more generic, so it can be used by multiple elements in the form. I'll pass in the element identifier to use to get the element and a description to use when showing the error:

Taxon id:
<script type="text/javascript">
var validate_int = function(id, description) {
  var ele = document.getElementById(id);
  var value = parseInt(ele.value);
  if (isNaN(value)) {
    alert(description + " must be an integer");
    return false;
  }
  /* parseInt ignores anything after the leading number */
  /* Convert back to guarantee the input contains only digits */
  ele.value = value.toString();
  return true;
}
</script>

<form method="GET" action="lookup_taxid"
    onsubmit="javascript:return validate_int('tax6', 'taxon id');">
Taxon id: <input type="text" size="7" name="tax_id" id="tax6"></input><br />
</form>

Here's how to use it with more than one element. The '&&' means "and" in Javascript.

Taxon id:
Genetic code:
<form method="GET" action="lookup_taxid"
    onsubmit="javascript:return (validate_int('tax7', 'taxon id') && validate_int('gc7', 'genetic code'));">
Taxon id: <input type="text" size="7" name="tax_id" id="tax7"></input><br />
Genetic code: <input type="text" size="7" name="gc" id="gc7"></input><br />
<button type="submit"<Find>/button>
</form>

In addition to the "onsubmit" element there is an "onchange" element which is called when mouse focus leaves an element and the element's value changed while it had the focus:

Taxon id:
Genetic code:
<form method="GET" action="lookup_taxid"
    onsubmit="javascript:return (validate_int('tax8', 'taxon id') && validate_int('gc8', 'genetic code'));">
Taxon id: <input type="text" size="7" name="tax_id" id="tax8"
    onchange="javascript:validate_int('tax8', 'taxon id')"></input><br />
Genetic code: <input type="text" size="7" name="gc" id="gc8"
    onchange="javascript:validate_int('gc8', 'genetic code)"></input><br />
</form>
I still do validation the "onsubmit" because I can submit the form without focus leaving an element.

MochiKit

Different browsers implement Javascript in different ways and use different event models. Javascript as a language isn't bad but isn't great. MochiKit is one (of many) libraries to make it easier to program in JavaScript. I'm going to use MochiKit for the rest of my examples.

MochiKit works as an external javascript library loaded through the "src" attribute of the script element in the HTML. Mochikit comes with TurboGears so to reference it from a TurboGears page add the following to your HTML

<script type="text/javascript" src="http://localhost:8080/tg_static/js/MochiKit.js"></script>
When I wrote this I pointed my browser to an HTML file and didn't go through a server. I instead used a file URL to point to MochiKit on my filesystem, as
<script type="text/javascript" src="file:///Library/Frameworks/Python.framework/Versions/2.4/lib/python2.4/site-packages/TurboGears-1.1a0-py2.4.egg/turbogears/static/js/MochiKit.js"></script>
and for my web site I'm using the version on the mochikit.org page
<script type="text/javascript" src="http://mochikit.org/MochiKit/MochiKit.js"></script>

Mochikit handles events a different (and better) way than standard Javascript. Javascript only allows one listener (handler) per event while MochiKit allows many. MochiKit also separates the document from the listeners so the HTML doesn't include a bunch of code saying "onEvent". It uses a "connect" function to connect an element to a listener function when a given event occurs. Here's an example:

Taxon id:
<script type="text/javascript">
var validate_int_mochikit = function (event) {
  var ele = event.src();
  var value = parseInt(ele.value);
  if (isNaN(value)) {
    alert("must be an integer");
    return false;
  }
  ele.value = value.toString();
  return true;
}
</script>

<form method="GET" action="lookup_taxid">
Taxon id: <input type="text" size="7" name="tax_id" id="tax9"></input>
</form>
<script type="text/javascript">
connect("tax9", "onchange", validate_int_mochikit);
</script>

When the "tax9" element sends an "onchange" event, MochiKit calls the validate_int_mochikit with the event information as the single parameter. The event contains a "src" method which is the DOM element which generated the event.

Showing an alert box is annoying and frustrating to users. Don't use it. Instead, try something less instrusive. Javascript can modify the DOM. I'll insert the text "must be an integer" next to the text entry box if the form elements value is not an integer. Because I don't like the existing test for non-integer input I'll change the code to use a regular expression instead.

Taxon id:
<script type="text/javascript">

/* match digits only */
var int_pattern = /^\d+$/;

var report_problems = function (event) {
  var tax = $("tax10");
  var tax_error = $("tax_error10");
  if (!int_pattern.test(tax.value)) {
    replaceChildNodes(tax_error, "must be an integer");
  } else {
    /* clear any error message which might be present */
    replaceChildNodes(tax_error, "");
  }
}
</script>

<form method="GET" action="lookup_taxid">
Taxon id: <input type="text" size="7" name="tax_id" id="tax10"></input>
<span style="color: red" id="tax_error10"></span>
</form>

<script type="text/javascript">
connect("tax10", "onchange", report_problems);
</script>
I think this is a more helpful and less annoying solution. There's still the problem that it only gets called when the element looses focus. There's a "onkeypress" event for every key press so what about doing the check then? Even better, you can tell MochiKit to stop processing a event. Stopping a key event means the input element won't be changed. I'll listen to the key presses and stop everything which isn't a digit.

The key() method returns information about the key pressed, and its "string" attribute is a string representation of the key. (Press "0" and the string will be "0".) I'll use the "indexOf" method to see if the input string is in the list of allowed characters. If it isn't, indexOf returns -1 so I'll stop the event.

Taxon id:
<script type="text/javascript">
var int_keys_only = function (event) {
  var c = event.key().string;
  if ("0123456789".indexOf(c) == -1) {
    /* unknown key pressed; ignore it */
    event.stop();
  }
}
</script>

<form method="GET" action="lookup_taxid">
Taxon id: <input type="text" size="7" name="tax_id" id="tax11"></input>
</form>
<script type="text/javascript">
connect("tax11", "onkeypress", int_keys_only);
</script>



Copyright © 2001-2020 Andrew Dalke Scientific AB