20

Using jquery I've added a change handler to a form. This works when any input is changed BUT only if the user manually changes an input and not when some other code changes the input.

Is there any way to detect if a form has changed even if its inputs are changed by code?

gdoron
  • 147,333
  • 58
  • 291
  • 367
kingsaul
  • 243
  • 1
  • 2
  • 7

7 Answers7

39

Yes, there seems to be some confusion over this. In an ideal world you would expect the onchange event to happen whenever the inputs change but thats not what happens. I'm sure for good reasons to - maybe not.

One way I've overcome this obstacle is to capture the form state into a variable just after displaying it and then just before submitting it to check if the state has changed and to act accordingly.

An easy state to store is what the serialize function returns. An easy place to store the state is using the data functionality. Both serialize and data are available with jquery.

Of course you can use other different forms of state (some form of hash) or storage for this state (standard global variable for example).

Here is some prototype code:

If your form id is 'xform' then you can call the following code when the form has displayed:

$('#xform').data('serialize',$('#xform').serialize());

And then, when you need to check, for example just before a button submit you can use:

if($('#xform').serialize()!=$('#xform').data('serialize')){
    // Form has changed!!!
}

You could wrap all this up into a copy & paste javascript snippet that will give you a formHasChanged() function to call wherever you need it (NOT TESTED):

$(function() {
    $('#xform').data('serialize',$('#xform').serialize());
});
function formHasChanged(){
    if($('#xform').serialize()!=$('#xform').data('serialize')){
        return(true);
    }
    return(false);
}

But I'll stop here otherwise I'll create yet another jquery plugin.

zaf
  • 22,776
  • 12
  • 65
  • 95
  • It works in a lot of cases but I'm in a situation where I have a couple of fields that are disabled in some situations and when it happen, they are not in the serialized value of the form and the system detect a change while comparing the two strings when there are no change. To work in this case, you must be sure to set the controls state before getting the result of serialize function. In any ways, this is a very nice solution! :) +1 – Samuel Jul 04 '14 at 19:36
  • This solution http://stackoverflow.com/questions/959670/generic-way-to-detect-if-html-form-is-edited is in my view much better because that handle disable fields. – Samuel Jul 04 '14 at 19:46
  • Avoid using jQuery in 2018+. A more performant algorithm independent of hash/serialization functions can be found in my answer below. – anthumchris Oct 01 '18 at 14:09
  • 3
    @AnthumChris Well, some may debate with you on that but I, for sure, thank you for the down vote. – zaf Oct 07 '18 at 08:31
  • For non-jQuery, refer to [`HTMLElement.dataSet`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset) (`.data()`, but native) and [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) passed to [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) (serialize, but native). Actual answer also down-voted. There [is no excuse to use Internet Explorer 6 these days](https://gist.github.com/amcgregor/d6ea618b772b52bb288cff72c8381640#jquery). Sorry, I mean jQuery. Because IE6 is what it compensated for. – amcgregor Jun 27 '23 at 18:56
10

Serializing the form is certainly an option, but it will not work if:

  • you want to know which fields have changed
  • it only needs to check a subset of the fields
  • dynamically adding or removing fields.

Fortunately, every form element has a default value associated with its object:

  • input, textarea : defaultValue
  • checkbox, radio : defaultChecked
  • select: defaultSelected

for ex: to ckeck if input or textarea has changed:

var changed = false;
$(":text,textarea").each(function(){
    changed = this.value != this.defaultValue;
    return !changed; // return if at least one control has changed value
});
Corneliu
  • 2,932
  • 1
  • 19
  • 22
  • **defaultValue** property seems to be obsolete: [link](https://developer.mozilla.org/en-US/docs/Archive/Mozilla/XUL/Property/defaultValue) – Heisenberg Dec 13 '19 at 12:12
5

This is easily achieved in JavaScript without jQuery. initChangeDetection() can be called multiple times:

function initChangeDetection(form) {
  Array.from(form).forEach(el => el.dataset.origValue = el.value);
}
function formHasChanges(form) {
  return Array.from(form).some(el => 'origValue' in el.dataset && el.dataset.origValue !== el.value);
}

Test on JS Bin


For older browsers that don't support newer arrow/array functions:
function initChangeDetection(form) {
  for (var i=0; i<form.length; i++) {
    var el = form[i];
    el.dataset.origValue = el.value;
  }
}
function formHasChanges(form) {
  for (var i=0; i<form.length; i++) {
    var el = form[i];
    if ('origValue' in el.dataset && el.dataset.origValue !== el.value) {
      return true;
    }
  }
  return false;
}
anthumchris
  • 8,245
  • 2
  • 28
  • 53
1

Not in a regular way.

You can change with input and then trigger the change event.

$('#inputId').val('foo').trigger('change');

or with this:

$('#inputId').val('foo').change();
gdoron
  • 147,333
  • 58
  • 291
  • 367
  • 2
    +1 for great answer. But who gave the downvote I think s/he don't know about `trigger('change')` or `change()` at all. – thecodeparadox Apr 25 '12 at 08:08
  • 2
    @thecodeparadox. too many fools have keyboard and mouse... :) – gdoron Apr 25 '12 at 08:09
  • but they don't know where to use them – thecodeparadox Apr 25 '12 at 08:25
  • I did not down vote, but I do not think the DOMAttrModified triggers when the value of an input field is changed by anything; value is a attribute and not a DOM node... http://stackoverflow.com/a/147011/295783 - also the OP wants to know if something changes the input and that could be a scanner or such which means he is not scripting the change anywhere – mplungjan Apr 25 '12 at 10:09
  • @mplungjan. I added the mutation event later. and anyway, you're correct, I'm deleting that part. – gdoron Apr 25 '12 at 10:11
0

Here is what i did (i found my solution using zaf's answer)

$("form").change(function() {
    $(this).data("changed","true");
});

$("input[type='submit']").click(function() {
    if($("form").data("changed") == "true") {
        var discard = confirm("Some unsaved changes. Discard them ?");
        if(!discard) return false;
    }
});
Xeltor
  • 4,626
  • 3
  • 24
  • 26
  • you could return `return confirm("...");` but i thought it would be harder to read that way – Xeltor Oct 23 '14 at 18:54
0

With vanilla javascript you can hash the values of the inputs.

You can hash the values of the form. I recommend just using the name of every input as key and the value as value. You then can use a query selector, to loop through each input of the form to get the hashes.

To check if values in the form changed you can then just compare the hashes of the forms. I store the initial hash in a data attribute of the form.

Here is one possible implementation:

function initFormHash(form) {
    form.setAttribute("data-initial-hash", getFormHash(form));
}

function getFormHash(form) {
    const formValues = {};

    form.querySelectorAll("input").forEach(input => {
        const inputType = input.getAttribute("type");
        const inputName = input.getAttribute("name");

        if (inputType === "hidden") return;
        if (!inputName) return;

        if (inputType === "checkbox" || inputType == "radio") {
            formValues[inputName] = input.checked;
            return;
        }

        formValues[inputName] = input.value;
    });

    form.querySelectorAll("textarea, select").forEach(textarea => {
        const name = textarea.getAttribute("name");

        if (!name) return;

        formValues[name] = textarea.value;
    })

    return JSON.stringify(formValues);
}

function formChanged(form) {
    return (form.getAttribute("data-initial-hash") !== getFormHash(form));
}
Hellow2
  • 33
  • 8
-1

Try onchange attribute According to W3c it should trigger anytime the content of an element, the selection, or the checked state have changed.

Stefan Rogin
  • 1,499
  • 3
  • 25
  • 41