In Firefox, I've got several objects that I need to trigger an event when a particular property of each is changed. I'm using object.watch(), however when I return the value of the property that was changed using "this", it returns the old value the first time, and "undefined" the second and subsequent times:
var myObject = {
"aProperty": 1
};
function propChanged(prop) {
alert(prop);
}
myObject.watch("aProperty", function () {
propChanged(this.aProperty);
});
myObject.aProperty = 2;//alerts "1"
myObject.aProperty = 3;//alerts "undefined"
The reason I can't just say alert(myObject.aProperty) is because this is meant to be a dynamic code that will apply the event handler to several, possibly unknown objects.
I'm just unsure exactly how to dynamically get the new value of the property using the watch method. I'm setting up a prototype for IE for this, so I'm not worried about it not working there. I just need to understand "this" and how it applies to the watch method's owner.
Edit>>
Here's the new code I'm using for cross browser, including the IE et al prototype:
var myObject = {};
if (!Object.prototype.watch) {
Object.prototype.watch = 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
if (Object.defineProperty) // ECMAScript 5
Object.defineProperty(this, prop, {
get: getter,
set: setter
});
else if (Object.prototype.__defineGetter__ && Object.prototype.__defineSetter__) { // legacy
Object.prototype.__defineGetter__.call(this, prop, getter);
Object.prototype.__defineSetter__.call(this, prop, setter);
}
}
};
}
if (!Object.prototype.unwatch) {
Object.prototype.unwatch = function (prop) {
var val = this[prop];
delete this[prop]; // remove accessors
this[prop] = val;
};
}
function propChanged(t, p, o, n) {
alert(o);
}
Object.defineProperty(myObject, "aProperty", {value: 2,
writable: true,
enumerable: true,
configurable: true});
myObject.watch("aProperty", propChanged);
myObject.aProperty = 3; //alerts 3
myObject.aProperty = 4; //alerts 4 (n is undefined in propChanged?
You need to return the value you want the property to have from the function you pass to watch.
myObject.watch("aProperty", function (prop, oldval, newval) {
propChanged(newVal);
return newVal;
});
should do it.
See the MDN docs for a full detail of the function but the relevant bit is
Watches for assignment to a property named prop in this object, calling handler(prop, oldval, newval) whenever prop is set and storing the return value in that property. A watchpoint can filter (or nullify) the value assignment, by returning a modified newval (or by returning oldval).
EDIT
Your edited code might work better this way
Object.prototype.watch = function (prop, handler) {
var fromPrototype = !Object.hasOwnProperty.call(this, prop),
val = this[prop],
getter = function () {
return fromPrototype ? Object.getPrototypeOf(this)[prop] : val;
},
setter = function (newval) {
fromPrototype = false;
return val = handler.call(this, prop, val, newval);
};
if (delete this[prop]) { // can't watch constants
if (Object.defineProperty) { // ECMAScript 5
Object.defineProperty(this, prop, {
get: getter,
set: setter,
configurable: true,
enumerable: true
});
} else if (Object.prototype.__defineGetter__ && Object.prototype.__defineSetter__) { // legacy
Object.prototype.__defineGetter__.call(this, prop, getter);
Object.prototype.__defineSetter__.call(this, prop, setter);
}
}
};
Related
Let there be an object userSingleton defined as such:
var userSingleton = new function() {
var _user = undefined;
Object.defineProperty(this, 'activeUser', {
get: function() {
console.log("Getter called, done something cool");
return _user;
},
set: function(val) {
console.log("Setter called, do something cooler");
_user = val;
}
});
}
Now if I go to use it, userSingleton.activeUser = {name: 'John Doe'}; works great! I get a "Setter called, do something cooler".
However, if I try to do userSingleton.activeUser.name = 'John Doe'; I instead get a "Getter called, done something cool" and userSingleton._user is not updated.
What's happening is it's trying to set the name property of the object returned by the getter (userSingleton.activeUser).
How do I make it call a particular function when any (unknown at definition time) property is assigned to / modified?
A revisited solution Proxy based with a deeply nested example (NB: ECMA Script 2015):
var handler = {
get: function (target, key) {
return target[key];
},
set: function (target, key, value) {
do_action(target, key, value);
if (typeof value === 'object') {
target[key] = new Proxy(value, handler);
} else {
target[key] = value;
}
}
};
function do_action(target, key, value) {
console.log("firing action on:", key, value)
}
function singletonUser() {
if (!this._singleton) {
const _user = {}
this._singleton = {
activeUser: new Proxy(_user, handler)
};
}
return this._singleton;
}
var userSingleton = singletonUser();
userSingleton.activeUser.name = 'pippo';
userSingleton.activeUser.age = 10;
// a deeply nested example
userSingleton.activeUser.github = {};
userSingleton.activeUser.github.followers = ["gino", "pino"]
Following is a sample of my object array.
$scope.arr = [{"A":"a","B":"b"},{"A":"c","B":"d"},{"A":"e","B":"f"},{"A":"g","B":"h"}];
Now I want trigger a function whenever property "A" value changes. Basically to get the count of "A" with a not empty value. Following are the sample scenarios need to trigger.
If value of "A" changed for a newly added object to the array.
If object removed where property "A" contains a value.
If value added to empty "A" in a object
If existing value emptied of "A" in a object
I have gone through Angular watch documentation and it is a possible solution. But my problem is how to watch for the specific property("A") in any object of array.
Appreciate any help for this.
If no possible solution in Angular, is there any alternative solution with underscore.js ?
For AngularJS, there's the $scope.$watch:
$scope.$watch("arr",function (newVal,oldVal){
console.log("value has changed from "+oldVal+" to "+newVal);
},true);
But this is super expensive.
For vanilla Javascript, here's a non-standard implementation called object.watch borrowed from here
/*
* 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;
}
});
}
It can be done using $watch.
e.g.:
$scope.$watch("arr",function (newVal,oldVal){
angular.forEach(newVal,function (val,index){
if(oldVal.indexOf(val)== -1){
//your code
}
})
},true);
Here third argument 'true' is for deep checking.
JavaScript is dynamic. Cool !
I have the following constructor function :
function Preferences () {
this._preferences = {}
}
var obj = new Preferences()
I want to achieve something like this:
>>> obj.something = 'value'
>>> this._preferences['something']
'value'
That is setting the property of the obj does not actually set it's own property but that of obj._preferences. That is I want to override the default behavior.
Is it possible ?
EDIT : I want to achieve this for all property names i.e the name of the property to be set is not already known.
Object.defineProperty(Preferences.prototype, 'something', {
get: function(){
return this._preferences.something;
},
set: function(value){
this._preferences.something = value;
}
});
should do it. It defines a property, 'something', using an accessor property instead of a data property, and will call the 'get' and 'set' functions to decide what do so when .something is accessed.
SOLUTION 1
Using Proxy object you can do something like this and handle runtime defined properties
function Preferences() {
this._preferences = {};
var prefProxy = new Proxy(this, {
get: function(target, property) {
return property in target?
target[property]:
target._preferences[property];
}
set: function(target, property, value, receiver) {
if(property in target){
target[property] = value;
} else {
target._preferences[property] = value;
}
}
});
return prefProxy;
};
SOLUTION 2
I can be wrong but i think what you are asking is solved returning _preferences
function Preferences () {
this._preferences = {};
return _preferences;
}
var obj = new Preferences()
SOLUTION 3
Using getter and setter you can redirect the property to _preferences
function Preferences () {
this._preferences = {}
Object.defineProperty(Preferences.prototype, 'something', {
get: function() {
return this._preferences['something'];
},
set: function(value) {
this._preferences['something'] = value;
}
});
}
var obj = new Preferences()
I've written a simple class (for want of a better word) in JavaScript. The class creates two accessor (get/set) properties called foo and bar, and two methods called setProperty and getProperty.
The get/set methods for the properties call getProperty and setProperty, which in turn, get and set the properties from the properties object.
I would expect to see infinite recursion here, where the properties are calling the methods, which are calling the properties, which are calling the methods...etc.
But it works...and I'm mind boggled as to how, and where the values are being stored!
Example code
var Test = (function () {
return function Test() {
var $this = this,
properties = {
foo: {
get: function () { return $this.getProperty("foo"); },
set: function (value) { $this.setProperty("foo", value); },
enumerable: false,
configurable: false
},
bar: {
get: function () { return $this.getProperty("bar"); },
set: function (value) { $this.setProperty("bar", value); },
enumerable: false,
configurable: false
}
};
Object.defineProperties(this, properties);
this.getProperty = function (name) {
console.log("get was definitely called!");
return properties[name];
};
this.setProperty = function (name, value) {
console.log("set was definitely called!");
properties[name] = value;
};
};
})();
var test = new Test();
//undefined
test.foo = "Hello World";
//set was definitely called!
//"Hello World"
test.bar = 3;
//set was definitely called!
//3
test.foo;
//get was definitely called!
//"Hello World"
test.bar;
//get was definitely called!
//3
Very interested to know why I'm not just getting a "too much recursion" error!
When you are calling defineProperties, you are creating properties in the test object, not properties in the properties object.
The properties object is not used as properties any more, only reused as storage for the property values. Setting the foo property will replace the property definition in the property object with the value.
When you set the test.foo property, that will call the setter code for the property and store the value in the properties object, but that doesn't affect the test.foo property. Once the properties are created, the object that was used to define them is not used any more.
The code is a bit confusing, as it first uses the properties object as definition for the properties, then reuses it for storage of property values. If you use two separate objects for this, it's clearer why setting a property doesn't affect the property itself:
var Test = (function () {
return function Test() {
var $this = this,
def = {
foo: {
get: function () { return $this.getProperty("foo"); },
set: function (value) { $this.setProperty("foo", value); },
enumerable: false,
configurable: false
},
bar: {
get: function () { return $this.getProperty("bar"); },
set: function (value) { $this.setProperty("bar", value); },
enumerable: false,
configurable: false
}
},
storage = {};
Object.defineProperties(this, def);
this.getProperty = function (name) {
console.log("get was definitely called!");
return storage[name];
};
this.setProperty = function (name, value) {
console.log("set was definitely called!");
storage[name] = value;
};
};
})();
I have an object, for example:
var o = {
a: 1
};
The user does:
o.a = 2;
How can I know if the o object has been modified? I cannot touch the o object so I cannot use Object.defineProperties().
Since you are in the node.js environment and thus don't have to care about crappy old JavaScript engines (i.e. old browsers) you can use Object.defineProperty() to define properties with accessor functions. This allows you to execute a custom function whenever a certain property is read/written - so you can simply log the write and e.g. store it in a separate property.
var o = {};
Object.defineProperty(o, 'a', {
get: function() {
return this.__a;
},
set: function(value) {
this.__a = value;
this.__a_changed = true;
}
});
o.__a = 1;
Whenever a value is assigned to o.a the __a_changed property will be set. Of course it would be even cleaner to execute whatever you want to do on change right in the set function - but it obviously depends on your code if you can do so in a useful way.
The easiest thing would obviously be to just check if the value is different than what you initialized it as. Another option would be to use Object.defineProperty.
var o = {};
var aValue = 2;
Object.defineProperty(o, "a", {
get: function() { return aValue; },
set: function(newValue) {
aValue = newValue;
// trigger event or set flag that it was changed
}
});
You could always wrap o with your own setter getter if the defineProperty doesn't do the job for you.
var myO = {
o: o, // original o
changed: false,
set: function (prop, val) {
this.o[prop] = val;
this[prop + "IsChanged"] = true;
},
get: function (prop) {
return this.o[prop];
}
};
myO.set("a", 2);
console.log(myO.aIsChanged); // true
A better solution would be to execute an event using something like Backbone.Events to exeute someting myO.on("change", function () { ... })
var myO = {
o: o, // original o
changed: false,
set: function (prop, val) {
this.o[prop] = val;
this.trigger("change", prop, val);
},
get: function (prop) {
return this.o[prop];
}
};
_.extend(myO, Backbone.events);
myO.on("change", function (prop, val) {
// Do stuff with the change
});
myO.set("a", 1);
I'm using the Underscore library's extend method here, fyi.
var o_backup = o.a;
if(o.a != o_backup){
// function to execute
}
Basically, you are just creating a variable which will save the a from the object o and then you're checking if it has been modified or not.
Also, you can do a setInterval() function if you want to check if it has been modified more than once.