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...
"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".
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
Reply
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
Reply
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<:|
Reply
Form Checker
Cheers for that Jake (and Bob). Worked lovely. Just got to do that array thing now...
Matty
Reply
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.
Reply
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
Reply
Password fields
You can also check password fields by specifying "password" in the CASE statement.
Reply
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
Reply
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!
Reply
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..
Reply
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
Reply
Show the rest of this thread
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?
Reply
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; }
Reply
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>
Reply
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>
Reply
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>
Reply
Show the rest of this thread
Will this work
Will this work if you use "Refresh fields on keyword change"? It will update the defaultValue value.
Reply
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
Reply
Show the rest of this thread
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
Reply
Re: Your example is not working
Should work now Samir. Jake must remember never to mix design and data!
Reply
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.
Reply
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.
Reply
Show the rest of this thread
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
Reply
I Love it
This code really helps me a lot
Thank you
Reply
<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>
Reply
But you can't track the form in onsubmit. Please let me know if you have any idea about it.
Reply
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.
Reply