I want to build an array of objects which look like this:
var someObject = {
id,
groupA {
propertyA: 0,
propertyB: 0,
},
groupB {
propertyA: 0,
propertyB: 0
totals {}
}
And add the following composite property:
Object.defineProperty(someObject.groupA, "propertyC",
{
get: function() {
return someObject.groupA.propertyA + someObject.groupA.propertyB;
}
});
And use the same method to add the properties:
groupB.propertyC -> groupB.propertyA + groupB.propertyB
totals.propertyA -> groupA.propertyA + groupB.propertyA
totals.propertyB -> groupA.propertyB + groupB.propertyB
totals.propertyC -> groupA.propertyC + groupB.propertyC
I got all this working by putting all this code in a function so it added someObject to an array.
But then I got to thinking that the read-only composite properties shouldn't need to be created for each object and could probably be in a prototype.
Does this make sense? And is it possible, and if so: how?
It can be done. You just need to make sure that groupA and groupB inherit from an object which has the composite property.
var proto = {};
Object.defineProperty(proto, 'propertyC', {
get : function() { return this.propertyA + this.propertyB; }
});
var someObj = {
id : '1',
groupA : Object.create(proto, {
propertyA : { value : 1 }, propertyB : { value : 2 }
}),
groupB : Object.create(proto, {
propertyA : { value : 3 }, propertyB : { value : 4 }
}),
totals : Object.create(proto, {
propertyA : { get : function() { return someObj.groupA.propertyA + someObj.groupB.propertyA; } },
propertyB : { get : function() { return someObj.groupA.propertyB + someObj.groupB.propertyB; } }
})
}
// Usage:
console.log(someObj.groupA.propertyC); // 3
console.log(someObj.groupB.propertyC); // 7
console.log(someObj.totals.propertyC); // 10
I don't know if understood well your question; but in general when you have members that you want to share across all the instances of a particular type then you should put them into the prototype of the constructor.
In your example, you're using object literal, which doesn't make it easy to do so, unless you extend the prototype of the Object constructor, which I would not recommend.
How about doing something like this:
var SomeType = function(){
this.id = 0;
this.groupA = {
propertyA: 0,
propertyB: 0
};
this.groupA = {
propertyA: 0,
propertyB: 0
};
this.total = {};
}
SomeType.prototype = {
constructor: SomeType
}
Object.defineProperty(SomeType.prototype, 'propertyC', {
get: function(){ return this.groupA.propertyA + this.groupA.propertyB }
});
Related
I'm fairly new to getters and setters and am looking for a way to listen for changes in an object to store the data immediately, without calling a Save() function everytime a value gets changed. This is how I do it right now:
var myObject = {
Data: {
enabled: true,
show: false
},
Save: function () {
//store myObject.Data to local storage
},
Load: function () {
//load data from local storage and assign it to myObject.Data
},
doSomething: function () {
myObject.Load();
if (myObject.Data.enabled) {
myObject.Data.show = true;
myObject.Save();
}
}
Now I would like to optimize this code so everytime a property in myObject.Data is changed, myObject.Save() is executed. The problem I'm experiencing is that it seems only possible to define a getter for a property that has just one value, but not for a property that is an object itself.
var myObj = {
_Data: {
a: 0,
b: 1,
c: 3
},
set Data (a) {
console.log(a);
}
};
myObj.Data.a = 2;
This obviously doesn't work since myObj.Data is not an object and doesn't have the same properties as myObj._Data.
Thanks in advance for any help.
You are likely interested in the Proxy object.
I used a very simple debounce function callHandler in order to avoid calling the onSet method dozens of times during array modifications. Otherwise, [1, 2, 3].splice(0, 1) would call the set handler once per item in the original array.
'use strict';
var myObject = {
Data: {
a: [1, 2, 3],
b: {c: ['test']}
},
Save: function() {
console.log('Save called');
},
}
function recursiveProxy(target, onSet) {
// For performance reasons, onSet will only be called one millesecond
// after the set handler has last been called.
var timeout;
function callHandler() {
clearTimeout(timeout);
timeout = setTimeout(onSet, 1);
}
var recursiveHandler = {
get: function(target, property) {
// If the property is something that could contain another object,
// we want to proxy it's properties as well.
if (typeof target[property] == 'object' && target[property] != null) {
return new Proxy(target[property], recursiveHandler);
}
return target[property];
},
set: function(target, property, value) {
console.log('Set called - queueing onSet');
callHandler();
target[property] = value;
return true;
}
}
return new Proxy(target, recursiveHandler);
}
myObject.Data = recursiveProxy(myObject.Data, myObject.Save);
myObject.Data.a.splice(0, 1);
myObject.Data.b.c[0] = 'test 2';
I believe you are looking for Defining a getter on existing objects using defineProperty
To append a getter to an existing object later at any time, use
Object.defineProperty().
var o = { a:0 }
Object.defineProperty(o, "b", { get: function () { return this.a + 1; } });
console.log(o.b) // Runs the getter, which yields a + 1 (which is 1)
For e.g:
var Data = {
enable: true,
show: false
};
Object.defineProperty(Data, 'doSomething', {
get: function() {
// get something;
},
set: function(something) {
// set something
}
});
What I essentially want to do is this:
Blog.prototype = {
set content(content) {
this.content = JSON.parse(content);
}
}
However, this results in infinite recursion.
I know I can do something like:
set content(content) {
this._content = JSON.parse(content);
},
get content() {
return this._content;
}
However, when I do JSON.stringify(blog), it doesn't include content, but includes _content, which is undesirable.
How can I go about doing this?
Make the "_content" variable non-enumerable.
Blog.prototype = {
set content(newContent) {
Object.defineProperty(this, "_content", {
value: JSON.parse(newContent),
writable: true
});
},
get content() {
return this._content;
}
};
By default, an the "enumerable" flag for an object property is false if not supplied explicitly in the call to defineProperty().
Someday the Symbol type will be universally supported, and it'd be a better choice for this because you can make a guaranteed unique property key that way. If you don't need IE support and can use Symbols:
Blog.prototype = () => {
const internalContent = Symbol("content key");
return {
set content(newContent) {
this[internalContent] = newContent;
},
get content() {
return this[internalContent];
}
};
}();
Symbol-keyed properties are ignored by JSON.stringify() so you don't have to bother with defineProperty(). The nice thing about the Symbol approach is that you don't have to worry about collisions. Every Symbol instance returned from Symbol() is distinct.
Use Set and Get with _content, and implement .toJson() to provide JSON.stringify with content instead of _content.
toJSON() {
return {
content: this._content
}
}
According to MDN .toJSON() role is:
If an object being stringified has a property named toJSON whose value
is a function, then the toJSON() method customizes JSON
stringification behavior: instead of the object being serialized, the
value returned by the toJSON() method when called will be serialized.
Using with a constructor function
function Blog() {}
Blog.prototype = {
set content(content) {
this._content = JSON.parse(content);
},
get content() {
return this._content;
},
toJSON() {
return {
content: this._content
}
}
};
var blog = new Blog();
blog.content = '{ "a": "5" }';
console.log(blog.content);
console.log(JSON.stringify(blog));
Using with ES6 class
class Blog {
set content(content) {
this._content = JSON.parse(content);
}
get content() {
return this._content;
}
toJSON() {
return {
content: this._content
}
}
};
const blog = new Blog();
blog.content = '{ "a": "5" }';
console.log(blog.content);
console.log(JSON.stringify(blog));
I was able to solve this by building off Pointy's answer:
var Blog = function () {
var content;
Object.defineProperty(this, "content", {
get: function() {
return content;
},
set: function(value) {
content = JSON.parse(value);
},
enumerable: true,
});
};
The trick here is the enumerable flag, which is false by default.
i have created a custom object that it's job is to give some more information on the data it holds...
so besides data, i have an object that is a base / "myObject" for all data i "deal" with, it will always carry its declaration name e.g Name, i also intend to have it categories itself (this is not my invention, that's kinda what namespaces are in .net c# where i feel more comfortable programming)
so this is the cause, ...some established form of common properties / values to all program data dealt with.
the problem is when it's within a collection /seq / set /array
i would like to be able to index it (in c# i heavily use indexers and enums)
where i could create an object and access it's data via it's a key.
//pName pVal (usually a data/json object could be anything)
var someRprop = Rprp("HomePhone" , { dtT: "string", dt: "0044-01...", cat "cusDat", division: "portal"; catId: 32})
function Rprp(parName, parVal) {
var cretdprp = {
pName:pPropName,
pVal: pVal,
pValAsint: parseInt(this.pVal)
};
return cretdprp;
}
now if i need to create an indexed by key collection i can't understand how i could create a matching collection-object to be able to access it via the property name.
so for instance, in the main object properties of any program i create, i have one such as "datatypes" :
//btw it has "nice" combination of Parentheses
function ProgMan(){
var Core = {
// here is where it's get little complicated for my current level of javascript
DataTypes: [
{
DateTime : Rprp("DateTime", {LenType :shortName, mxL: 35, isNumeric: false//... etc})
}
],
DataNcnsts: { I32 : "int", str: "string", Dt : "DateTime" },
HelmFactory : { }
};
return Core;
}
Edit added context of usage
TabularsWHRM: function () {
var rtWH = {
DTNDataTypes: function () {
var rtarr = new Array();
rtarr =[ Rprp("DateTime", {dateTime: this.DbLengts.pVal.vals.NameShort}),Rprp("int32", {Int32 : this.DbLengts.pVal.vals.NameShort }), Rprp("bool", { Boolean : this.DbLengts.pVal.vals.NameShort }), Rprp("objct", { Object: undefined }), Rprp("csvString", { CsvString: this.DbLengts.pVal.vals.csvString }), Rprp("string", { String: this.DbLengts.pVal.vals.string }), Rprp("Json", { Json: undefined }), Rprp("FileObj", { File: this.DbLengts.pVal.vals.NameShort })];
var tk = rtarr["DateTime"];
console.log("MyNamedkey >" + key.pName + " has Length value " + rtarr[key].pVal);
for (var key in rtarr) {
console.log("key " + key.pName + " has value " + rtarr[key].pVal.dateTime);
if (key == this.DTNDataTypes.dateTime) {
//dateTime: "DateTime", int32: "Int32", bool : "Boolean", objct: "Object",csvString: "CsvString", string : "String", Json: "Json", aFileObj: "File"}
}
}
return rtarr;
},
DbLengts: Rprp ("DbLengts",
{
vals : {
NameShort: 25,
Name: 50, NameLong: 150,
PathIO: 450, ShortDesc: 150, Desc: 450,
CommentsL1: 1000, CommentsL2: 2000, Text: 4000
,generate: function (pDTNDataTypes){
var s = pDTNDataTypes;
var rtIval = -1;
switch (s) {
case this.names.nameShort : rtIval = this.NameShort;
case this.names.name: rtIval = this.Name;
case this.names.nameLong: rtIval = this.NameLong;
case this.names.pathIO: rtIval = this.PathIO;
case this.names.shortDesc: rtIval = this.ShortDesc;
case this.names.desc: rtIval = this.Desc;
case this.names.commentsL1: rtIval = this.CommentsL1;
case this.names.commentsL2: rtIval = this.CommentsL2;
case this.names.text: rtIval = this.Text;
default: rtIval = 800;
break;
}
return parseInt(rtIval);
}
},
names :
{
nameShort : "NameShort", name : "Name", nameLong : "NameLong", pathIO : "PathIO", shortDesc : "ShortDesc", desc : "Desc", commentsL1 : "CommentsL1", commentsL2 : "CommentsL2", text : "Text"
}
})
};
return rtWH;
},
I have the following angularJS service
define(["angular"], function(Angular) {
var dataStorageService = function() {
var serviceConstructor = function() {
var _getColor = function(color) {
return this.config.categoryColorMapping.colors[color];
}
}
var serviceInstance = new serviceConstructor();
angular.extend(serviceInstance.prototype, {
config: {
numberOfMessagesDisplayed: 5,
maxTitleLength: 48,
maxPreambleLength: 140,
categoryColorMapping: {
colors : {
nyheter: '#2B2B2B',
sport: '#F59331',
underholding: '#F9B00D'
},
categories: {
nyheter: _getColor('nyheter'),
sport: _getColor('sport'),
underholding: _getColor('underholding')
}
}
},
get: function(param) {
if(this.config.hasOwnProperty(param)) {
return this.config[param];
} else {
console.warn('Playlist::configService:no "' + param + '" config found');
return false;
}
},
set: function(param, value) {
this.config[param] = value;
}
});
return serviceInstance;
};
return dataStorageService;
});
now my goal is to make public the following methods:
get
set
and I want '_getColor' method private but I want to use it within the JSON object config. When I run the code I have
"ReferenceError: _getColor is not defined"
is it possibie to achievie it this way? (to have _getColor private and use it within the JSON object within angular.extend?)
Functions can be shared and still be private, instance specific private members have to be defined in the constructor though. Since your private function doesn't need to access instance specific private members you can do the following:
define(["angular"], function(Angular) {
var dataStorageService = function() {
var serviceConstructor = function() {
}
var serviceInstance = new serviceConstructor();
//IIFE returning object that will have private members as closure
// privileged methods have to be in the same function body as the
// private fucnction
serviceInstance.prototype = (function() {
var _getColor = function(instance, color) {
return instance.config.categoryColorMapping.colors[color];
};
return {
constructor: serviceConstructor
,config: {
numberOfMessagesDisplayed: 5,
maxTitleLength: 48,
maxPreambleLength: 140,
categoryColorMapping: {
colors : {
nyheter: '#2B2B2B',
sport: '#F59331',
underholding: '#F9B00D'
},
categories: {
//since categories is a sub object of serviceinstance.categorycolormapper
// it is not possible to get the instance of serviceinstance
// at this time unless you set it in the constructor
// solution could be that each serviceinstance has it's own categorycolormaper
// and when categorycolormapper is created pass the serviceinstance instance
nyheter: _getColor(this,'nyheter'),
sport: _getColor(this, 'sport'),
underholding: _getColor(this, 'underholding')
}
}
},
get: function(param) {
if(this.config.hasOwnProperty(param)) {
return this.config[param];
} else {
console.warn('Playlist::configService:no "' + param + '" config found');
return false;
}
},
set: function(param, value) {
this.config[param] = value;
}
}
}());
return serviceInstance;
};
return dataStorageService;
});
More info on constructor functions and prototype can be found here: https://stackoverflow.com/a/16063711/1641941
Functions added to the prototype are defined outside the lexical scope of the constructor, and therefore have no access to "private" methods.
The former are shared between all instances, and the latter are per-instance. The only way to get around this is to explicitly export the (per-instance) function as a property of the instance, making it non-private.
Within the definition of serviceConstructor add following line, after definition of _getColor
serviceConstructor.prototype._getColor = _getColor ;
i have a function that loop all object properties and return value if it qualify certain condition
basically this is how i m doing
//an enum
var BillingType = Object.freeze({
PayMonthly: { key: 'Monthly', value: 1 },
PayYearly: { key: 'Yearly', value: 2 }
});
now to make it work i do this
for (var property in BillingType ) {
if (BillingType .hasOwnProperty(property)) {
if (value === BillingType [property].value) {
return BillingType [property].key;
}
}
}
it works fine but to make it generic for all enums i changed code to
getValue = function (value, object) {
for (var property in object) {
if (object.hasOwnProperty(property)) {
if (value === object[property].value) {
return object[property].key;
}
}
}
}
now when i try to call from other functions
enumService.getValue(1, 'BillingModel');
rather to loop all properties it start loop on its characters.
how can i convert string to object or m doing it totally wrong . any help will be appreciated
Regards
Your getValue looks fine, just call it using
enumService.getValue(1, BillingModel); // <-- no quotes
and here is a working fiddle: http://jsfiddle.net/LVc6G/
and here is the code of the fiddle:
var BillingType = Object.freeze({
PayMonthly: { key: 'Monthly', value: 1 },
PayYearly: { key: 'Yearly', value: 2 }
});
var getValue = function (value, object) {
for (var property in object) {
if (object.hasOwnProperty(property)) {
if (value === object[property].value) {
return object[property].key;
}
}
}
};
alert(getValue(1, BillingType));