My objective is to observe an input value and trigger a handler when its value gets changed programmatically. I only need it for modern browsers.
I have tried many combinations using defineProperty and this is my latest iteration:
var myInput=document.getElementById("myInput");
Object.defineProperty(myInput,"value",{
get:function(){
return this.getAttribute("value");
},
set:function(val){
console.log("set");
// handle value change here
this.setAttribute("value",val);
}
});
myInput.value="new value"; // should trigger console.log and handler
This seems to do what I expect, but it feels like a hack as I am overriding the existing value property and playing with the dual status of value (attribute and property). It also breaks the change event that doesn't seem to like the modified property.
My other attempts:
a setTimeout/setInterval loop, but this is not clean either
various watch and observe polyfills, but they break for an input value property
What would be a proper way to achieve the same result?
Live demo: http://jsfiddle.net/L7Emx/4/
[Edit] To clarify: My code is watching an input element where other applications can push updates (as a result of ajax calls for example, or as a result of changes in other fields). I have no control on how the other applications push updates, I am just an observer.
[Edit 2] To clarify what I mean by "modern browser", I'd be very happy with a solution that works on IE 11 and Chrome 30.
[Update] Updated demo based on the accepted answer: http://jsfiddle.net/L7Emx/10/
The trick suggested by #mohit-jain is to add a second input for user interaction.
if the only problem with your solution is breaking of change event on value set. thn you can fire that event manually on set. (But this wont monitor set in case a user makes a change to the input via browser -- see edit bellow)
<html>
<body>
<input type='hidden' id='myInput' />
<input type='text' id='myInputVisible' />
<input type='button' value='Test' onclick='return testSet();'/>
<script>
//hidden input which your API will be changing
var myInput=document.getElementById("myInput");
//visible input for the users
var myInputVisible=document.getElementById("myInputVisible");
//property mutation for hidden input
Object.defineProperty(myInput,"value",{
get:function(){
return this.getAttribute("value");
},
set:function(val){
console.log("set");
//update value of myInputVisible on myInput set
myInputVisible.value = val;
// handle value change here
this.setAttribute("value",val);
//fire the event
if ("createEvent" in document) { // Modern browsers
var evt = document.createEvent("HTMLEvents");
evt.initEvent("change", true, false);
myInput.dispatchEvent(evt);
}
else { // IE 8 and below
var evt = document.createEventObject();
myInput.fireEvent("onchange", evt);
}
}
});
//listen for visible input changes and update hidden
myInputVisible.onchange = function(e){
myInput.value = myInputVisible.value;
};
//this is whatever custom event handler you wish to use
//it will catch both the programmatic changes (done on myInput directly)
//and user's changes (done on myInputVisible)
myInput.onchange = function(e){
console.log(myInput.value);
};
//test method to demonstrate programmatic changes
function testSet(){
myInput.value=Math.floor((Math.random()*100000)+1);
}
</script>
</body>
</html>
more on firing events manually
EDIT:
The problem with manual event firing and the mutator approach is that the value property won't change when user changes the field value from browser. the work around is to use two fields. one hidden with which we can have programmatic interaction. Another is visible with which user can interact. After this consideration approach is simple enough.
mutate value property on hidden input-field to observe the changes and fire manual onchange event. on set value change the value of visible field to give user feedback.
on visible field value change update the value of hidden for observer.
The following works everywhere I've tried it, including IE11 (even down to IE9 emulation mode).
It takes your defineProperty idea a bit further by finding the object in the input element prototype chain that defines the .value setter and modifying this setter to trigger an event (I've called it modified in the example), while still keeping the old behavior.
When you run the snippet below, you can type / paste / whatnot in the text input box, or you can click the button that appends " more" to the input element's .value. In either case, the <span>'s content is synchronously updated.
The only thing that's not handled here is an update caused by setting the attribute. You could handle that with a MutationObserver if you want, but note that there's not a one-to-one relationship between .value and the value attribute (the latter is just the default value for the former).
// make all input elements trigger an event when programmatically setting .value
monkeyPatchAllTheThings();
var input = document.querySelector("input");
var span = document.querySelector("span");
function updateSpan() {
span.textContent = input.value;
}
// handle user-initiated changes to the value
input.addEventListener("input", updateSpan);
// handle programmatic changes to the value
input.addEventListener("modified", updateSpan);
// handle initial content
updateSpan();
document.querySelector("button").addEventListener("click", function () {
input.value += " more";
});
function monkeyPatchAllTheThings() {
// create an input element
var inp = document.createElement("input");
// walk up its prototype chain until we find the object on which .value is defined
var valuePropObj = Object.getPrototypeOf(inp);
var descriptor;
while (valuePropObj && !descriptor) {
descriptor = Object.getOwnPropertyDescriptor(valuePropObj, "value");
if (!descriptor)
valuePropObj = Object.getPrototypeOf(valuePropObj);
}
if (!descriptor) {
console.log("couldn't find .value anywhere in the prototype chain :(");
} else {
console.log(".value descriptor found on", "" + valuePropObj);
}
// remember the original .value setter ...
var oldSetter = descriptor.set;
// ... and replace it with a new one that a) calls the original,
// and b) triggers a custom event
descriptor.set = function () {
oldSetter.apply(this, arguments);
// for simplicity I'm using the old IE-compatible way of creating events
var evt = document.createEvent("Event");
evt.initEvent("modified", true, true);
this.dispatchEvent(evt);
};
// re-apply the modified descriptor
Object.defineProperty(valuePropObj, "value", descriptor);
}
<input><br><br>
The input contains "<span></span>"<br><br>
<button>update input programmatically</button>
I only need it for modern browsers.
How modern would you like to go? Ecma Script 7 (6 will be made final in December) might contain Object.observe. This would allow you to create native observables. And yes, you can run it! How?
To experiment with this feature, you need to enable the Enable
Experimental JavaScript flag in Chrome Canary and restart the browser.
The flag can be found under 'about:flags’
More info: read this.
So yeah, this is highly experimental and not ready in the current set of browsers. Also, it's still not fully ready and not 100% if it's coming to ES7, and the final date for ES7 isn't even set yet. Still, I wanted to let you know for future use.
Since you are already using polyfills for watch/observe, etc, let me take the opportunity to suggest to you Angularjs.
It offers exactly this functionality in the form of it's ng-models. You can put watchers on the model's value, and when it changes, you can then call other functions.
Here is a very simple, but working solution to what you want:
http://jsfiddle.net/RedDevil/jv8pK/
Basically, make a text input and bind it to a model:
<input type="text" data-ng-model="variable">
then put a watcher on the angularjs model on this input in the controller.
$scope.$watch(function() {
return $scope.variable
}, function(newVal, oldVal) {
if(newVal !== null) {
window.alert('programmatically changed');
}
});
There is a way to do this.
There is no DOM event for this, however there is a javascript event that triggers on an object property change.
document.form1.textfield.watch("value", function(object, oldval, newval){})
^ Object watched ^ ^ ^
|_ property watched | |
|________|____ old and new value
In the callback you can do whatever.
In this example, we can see this effect (Check the jsFiddle) :
var obj = { prop: 123 };
obj.watch('prop', function(propertyName, oldValue, newValue){
console.log('Old value is '+oldValue); // 123
console.log('New value is '+newValue); // 456
});
obj.prop = 456;
When obj change, it activates the watch listener.
You have more information in this link : http://james.padolsey.com/javascript/monitoring-dom-properties/
I wrote the following Gist a little while ago, which allows to listen for custom events cross browser (including IE8+).
Have a look at how I'm listening for onpropertychange on IE8.
util.listenToCustomEvents = function (event_name, callback) {
if (document.addEventListener) {
document.addEventListener(event_name, callback, false);
} else {
document.documentElement.attachEvent('onpropertychange', function (e) {
if(e.propertyName == event_name) {
callback();
}
}
};
I'm not sure the IE8 solution works cross browser, but you could set a fake eventlistener on the property value of your input and run a callback once the value of value changes triggered by onpropertychange.
This is an old question, but with the newish JS Proxy object, triggering an event on a value change is pretty easy:
let proxyInput = new Proxy(input, {
set(obj, prop, value) {
obj[prop] = value;
if(prop === 'value'){
let event = new InputEvent('input', {data: value})
obj.dispatchEvent(event);
}
return true;
}
})
input.addEventListener('input', $event => {
output.value = `Input changed, new value: ${$event.data}`;
});
proxyInput.value = 'thing'
window.setTimeout(() => proxyInput.value = 'another thing', 1500);
<input id="input">
<output id="output">
This creates a JS proxy object based on the original input DOM object. You can interact with the proxy object in the same way you'd interact with the origin DOM object. The difference is that we've overridden the setter. When the 'value' prop is changed, we carry out the normal, expected operation, obj[prop] = value, but then if the prop's value is 'value' we fire a custom InputEvent.
Note that you can fire whatever kind of event you'd like with new CustomEvent or new Event. "change" might be more appropriate.
Read more on proxies and custom events here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events
Caniuse:
https://caniuse.com/#search=proxy
Related
Is it possible in JavaScript to listen for a change of attribute value? For example:
var element=document.querySelector('…');
element.addEventListener( ? ,doit,false);
element.setAttribute('something','whatever');
function doit() {
}
I would like to respond to any change in the something attribute.
I have read up on the MutationObserver object, as well as alternatives to that (including the one which uses animation events). As far as I can tell, they are about changes to the actual DOM. I’m more interested in attribute changes to a particular DOM element, so I don’t think that’s it. Certainly in my experimenting it doesn’t seem to work.
I would like to do this without jQuery.
Thanks
You need MutationObserver, Here in snippet I have used setTimeout to simulate modifying attribute
var element = document.querySelector('#test');
setTimeout(function() {
element.setAttribute('data-text', 'whatever');
}, 5000)
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.type === "attributes") {
console.log("attributes changed");
// Example of accessing the element for which
// event was triggered
mutation.target.textContent = "Attribute of the element changed";
}
console.log(mutation.target);
});
});
observer.observe(element, {
attributes: true //configure it to listen to attribute changes
});
<div id="test">Dummy Text</div>
Additionally, mutation.target property gives the reference to mutated/changed node.
This question is already answered, but I'd like to share my experiences, because the mutation observer did not bring me the insights in needed.
Note This is some kind of hacky solution, but for (at least) debugging purposes quite good.
You can override the setAttribute function of a particalar element. This way you can also print the callstack, and get an insight of "who" changed the attribute value:
// select the target element
const target = document.querySelector("#element");
// store the original setAttribute reference
const setAttribute = target.setAttribute;
// override setAttribte
target.setAttribute = (key: string, value: string) => {
console.trace("--trace");
// use call, to set the context and prevent illegal invocation errors
setAttribute.call(target, key, value);
};
I created a custom element in Aurelia and I also have the valueChanged, however I need to do a certain action only when the value is changed outside of the custom element. Since the signature is valueChanged(newValue, oldValue), how would I know when the value gets changed from the ViewModel and not from the custom element itself? Is that doable somehow with an observer or observable?
I actually got kind of a working sample, I saw that there's also an __array_observer__ property when the value is changed from the ViewModel, and it works but it's probably not ideal. So I got this piece of code which kinda works
valueChanged(newValue, oldValue) {
if (newValue !== oldValue && newValue.__array_observer__) {
// value got changed outside of this custom element
}
}
This is probably not ideal though, or is it? Any other suggestion in knowing where the value got changed outside of the custom element?
EDIT
As much as possible, I'm looking for a solution that will still have access to the custom element. Even if I want to get triggered by an external value change call, I still need to call an internal function of the same custom element.
EDIT #2
To give a little more description of my issue, I need to know when the value got changed from the outside because this will trigger an action that will re-affect the value. Without knowing if the change was from the outside of the custom element, I fall in a recursive call with no way to stop it. What I'm looking for is similar to what used to be the caller and the callee but this was removed with ES5 and Strict Mode, however this would have been very useful.
Still looking for an answer :(
You could use a CustomBindingBehavior to intercept the updateTarget event. For instance:
export class InterceptBindingBehavior {
bind(binding, scope, interceptor) {
binding['originalUpdateTarget'] = binding['updateTarget'];
binding.updateTarget = val => {
alert('property was changed outside of the element');
//do something here
binding['originalUpdateTarget'](val);
}
}
unbind(binding, scope) {
binding.updateTarget = binding['originalUpdateTarget'];
binding['originalUpdateTarget'] = null;
}
}
Usage:
<template>
<require from="./intercept-binding-behavior"></require>
<some-element value.bind="message & intercept"></some-element>
</template>
Runnable example: https://gist.run/?id=bcd7d39ed94856caf586f224f89fd1ff
I haven't tested this in many cases and I'm not sure if it's best way.
If you want to do the opposite (intercept when the property is changed from the element instead of the VM) just replace updateTarget for updateSource.
More info about CustomBindingBehaviors http://aurelia.io/hub.html#/doc/article/aurelia/binding/latest/binding-binding-behaviors/8
Hope this helps!
As discussed in gitter, you can use a suppress flag
value: number;
suppressValueChanged: boolean;
valueChanged(){
if(this.suppressValueChanged){
this.suppressValueChanged = false;
this.logger.debug("the value has been changed from inside the element");
return;
}
this.logger.debug("the value has been changed from outside the element");
// here comes the code to run when the value is changed outside
}
internalSetValue(value: number){
this.suppressValueChanged = true;
this.value = value;
}
The reason I reset the flag in the changed method is that depending on the circumstances valueChanged can be called by Aurelia asynchronously so you cannot just do the following
this.suppressValueChanged = true;
this.value = 123;
this.suppressValueChanged = false;
Sometimes, using a task will work
this.taskQueue.queueTask(() => {
this.suppressValueChanged = true;
this.value = 123;
this.suppressValueChanged = false;
});
It really depends where exactly in Aurelia code you are changing the value. I've found that the first option gives the most consistent result.
I'd like to know how to detect changes to an html checkbox input, even when its changed programatically like this:
checkboxDomNode.checked = true
The browser obviously knows it changed since it gets a visual checkmark, and also any :checked css pseudo class gets triggered. I'm actually trying to emulate the :checked css pseudo class in javascript, which is why i'm trying to find a robust way to do this.
I've tested the "change" event, and it is not triggered on programatic changes like the above. It also looks like MutationObserver doesn't support changes made to that property.
A small extension to the answer of Alan Darmasaputra: Instead of performing two tasks on each change you can use a simple object wrapper for your checkbox, with a getter and a setter for your property:
var checkboxObject = {
node: document.querySelector('input[type=checkbox]'), // or whatever query you wish to perform
get checked() {
return this.node.checked;
},
set checked(value) {
var oldValue = this.node.checked;
this.node.checked = !!value;
if(oldValue !== !!value) {
this.node.dispatchEvent(new Event("change"));
}
}
};
checkboxObject.node.addEventListener('change',function(event){
//do something
});
// calls checkboxObject[get checked]
var state = checkboxObject.checked;
// calls checkboxObject[set checked] with argument 'false'
checkboxObject.checked = false;
As you can see, this requires some initial configuration, but then you can both programmatically change the checked state, and dispatch the event in one assignment.
More info: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set
Working fiddle:
var checkboxObject = {
node: document.querySelector('input[type=checkbox]'), // or whatever query you wish to perform
get checked() {
return this.node.checked;
},
set checked(value) {
var oldValue = this.node.checked;
this.node.checked = !!value;
if(oldValue !== !!value) {
this.node.dispatchEvent(new Event("change"));
}
}
};
document.getElementById('check').addEventListener('click', function() {
checkboxObject.checked = true;
});
document.getElementById('uncheck').addEventListener('click', function() {
checkboxObject.checked = false;
});
checkboxObject.node.addEventListener('change',function(event){
document.getElementById('log').innerText += 'checkbox value changed! New value: ' + event.target.checked + '\n';
});
// Why implementing this directly on the checkbox element is not a good idea:
console.log( Object.getOwnPropertyDescriptor( Object.getPrototypeOf(checkboxObject.node), 'checked' ));
<button id="check">check</button>
<button id="uncheck">uncheck</button>
<input type="checkbox">
<div id="log" style="white-space: pre-line"></div>
**EDIT: **
Implementing this behaviour directly on the checkbox element is not a good idea. With Object.getOwnPropertyDescriptor we can check the type of a property ( an accessor descriptor, or a data descriptor ). If we do that for the checked property o the prototype of checkboxObject.node, as follows:
console.log( Object.getOwnPropertyDescriptor( Object.getPrototypeOf(checkboxObject.node), 'checked' ));
the output will be:
{
"get": function () { [native code] },
"set": function () { [native code] },
"enumerable": true,
"configurable": true
}
This means that the element already has a getter and setter defined for the property checked, in its prototype that is. It IS possible to implement logic where you Object.getPrototypeOf() your checkbox, and call the setter on it explicitly to invoke the native behaviour, but that would make things quite hard to understand real soon, especially because you'd have to bind the context to be your checkbox element for that call. I'll leave it up to you to make a decision about that.
Also keep in mind that an existing (prototype) descriptor might be non-writable (in this case it's not, otherwise it would be in the output above), which means that a normal property assignment for overriding this will fail. In that case you'd have to Object.defineProperty() directly on your checkbox element, to ensure you're not actually trying to override the immutable prototype setters / getters.
Why don't you try two way binding using any javaScript frameworks like AngularJS or KnockoutJS. You are also welcome to do it with pure javaScript.
Have a look in this article,
http://www.lucaongaro.eu/blog/2012/12/02/easy-two-way-data-binding-in-javascript/
Simple Example with AngularJS
HTML :
<div ng-controller="checkCtrl">
Check It : <input type="checkbox" id="chkbox" ng-model="checked" />
<p ng-model="checked">{{checked}}</p>
</div>
AngularJS :
var testApp = angular.module("testApp", []);
testApp.controller("checkCtrl", function($scope){
$scope.checked = true;
});
jsFiddle
Properties that are programatically modified doesn't fire Events. You could instead try adding onchange listener
checkboxDomNode.addEventListener('change',function(event){
//do something
})
and then after modifying your property, fire the event
checkboxDomNode.dispatchEvent(new Event("change"))
Tested on Chrome 53
Is it possible to revert a value change to a view model with something other than a custom binding handler (maybe an extender) without the subscriptions firing?
For example, say you have a numeric field that only allows values up to 100. If someone types 101, we want the value to drop back to the previous value and most importantly not fire any subscriptions on the reverted value.
I'm trying to find a generic way of accomplishing this without having to write a custom binding handler that inherently would require duplication of core knockout code to handle text fields, select fields, etc.
Yes, it can be done with an extender, like this:
ko.extenders.numeric = function(target, properties) {
var result = ko.computed({
read: target,
write: function(newValue) {
var current = target();
var valueToWrite = newValue;
if(properties) {
if(properties.maxNum && properties.maxNum < newValue) {
valueToWrite = current;
}
if(properties.minNum && properties.minNum > newValue) {
valueToWrite = current;
}
}
if(valueToWrite !== current) {
target(valueToWrite);
} else {
target.notifySubscribers(valueToWrite);
}
}
});
result(target());
return result;
};
And this is how you use it:
self.number = ko.observable().extend({numeric: { minNum: 50, maxNum: 100} });
You can test that in the fiddle I've created.
You can comment the target.notifySubscribers(valueToWrite) line but what will happen is that if you change that value from outside (like in an input element), the value will not be updated back to the previous one.
I went down the same route that #Jalayn had suggested already, and ended up doing something similar to the issue listed in the comments on his answer. I'm still not a huge fan of this as it requires you to check at the top of the subscription to see if the value has actually changed, but at least it is possible.
The full solution and QUnit tests are posted here: https://github.com/gotdibbs/ko.extenders.filteredUpdate/.
The key components to making this work are an extender to "protect" the view model from unwanted changes using a computed observable, and a custom function extending subscribables to work in place off a normal subscription which would fire on every change regardless of if the value is actually changing.
Using jquery I've added a change handler to a form.
This works when any input is changed BUT only if the user manually changes an input and not when some other code changes the input.
Is there any way to detect if a form has changed even if its inputs are changed by code?
Yes, there seems to be some confusion over this. In an ideal world you would expect the onchange event to happen whenever the inputs change but thats not what happens. I'm sure for good reasons to - maybe not.
One way I've overcome this obstacle is to capture the form state into a variable just after displaying it and then just before submitting it to check if the state has changed and to act accordingly.
An easy state to store is what the serialize function returns. An easy place to store the state is using the data functionality. Both serialize and data are available with jquery.
Of course you can use other different forms of state (some form of hash) or storage for this state (standard global variable for example).
Here is some prototype code:
If your form id is 'xform' then you can call the following code when the form has displayed:
$('#xform').data('serialize',$('#xform').serialize());
And then, when you need to check, for example just before a button submit you can use:
if($('#xform').serialize()!=$('#xform').data('serialize')){
// Form has changed!!!
}
You could wrap all this up into a copy & paste javascript snippet that will give you a formHasChanged() function to call wherever you need it (NOT TESTED):
$(function() {
$('#xform').data('serialize',$('#xform').serialize());
});
function formHasChanged(){
if($('#xform').serialize()!=$('#xform').data('serialize')){
return(true);
}
return(false);
}
But I'll stop here otherwise I'll create yet another jquery plugin.
Serializing the form is certainly an option, but it will not work if:
you want to know which fields have changed
it only needs to check a subset of the fields
dynamically adding or removing fields.
Fortunately, every form element has a default value associated with its object:
input, textarea : defaultValue
checkbox, radio : defaultChecked
select: defaultSelected
for ex: to ckeck if input or textarea has changed:
var changed = false;
$(":text,textarea").each(function(){
changed = this.value != this.defaultValue;
return !changed; // return if at least one control has changed value
});
This is easily achieved in JavaScript without jQuery. initChangeDetection() can be called multiple times:
function initChangeDetection(form) {
Array.from(form).forEach(el => el.dataset.origValue = el.value);
}
function formHasChanges(form) {
return Array.from(form).some(el => 'origValue' in el.dataset && el.dataset.origValue !== el.value);
}
Test on JS Bin
For older browsers that don't support newer arrow/array functions:
function initChangeDetection(form) {
for (var i=0; i<form.length; i++) {
var el = form[i];
el.dataset.origValue = el.value;
}
}
function formHasChanges(form) {
for (var i=0; i<form.length; i++) {
var el = form[i];
if ('origValue' in el.dataset && el.dataset.origValue !== el.value) {
return true;
}
}
return false;
}
Not in a regular way.
You can change with input and then trigger the change event.
$('#inputId').val('foo').trigger('change');
or with this:
$('#inputId').val('foo').change();
Here is what i did (i found my solution using zaf's answer)
$("form").change(function() {
$(this).data("changed","true");
});
$("input[type='submit']").click(function() {
if($("form").data("changed") == "true") {
var discard = confirm("Some unsaved changes. Discard them ?");
if(!discard) return false;
}
});
Try onchange attribute
According to W3c it should trigger anytime the content of an element, the selection, or the checked state have changed.