简体   繁体   中英

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="text" 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.

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;
}

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).

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;
        });
    }
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM