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']") && !$(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']") && 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']") && 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']") && !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 && 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']") && obj.checked) { if (oldValues.get(obj.name)) { removeFromChangedValuesList(obj); } else { addToChangedValuesList(obj); } return; } else if ($(obj).is("input[@type='checkbox']") && !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 < 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 < 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.
<% String[] cList = request.getParameterValues("form1TrackList"); for (int i = 0; i < cList.length; i++) { out.println(cList[i]); } %>
I hope this plugin solves some of your problems, it definitely helped me out. Let me know of bugs, improvements, etc.
Recent Comments