Select/submit only changed form fields with jQuery
I'm looking for a way to submit only changed form fields to the server. So, let's say I have a form
<form>
<input type="te开发者_StackOverflow中文版xt" name="a"/>
<select name="b">...</select>
<input type="checkbox" name="c"/>
</form>
which is populated with certain data already. The user edits the form and clicks submit. If the user only changed input b, then I want to submit only input b. If only a and c were changed, I want to submit only a and c. And so on.
I could write something myself to accomplish this, but I am wondering maybe there is already something out there that I could use? Ideally, I would like the code to be short. Something like this would be perfect:
$('form').serialize('select-only-changed');
Also, I came across this http://code.google.com/p/jquery-form-observe/ , but I see there are issues with it. Is this plugin working solidly?
Another approach would be to serialize
the form when the page loads, and then on submit, only submit the changes.
$(function() {
var $form = $('form');
var startItems = convertSerializedArrayToHash($form.serializeArray());
$('form').submit() {
var currentItems = convertSerializedArrayToHash($form.serializeArray());
var itemsToSubmit = hashDiff( startItems, currentItems);
$.post($form.attr('action'), itemsToSubmit, etc.
}
});
Then, all you have to write is the hashDiff
function, which is straightforward and generally useful.
This is nice because it can easily be packaged into a plugin, and it can work repeatedly on the same form if you're using Ajax.
function hashDiff(h1, h2) {
var d = {};
for (k in h2) {
if (h1[k] !== h2[k]) d[k] = h2[k];
}
return d;
}
function convertSerializedArrayToHash(a) {
var r = {};
for (var i = 0;i<a.length;i++) {
r[a[i].name] = a[i].value;
}
return r;
}
Here's a minimal test:
describe('hashDiff()', function() {
it('should return {} for empty hash',function() {
expect(hashDiff({},{})).toEqual({});
});
it('should return {} for equivalent hashes',function() {
expect(hashDiff({a:1,b:2,c:3},{a:1,b:2,c:3})).toEqual({});
});
it('should return {} for empty hash',function() {
expect(hashDiff({a:1,b:2,c:3},{a:1,b:3,c:3})).toEqual({b:3});
});
});
Another option would be to mark the fields as disabled
before they are submitted. By default disabled
fields will not be serialized or submitted with a default form post.
Simple example:
function MarkAsChanged(){
$(this).addClass("changed");
}
$(":input").blur(MarkAsChanged).change(MarkAsChanged);
$("input[type=button]").click(function(){
$(":input:not(.changed)").attr("disabled", "disabled");
$("h1").text($("#test").serialize());
});
on jsfiddle.
You could add an 'oldvalue' parameter to the input field. Populate this value at the time the page is generated either with JavaScript or on the server-side.
<input name="field1" value="10" oldvalue="10">
Then use the following function to serialize:
function serializeForm() {
data = "";
$("input,textarea").each(function (index, obj) {
if ($(obj).val() != $(obj).attr("oldvalue")) {
data += "&" + $(obj).serialize();
}
});
return data.substr(1);
}
After the data has been sent to the server, your script could update the 'oldvalue' parameters to prevent the data from being sent again unless a further change is made.
The simplest solution would be to add something like:
$(function() {
$("input, select").change(function() {
$(this).addClass("changed");
});
});
Then just select on the .changed
class to get the elements that have been changed.
More information on the jQuery change
event: http://api.jquery.com/change/
As @Martin points out below, the change
event is only triggered for text inputs after they click off the input. If this is just to save some bandwidth, I would recommend binding on the click
event instead. You may get sent some fields that haven't actually changed, but probably better to air on the side of getting too much back than too little.
You could try adding a class to each field which has been changed and remove the others prior to calling $('form').serialize()
.
$(function() {
$(':input').change(function() {
$(this).addClass('changed');
});
$('form').submit(function () {
$('form').find(':input:not(.changed)').remove();
return true;
});
});
Though this solution is destructive and only works if you're not using AJAX (a solution exists even for AJAX but it gets even more complicated).
I may be missing something but I tried this and the hashDiff function returned an "undefined" error for the first form element it tried to process.
I implemented something a bit simpler which seems to work fine.
$('#submitChangesOnlyButton').click(function () {
var formAfterEdit = $('#myForm').serializeArray()
var itemsToSubmit = checkDiff(formBeforeEdit,formAfterEdit);
})
...
function checkDiff(before, after) {
var whatsChanged = [];
for (i = 0; i < before.length; i++) {
if (after[i].value !== before[i].value) {
whatsChanged.push(after[i]);
}
}
return whatsChanged;
}
just compare betwen current value and default value like this:
var toBeSubmited = new FormData();
for(var i in formObj)
if('value' in formObj[i] && formObj[i].value!=formObj[i].defaultValue){ //is an input or select or textarea
toBeSubmited.add(i, formObj[i].value);
}
//now send "toBeSubmited" form object
$.ajax(...)
My solution
$('form').each(function(){
var $form = $(this);
if (!$form.hasClass('send-all')){
var watchedFields = 'input:not([type="hidden"]):not([type="checkbox"]), select, textarea';
$form.find(watchedFields).addClass('watched').on('change', function() {
$(this).addClass('changed');
});
$form.submit(function(e){
var data = $form.serializeArray();
// Loop fields
for (i = 0; i < data.length; i++){
$field = $form.find('[name="' + data[i].name + '"]');
// Prevent send unchanged fields
if ($field.hasClass('watched') && !$field.hasClass('changed')) $field.prop('disabled', 'disabled');
// Send changed but set to readonly before
else $field.prop('readonly', 'readonly');
}
return true;
});
}
});
精彩评论