logo

Tracking changes to a form

About a month ago I learnt of an IE specific event, called "onBeforeUnLoad", that allows you to capture the moment a user leaves a page. Using this, I wrote an article that described how to remind a user to save if they leave a page that happens to be a document in edit mode.

What this article overlooked was that even if a form is in edit mode it may not necessarily need saving as it may not have actually been altered. Wait though, Bob Mattler has come up with a solution. We can now loop through all the fields on the form and compare them to the original values. If any fields have changed then we can use the onBeforeUnload event to remind the user to save. Otherwise there is no need to confuse them with this method.

How did Bob do it? Well, his method makes use of the Document Object Model (DOM), with which we can check whether an input's current value is the same as its original value. Consider the following table. This shows us the way we can access the current and original value for each of the different inputs types:

Input Type Current Value Original Value
text obj.value obj.defaultValue
textarea obj.value obj.defaultValue
select-one obj.options[x].selected obj.options[x].defaultSelected
select-multiple obj.options[x].selected obj.options[x].defaultSelected
check-box obj.checked obj.defaultChecked
radio-button obj.checked obj.defaultChecked


Let's demonstrate using the form below. Click on the "Check" button first and see that is says "Unchanged". Now make some changes and press it again, it should say "Changed". Press the "Reset" button to send the values back to their defaults and you can do it again...

Type Field
text:
text:
radio-group: 1
2
3
check-box: Mail Me
Please Reply
select-one:
select-multi:
textarea:
 


"So!", you're saying "where's the code?". Well, it's here:

function isFormChanged() {
var rtnVal = false;
var frm = document.forms[0];
var ele = frm.elements;

for ( i=0; i < ele.length; i++ ) {
if ( ele[i].type.length > 0 ) {
if ( isElementChanged( ele, i ) ) {
rtnVal = true;
break;
}
}
}
return rtnVal;
}

function isElementChanged( ele, i ) {
var isEleChanged = false;

switch ( ele[i].type ) {

case "text" :
if ( ele[i].value != ele[i].defaultValue ) return true;
break;

case "textarea" :
if ( ele[i].value != ele[i].defaultValue ) return true;
break;

case "radio" :
val = "";
if ( ele[i].checked != ele[i].defaultChecked ) return true;
break;

case "select-one" :
for ( var x =0 ; x <ele[i].length; x++ ) {
if ( ele[i].options[ x ].selected
!= ele[i].options[ x ].defaultSelected )
return true;
}
break;

case "select-multiple" :
for ( var x =0 ; x <ele[i].length; x++ ) {
if ( ele[i].options[ x ].selected
!= ele[i].options[ x ].defaultSelected )
return true;
}
break;

case "checkbox" :
if ( ele[i].checked != ele[i].defaultChecked ) return true;

default:
return false;
break;
}
}

Basically this code does a loop through every element on the form and checks for a change. When it finds a change it returns true, otherwise it returns false.

The final step is to call the function. We did it here from a button using the following line of code in its onclick event:

alert( (isFormChanged()) ? 'Changed':'Unchanged');

You can download a sample html file from the "Document Detail" table on the left of this page. This demonstrates this method being used in conjunction with the onBeforeUnload event (IE Only!). This form contains all the source code you will need ;-)

About the author: Bob Mattler has been a Domino developer for 7 years and currently works for AngleBar.com



Note: This method checks EVERY field on the form. It will often be the case you don't want to do this. In that case modify the "isFormChanged()" function to accept an array of field objects and loop through these rather than "frm.elements".

Note: Any changes made to the form in the onLoad event using JavaScript will be considered a change as the value will no longer be the "default".

Feedback

  1. Doesn't work properly on textareas in IE4

    Jake

    I tried this code, it works fine on IE5, but on IE4 when the textarea has a carriage return in it thinks the field has changed and returns a true. Any way to get around this problem ?

    Thanks Patrick

  2. This opens for a generic 'history of changes'

    Just for the record --- this would tracking changes to a document (tracking changes to a form would be an issue for designers to keep track of the design changes - interesting as well). I have sort of been looking for stuff like this since I always had my generic "history of changes" function (stores the changes made to any of the fields on a document in a history-field) which works nicely on the Notes client (querySave), but I had problems getting this to work from the web. Using the mentioned event will finally make this possible from the web as well. Thanks!

    << Herbert http://van.vliet.net

    1. Re: This opens for a generic 'history of changes'

      You just identified yourself as a Notes developer:

      A form on the web is different from a form in Notes and therefor the title is correct.

      --E<:|

  3. Form Checker

    Cheers for that Jake (and Bob). Worked lovely. Just got to do that array thing now...

    Matty

  4. You can also use onChange events.

    I can see value in the pre-unload event (wish for something cross-browser, though), but I'd prefer to use onchange handlers to the storage/comparison method being proposed.

    From the onload event, you can sweep through the form controls, placing any existing onchange handlers in a temponchange method for the control. Set the new handler to a function that will first try to run a temp handler (if it exists), and then runs any other handler code, such as setting a hidden form input called Change to true, or something like that.

    The only benefit I can see of the storage/comparison method is that it will not report a change if you alter a field value and then set it back to its original value (using onchange's will report this as a change - actually two of them).

    On the other hand, you don't have to ensure that your initial storage sweep happens before any intialization massaging of the data, etc.

    It seems a bit cleaner. Probably comes down to personal preference, though.

      • avatar
      • Matti Lattu
      • Wed 27 Oct 2004

      Re: You can also use onChange events.

      Hi!

      I have a problem with this: it doesn´t work with keyword fields which has "refresh field on keyword change" option checked.

      Any suggestions?

      Matti

  5. Password fields

    You can also check password fields by specifying "password" in the CASE statement.

  6. A few suggestions

    1. The text and text-area switch can be combined with an || 2. The for loop in select-one can be removed by just testing defaultSelected on the selected option (i.e. the option with index ele[i].selectedIndex). If the defaultSelected of this option is true then the value hasn't changed.

    Finally you can get round the problem with values set in the onLoad possibly erroneously being considered as changed. In the onLoad event, just set the defaultValue/defaultSelected property to match the value you are setting.

    Cheers,

    Ashley

    1. Re: A few suggestions

      I must apologise that my suggestion for having multiple values on a single case statement separated by || doesn't work. I tried several other combinations with commas etc but it appears that you need one entry for each type. I thought it did work when I made the suggestion only because I forgot to delete the text-area case statement when I tested it. Humble pie for dinner I think!

    • avatar
    • VV
    • Sat 22 May 2004

    Small problem

    Hi,This code to track the fields on the form on the web has impressed me a lot and i have used it to solve a problem i had, but what i found was when i refresh the browser window(i am using IE 6.0), what happens is that the javascript function is called even though the fields are not modified,

    How do i solve this problem, Help on this would be greatly appreciated... Thanks Vv..

      • avatar
      • Jake
      • Sat 22 May 2004

      Re: Small problem

      Hi VV,

      I've just tested the code in IE6 and it seems to work still.

      Are you saying that you've made no changes at all to any of the fields and just refreshing the page causes the prompt to appear? Strange.

      Is this in your own copy or on the html file attached to this article?

      Jake

      Show the rest of this thread

    1. Problem with spaces

      Hi,

      I have a text field with value '1 000' after, I don't change anything do the check and it says my text field is changed. If I print the defaultValue and the value it says for both '1 000'. If I check the characters the second char of the value is a space however the second char of the defaultValue isn't. Any ideas?

  7. just a little cleanup

    Useful scriptlet and discussion. Cleaned up the script a little...

    function isFormChanged(form) { var changed = false; if (!form) form = document.forms[0]; var fields = form.elements;

    for (var i = 0; !changed && i < fields.length; i++ ) changed = isFieldChanged(fields[i]);

    return changed; }

    function isFieldChanged(field) { var changed = false;

    switch (field.type) { case "text": case "textarea": case "password": changed = field.value != field.defaultValue; break; case "radio": case "checkbox": changed = field.checked != field.defaultChecked; break; case "select-one": case "select-multiple": for (var i = 0; !changed && i < field.length; i++) changed = field.options[i].selected != field.options[i].defaultSelected; break; } return changed; }

    1. Feedback on your feedback form

      Hi, you might want to enclose the feedback text in <pre></pre> tags. I'm testing to see if we can use the tags ourselves... if it works, the folloing code should be indented and the <pre></pre> tags should not be displayed...

      <pre> function foo() { var x = 0; for (var i = 0; i < 10; i++) x += i; return x; } </pre>

    • avatar
    • Campbeln
    • Thu 29 Jul 2004

    Updated Code!

    Hey All, <p> I have made some modifications to the code that I'd like to give back. I've cleaned up the code a bit, added way too many comments (as is my coding style) and tweaked a little bit of functionality around the select* processing. If the developer hasn't defined a default value, the code didn't function correclty. <p> If you have any questions, just let me know (search Google for 'campbeln' and you'll find me, I'm the Beastie Boy fan). Be sure to browse the code as the comments should explain the non-default value thing I added.

    http://opensource.campbeln.com/IsElementChanged.js

    <pre> //############################################################ //# Determines if the passed oElement has been changed by the user //# NOTE: This is a heavely modified version of the code at: http://codestore.net/store.nsf/unid/DOMM-4UTKE6?OpenDocument //############################################################ //# Last Updated: July 29, 2004 function isElementChanged(oElement) { //#### Determine the .toLowerCase'd .type of the passed oElement and process accordingly switch (oElement.type.toLowerCase()) { case "text": case "textarea": //#### If the oElement's .value differes from it's .defaultValue, return true if (oElement.value != oElement.defaultValue) { return true; } break;

    case "radio": case "checkbox": //#### If the oElement's .checked value differes from it's .defaultChecked value, return true if (oElement.checked != oElement.defaultChecked) { return true; } break;

    case "select-one": case "select-multiple": var i; var bDefaultValueSpecified = false;

    //#### Traverse oElement's .options to determine if the developer specified any as .defaultSelected for (i = 0; i < oElement.options.length; i++) { //#### If the current .option is set as .defaultSelected, flip bDefaultValueSpecified and set i so we fall from the loop if (oElement.options[i].defaultSelected) { bDefaultValueSpecified = true; i = oElement.options.length; } }

    //#### Traverse oElement's .options for (i = 0; i < oElement.options.length; i++) { //#### If the developer set some .defaultSelected .options if (bDefaultValueSpecified) { //#### If the oElement's .selected value differes from it's .defaultSelected value, return true if (oElement.options[i].selected != oElement.options[i].defaultSelected) { return true; } } //#### Else there are not any .defaultSelected .options set, so if the user has selected something other then the first .option, return true else if (rInput.options[j].selected && j != 0) { return true; } } break; }

    //#### If we make it here, the oElement has not changed, so return false return false; } </pre>

      • avatar
      • Campbeln
      • Thu 29 Jul 2004

      Updated Update!

      Mental note... read other comments first (I missed the "password" field).

      <pre> //############################################################ //# Determines if the passed oElement has been changed by the user //# NOTE: This is a heavely modified version of the code at: http://codestore.net/store.nsf/unid/DOMM-4UTKE6?OpenDocument //############################################################ //# Last Updated: July 29, 2004 function isElementChanged(oElement) { //#### Determine the .toLowerCase'd .type of the passed oElement and process accordingly switch (oElement.type.toLowerCase()) { case 'text': case 'textarea': case 'password': //#### If the oElement's .value differes from it's .defaultValue, return true if (oElement.value != oElement.defaultValue) { return true; } break;

      case 'radio': case 'checkbox': //#### If the oElement's .checked value differes from it's .defaultChecked value, return true if (oElement.checked != oElement.defaultChecked) { return true; } break;

      case 'select-one': case 'select-multiple': var i; var bDefaultValueSpecified = false;

      //#### Traverse oElement's .options to determine if the developer specified any as .defaultSelected for (i = 0; i < oElement.options.length; i++) { //#### If the current .option is set as .defaultSelected, flip bDefaultValueSpecified and set i so we fall from the loop if (oElement.options[i].defaultSelected) { bDefaultValueSpecified = true; i = oElement.options.length; } }

      //#### Traverse oElement's .options for (i = 0; i < oElement.options.length; i++) { //#### If the developer set some .defaultSelected .options if (bDefaultValueSpecified) { //#### If the oElement's .selected value differes from it's .defaultSelected value, return true if (oElement.options[i].selected != oElement.options[i].defaultSelected) { return true; } } //#### Else there are not any .defaultSelected .options set, so if the user has selected something other then the first .option, return true else if (rInput.options[j].selected && j != 0) { return true; } } break; }

      //#### If we make it here, the oElement has not changed, so return false return false; }

      </pre>

      Show the rest of this thread

    • avatar
    • David Schmidt
    • Fri 7 Jan 2005

    Will this work

    Will this work if you use "Refresh fields on keyword change"? It will update the defaultValue value.

    1. Re: Will this work

      Hi David,

      You are right. If you use "Refresh fields on keyword change", then 'select-one' and 'select-multiple' wont work.

      Any workarounds?

      Rgds Amit

      Show the rest of this thread

  8. Your example is not working

    I change values on this page's form and "Check" button always says "Unchanged". I'm using IE 7.0.1537.11

      • avatar
      • Jake Howlett
      • Mon 21 May 2007

      Re: Your example is not working

      Should work now Samir. Jake must remember never to mix design and data!

    • avatar
    • Ben
    • Mon 20 Aug 2007

    Fieldset Problems

    I had a problem dealing with a fieldset element on a form with this code. I needed to add to the isFormChanged() function:

    [code] if ( 'FIELDSET' != ele[i].tagName ) { [/code]

    This avoids submitting the fieldset element to the isElementChanged() function which could not handle a fieldset.

    1. help storing chaged values

      bah! what is the best method to pass the changed values to an SQL database? i'm a bit of a rookie - be gentle if you can!

      thanks.

      Show the rest of this thread

    2. Issue with hidden fields

      It works freat ! But I have an issue with hidden fields that I use in my form. I tried to have the same code as for TEXT but it doesn't track the changes... Any idea ? Thanks

    • avatar
    • kalim
    • Tue 25 Mar 2008

    I Love it

    This code really helps me a lot

    Thank you

      • avatar
      • dodi
      • Fri 6 Nov 2009

      <html>

      <head>

      <title>Script Demo Gops</title>

      <script language="JavaScript">

      var isChange = null;

      function CheckChange()

      {

      if (isChange)

      {

      alert("Value changed");

      }

      }

      window.onload=function(){

      var f=document.forms[0];

      for(var i=0;i<f.elements.length;i++){

      if(f.elements[i].getAttribute('onchange')==null){

      f.elements[i].onchange=function(){

      isChange=true;;

      }

      }

      }

      }

      </script>

      </head>

      <body>

      <form name="evt" onsubmit="return CheckChange();">

      <input type="text" value="abc"><br>

      <input type="text" value="def"><br>

      <input type="text" value="ghi"><br>

      <br><input type="reset" value="Reset"><input type="submit" value="Submit Form">

      </form>

      </body>

      </html>

  9. But you can't track the form in onsubmit. Please let me know if you have any idea about it.

  10. hi all,

    is there any way to save the changed values as well as the default value to a hidden field.

    If there is one please let me know.

    thanks.

Your Comments

Name:
E-mail:
(optional)
Website:
(optional)
Comment:



Navigate other articles in the category "JavaScript"

« Previous Article Next Article »
Modular Field Validation Script II   Debugging JavaScript in Netscape

About This Article

Author: Jake Howlett
Category: JavaScript
Hat Tip: Bob Mattler
Keywords: form; change; save;

Attachments

frmChng.htm (6 Kbytes)

Options

Feedback
Print Friendly

Let's Get Social


About This Website

CodeStore is all about web development. Concentrating on Lotus Domino, ASP.NET, Flex, SharePoint and all things internet.

Your host is Jake Howlett who runs his own web development company called Rockall Design and is always on the lookout for new and interesting work to do.

You can find me on Twitter and on Linked In.

Read more about this site »