jQuery: Track changes in your forms

scripts

Update: This plugin has been updated. Please follow this link for the updated plugin information, demo and download.

While working on an assignment, I came up with this small, useful plugin which would allow you to track changes in your forms. The idea was to ensure that a user can only save changes, if he or she has made any. Once a form is loaded, with the previously saved values or the default values, any changes made to the form fields will become part of a list which will be passed on when the form is submitted.

The basic usage is as follows:

On document ready, add “trackChanges” to the form you want to be tracked:

    // only events
    var oldvals1 = $('#form1').trackChanges({events: "change blur keypress keydown click"});
 
    // all the options
    var oldvals3 = $('#form3').trackChanges({
          changeListName: "form3List",  // changed field names will be in this list (if not given defaults to {formname}TrackList)
          events: "change blur keypress keydown click",  // events on which the tracking should occur
          changeListVisible: true, // should the change list be visible
          changeListClass: "custom" // css class applied to the change list
    });

You can download an experimental/alpha version of the plugin here. I have tested it on Firefox 2/3 and Opera 9.5+ only. So all the IE users let me know if you find any bugs (I am sure there will be ;)).

You can check out a dirty demo here.

Let’s disect the plugin then, and since this is my first jQuery plugin, you may as well treat this as a small how-to-make-a-jquery-plugin tutorial.

Objective: Track changes made to all the fields/elements which are part of a form. Make sure that when the form is submitted, a list of changed field names is sent to the server.

To begin, let us consider a page with a form in it:

<form id="form1" style="border: 1px solid gray; padding: 20px;" method="post">
<input id="textf1a" name="textf1" type="text" value="Text1" />
<input id="textf2a" name="textf2" type="text" value="Text2" />
<input id="textf3a" name="textf3" type="text" value="Text3" />
<input id="textf4a" name="textf4" type="text" value="Text4" />
<input id="rd1a" name="rd" type="radio" value="1" /> rd1
<input id="rd2a" name="rd" type="radio" value="2" /> rd2
<input id="c1a" name="check1" type="checkbox" value="OFF" />
<select id="country1a" name="country" size="1">
     <option>India</option>
     <option>USA</option>
</select>
<input id="submitButtona" name="buttonSubmita" type="submit" value="Submit" />
</form>

Now, as we see here the form is pre-populated with some values. These values constitute the “old values”. When the form is loaded and if its marked to be tracked, all these field values will be saved into a JavaScript variable. Let us first define the variable which will hold these values:

    // container which holds the initial values
    var oldValues = {
        set: function(key, val) {
            this[key] = val;
        },
        get: function(key) {
            return this[key];
        }
    };

This pretty much works like a map. We aren’t really bothered about maintaining unique keys, etc as we should really never have a scenario where duplicates get into the list.

Now let us load up the object with the form’s field values. Additionally, we will bind the fields with the events on which tracking will take place.

    // here we read the original values and populate the oldValues object
    this.each(function() { // start a loop for each element that is matched
        $(this).find("input").each(function(count, obj) { // find all input tags
            // check if the input tag is not a radio button or a checkbox
            if (!$(obj).is("input[@type='radio']") &amp;&amp; !$(obj).is("input[@type='checkbox']")) {
                oldValues.set(obj.name, obj.value);
            } else {
                // set the radio button value if it is checked
                if ($(obj).is("input[@type='radio']") &amp;&amp; obj.checked) {
                    oldValues.set(obj.name, obj.value);
                }
                // set the value to true if its a checkbox and is checked
                else if ($(obj).is("input[@type='checkbox']") &amp;&amp; obj.checked) {
                    oldValues.set(obj.name, true);
                }
                // set the value to false if its a checkbox and is unchecked
                else if ($(obj).is("input[@type='checkbox']") &amp;&amp; !obj.checked) {
                    oldValues.set(obj.name, false);
                }
            }
 
            // bind the events to the element
            $(obj).bind(options.events, function() {
                addToChangesList(obj);
            });
 
        });
 
        // do the same as above, but for select elements
        $(this).find("select").each(function(count, obj) {
            // don't save the value if its the hidden changelist itself
            if (obj.name == cName) {
                return;
            }
 
            // if the select element has no value set for an option,
            // set the option text as the value
            if (obj.value &amp;&amp; obj.value == "") {
                oldValues.set(obj.name, obj.options[obj.selectedIndex].text);
            } else {
                oldValues.set(obj.name, obj.value);
            }
 
            // bind the events to the element
            $(obj).bind(options.events, function() {
                addToChangesList(obj);
            });
        });
    });

Okay, so let us now create the list which will hold the name of the changed fields.

    // this is the list which is appended at the very bottom of the form
    var cName = options.changeListName;
    var displayProp = "display: none;";
    if (options.changeListVisible) {
        displayProp = "display: block;";
    }
    var changesList = '
<select id="'+ cName +'" class="' + options.changeListClass + '" style="' + displayProp + '" multiple="multiple" name="'+ cName +'"></select>
 
';
    this.append(changesList);

Since the list is appended inside the form itself, it is submitted along with it. Now lets tackle the addition and removal of a field name from this list as and when a change takes place or a change is reverted back.

    // checks if the value has changed and accordingly
    // adds or removes the element name from the list
    function addToChangesList(obj) {
        if ($(obj).is("input[@type='checkbox']") &amp;&amp; obj.checked) {
            if (oldValues.get(obj.name)) {
                removeFromChangedValuesList(obj);
            } else {
                addToChangedValuesList(obj);
            }
            return;
        } else if ($(obj).is("input[@type='checkbox']") &amp;&amp; !obj.checked) {
            if (!oldValues.get(obj.name)) {
                removeFromChangedValuesList(obj);
            } else {
                addToChangedValuesList(obj);
            }
            return;
        }
        if (oldValues.get(obj.name) != obj.value) {
            if(!$(obj).is("input[@type='radio']")) {
                addToChangedValuesList(obj);
            } else {
                if (obj.checked) {
                    addToChangedValuesList(obj);
                }
            }
        } else {
            removeFromChangedValuesList(obj);
        }
    };

I will probably simplify/optimize the code here as and when I get time. Following are the methods to add and remove from the list:

    // removes an element from the list
    function removeFromChangedValuesList(obj) {
        var cvs = $(obj).parent("form").find("#" + cName)[0];
        if (!cvs) {
            return;
        }
 
        len = cvs.length;
        for (i = 0; i &lt; len; i++) {
            if (obj.name == cvs.options[i].value) {
                cvs.options[i] = null;
                return;
            }
        }
    }
 
    // adds an element to the list
    function addToChangedValuesList(obj) {
        var cvs = $(obj).parent().find("#" + cName)[0];
        if (!cvs) {
            return;
        }
 
        len = cvs.length;
        if (len == 0) {
            cvs.options[len] = new Option(obj.name, obj.name, true);
            cvs.options[len].selected = true;
        } else {
            for (i = 0; i &lt; len; i++) {
                if (obj.name == cvs.options[i].value) {
                    return;
                }
            }
            cvs.options[len] = new Option(obj.name, obj.name, true);
            cvs.options[len].selected = true;
        }
    }

I return the old values object in the response, just in case if the user wants to do some manual checks.

    // we return the old values object, such that the user has access to the initial values
    // the change list is available in the form as an element
    // the new values are obviously with the user in the form
    return oldValues;

As you might have guessed, since we are sending a select list with multiple values, and each value is selected by default; on the server side all we need to do is retrieve the change list is:

The following code snippet is in Java/JSP.

    &lt;%
	String[] cList = request.getParameterValues("form1TrackList");
	for (int i = 0; i &lt; cList.length; i++) {
		out.println(cList[i]);
	}
    %&gt;

I hope this plugin solves some of your problems, it definitely helped me out. Let me know of bugs, improvements, etc. :)

4 Responses to “jQuery: Track changes in your forms”

  1. Camson Says:
    September 3rd, 2008 at 1:38 am

    Hi there,
    I just wanted to let you know your demo is not working with IE 7.
    http://zhandwa.com/downloads/trackchanges/demo/

    Camson

  2. zhandwa Says:
    September 3rd, 2008 at 2:21 pm

    Thanks Camson. I will check whats going wrong. :)

  3. Camson Says:
    September 30th, 2008 at 6:30 pm

    Sorry I was not be specific what was not working.
    it is the selection box that is not working in Form 2

  4. Surajeet Says:
    December 24th, 2008 at 10:21 pm

    Hi..any solution for not working in IE. I want to use this code for my requirement. If you resolve this solution it would be really great for me. Thanks in advance.

Leave a Reply

Icons by N.Design Studio. Designed By Ben Swift. Powered by WordPress and Free WordPress Themes
Entries RSS Comments RSS Log in
Site design and layout modified by me.