detect value change in input tag with vanilla javascript and MutationObserver

To understand what is going on is necessary to clear up the difference between attribute (content attribute) and property (IDL attribute). I won't expand on this as in SO there are already excellent answers covering the topic:

  • Properties and Attributes in HTML
  • .prop() vs .attr()
  • What is happening behind .setAttribute vs .attribute=?

When you change the content of a input element, by typing in or by JS:

targetNode.value="foo";

the browser updates the value property but not the value attribute (which reflects the defaultValue property instead).

Then, if we look at the spec of MutationObserver, we will see that attributes is one of the object members that can be used. So if you explicitly set the value attribute:

targetNode.setAttribute("value", "foo");

MutationObserver will notify an attribute modification. But there is nothing like properties in the list of the spec: the value property can not be observed.

If you want to detect when an user alters the content of your input element, the input event is the most straightforward way. If you need to catch JS modifications, go for setInterval and compare the new value with the old one.

Check this SO question to know about different alternatives and its limitations.


the value property can be observed, Don't waste your time.

function changeValue (event, target) {
    document.querySelector("#" + target).value = new Date().getTime();
}
 
function changeContentValue () {
    document.querySelector("#content").value = new Date().getTime();
}
 
Object.defineProperty(document.querySelector("#content"), "value", {
    set:  function (t) {
        alert('#changed content value');
        var caller = arguments.callee
            ? (arguments.callee.caller ? arguments.callee.caller : arguments.callee)
            : ''
 
        console.log('this =>', this);
        console.log('event => ', event || window.event);
        console.log('caller => ', caller);
        return this.textContent = t;
    }
});
<form id="form" name="form" action="test.php" method="post">
        <input id="writer" type="text" name="writer" value="" placeholder="writer" /> <br />
        <textarea id="content" name="content" placeholder="content" ></textarea> <br />
        <button type="button" >Submit (no action)</button>
</form>
<button type="button" onClick="changeValue(this, 'content')">Change Content</button>


This works and preserves and chains the original setter and getter so everything else about your field still works.

var registered = [];
var setDetectChangeHandler = function(field) {
  if (!registered.includes(field)) {
    var superProps = Object.getPrototypeOf(field);
    var superSet = Object.getOwnPropertyDescriptor(superProps, "value").set;
    var superGet = Object.getOwnPropertyDescriptor(superProps, "value").get;
    var newProps = {
      get: function() {
        return superGet.apply(this, arguments);
      },
      set: function (t) {
        var _this = this;
        setTimeout( function() { _this.dispatchEvent(new Event("change")); }, 50);
        return superSet.apply(this, arguments);
      }
    };
    Object.defineProperty(field, "value", newProps);
    registered.push(field);
  }
}