Using getter / setter for dynamic object properties - javascript

I have an object called status where I want to keep track of any status of a class.
Beside setting various statuses I also want to keep track of how long these have been active. Now instead of defining a second property for every status to track the time, this sounded like a job for getter / setter.
That's where I'm stuck. How do I make them dynamic so they trigger for each property of status?
var Person = function(options) {
this.name = options.name;
var _statusChanged = {};
var _status = {};
// How to make this dynamic?
var expr = "isOnfire";
this.status = {
get [expr]() {
console.log(_statusChanged);
return _status[expr];
},
set [expr](val) {
_status[expr] = val;
_statusChanged[expr] = new Date();
return _status[expr];
}
};
};
var John = new Person({
name: "John"
});
John.status.isOnfire = true;
John.status.hasPinkShirt = true;
console.log(John, John.status.isOnfire, John.status.hasPinkShirt);

If you have a list of these, just create the getters/setters in a loop, e.g.:
this.status = {};
["isOnFire", "hasPinkShirt"].forEach((name) => {
Object.defineProperty(status, name {
get() {
console.log(_statusChanged);
return _status[name];
},
set(val) {
_status[name] = val;
_statusChanged[name] = new Date();
return _status[name];
}
});
});
If they could be anything, then you'll want to use a Proxy object. With a proxy, you can capture all gets/sets without knowing property names in advance:
this.status = new Proxy(_status, {
get(target, propKey, receiver) {
// handle get
return _status[propKey];
},
set(target, propKey, value, receiver) {
// handle set
_status[propKey] = value;
_statusChanged[propKey] = new Date();
return true; // Tells the proxy the assignment worked
}
});
(Or you might use Reflect.get and Reflect.set, but even Firefox doesn't have them yet.)
Here's an article going into proxies in more detail.
Here's an example, but you'll need to run it in a recent version of Firefox because support or Proxy in the wild is still really thin on the ground, and by their nature, you can't shim/polyfill proxies.
(function() {
"use strict";
var _status = {};
var _statusChanged = {};
var status = new Proxy(_status, {
get(target, propKey, receiver) {
snippet.log(propKey + " requested");
return _status[propKey];
},
set(target, propKey, value, receiver) {
snippet.log(propKey + " set to " + value);
_status[propKey] = value;
_statusChanged[propKey] = new Date();
return true; // Tells the proxy the assignment worked
}
});
status.foo = "bar";
snippet.log("foo = " + status.foo);
})();
<!-- Script provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>
Until you can use them, you'll need to make setting a status a method call, not an assignment.

You need an object called an ECMAScript 6 Proxy. In Firefox, they're on by default. At one point they were implemented in Chrome under "experimental JavaScript" but they seem to have been removed temporarily; see this ES6 compatibility table.
This code works in Firefox:
var output = function(text) {
var line = document.createElement('div');
line.innerHTML = text;
document.getElementById('output').appendChild(line);
}
var Person = function(options) {
this.name = options.name;
var _status = {};
var _statusChanged = {};
this.status = new Proxy(_status,{
get: function(target,property) {
return target[property];
},
set: function(target,property,value) {
_statusChanged[property] = new Date();
output("set " + property + " to " + value + " at " + _statusChanged[property]);
_status[property] = value;
}
});
this.show = function(property) {
output("Property " + property + " is " + _status[property] + " since " + _statusChanged[property]);
}
};
var John = new Person({
name: "John"
});
John.status.isOnfire = true;
John.status.hasPinkShirt = true;
John.show("isOnfire");
John.show("hasPinkShirt");
<div id="output"></div>

Maybe that would work for you
http://jsfiddle.net/oksbLyqf/16/
var Person = function (options) {
this.name = options.name;
var _statusChanged = {};
var _status = {};
var expr = '';
var addStatusProperty = function (prop) {
expr = prop;
Object.defineProperty(otherStatus, expr, {
get: function () {
console.log(_statusChanged);
return _status[expr];
},
set: function (val) {
_status[expr] = val;
_statusChanged[expr] = new Date();
return _status[expr];
}
});
};
var setStatusProperty = function (prop, val) {
expr = prop;
if (_status[expr]) {
otherStatus[expr] = val;
return _status[expr];
} else {
addStatusProperty(expr);
otherStatus[expr] = val;
return _status[expr];
}
};
var getStatusProperty = function (prop) {
expr = prop;
return _status[expr]
};
this.status = {
addProperty: addStatusProperty,
setProperty: setStatusProperty,
getProperty: getStatusProperty
};
var otherStatus = this.status;
};
var John = new Person({
name: "John"
});
John.status.setProperty('isOnfire', true);
John.status.setProperty('hasPinkShirt', true);
console.log(John, John.status.getProperty('isOnfire'), John.status.getProperty('hasPinkShirt'));

Related

Find name of property owner in javascript

An example from some javascript course that I am following:
var Tornado = function(name, cities, degree) {
this.name = name;
this.cities = cities;
this.degree = degree;
};
Tornado.prototype = {
nCities: function() {
return this.cities.length
},
valueOf: function() {
return this.nCities() * this.degree;
},
toString: function() {
return this.cities[0][0].toString() + " " + this.name;
}
}
cities = [["Washington", 1], ["Rotterdam", 2]]
var x = new Tornado("crazy", cities, 3)
console.log(x.nCities())
console.log(x.valueOf())
console.log(x + 16)
console.log(x.toString() + "... wow!")
Object.prototype.findOwnerOfProperty = function(propName) {
var currentObject = this;
while(currentObject !== null) {
if(currentObject.hasOwnProperty(propName)) {
return currentObject;
} else {
currentObject = currentObject.__proto__;
}
}
return "No property found!";
};
console.log(x.findOwnerOfProperty("toString"));
The findOwnerOfProperty function returns the object where the property is defined. This is nice, but it would be nicer to also have the name of that object (Tornado.prototype in this example), how can I do that?
No built-in solution. but you can make a property
this._constructor = arguments.callee.name;
inside Tornado function and make a
getConstructor:function(){
return this._constructor;
}
inside prototype.
BTW , forgot to mention that you should remake
var Tornado = function
to:
function Tornado

Can't enumerate getters/setters properties

I am working on some reflections code to try to scrape out properties and functions, but I can't seem to get the getters/setters at all.
The reflection code I have for properties is:
Reflector = function() { };
Reflector.getProperties = function(obj) {
var properties = [];
var proto = obj;
while (proto != Object.prototype) {
console.log('Scrapping proto: ', proto);
for (var prop in proto) {
console.log('typeof ' + prop + ": ", typeof obj[prop]);
if (typeof obj[prop] != 'function') {
properties.push(prop);
}
}
proto = Object.getPrototypeOf(proto);
}
return properties;
};
And a sample of it running (with my debug messages) is:
var SimpleTestObject = function() {
this.value = "Test1";
this._hiddenVal = "Test2";
this._readOnlyVal = "Test3";
this._rwVal = "Test4";
};
SimpleTestObject.prototype = {
get readOnlyVal() {
return this._readOnlyVal;
},
get rwVal() {
return this._rwVal;
},
set rwVal(value) {
this._rwVal = value;
},
func1: function() {
// Test
}
};
SimpleTestObject.func2 = function(test) { /* Test */ };
SimpleTestObject.outsideVal = "Test5";
var props = Reflector.getProperties(SimpleTestObject);
console.log('props: ', props);
console.log('Object.getOwnPropertyNames: ', Object.getOwnPropertyNames(SimpleTestObject));
console.log('rwVal property descriptor: ', Object.getOwnPropertyDescriptor(SimpleTestObject, 'rwVal'));
console.log('rwVal (2) property descriptor: ', Object.getOwnPropertyDescriptor(Object.getPrototypeOf(SimpleTestObject), 'rwVal'));
What I expect to see as output to my Reflection.getProperties(SimpleTestObject) is ['readOnlyVal', 'rwVal', 'outsideVal'], but instead I am only seeing outsideVal. Further, when I tried to using getOwnPropertyDescriptor() to see if the rwVal was enumerable, it came back as undefined. So, thinking maybe it somehow got showed into the prototype above, I tried going up a level and still got undefined.
For enumerate the getters please use Object.keys or Object.getOwnPropertiesNames on prototype instead of constructor or/and instance:
function readGetters(obj) {
var result = [];
Object.keys(obj).forEach((property) => {
var descriptor = Object.getOwnPropertyDescriptor(obj, property);
if (typeof descriptor.get === 'function') {
result.push(property);
}
});
return result;
}
var SimpleTestObject = function() {
this.value = "Test1";
this._hiddenVal = "Test2";
this._readOnlyVal = "Test3";
this._rwVal = "Test4";
};
SimpleTestObject.prototype = {
get readOnlyVal() {
return this._readOnlyVal;
},
get rwVal() {
return this._rwVal;
},
set rwVal(value) {
this._rwVal = value;
},
func1: function() {
}
};
SimpleTestObject.func2 = function(test) { /* Test */ };
SimpleTestObject.outsideVal = "Test5";
// For constructor
console.log(readGetters(SimpleTestObject.prototype));
// For instance
var instance = new SimpleTestObject();
console.log(readGetters(Object.getPrototypeOf(instance)));
you can enumerate setter/getter properties by Object.getOwnPropertyNames if you use getter and setter with Object.defineProperty or Object.defineProperties
const _name = Symbol();
const _age = Symbol();
class Dog {
constructor(name, age) {
Object.defineProperties(this, {
name: {
// you can set enumerable true explicitly if you want
//enumerable:true ,
set(value) {
this[_name] = name;
},
get() {
return this[_name];
}
},
age: {
set(value) {
this[_age] = age;
},
get() {
return this[_age];
}
},
book: {
get() {
return "Book"
}
}
});
this.name = name;
this.age = age;
}
}
const dog = new Dog("spike", 3);
console.log(Object.getOwnPropertyNames(dog));

Javascript, implementing custom Object.Create

I need to implement inheritance tree in JavaScript where each node can have more than 1 parent. We have to implement Object.Create and Object.call methods on our own. We are specifically not allowed to use new keyword. Here is what I have so far:
var myObject = {
hash:0,
parents: [],
create: function(args){
//TODO check if not circular
if(args instanceof Array){
for(i=0;i<args.length;i++){
this.parents.push(args[i]);
}
}
return this;
},
call : function(fun,args){
//TODO: dfs through parents
return this[fun].apply(this,args);
},
}
var obj0 = myObject.create(null);
obj0.func = function(arg) { return "func0: " + arg; };
var obj1 = myObject.create([obj0]);
var obj2 = myObject.create([]);
obj2.func = function(arg) { return "func2: " + arg; };
var obj3 = myObject.create([obj1, obj2]);
var result = obj0.call("func", ["hello"]);
alert(result);
//calls the function of obj2 istead of obj0
The problem with this code is that I get a call to obj2's function instead of obj0's. I'm suspecting that create() function should not return this, but something else instead (create instance of itself somehow).
In your current solution, you are not actually creating a new object with your myObject.create() function, you are just using the same existing object and resetting it's parent array. Then, when you define .func() you are overriding that value, which is why func2: appears in your alert.
What you need to do is actually return a brand new object. returning this in your myObject.create() will just return your existing object, which is why things are getting overridden.
To avoid using the new keyword, you'll want to do either functional inheritance or prototypal inheritance. The following solution is functional inheritance:
function myObject (possibleParents) {
//create a new node
var node = {};
//set it's parents
node.parents = [];
//populate it's parents if passed in
if (possibleParents) {
if (possibleParents instanceof Array) {
for (var index = 0; index < possibleParents.length; index++) {
node.parents.push(possibleParents[index]);
}
} else {
node.parents.push(possibleParents);
};
}
//
node.call = function(fun,args) {
return this[fun].apply(this,args);
};
return node;
};
var obj0 = myObject();
obj0.func = function(arg) { return "func0: " + arg; };
var obj1 = myObject([obj0]);
var obj2 = myObject();
obj2.func = function(arg) { return "func2: " + arg; };
var obj3 = myObject([obj1, obj2]);
var result = obj0.call("func", ["hello"]);
alert(result); // this will successfully call "func0: " + arg since you created a new object
I managed fix this problem only by using function instead of variable.
function myObject () {
this.parents = [];
this.setParents = function(parents){
if(parents instanceof Array){
for(i=0;i<parents.length;i++){
this.parents.push(parents[i]);
}
}
};
this.call = function(fun,args) {
return this[fun].apply(this,args);
};
}
var obj0 = new myObject();
obj0.func = function(arg) { return "func0: " + arg; };
var obj2 = new myObject();
obj2.func = function(arg) { return "func2: " + arg; };
var result = obj0.call("func", ["hello"]);
alert(result);

Getter in object isn't returning a value Javascript

I have a problem with return a value from an object.
my object looks like this.
function XYZ(date, startT)
{
var _date=date;
var _startT=startT;
this.get_date = function() {
return _date;
};
this.set_date = function(value) {
_date=value;
};
this.get_startT = function() {
return _startT;
};
this.set_startT = function(value) {
_startT=value;
};
this.toString()
return (_date + " " _startT);
}
then i create an Array like this
jsData[0] =new XYZ("2012-11-11","8:00");
jsData[1] = new XYZ("2012-03-03","8:00");
when i want to use get_date method it didn't return me the value but the get_startT method works fine.
When i show object with .toString method it also show me full object
Please help.
It works if you fix all the syntax errors:
function XYZ(date, startT) {
var _date=date;
var _startT=startT;
this.get_date = function() {
return _date;
};
this.set_date = function(value) {
_date=value;
};
this.get_startT = function() {
return _startT;
};
this.set_startT = function(value) {
_startT=value;
};
}
var jsData = [];
jsData[0] = new XYZ("2012-11-11","8:00");
jsData[1] = new XYZ("2012-03-03","8:00");
display("jsData[0].get_date() = " + jsData[0].get_date());
Output:
jsData[0].get_date() = 2012-11-11
Live Copy | Source
Other than obvious typos, here's what I did:
Put { and } around the function body.
Removed the this.toString() which was non-functional (a no-op, as you didn't store the result anywhere).
Removed the return at the end, because returning a string primitive out of a constructor function is another no-op.
Declared jsData.
Initialized jsData.
You appear to be missing a opening bracket { after
function XYZ(date, startT)
And one at the end of your code. (})
Try adding methods to the function prototype like this:
function XYZ(date, startT) {
this._date = date;
this._startT = startT;
}
XYZ.prototype.get_date = function() {
return this._date;
}
XYZ.prototype.set_date = function(value) {
this._date = value;
}
XYZ.prototype.get_startT = function() {
return this._startT;
}
XYZ.prototype.set_startT = function(value) {
this._startT = value;
}
XYZ.prototype.toString = function() {
return this._date + " " + this._startT;
}
var myXYZ = new XYZ("2012-11-11","8:00");
myXYZ.toString(); // "2012-11-11 8:00"
I tested that in the console and it outputs the final string correctly.

override .toString() and return as object

is it possible in JavaScript?
Something like:
Response.Cookies =
function() {
return document.cookie;
};
Response.Cookies.toString =
function() {
Cookies = {};
this().replace(/([^=]+)=([^;]+);?/g,
function(foo, label, value) {
return Cookies[label] = value;
});
return Cookies;
};
alert(Response.Cookies); // "does not work"
This is what i think you want:
var Response = {};
Response.Cookie =
function() {
var self = this;
Cookies = {};
this.Cookies = document.cookie;
this.Cookies = this.Cookies.replace(/([^=]+)=([^;]+);?/g,
function(foo, label, value) {
Cookies[label] = value;
});
this.Cookies = Cookies;
};
Response.Cookie();
for(var cookie in Response.Cookies){
alert(cookie + ' = ' +Response.Cookies[cookie])
}
Fiddle: http://jsfiddle.net/maniator/Yb8NK/
UPDATE:
This is a version without ever calling the Response.Cookie(): http://jsfiddle.net/maniator/Yb8NK/25/
var Response = {
Cookies: null
}
Response.Cookie = (function() {
var self = Response;
Cookies = {};
self.Cookies = document.cookie;
self.Cookies = self.Cookies.replace(/([^=]+)=([^;]+);?/g,
function(foo, label, value) {
Cookies[label] = value;
});
self.Cookies = Cookies;
})();
for (var cookie in Response.Cookies) {
alert(cookie + ' = ' + Response.Cookies[cookie])
}
UPDATE #2:
Even better version:
var Response = {};
Response.Cookies = (function() {
var cookies = {};
var doc_cookies = document.cookie;
doc_cookies = doc_cookies.replace(/([^=]+)=([^;]+);?/g,
function(foo, label, value) {
cookies[label] = value;
});
return cookies;
})();
for (var cookie in Response.Cookies) {
alert(cookie + ' = ' + Response.Cookies[cookie])
}
Fiddle: http://jsfiddle.net/maniator/Yb8NK/29/
No, toString() needs to return a string, otherwise the implicit conversion of objects to strings (as it is being performed by alert()) simply fails. What are you trying to achieve?
Try invoking Response.Cookies, rather than just referencing it:
alert(Response.Cookies());

Categories

Resources