16

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?

Heretic Monkey
  • 11,687
  • 7
  • 53
  • 122
zero_cool
  • 3,960
  • 5
  • 39
  • 54
  • `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? – CertainPerformance Mar 06 '19 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. – zero_cool Mar 06 '19 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 – CertainPerformance Mar 06 '19 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. – zero_cool Mar 06 '19 at 23:37

3 Answers3

15

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>
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
3

Consider creating and triggering input events

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

then

myelement.dispatchEvent(event);

more info

Khaledonia
  • 2,054
  • 3
  • 15
  • 33
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.

rolinger
  • 2,787
  • 1
  • 31
  • 53