Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the modern way of catching all changes to the value of an HTML input element?

Is there a standard way of catching all changes to the value of an HTML input element, despite whether it's changed by user input or changed programmatically?

Considering the following code (which is purely for example purposes and not written with good coding practices, you may consider it as some pseudo-code that happens to be able to run inside some web browsers :P )

<!DOCTYPE html>
<html>
<head>
    <title>Test Page</title>
    <script>
        window.onload = () => {
            var test = document.getElementById("test");

            test.onchange = () => {
                console.log("OnChange Called");
                console.log(test.value);
            }  // Called when the input element loses focus and its value changed

            test.oninput = () => {
                console.log("OnInput Called");
                console.log(test.value);
            }  // Called whenever the input value changes

            test.onkeyup = () => {
                console.log("OnKeyUp Called");
                console.log(test.value);
            }  // some pre-HTML5 way of getting real-time input value changes
        }
    </script>
</head>
<body>
    <input type="text" name="test" id="test">
</body>
</html>

However none of those events will fire when the value of the input element is changed programmatically, like someone doing a

document.getElementById("test").value = "Hello there!!";

To catch the value that's changed programmatically, usually one of two things can be done in the old days:

1) Tell the coders to fire the onchange event manually each time they change the input value programmatically, something like

document.getElementById("test").value = "Hello there!!";
document.getElementById("test").onchange();

However for this project at hand the client won't accept this kind of solution since they have many contractors/sub-contractors that come and go and I guess they just don't trust their contractors to follow this kind of rules strictly, and more importantly, they have a working solution from one of their previous contracts which is the second old way of doing things

2) set a timer that checks the input element value periodically and calls a function whenever it's changed, something like this

        var pre_value = test.value;

        setInterval(() => {
            if (test.value !== pre_value) {
                console.log("Value changed");
                console.log(test.value);
                pre_value = test.value;
            }
        }, 200);  // checks the value every 200ms and see if it's changed

This looks like some dinosaur from way back the jQuery v1.6 era, which is quite bad for all sorts of reasons IMHO, but somehow works for the client's requirements.

Now we are in 2019 and I'm wondering if there are some modern way to replace the above kind of code? The JavaScript setter/getter seems promising, but when I tried the following code, it just breaks the HTML input element

        Object.defineProperty(test, "value", {
            set: v => {
                this.value = v;
                console.log("Setter called");
                console.log(test.value);
            },
            get: ()=> {
                console.log("Getter called");
                return this.value;
            }
        });

The setter function will be called when the test.value is programmatically assigned, but the input element on the HTML page will somehow be broken.

So any idea on how to catch all changes to the value of an HTML input element and call the handler function other than the ancient "use a polling timer" method?

NOTICE: Take note that all the code here are just for example purposes and should not be used in real systems, where it's better to use the addEventListener/attachEvent/dispatchEvent/fireEvent etc. methods

like image 896
hellopeach Avatar asked Oct 27 '25 10:10

hellopeach


2 Answers

To observe assignments and retrieval to the .value of an element, Object.defineProperty is the way to go, but you need to call the original setter and getter functions inside your custom methods, which are available on HTMLInputElement.prototype:

const { getAttribute, setAttribute } = test;
test.setAttribute = function(...args) {
  if (args[0] === 'value') {
    console.log('Setting value');
  }
  return setAttribute.apply(this, args);
};
test.getAttribute = function(...args) {
  if (args[0] === 'value') {
    console.log('Getting value');
  }
  return getAttribute.apply(this, args);
};



test.setAttribute('value', 'foo');
console.log(test.getAttribute('value'));
<input type="text" name="test" id="test">

Note the use of methods rather than arrow functions - this is important, it allows the this context to be preserved. (you could also use something like set.call(input, v), but that's less flexible)

That's just for changes to .value. You can monkeypatch something similar for setAttribute('value, if you want:

const { setAttribute } = test;
test.setAttribute = function(...args) {
  if (args[0] === 'value') {
    console.log('attribute set!');
  }
  return setAttribute.apply(this, args);
};

test.setAttribute('value', 'foo');
<input type="text" name="test" id="test">
like image 75
CertainPerformance Avatar answered Oct 28 '25 23:10

CertainPerformance


The standard way is to not fire a change event when the value has been changed programmatically.
Not only are there too many ways to set the value of an input programmatically, but moreover, that's just a call for endless loop.

If in any of your callbacks your input's value is set, then you'll crash the page.

Wanna try?

let called = 0; // to avoid blocking this page
const { set, get } = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
Object.defineProperty(inp, 'value', {
  set(v) {
    const ret = set.call(this, v);
    if(++called < 20) // I limit it to 20 calls to not kill this page
      this.dispatchEvent(new Event('all-changes'));
    return ret;
  },
  get() { return get.call(this); }
});

inp.addEventListener('all-changes', e => {
  inp.value = inp.value.toUpperCase();
  console.log('changed');
});

btn.onclick = e => inp.value = 'foo';
<input id="inp">
<button id="btn">set value</button>

So the best is still to only call whatever callback directly from the code responsible of the change.

like image 34
Kaiido Avatar answered Oct 28 '25 22:10

Kaiido



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!