const { get, set } = Object.getOwnPropertyDescriptor(HTMLSelectElement.prototype, 'value');
let countryElement = $('select[name="country_id"]').get(0);
Object.defineProperty(countryElement, 'value', {
get() {
console.log('calling get');
return get.call(this);
},
set(newVal) {
console.log('New value assigned to selected[name="country_id"]: ' newVal);
return set.call(this, newVal);
}
});
If I run document.querySelector('select[name="country_id"]').value = 'CA'
then the set function is called. If I run $('select[name="country_id"]').val('CA')
the set function is not called.
My goal is to observe changes to a form field which are executed by a third party JS tool which I cannot change. However, I can't get it to work. As I dove into the issue, I found that I can even get jQuery to change the value without going through my set function. But I don't see how that's possible.
CodePudding user response:
Look into jQuery's source code. val
does, eventually:
hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
// If set returns undefined, fall back to normal setting
if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) {
this.value = val;
}
So it accesses and calls jQuery.valHooks.select.set
, which does, elsewhere in the source:
set: function( elem, value ) {
var optionSet, option,
options = elem.options,
values = jQuery.makeArray( value ),
i = options.length;
while ( i-- ) {
option = options[ i ];
/* eslint-disable no-cond-assign */
if ( option.selected =
jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1
) {
optionSet = true;
}
/* eslint-enable no-cond-assign */
}
// Force browsers to behave consistently when non-matching value is set
if ( !optionSet ) {
elem.selectedIndex = -1;
}
return values;
}
It iterates through the options. The only lines that actually change the DOM in the above function is:
option.selected = jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1
or
elem.selectedIndex = -1;
So, jQuery isn't calling the value setter at all. You could emulate what it's doing and set the value of a <select>
by assigning to the selected
property of the option you want to make selected.
const { get, set } = Object.getOwnPropertyDescriptor(HTMLSelectElement.prototype, 'value');
let countryElement = $('select[name="country_id"]').get(0);
Object.defineProperty(countryElement, 'value', {
get() {
console.log('calling get');
return get.call(this);
},
set(newVal) {
console.log('New value assigned to selected[name="country_id"]: ' newVal);
return set.call(this, newVal);
}
});
countryElement.children[1].selected = true;
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<select name="country_id">
<option>foo</option>
<option value="CA">CA</option>
</select>
My goal is to observe changes to a form field which are executed by a third party JS tool which I cannot change.
There are a number of ways to change a <select>
value, which include .value
, setting the .selected
property of an option, and setting the .selectedIndex
of the select. You'll need to implement all of them to do what you want reliably.