17

I've got an input with Google AutoComplete connected.

When the user moves up and down through the seachResults the value of the input is changed dynamically via JavaScript.

I'd like to capture the value that is in the input at any given time.

I've tried onChange and onInput but since no event is getting fired, and the value of the DOM Node is not getting set - there are no updates.

How do you detect changes to the input that are dynamically updated via JavaScript?

4
  • the value of the DOM Node is not getting set Wait, is the value being changed via Javascript, or not? Title implies it is, but that line there implies that it's not? Commented Mar 6, 2019 at 23:33
  • Make no mistake - the value of the input does change - I believe via Google - but the value attribute does not update unless an event is fired. For example, if onBlur or onClick is fired - the value attribute updates but when the results are changed via google autoComplete - it does not. Commented Mar 6, 2019 at 23:34
  • 1
    The value attribute shouldn't change, though the value property should. How are you determining that it doesn't update, are you just listening for an event and not seeing one that triggers the listener? A live snippet (or a link) that illustrates the behavior would probably clear things up significantly Commented Mar 6, 2019 at 23:35
  • Potato? Potawto? What do you mean by property? The DOM Node of the input has a value attribute. When I'm watching the DOM using the browser inspector the value does not change unless an event is fired. Commented Mar 6, 2019 at 23:37

6 Answers 6

24

The .value attribute will only change if the script-writer has called setAttribute('value' to set the new value, which is quite an odd thing to do. In almost all situations, I would expect the value to be set by assigning to the value property directly, eg:

input.value = 'foo';

Calling setAttribute will indeed show a change in the inspected DOM attribute, eg:

<input value="somevalue">

const input = document.querySelector('input');
input.setAttribute('value', 'foo');
console.log(input.outerHTML);
<input>

But just assigning to the .value property of the element will not result in such a change:

const input = document.querySelector('input');
input.value = 'foo';
console.log(input.outerHTML);
<input>

Assigning to the .value actually invokes a setter function on HTMLInputElement.prototype:

console.log(HTMLInputElement.prototype.hasOwnProperty('value'));

You can shadow this by putting a getter/setter for value directly on the element itself, with the setter invoking your own function, allowing you to listen for changes:

const { get, set } = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
const input = document.querySelector('input');
Object.defineProperty(input, 'value', {
  get() {
    return get.call(this);
  },
  set(newVal) {
    console.log('New value assigned to input: ' + newVal);
    return set.call(this, newVal);
  }
});


// example of autocomplete trying to change the input value:
input.value = 'foo';
<input>

Sign up to request clarification or add additional context in comments.

5 Comments

this looks promising. I will give this a shot :)
Unfortunately, it did not :/
Can you post the code you're using so there's a minimal reproducible example to debug? As you can see from the live snippets (just press "Run code snippet"), the Javascript-initiated changes to the .value of the inputs look to be intercepted and observed as desired.
The shadow getter/setter worked perfectly for me. This helped me hack around a bug nicely.
Nailed it! the overriding of get and set is the way to go if you can not trigger the evens manually using JavaScript when changing the value.
3

You can run this after programmatically changing the .value

document.querySelector('#element-id').dispatchEvent(new Event('input'));

Listen for a value change:

document.querySelector('#element-id').addEventListener('input', function() {
  console.log(this.value);
});

Comments

2

Consider creating and triggering input events

var event = new Event('input', {
    bubbles: true,
    cancelable: true,
});

then

myelement.dispatchEvent(event);

more info

Comments

1

You can start events manually with JS.

<script>
const my_input = document.getElementById("my_input");
my_input.onchange=function(){console.log("changed!");};
const event = new Event('change');
my_input.dispatchEvent(event);//console writes "changed!" now.
//"event" can be reused like: 
//my_input2.dispatchEvent(event);
//my_input3.dispatchEvent(event);
</script>

Comments

1

I liked the idea of overriding the setter method to allow for callbacks, but the answers provided only work if you need to add a single 'event listener' to the element. They fail if multiple are added since each previous callback overrides the previous. Here is the solution I came up with to allow for any number of callbacks to be added:

function addProgramaticChangeListener(selector, callback) {
        const input = document.querySelector(selector);
        if (input) {
            const desc = Object.getOwnPropertyDescriptor(input, "value") 
                ? Object.getOwnPropertyDescriptor(input, "value") 
                : Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value");             
            
            if(input.setterCallbacks && input.setterCallbacks.length){                
                input.setterCallbacks.push(callback)
            }else{
                input.setterCallbacks = [callback]
                Object.defineProperty(input, "value", {
                    configurable: true,
                    get: desc.get,
                    set: function (v) {                                                      
                        for(var callback of input.setterCallbacks){
                            callback(v)
                        }  
                        desc.set.call(this, v);                        
                    }
                });
            }  
        }
    }

Each callback is added to the element and referenced in the setter. Whenever the input is changed, all callbacks will be fired with the new value passed.

Comments

0

Other solutions above require listening to all inputs or all of the DOM, but here is a different solution designed to detect a value change of a specific element:

You can use a setInterval. I needed to observe a specific DOM element value changing and I was able to target the specific elementID with the following:

var watchID ;
var el = document.getElementById('elementID') ;
var oldValue = el.value ;
var watchCount = 0 ;  // create a built in timeout function
watchID = setInterval(function() {
  watchCount++ ;
  if (el.value != oldValue) {
    console.log("Element Changed:" +el.value) ;
    ... do something ...
    clearInterval(watchID) ;
  }
  // 30 second timeout
  if (watchCount == 60) {
    console.log("Interval Timeout")          
    clearInterval(watchID) ;
  }
},500) ;  // execute every 1/2 second for 30 seconds

In my case, I had a complex file download that needed a bit of a different hacky solution and the above worked without any eventListeners or other more complex solutions that monitored everything (like: MutationObserver). Of course you don't need the custom timeout...but if you don't, you may want a more friendly solution than mine. A continuous interval could strain resources.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.