I'm creating a re-usable object that would contain multiple properties. I would like to fire an event within the object that will update some of its own properties when a specific property is changed through assignment. I have read something similar to this but what they used was an object. How can I achieve this?
My apologies if this is something basic but I don't really have a formal training is JavaScript or in-dept knowledge how JavaScript works.
also I would like to add that this is something that should work in IE11 and up.
I have tested the method from this but unfortunately I don't really understand how can I implement it on my case.
Listening for variable changes in JavaScript
var test;
function myObject(){
this.dataSource = null;
this.changeEvent = function(val){
//do something inside
}
}
test = new myObject();
test.dataSource = 'dataSource'; //trigger changeEvent() inside myObject
When creating the object, wrap it in a Proxy.
The Proxy object is used to define custom behavior for fundamental operations (e.g. property lookup, assignment, enumeration, function invocation, etc).
Implement a set trap which performs the needed internal changes.
OK, so I'll start from the method you've suggested and it seems you like that approach.
Sorry, if this seems obvious but better to explain!
Basically watch watches properties within your object (say datasource) and when a new value is assigned - fires the callback function. So, watch takes two params - what to watch, and what to do (when the property you are watching changes).
The caveat with this approach is it's a a non-standard and was not implemented by other browsers. Although, we can get a polyfill (which declares it if it does not exist).
https://gist.github.com/eligrey/384583
Warning from Mozilla's own docs:
Deprecation warning: Do not use watch() and unwatch()! These two
methods were implemented only in Firefox prior to version 58, they're
deprecated and removed in Firefox 58+. In addition, using watchpoints
has a serious negative impact on performance, which is especially true
when used on global objects, such as window. You can usually use
setters and getters or proxies instead.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/watch
So, to implement using a polyfil of watch (thank Eli Grey for the Polyfill)
First, register the polyfill (put it somewhere which will run before anything else in JS, or put it in a polyfill.js file and import it first on your HTML page!)
/*
* object.watch polyfill
*
* 2012-04-03
*
* By Eli Grey, http://eligrey.com
* Public Domain.
* NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
*/
// object.watch
if (!Object.prototype.watch) {
Object.defineProperty(Object.prototype, "watch", {
enumerable: false
, configurable: true
, writable: false
, value: function (prop, handler) {
var
oldval = this[prop]
, newval = oldval
, getter = function () {
return newval;
}
, setter = function (val) {
oldval = newval;
return newval = handler.call(this, prop, oldval, val);
}
;
if (delete this[prop]) { // can't watch constants
Object.defineProperty(this, prop, {
get: getter
, set: setter
, enumerable: true
, configurable: true
});
}
}
});
}
// object.unwatch
if (!Object.prototype.unwatch) {
Object.defineProperty(Object.prototype, "unwatch", {
enumerable: false
, configurable: true
, writable: false
, value: function (prop) {
var val = this[prop];
delete this[prop]; // remove accessors
this[prop] = val;
}
});
}
Then, to use (using your example);
var test;
**function myObject(){
this.dataSource = null;
this.changeEvent = function (id, oldval, newval) {
console.log(id + ' changed from ' + oldval + ' to ' + newval);
return newval;
}
this.watch('datasource', this.changeEvent);
}**
test = new myObject();
test.dataSource = 'dataSource'; //trigger changeEvent() inside myObject
However, I would look into Event Listeners and trigger events when objects change - but that solution should work for you, especially with watch
Listening for variable changes in JavaScript
You can store the value of dataSource in local storage and then compare and check if it has been changed.
var test;
function myObject()
{
this.dataSource = null;
this.changeEvent = function(val)
{
console.log("Value has been changed.");
}
}
test = new myObject();
test.dataSource = 'dataSource';
console.log("Before change" + test.dataSource);
localStorage.setItem("dataSource", test.dataSource);
var newVal = "dtSrc";
test.dataSource = newVal;
var originalVal = localStorage.getItem("dataSource");
console.log("After change" + test.dataSource);
if(newVal == originalVal)
console.log("Value has not changed.");
else
test.changeEvent();
Let me know if it works for you. If not, let me know the expected output.
You can use like :
var myObject = {
_dataSource: null,
changeEvent : function(val){
//do something inside
alert(val);
}
};
Object.defineProperty(myObject, "dataSource", {
get: function(){
return this._dataSource;
},
set: function(newValue){
this._dataSource=newValue;
this.changeEvent(this.dataSource);
}
});
myObject.dataSource= "dataSource";
Related
Hy I wrote a quick and dirty list ui in js and html that can be filtered:
https://jsfiddle.net/maxbit89/2jab4fa4/
So the usage of this looks like this (Fiddle line: 96):
var list = new ui_list(document.body, 200, 300, "Test");
var encoder = function(dom, value) {
console.log("begin encoding");
console.log(value)
dom.innerHTML = value.n;
}
list.add({'n': 1}, function() {
this.value.n++;
console.log(this.value.n);
// this.value = this.value;
}, encoder);
So what this basicaly does is create a List and adds a Element to it that has an Object: {'n': 1} as a value and a onClickHandler(second parameter on list.add) wich should increase the value by 1 (fiddle line: 104)
But it won't do this untill you uncomment the line 106 in the fiddle.
(Tested with FireFox 50.1.0, and Edge Browser)
Has any body an idea why js behaves like this?
In a much simplier example this works just fine:
var myObj= {
'onvalueChange' : function() {
console.log('value changed');
},
'print' : function() {
console.log('value:');
console.log(this.value);
console.log(this.value.n);
}
};
Object.defineProperty(myObj, "value", {
get: function() { return this._value; },
set: function(value) {
this.onvalueChange();
this._value = value;
}
});
myObj.value = {'n' : 1};
myObj.value.n++;
myObj.print();
First you have the setter defined like this:
set: function (value) {
this.encoder(this, value);
this._value = value;
}
that means that every time the value is set, the encoder will be called with the new value to update the equivalent DOM element.
But then inside the event listener function you have:
function() {
this.value.n++;
console.log(this.value.n);
//this.value = this.value;
}
where you think that this.value.n++ is setting the value (means it calls the setter which means the encoder will be called to update the DOM element). But it's not true. this.value.n++ is actually calling the getter. To explain more this:
this.value.n++;
is the same as:
var t = this.value; // call the getter
t.n++; // neither call the getter nor the setter. It just uses the reference (to the object) returned by the getter to set n
So, when you uncomment the line this.value = this.value;, the setter gets called, and the encoder gets called to update the DOM element.
So to fix the issue you have to either:
Make a call inside the getter to the encoder as you did for the setter (but this solution is very hacky as it will update the DOM element on every getter call even if nothing is being set).
Change this this.value.n++; to actually call the setter like: this.value = {n: this.value.n + 1}; (but this is hacky too as if value has a lot of key-value pairs then you have to enlist them all here just to set n).
Call the encoder inside the event listener which will be the best way to do it (or if you don't want to pass the parameters to it make another function (for example this.callEncoder()) that will call it and [you] call the new function instead inside the event listener).
I want to be able to provide a JavaScript function that will be called whenever any property of a specified object is being queried or updated. Is that possible, if so, how?
To give a simple example, if I have obj = { a:3 }, I want to have some function called whenever any code queries obj.a, and be able to return whatever I want instead of its current value, e.g. 4 instead of 3.
It's easy, you can use Object.prototype.defineProperty. more on it here.
To answer your question:
var obj = {};
Object.defineProperty(obj, "a", {
get: function() {console.log("I've been accessed"); return 5;//or whatever value}
});
console.log(obj.a)
Working fiddle
Update
The above can be shorthanded.
Object.prototype.addMonitoredGetter = function(property, value, callback) {
Object.defineProperty(this, property, {
writable: false,
get: function() {callback(); return this[property]
};
};
And the callback here could be the monitoring function. Of course, needs default params and checks, but it should do the trick.
There's a watch method available for Firefox...
o.watch("p", function(...)
http://jsfiddle.net/NTc52/
I've been looking into JavaScript frameworks such as Angular and Meteor lately, and I was wondering how they know when an object property has changed so that they could update the DOM.
I was a bit surprised that Angular used plain old JS objects rather than requiring you to call some kind of getter/setter so that it could hook in and do the necessary updates. My understanding is that they just poll the objects regularly for changes.
But with the advent of getters and setters in JS 1.8.5, we can do better than that, can't we?
As a little proof-of-concept, I put together this script:
(Edit: updated code to add dependent-property/method support)
function dependentProperty(callback, deps) {
callback.__dependencies__ = deps;
return callback;
}
var person = {
firstName: 'Ryan',
lastName: 'Gosling',
fullName: dependentProperty(function() {
return person.firstName + ' ' + person.lastName;
}, ['firstName','lastName'])
};
function observable(obj) {
if (!obj.__properties__) Object.defineProperty(obj, '__properties__', {
__proto__: null,
configurable: false,
enumerable: false,
value: {},
writable: false
});
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
if(!obj.__properties__[prop]) obj.__properties__[prop] = {
value: null,
dependents: {},
listeners: []
};
if(obj[prop].__dependencies__) {
for(var i=0; i<obj[prop].__dependencies__.length; ++i) {
obj.__properties__[obj[prop].__dependencies__[i]].dependents[prop] = true;
}
delete obj[prop].__dependencies__;
}
obj.__properties__[prop].value = obj[prop];
delete obj[prop];
(function (prop) {
Object.defineProperty(obj, prop, {
get: function () {
return obj.__properties__[prop].value;
},
set: function (newValue) {
var oldValue = obj.__properties__[prop].value;
if(oldValue !== newValue) {
var oldDepValues = {};
for(var dep in obj.__properties__[prop].dependents) {
if(obj.__properties__[prop].dependents.hasOwnProperty(dep)) {
oldDepValues[dep] = obj.__properties__[dep].value();
}
}
obj.__properties__[prop].value = newValue;
for(var i=0; i<obj.__properties__[prop].listeners.length; ++i) {
obj.__properties__[prop].listeners[i](oldValue, newValue);
}
for(dep in obj.__properties__[prop].dependents) {
if(obj.__properties__[prop].dependents.hasOwnProperty(dep)) {
var newDepValue = obj.__properties__[dep].value();
for(i=0; i<obj.__properties__[dep].listeners.length; ++i) {
obj.__properties__[dep].listeners[i](oldDepValues[dep], newDepValue);
}
}
}
}
}
});
})(prop);
}
}
return obj;
}
function listen(obj, prop, callback) {
if(!obj.__properties__) throw 'object is not observable';
obj.__properties__[prop].listeners.push(callback);
}
observable(person);
listen(person, 'fullName', function(oldValue, newValue) {
console.log('Name changed from "'+oldValue+'" to "'+newValue+'"');
});
person.lastName = 'Reynolds';
Which logs:
Name changed from "Ryan Gosling" to "Ryan Reynolds"
The only problem I see is with defining methods such as fullName() on the person object which would depend on the other two properties. This requires a little extra markup on the object to allow developers to specify the dependency.
Other than that, are there any downsides to this approach?
JsFiddle
advent of getters and setters in JS 1.8.5 - are there any downsides to this approach?
You don't capture any property changes apart from the observed ones. Sure, this is enough for modeled entity objects, and for anything else we could use Proxies.
It's limited to browsers that support getters/setters, and maybe even proxies. But hey, who does care about outdated browsers? :-) And in restricted environments (Node.js) this doesn't hold at all.
Accessor properties (with getter and setter) are much slower than real get/set methods. Of course I don't expect them to be used in critical sections, and they can make code looking much fancier. Yet you need to keep that in the back of your mind. Also, the fancy-looking code can lead to misconceptions - normally you would expect property assignment/accessing to be a short (O(1)) operation, while with getters/setters there might be a lot of more happening. You will need to care not forgetting that, and the use of actual methods could help.
So if we know what we are doing, yes, we can do better.
Still, there is one huge point we need to remember: the synchronity/asynchronity (also have a look at this excellent answer). Angular's dirty checking allows you to change a bunch of properties at once, before the event fires in the next event loop turn. This helps to avoid (the propagation of) semantically invalid states.
Yet I see the synchronous getters/setters as a chance as well. They do allow us to declare the dependencies between properties and define the valid states by this. It will automatically ensure the correctness of the model, while we only have to change one property at a time (instead of changing firstName and fullName all the time, firstName is enough). Nevertheless, during dependency resolving that might not hold true so we need to care about it.
So, the listeners that are not related to the dependencies management should be fired asynchronous. Just setImmediate their loop.
I can't help but notice there are two seemingly useless functions in the source code of jQuery (For v1.9.1, it's line 2702 and line 2706):
function returnTrue() {
return true;
}
function returnFalse() {
return false;
}
Which both are called quite often within jQuery. Is there a reason why they don't simply substitute the function call with a boolean true or false?
If an object property, function argument, etc expects a function you should provide a function not a boolean.
For example in vanilla JavaScript:
var a = document.createElement("a");
a.href = "http://www.google.com/";
/*
* see https://developer.mozilla.org/en-US/docs/DOM/element.onclick
* element.onclick = functionRef;
* where functionRef is a function - often a name of a function declared
* elsewhere or a function expression.
*/
a.onclick = true; // wrong
a.onclick = returnTrue; // correct
a.onclick = function() { return true; }; // correct
Also, writing:
someProperty: returnTrue,
Is more convenient than writing:
someProperty: function(){
return true;
},
Especially since they are called quite often.
it was used like this:
stopImmediatePropagation: function() {
this.isImmediatePropagationStopped = returnTrue;
this.stopPropagation();
}
here isImmediatePropagationStopped is a query method. used like this event.isImmediatePropagationStopped()
of course, you can define a instance method, like:
event.prototyoe.isImmediatePropagationStopped = function() { return this._isImmediatePropagationStopped };
stopImmediatePropagation: function() {
this._isImmediatePropagationStopped = true; //or false at other place.
this.stopPropagation();
}
but you have to introduce a new instance property _isImmediatePropagationStopped to store the status.
with this trick, you can cut off bunch of instance properties for hold true/false status here, like _isImmediatePropagationStopped, _isDefaultPrevented etc.
so that, in my opinion, this is just a matter of code style, not right or wrong.
PS: the query methods on event, like isDefaultPrevented , isPropagationStopped, isImmediatePropagationStopped are defined in DOM event level 3 sepc.
spec: http://www.w3.org/TR/2003/NOTE-DOM-Level-3-Events-20031107/events.html#Events-Event-isImmediatePropagationStopped
I don't really know how to explain this but I'll show you code and tell you what I'd like to achieve.
Let's say I make a quick object:
var test = {};
And then I set a property to it: (I insist on the syntax, it mustn't use any function as the setter)
test.hello = 'world';
Pretty simple, eh? Now I'd like to add a function to that object that would get called everytime a new property gets set. Like this:
var test = {
newPropertyHasBeenSet: function(name){
console.log(name + 'has been set.');
}
};
test.hello = 'world';
// Now newPropertyHasBeenSet gets called with 'hello' as an argument.
// hello has been set.
I don't know if it's possible, but that would be quite amazing. Anyone has an idea of how to achieve so?
EDIT: I'd like also to be able to do the same for property get (so test.hello would call get('hello') for example).
EDIT2: This is for server-side javascript using node.js.
Thanks a lot and have a nice day!
try this example in chrome (as mentioned in previous comments it uses ES6 Proxy):
var p = new Proxy(
{},
{
get: function(obj, name) {
console.log('read request to ' + name + ' property');
if (name == 'test_test') return 1234;
else return 'Meh';
},
set: function(obj, name, value) {
console.log('write request to ' + name + ' property with ' + value + ' value');
},
}
);
console.log(p.test_test);
console.log(p.test);
p.qqq = 'test';
result:
read request to test_test property
1234
read request to test property
Meh
write request to qqq property with test value
var test = {};
Object.defineProperty(test, "hello", {
get : function () {
return this._hello;
},
set : function (val) {
alert(val);
this._hello = val;
}
});
test.hello = "world";
Something like that. But it will not work on old browsers.
You can find more options here: http://robertnyman.com/javascript/javascript-getters-setters.html
If you really insist on keeping the test.hello = "world" syntax to detect changes for existing properties, then you'll have to wait a few years for Object.watch to become part of the next EcmaScript standard.
Luckily, you can do the same in EcmaScript 5 using Object.defineProperty. Eli Grey made a nice Object.watch polyfill which you can call like this:
var test = {};
test.watch("hello", function(propertyName, oldValue, newValue) {
console.log(propertyName + " has been set to " + newValue);
});
test.hello = "world"; // triggers the watch handler
You could modify his code to trigger a different handler inside the getter as well, so you can detect property accesses.
Unfortunately, browser support is limited to modern browsers including Internet Explorer 9, Firefox 4, Chrome, Opera 12 and Safari 5.
If you want to trigger a handler when a new property is set, you'll have even more trouble. The best you could do is wrapping your object inside a proxy and placing a set trap. You can then detect whether the property already existed by testing if this.getOwnPropertyDescriptor(name) returns a 'truthy' value. The Proxy API is very experimental though and only a few browsers provide a prototype implementation to play with. You'll probably have to wait quite a while to get a completed API with decent browser support.
you need a library that provides key-value observing and bindings.
ember-metal is one such library.
basically you create objects, and you can register observers on properties of those objects.
var obj = Em.Object.create({
val: null
valDidChange:function(){...}.observes('val')
});
valDidChange will fire whenever val property changes, so
obj.set('val', 'newValue');
will cause the observer to fire.
What about something like this? Here's a jsfiddle.
var objectManager = function(obj, setCallback){
this.obj = obj;
this.setCallback = setCallback;
};
objectManager.prototype.setProperty = function(prop, value){
this.obj[prop] = value;
this.setCallback(prop);
};
objectManager.prototype.getObj = function(){
return this.obj;
};
// USAGE:
var testMgr = new objectManager({}, function(prop){
console.log(name + ' has been set.');
});
testMgr.setProperty("hello", "world"); //should log "hello has been set.";