How to Check That an HTML Form Has Been Changed

Forms. Your HTML web application would fail without them — they’re the basis of most user data transmissions between the browser and server. You’ve no doubt read many articles describing the form tags and data validation with HTML5 or JavaScript. However, today we’ll discuss how to check whether the user has changed form data.

Why Check for Form Updates?

There are many reasons why you might want to check whether a form has changed. For example, if a user has updated one or more fields but clicks away from the page, you could display an “updates were not saved” warning. You could even give them the option to save the data (via Ajax). Alternatively, if no updates are made, your application’s efficiency could be improved by not attempting to validate or re-save the data on the server.

The JavaScript OnChange Event — and Why It Can’t Be Used

You can attach a JavaScript onchange event handler to any HTML form element. While that appears to be a viable method — and I’ve seen used elsewhere — there are a number of problems with the approach:

  • If the user changes a value then changes it back, the application will still think an update has occurred.
  • If a form value is updated using JavaScript, the onchange event handler won’t be fired.
  • Adding onchange handlers to every element on a large forms incurs a browser processing overhead.
  • If elements are added to or removed from the form you will need to attach and detach event handlers accordingly.
  • The onchange event for checkboxes and radio buttons do not work as expected in a certain browser. (I suspect you can guess which one!)
  • There’s a far easier method…

Comparing Default Values

Fortunately, we don’t need to go through the rigmarole of complex event handling. Every form element has a default value associated with its object, i.e., the data the form control showed when the page loaded. This can be checked against the current value to discover whether a change has been made.

Unfortunately, the default value properties differ between form element types…

Textual <input> and <textarea> Fields

Let’s start with the easy elements. All textarea and input tags which are not “checkbox” or “radio” types have a defaultValue property. We can compare this string against the current value to determine whether a change has occurred, e.g.:


<!-- name input -->
<input type="text" id="name" name="name" value="Jonny Dough" />

<script>
var name = document.getElementById("name");
if (name.value != name.defaultValue) alert("#name has changed");
</script>
note: New HTML5 input types

If you’re using HTML4 or XHTML, your text input types will either be “text,” “hidden,” “password” or “file.” The new types introduced in HTML5 also have a defaultValue property and can be inspected in the same way. This includes email, tel, url, range, date, color, and search.

Checkboxes and Radio Buttons

Checkboxes and radio buttons have a defaultChecked property. This will either be true or false and it can be compared against the element’s checked property, e.g.:


<!-- newsletter opt-in -->
<input type="checkbox" id="optin" name="optin" checked="checked" />

<script>
var optin = document.getElementById("optin");
if (optin.checked != optin.defaultChecked) alert("#optin has changed");
</script>

Note that checkboxes and radio buttons also have a defaultValue property, but it’s whatever was assigned to the value attribute — not the current state of the button.

Drop-down <select> Boxes

If you’re using a select box, it’ll most probably be one which allows the user to choose a single item from a drop-down list.

Here’s where it gets a little complicated. The select box itself does not provide a default value property, but we can inspect its (array-like) collection of option elements. When the page is loaded, the option with a ‘selected’ attribute has its defaultSelected property set to true.

We can retrieve the currently selected option’s index number from the select node’s selectedIndex property. Therefore, if that option has it’s defaultSelected set to false, the select box must have changed, e.g.:


<!-- job title select box -->
<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>

<script>
var job = document.getElementById("job");
if (!job.options[job.selectedIndex].defaultSelected) alert("#job has changed");
</script>

This code will work for any single-choice select box where one option has a ‘selected’ attribute. Unfortunately, there are a number of catches:

  • If no options have a ‘selected’ attribute, the browser will default to the first — but it’s defaultSelected property will be false.
  • If two or more options have a ‘selected’ attribute (illogical, but possible), all will have the defaultSelected property set to true, but the browser can only default to the last one.
  • A multiple select box allows the user to highlight any number of options:

<!-- skills multi-select box -->
<select id="skills" name="skills" multiple="multiple">
	<option selected="selected">HTML</option>
	<option selected="selected">CSS</option>
	<option selected="selected">JavaScript</option>
	<option>PHP</option>
</select>

Multiple-choice select boxes are not popular — probably because a series of checkboxes offers a more user-friendly interface. However, when they are used, more than one option can have its defaultSelected property set to true. The select node’s selectedIndex property is not valid so we must loop through each option in turn to discover whether its selected property matches the defaultSelected property.

The following code will check for changes to any select box no matter how the options are defined:


var
	skills = document.getElementById("skills"),
	c = false, def = 0, o, ol, opt;

for (o = 0, ol = n.options.length; o < ol; o++) {
	opt = skills.options[o];
	c = c || (opt.selected != opt.defaultSelected);
	if (opt.defaultSelected) def = o;
}
if (c && !skills.multiple) c = (def != skills.selectedIndex);

if (c) alert("#skills has changed");

That’s how you check whether any form element has changed.

But wouldn’t it be great if we had a generic, reusable JavaScript function which could detect changes to any form, worked in all browsers and didn’t require a chunky library? Keep an eye on SitePoint — it’ll be coming very soon!

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.

  • Jagadish

    Thanks fot the post. :)

  • Justin Noel

    Why not serialize all the form values (except a special hidden one) when the form loads or is saved. Then, each time you need to check if the form has changed, serialize it again and compare the previous serialized string against the current one?

    jQuery, MooTools, etc make this quite trivial. They each have methods for serializing forms for submission via AJAX.

    • http://www.optimalworks.net/ Craig Buckler

      That’s certainly an option, but what if you want to know which fields have changed? What if you only care about a subset of the fields? Or what if you’re adding or removing fields dynamically? You’d need to go through at least two serialization procedures (which process every form element) every time a field was added or removed.

      Besides, it’s possible to create a form update checking function which overcomes that issue, doesn’t need to be run on page load, is less than 1Kb (uncompressed), and doesn’t require a library so it remains compatible with all of them. It’ll be available on SitePoint early next week.

      • http://downeastinternet.com Jack

        Besides, it’s possible to create a form update checking function which overcomes that issue, doesn’t need to be run on page load, is less than 1Kb (uncompressed), and doesn’t require a library so it remains compatible with all of them. It’ll be available on SitePoint early next week.

        What is the URL of that script?

  • Wolf_22

    Craig, I noticed in a couple of your examples that you used “n.whatever” to target your element. Why ‘n’? Did you use this to save space in your code snippet or is there some logical context involved with it?

  • chicco

    Hello,
    in the first script n is name, isn’t? I Think there is a bug in ten code…

  • http://www.optimalworks.net/ Craig Buckler

    Sorry everyone — it appears that a couple of element names were accidentally modified. They’ve now been fixed.

  • simeonboxco

    Quite helpful. thanks for this post.

  • RationalRabbit

    So, where is this form update checking function you promised to make available last February?
    It is now October, and a search of SitePoint returns no such function. Nor do any of your available articles, although there are posts from you as recent as yesterday.

    What’s the story, Craig?

  • Glendel J. Fyne

    Hello Craig,

    I implemented an small function with the validations that you recommend on this post. I am not sure if it is the best way to implement those validations but I hope that this function helps to others to use what you describe on this post. Here is the function :

    var hasChanged = function( jObj, options ) {

    var hasChangedObj = false;

    if ( this instanceof jQuery ) {

    options = jObj;

    jObj = this;

    } else if ( !( jObj instanceof jQuery ) ) {

    jObj = jQuery( jObj );

    }

    jObj.each( function( item, element ) {

    var jElement = jQuery( element );

    if ( jElement.is( ‘form’ ) ) {

    hasChangedObj = hasChanged( jElement.find( ‘input, textarea, select’ ), options );

    if ( hasChangedObj ) { return( false ); }

    } else if ( jElement.is( ‘:checkbox’ ) || jElement.is( ‘:radio’ ) ) {

    if ( element.checked != element.defaultChecked ) {

    hasChangedObj = true;

    return( false );

    }

    } else if ( jElement.is( ‘input’ ) || jElement.is( ‘textarea’ ) ) {

    if ( element.value != element.defaultValue ) {

    hasChangedObj = true;

    return( false );

    }

    } else if ( jElement.is( ‘select’ ) ) {

    var option;

    var defaultSelectedIndex = 0;

    var numberOfOptions = element.options.length;

    for ( var i = 0; i < numberOfOptions; i++ ) {

    option = element.options[ i ];

    hasChangedObj = ( hasChangedObj || ( option.selected != option.defaultSelected ) );

    if ( option.defaultSelected ) { defaultSelectedIndex = i; }

    }

    if ( hasChangedObj && !element.multiple ) { hasChangedObj = ( defaultSelectedIndex != element.selectedIndex ); }

    if ( hasChangedObj ) { return( false ); }

    }

    } );

    return( hasChangedObj );

    };

    jQuery.fn.extend( {

    hasChanged : hasChanged

    } );

    The ways to use this function are :

    if ( jQuery( 'selector' ).hasChanged() ) { console.log( 'Objects have changed!' ); }

    if ( hasChanged( 'selector' ) ) { console.log( 'Objects have changed!' ); }

    if ( hasChanged( document.getElementById( 'id' ) ) ) { console.log( 'Object have changed!' ); }

    You can validate if a single field has changed, if several fields have changed, if a whole form has changed, its up to the selector and/or to the object that you use or pass as parameter.

    Note : It works with jQuery.