2

I'd like to subscribe for all changes of value property in all inputs using custom setter:

Object.defineProperty(HTMLInputElement.prototype, 'value', {
    set: function(newValue) {
       // do some logic here

       // WHAT PUT HERE to call "super setter"?
    }
});

If I use this.value = newValue; I'm getting Maximum call stack size exceeded which is quite right but...

Nevermind. What should I call to change value in correct way? Here is JSFIDDLE with more detailed explanation.

Remek Ambroziak
  • 790
  • 8
  • 11
  • My answer to this question (which is currently the accepted answer) was incorrect. I suggest changing the accepted answer to [this one](https://stackoverflow.com/a/55993131/). If you do, please ping me so I can delete my answer. Happy coding! – T.J. Crowder Nov 08 '22 at 08:15

5 Answers5

8

Yes, this can be achieved using JavaScript:

(function (realHTMLInputElement) {
    Object.defineProperty(HTMLInputElement.prototype, 'value', {
        set: function (value) {
            // Do some logic here
            return realHTMLInputElement.set.call(this, value);
        },
    });
}(Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')));

We use a IIFE to pass in the original definition for value and then call the original set function from within our new definition.

If you combine that with a capture input event handler on document,¹ you get notified of all changes, whether via code (through the above) or by the user.

function yourCustomLogic(input, value) {
    console.log(`${input.id ? "Input #" + input.id : "An input"} set to "${value}"`);
}

// Catch programmatic changes
(function (realHTMLInputElement) {
    Object.defineProperty(HTMLInputElement.prototype, "value", {
        set: function (value) {
            // Do some logic here
            yourCustomLogic(this, value);
            return realHTMLInputElement.set.call(this, value);
        },
    });
})(Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value"));

// Catch end-user changes
document.addEventListener(
    "input",
    (event) => {
        if (event.target.tagName === "INPUT") {
            yourCustomLogic(event.target, event.target.value);
        }
    },
    true  // Capture handler
);

document.getElementById("example").value = "Updated by code.";
<input type="text" id="example">

The reason for using a capture phase handler (rather than bubbling phase) is twofold:

  1. Officially, input doesn't bubble, although most (all?) current browsers bubble it despite what the spec says

  2. It prevents event handlers that stop propagation from stopping the event before it gets to document

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
bower
  • 81
  • 2
  • 3
2

This worked for me. Define the value property for specific instance of HTMLInputElement, so you can use parent's value property.

// A specific instance of HTMLInputElement
var txt = document.getElementById('txtInputTagId');

// define value property for your input instance
Object.defineProperty(txt, 'value', {
    set: function(newValue) {
        var valueProp = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
        valueProp.set.call(txt, newValue);

        // Do some logic here
        console.log('setted');
    }
});
cristian.d
  • 135
  • 1
  • 8
  • 1
    This is actually brilliant solution! Thank you. For me, it is very useful, because I was creating own components, like number formatters etc, which were hook to with "oninput" event, but it was far from ideal, since every programatical update to input's value had to trigger native event to make formatter work. Also triggering event someone run waterfall of ajax requests and that was BAD. On the other hand, hooking value is great, because it does not fire on user input, but it handles all programataical updates. Also browser support seems fine, so I wonder why this solution is so rare. – micropro.cz May 07 '20 at 14:24
  • 1
    Also I can use this for my own – micropro.cz May 07 '20 at 14:26
1

This answer was incorrect. Please see my update to this answer, which captures changes both via code and via user input.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 1
    I'm confused at this answer.... don't all the other examples show this is possible or am I misunderstanding what's being claimed here? – gman Nov 08 '22 at 07:31
  • @gman - Thank you for your comment! The answer was wrong. I can't delete it, but I've updated it to point to the correct answer (which I've updated; as it was originally written, it didn't do what the OP asked -- "subscribe to *all* changes" -- but it handled the changes in code, and I was able to add handling for changes by the end user). Thanks again for bringing my attention to this! :-) – T.J. Crowder Nov 08 '22 at 08:14
0

cristian.d solution worked, but it needs also getter to be set:

function logOnInputChange(obj) {
    var valueProp = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
    // define value property for your input instance
    Object.defineProperty(obj, 'value', {
        set: function(newValue) {
            console.debug(this,'value',this.value,'=>',newValue);
            console.trace();
            valueProp.set.call(this, newValue);
        },
        get : valueProp.get,
    }); 
}

this is to show how it is used:

<input id=i1 value="init value" type=text />

and onload handler:

var inp;
window.onload = function () {
    inp = document.getElementById('i1');
    inp.value = "init value set by onload";
    console.log('input value:',inp.value);
    logOnInputChange(inp);
    inp.value = "hello!";
    console.log('input value:',inp.value);
};
okharch
  • 387
  • 2
  • 10
-1

just added following code

return this.defaultValue = newValue;

https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement

GracefulLight
  • 105
  • 1
  • 5