How to Write a Generic Form Update Detection Function in JavaScript
In my last post we discovered how to check whether individual form elements had been changed by the user. Today, we’ll use that information to write JavaScript code which can detect updates to any form.
Here are the examples and code links:
Our Prerequisites
Since we’re all good developers, we’ll define our requirements before cutting any code:
- We’ll write a function, FormChanges(), which accepts a single overloaded form argument — either the form’s DOM node or the string ID.
- The function will return an array of form element nodes which the user has changed. This allows us to determine which fields have changed or, if the array is empty, that no fields have changed.
- The function will return NULL if no form could be found.
- We won’t depend on any specific JavaScript library so the function remains compatible with all of them.
- It must work in all modern browsers — and IE6 or IE7.
The FormChanges() Function
To ease you in gently, here’s the start of our function:
function FormChanges(form) {
We’re overloading the form argument — it can be a DOM element but, if it’s an ID string, we need to locate that element in the DOM:
if (typeof form == "string") form = document.getElementById(form);
If we don’t have a form node, the function will return null without doing any further work:
if (!form || !form.nodeName || form.nodeName.toLowerCase() != "form") return null;
We’ll now declare variables which we’ll use throughout the function:
- ‘changed’ is the returned array of form elements which have been updated by the user
- ‘n’ is a form element node
- ‘c’ is set to true if an element has been changed
- ‘def’ is the default option for select boxes
- ‘o’, ‘ol’, and ‘opt’ are temporary variables used within loops
var changed = [], n, c, def, o, ol, opt;
We can now start our main loop which examines every form element in turn. c is initially set to false indicating that no changes have been made to the element we’re inspecting:
for (var e = 0, el = form.elements.length; e < el; e++) {
n = form.elements[e];
c = false;
Next, we’ll extract the node name (input, textarea, select) and examine it within a switch statement. We’re only looking for select and non-select nodes, so a switch statement isn’t strictly necessary. However, it is easier to read and allows us to add further node types when they are introduced.
Note that most browsers return the node name in uppercase but we’re playing it safe and always converting the string to lowercase.
switch (n.nodeName.toLowerCase()) {
The first case statement evaluates select
drop-downs. This is the most complex check since we must loop through all child option
elements to compare the selected and defaultSelected properties.
The loop also sets def to the last option with a ‘selected’ attribute. If we have a single-choice box, def is then compared against that node’s selectedIndex property to ensure we’re handling situations where no option
or more than one option
element has a ‘selected’ attribute (refer to the previous article for a full explanation).
// select boxes
case "select":
def = 0;
for (o = 0, ol = n.options.length; o < ol; o++) {
opt = n.options[o];
c = c || (opt.selected != opt.defaultSelected);
if (opt.defaultSelected) def = o;
}
if (c && !n.multiple) c = (def != n.selectedIndex);
break;
We now need to handle input
and textarea
elements. Note that our case "textarea":
statement does not use a break so it falls through into the case "input":
code.
Checkboxes and radio elements have their checked and defaultChecked properties compared while all other types have their value compared with the defaultValue:
// input / textarea
case "textarea":
case "input":
switch (n.type.toLowerCase()) {
case "checkbox":
case "radio":
// checkbox / radio
c = (n.checked != n.defaultChecked);
break;
default:
// standard values
c = (n.value != n.defaultValue);
break;
}
break;
}
If the value of c is true, the element has changed so we append it to the changed array. The loop is now complete:
if (c) changed.push(n);
}
We just need to return the changed array and end the function:
return changed;
}
Example Uses
Assume we’ve created the following form:
<form id="myform" action="index.html" method="post">
<fieldset>
<legend>Your profile</legend>
<input type="hidden" id="changed" name="changed" value="yes" />
<div>
<label for="name">name:</label>
<input type="text" id="name" name="name" value="Jonny Dough" />
</div>
<div>
<label for="job">job title:</label>
<select id="job" name="job">
<option>web designer</option>
<option selected="selected">web developer</option>
<option>graphic artist</option>
<option>IT professional</option>
<option>other</option>
</select>
</div>
<div>
<button type="submit">Update Profile</button>
</div>
</fieldset>
</form>
We can check whether the user has changed any form fields using code such as:
var changed = FormChanges("myform");
alert(changed.length + " field(s) have been updated.");
Or, if no changes have occurred, we could update the hidden “changed” value to “no” when the form is submitted. This would allow the server-side code to skip field validation and database updates:
var form = document.getElementById("myform");
form.onsubmit = function() {
if (FormChanges(form).length == 0) {
document.getElementById("changed").value = "no";
}
return true;
}
(Note: changing “yes” to “no” degrades gracefully since, if JavaScript isn’t available, the server will always process the incoming data.)
I hope you find it useful.