How to Write a Generic Form Update Detection Function in JavaScript

Tweet

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.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Andy Parkhill

    Hi Craig,
    Thanks for providing this solution. I have just checked the function against some forms, and it appears to fail to detect changes made in textareas. I’m using IE7 as a browser.
    Regards,
    Andy.

    • Andy Parkhill

      Craig,

      Apologies, after a little further digging, it appears that this textarea is actually using an IFRAME under the covers (it is a SharePoint 2010 page). So your function above is probably valid for typical HTML pages. Sorry!

      Regards,
      Andy.