What is the event when an <input> element's value is changed via JavaScript code? For example:
$input.value = 12;
The input event is not helping here because it's not the user who is changing the value.
When testing on Chrome, the change event isn't fired. Maybe because the element didn't lose focus (it didn't gain focus, so it can't lose it)?
There is no built-in event for that. You have at least four choices:
Any time you change $input.value in code, call the code you want triggered by the change
Poll for changes
Give yourself a method you use to change the value, which will also do notifications
(Variant of #3) Give yourself a property you use to change the value, which will also do notifications
Of those, you'll note that #1, #3, and #4 all require that you do something in your code differently from just $input.value = "new value"; Polling, option #2, is the only one that will work with code that just sets value directly.
Details:
The simplest solution: Any time you change $input.value in code, call the code you want triggered by the change:
$input.value = "new value";
handleValueChange();
Poll for changes:
var last$inputValue = $input.value;
setInterval(function() {
var newValue = $input.value;
if (last$inputValue != newValue) {
last$inputValue = newValue;
handleValueChange();
}
}, 50); // 20 times/second
Polling has a bad reputation (for good reasons), because it's a constant CPU consumer. Modern browsers dial down timer events (or even bring them to a stop) when the tab doesn't have focus, which mitigates that a bit. 20 times/second isn't an issue on modern systems, even mobiles.
But still, polling is an ugly last resort.
Example:
var $input = document.getElementById("$input");
var last$inputValue = $input.value;
setInterval(function() {
var newValue = $input.value;
if (last$inputValue != newValue) {
last$inputValue = newValue;
handleValueChange();
}
}, 50); // 20 times/second
function handleValueChange() {
console.log("$input's value changed: " + $input.value);
}
// Trigger a change
setTimeout(function() {
$input.value = "new value";
}, 800);
<input type="text" id="$input">
Give yourself a function to set the value and notify you, and use that function instead of value, combined with an input event handler to catch changes by users:
$input.setValue = function(newValue) {
this.value = newValue;
handleValueChange();
};
$input.addEventListener("input", handleValueChange, false);
Usage:
$input.setValue("new value");
Naturally, you have to remember to use setValue instead of assigning to value.
Example:
var $input = document.getElementById("$input");
$input.setValue = function(newValue) {
this.value = newValue;
handleValueChange();
};
$input.addEventListener("input", handleValueChange, false);
function handleValueChange() {
console.log("$input's value changed: " + $input.value);
}
// Trigger a change
setTimeout(function() {
$input.setValue("new value");
}, 800);
<input type="text" id="$input">
A variant on #3: Give yourself a different property you can set (again combined with an event handler for user changes):
Object.defineProperty($input, "val", {
get: function() {
return this.value;
},
set: function(newValue) {
this.value = newValue;
handleValueChange();
}
});
$input.addEventListener("input", handleValueChange, false);
Usage:
$input.val = "new value";
This works in all modern browsers, even old Android, and even IE8 (which supports defineProperty on DOM elements, but not JavaScript objects in general). Of course, you'd need to test it on your target browsers.
But $input.val = ... looks like an error to anyone used to reading normal DOM code (or jQuery code).
Before you ask: No, you can't use the above to replace the value property itself.
Example:
var $input = document.getElementById("$input");
Object.defineProperty($input, "val", {
get: function() {
return this.value;
},
set: function(newValue) {
this.value = newValue;
handleValueChange();
}
});
$input.addEventListener("input", handleValueChange, false);
function handleValueChange() {
console.log("$input's value changed: " + $input.value);
}
// Trigger a change
setTimeout(function() {
$input.val = "new value";
}, 800);
<input type="text" id="$input">
Based on #t-j-crowder and #maciej-swist answers, let's add this one, with ".apply" function that prevent infinite loop without redefining the object.
function customInputSetter(){
var descriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value");
var originalSet = descriptor.set;
// define our own setter
descriptor.set = function(val) {
console.log("Value set", this, val);
originalSet.apply(this,arguments);
}
Object.defineProperty(HTMLInputElement.prototype, "value", descriptor);
}
I'd add a 5th option based on T.J. Crowder suggestions.
But instead of adding new property You could change the actual "value" property to trigger additional action when set - either for the specific input element, or for all input objects:
//First store the initial descriptor of the "value" property:
var descriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value");
var inputSetter = descriptor.set;
//Then modify the "setter" of the value to notify when the value is changed:
descriptor.set = function(val) {
//changing to native setter to prevent the loop while setting the value
Object.defineProperty(this, "value", {set:inputSetter});
this.value = val;
//Custom code triggered when $input.value is set
console.log("Value set: "+val);
//changing back to custom setter
Object.defineProperty(this, "value", descriptor);
}
//Last add the new "value" descriptor to the $input element
Object.defineProperty($input, "value", descriptor);
Instead of changing the "value" property for specific input element, it can be changed generically for all input elements:
Object.defineProperty(HTMLInputElement.prototype, "value", descriptor);
This method works only for change of value with javascript e.g. input.value="new value". It doesn't work when keying in the new value in the input box.
Here is a solution to hook the value property changed for all inputs:
var valueDescriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value");
HTMLInputElement.prototype.addInputChangedByJsListener = function(cb) {
if(!this.hasOwnProperty("_inputChangedByJSListeners")) {
this._inputChangedByJSListeners = [];
}
this._inputChangedByJSListeners.push(cb);
}
Object.defineProperty(HTMLInputElement.prototype, "value", {
get: function() {
return valueDescriptor.get.apply(this, arguments);
},
set: function() {
var self = this;
valueDescriptor.set.apply(self, arguments);
if(this.hasOwnProperty("_inputChangedByJSListeners")){
this._inputChangedByJSListeners.forEach(function(cb) {
cb.apply(self);
})
}
}
});
Usage example:
document.getElementById("myInput").addInputChangedByJsListener(function() {
console.log("Input changed to \"" + this.value + "\"");
});
One possible strategy is to use a mutationObserver to detect changes in attributes as follows:
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(){
console.log('hello')});
});
observer.observe($input, {
attributes: true
});
Although this in itself will not detect a change like:
$input.value = 12
it WILL detect a change of the actual value attribute:
$input.setAttribute('value', 12)
So if it is you that is setting the value programatically, just be sure to alter the attribute alongside the value = 12 statement and you can have the desired result.
A simple way is just to trigger an input event when you change the value.
You can do this in plain javascript.
Early in your script, put something like this:
let inputEvent = new Event('input',{bubbles:true,cancelable: true});
You can change 'input' to the event you want, 'change', 'blur', etc
Then, any time you change a value, just call this event on the same element
input.value = 12;// <- your example
input.dispatchEvent(inputEvent);// <- calling an event
This is vanilla javascript
I don't think there is an event that will cover all the programatically assigned scenarios for an input. But, I can tell you that you can programatically "fire" an event (a custom event, a regular event or just trigger an event handler[s])
It appears to me that you're using jQuery, and so, you could use:
$('input#inputId').val('my new value...').triggerHandler('change');
In that example, you're assigning a value, and forcing a call to the handler (or handlers) binded with the "change" event.
Event when input value is changed by JavaScript?
We can use event triggering with target.dispashEvent, as explained in this question.
Example with a text input:
const input = document.querySelector('.myTextInput');
//Create the appropriate event according to needs
const change = new InputEvent('change');
//Change the input value
input.value = 'new value';
//Fire the event;
const isNotCancelled = input.dispatchEvent(change);
When any handler on that event type doesn't use event.preventDefault(), isNotCancelled will be true, otherwise, it will be false.
Related
With MutationObserver.observe(), I can listen to changes in a certain attribute. For example, if I have a Div, and the attribute value changes, then the assigned callback would be called. However, if the property (of the same name) is changed, it would not be called:
const observer = new MutationObserver(callback);
divNode = document.getElementById('my-id');
// Start observing the target node for configured mutations
observer.observe(divNode, {attributes: true});
// ...
// This will trigger `callback` to be called
divNode.setAttribute('value', 'something new')
// This will do nothing, since value is a property, not attribute
divNode.value = 'something new'
I believe (but haven't tested) this is an issue since value is not a "known" attribute of <div>, thus Javascript will not automatically update the associated attribute when the property is updated.
What's the best way to listen to divNode.value specifically?
I don't think a MutationObserver can do this. A different approach would be to overwrite the .value getter/setter:
const input = document.querySelector('input');
const { get, set } = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
Object.defineProperty(input, 'value', {
get() {
console.log('got');
return get.call(this);
},
set(newVal) {
console.log('set');
return set.call(this, newVal);
}
});
input.value = 'some value';
<input>
But this is extremely weird. I would hope never to see this in serious code, unless you're in an odd situation where you need to observe changes that you have no control over otherwise (such as when running someone else's script).
Using Proxy
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
function watchPropsOn(el) {
return new Proxy(el, {
get(target, propKey, receiver) {
console.log('get', propKey);
return el[propKey];
},
set(target, propKey, value, receiver) {
console.log('set', propKey, value);
target[propKey] = value;
}
});
}
let divNode = document.getElementById('my-id');
let divProxy = watchPropsOn(divNode);
divProxy.value = 'some text';
console.log(divProxy.value);
<input id="my-id">
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
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">
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.
I have a simple viewmodel with an observalbe array of items, and an observable holding the selected item. I subscribe to the changes of selected item, and I can see in my tests that the handler is fired even when I assign the same value again and again, so there should not be any change. The following code shows 3 alerts with all the same "changed to ..." text.
view.SelectedItem(view.Items()[0]);
view.SelectedItem.subscribe(function(newValue) {
alert("changed to " + ko.toJSON(newValue));
});
view.SelectedItem(view.Items()[0]);
view.SelectedItem(view.Items()[0]);
view.SelectedItem(view.Items()[0]);
Here is a demo fiddle.
Apparently, selecting an item, even if it's the same one as what's already selected, triggers the change event, calling the function specified when subscribing.
If you want to be notified of the value of an observable before it is about to be changed, you can subscribe to the beforeChange event. For example:
view.SelectedItem.subscribe(function(oldValue) {
alert("The previous value is " + oldValue);
}, null, "beforeChange");
Source
This could help you determine whether or not the value has changed.
You can create function to have access to old and new values for compare it:
ko.subscribable.fn.subscribeChanged = function(callback) {
var previousValue;
this.subscribe(function(oldValue) {
previousValue = oldValue;
}, undefined, 'beforeChange');
this.subscribe(function(latestValue) {
callback(latestValue, previousValue);
});
};
You could add this function to some file with you ko extensions. I once found it on stackoverflow but can't remeber link now. And then you could use it like this:
view.SelectedItem.subscribeChanged(function(newValue, oldValue) {
if (newValue.Name != oldValue.Name || newValue.Quantity != oldValue.Quantity) {
alert("changed to " + ko.toJSON(newValue));
}
});
Fiddle
I ended up creating my own method based on a thread on a forum:
// Accepts a function(oldValue, newValue) callback, and triggers it only when a real change happend
ko.subscribable.fn.onChanged = function (callback) {
if (!this.previousValueSubscription) {
this.previousValueSubscription = this.subscribe(function (_previousValue) {
this.previousValue = _previousValue;
}, this, 'beforeChange');
}
return this.subscribe(function (latestValue) {
if (this.previousValue === latestValue) return;
callback(this.previousValue, latestValue);
}, this);
};
I am fairly new to knockout and am trying to figure out how to put two pieces that I understand together.
I need:
Items that are dependent on each other.
Input value validation on the items.
Example:
I have startTime in seconds, duration in seconds, and stopTime that is calculated from startTime + duration
startTime cannot be changed
duration and stopTime are tied to input fields
stopTime is displayed and entered in HH:MM:SS format
If the user changes stopTime, duration should be calculated and automatically updated
If the user changes duration, stopTime should be calculated and automatically updated
I can make them update each other (assume Sec2HMS and HMS2Sec are defined elsewhere, and convert between HH:MM:SS and seconds):
this.startTime = 120; // Start at 120 seconds
this.duration = ko.observable(0);
// This dependency works by itself.
this.stopTimeFormatted = ko.computed({
read: function () {
return Sec2HMS(this.startTime + parseInt(this.duration()), true);
},
write: function (value) {
var stopTimeSeconds = HMS2Sec(value);
if (!isNaN(stopTimeSeconds)) {
this.duration(stopTimeSeconds - this.startTime);
} else {
this.duration(0);
}
},
owner: this
});
Or, I can use extenders or fn to validate the input as is shown in the knockout docs:
ko.subscribable.fn.HMSValidate = function (errorMessage) {
//add some sub-observables to our observable
var observable = this;
observable.hasError = ko.observable();
observable.errorMessage = ko.observable();
function validate(newValue) {
var isInvalid = isNaN(HMS2Sec(newValue));
observable.hasError(isInvalid ? true : false);
observable.errorMessage(isInvalid ? errorMessage : null);
}
//initial validation
validate(observable());
//validate whenever the value changes
observable.subscribe(validate);
//return the original observable
return observable;
};
this.startTime = 120; // Start at 120 seconds
this.duration = ko.observable(0);
this.stopTimeHMS = ko.observable("00:00:00").HMSValidate("HH:MM:SS please");
But how do I get them working together? If I add the HMSValidate to the computed in the first block it doesn't work because by the time HMSValidate's validate function gets the value it's already been changed.
I have made it work in the first block by adding another observable that keeps track of the "raw" value passed into the computed and then adding another computed that uses that value to decide if it's an error state or not, but that doesn't feel very elegant.
Is there a better way?
http://jsfiddle.net/cygnl7/njNaS/2/
I came back to this after a week of wrapping up issues that I didn't have a workaround for (code cleanup time!), and this is what I have.
I ended up with the idea that I mentioned in the end of the question, but encapsulating it in the fn itself.
ko.subscribable.fn.hmsValidate = function (errorMessage) {
var origObservable = this;
var rawValue = ko.observable(origObservable()); // Used for error checking without changing our main observable.
if (!origObservable.hmsFormatValidator) {
// Handy place to store the validator observable
origObservable.hmsFormatValidator = ko.computed({
read: function () {
// Something else could have updated our observable, so keep our rawValue in sync.
rawValue(origObservable());
return origObservable();
},
write: function (newValue) {
rawValue(newValue);
if (newValue != origObservable() && !isNaN(HMS2Sec(newValue))) {
origObservable(newValue);
}
}
});
origObservable.hmsFormatValidator.hasError = ko.computed(function () {
return isNaN(HMS2Sec(rawValue()));
}, this);
origObservable.hmsFormatValidator.errorMessage = ko.computed(function () {
return errorMessage;
}, this);
}
return origObservable.hmsFormatValidator;
};
What this does is creates another computed observable that acts as a front/filter to the original observable. That observable has some other sub-observables, hasError and errorMessage, attached to it for the error states. The rawValue keeps track of the value as it was entered so that we can detect whether it was a good value or not. This handles the validation half of my requirements.
As for making two values dependent on each other, the original code in my question works. To make it validated, I add hmsValidate to it, like so:
this.stopTimeFormatted = ko.computed({
read: function () {
return Sec2HMS(this.startTime + parseInt(this.duration()), true);
},
write: function (value) {
this.duration(HMS2Sec(value) - this.startTime);
},
owner: this
}).hmsValidate("HH:MM:SS please");
See it in action here: http://jsfiddle.net/cygnl7/tNV5S/1/
It's worth noting that the validation inside of write is no longer necessary since the value will only ever be written by hmsValidate if it validated properly.
This still feels a little inelegant to me since I'm checking isNaN a couple of times and having to track the original value (especially in the read()), so if someone comes up with another way to do this, I'm all ears.
I declare a variable which gets it's value through another event out of a select-box. Since there are different events which change the variable (lets name it z), I want to get informed when the variable gets changed.
So my question is: What is the best way to get informed when a variable gets changed?
z.change(function(){}); throws an error.
Are there ways to do this without a hidden input-field or other helpers like that?
It's not really possible, but some browsers support getters and setters, and with them you could implement something that called an external function when the value is chanced. If you want this to work in all browsers, then you could go the old fashioned way of doing this:
var Item = function (val) {
this._val = val;
}
Item.Prototype.setValue = function (val) {
this._val = val;
// call external function here!
}
Item.Prototype.getValue = function () {
return this._val;
}
And then always remember to only access the property through these functions.
You should be interested in http://plugins.jquery.com/project/watch which is not as complete as SpiderMonkey's method, but actually works.
try this:
var book = {
_year: 2004,
edition: 1
};
Object.defineProperty(book, "year", {
get: function(){
return this._year;
},
set: function(newValue){
if (newValue > 2004) {
this._year = newValue;
this.edition += newValue - 2004;
}
}
});
book.year = 2005;
alert(book.edition);
I use this method wrote a bidirectional model view binding jquery plugin jQueryMV https://github.com/gonnavis/jQueryMV .