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;
},
Related
In mapper object. I have map2 property map2 format is changed compared to others it has inner object. Due to that I put if block in my for loop to map inner object. But I feel its bad thing. If in future I add another property in mapper with nested object means. then I put another else if statement to handle this.. Any other correct way to code?
const mapper = {
map1: {
font_size: "font-size",
font_width: "font-width",
},
map2: {
"line_color": "background-color",
thickness: {
"false": "width",
"true": "height"
}
},
map3: {
border_radius: "border-radius"
},
map4 : {
border_radius: "border-radius",
font_width: "font-width"
},
map5 : {
border_radius: "border-radius",
font_size: "font-size",
}
}
function styleObjectFetch(mapObject,json) {
var x = {};
for (let eachProp of Object.keys(mapObject)) {
if (json.hasOwnProperty(eachStyleProp)) {
let value = mapObject[eachProp];
if (eachProp==="thickness") {
value = value[json.vertical];
}
x[value] = json[eachProp];
}
}
return x;
}
function fetchData(mapText,json)
{
if(mapText && mapper[mapText])
{
styleObjectFetch(mapper[mapText],json)
}
}
fetchData("map1",{
font_size : "30px",
thickness : "false"
})
I need more flexible and maintaineable code?
I have a JSON that looks like this, I get it from a PHP file that calls Yahoo Finance API,
It's the first time I see a JSON like this.
I looked everywhere but all I manage to do is console log it... I'd like to display it into a table, or a ul, with AJAX
I'd like to access everything and display just what I need or everything.
I tried a bunch of different code snippets from everywhere but couldn't make it work, in three days !...
I'm using scheb/yahoo-finance-api on packagist for that, if it helps.
Thanks for your help.
{
query: {
count: 1,
created: "2017-06-07T12:34:44Z",
lang: "en-US",
results: {
quote: {
symbol: "APLL",
Symbol: "APLL",
LastTradePriceOnly: "0.119",
LastTradeDate: "6/6/2017",
LastTradeTime: "11:13am",
Change: "+0.023",
Open: "0.119",
DaysHigh: "0.119",
DaysLow: "0.110",
Volume: "300"
}
}
}
}
$(function(){
$("#get-data").click(function() {
//do ajax here and load data
var showData = $('#show-data');
var $data = $.getJSON("data.php", function(data) {
// $data = $data.responseText;
function buildTree(data, container) {
$data.forEach(function(node) {
var el = document.createElement(node.tag);
if (Array.isArray(node.content)) {
buildTree(node.content, el);
}
else if (typeof(node.content) == 'object') {
buildTree([node.content], el);
}
else {
el.innerHTML = node.content;
}
container.appendChild(el);
});
}
console.log($data);
buildTree($data, document.body);
});
});
});
That's the one I have for now, I deleted all the others, I took it form here and modified it with no success tho..
Thank you for answering :)
literal notation, not Json.
You can go over this in a for in loop, something like this:
var x = {
query: {
count: 1,
created: "2017-06-07T12:34:44Z",
lang: "en-US",
results: {
quote: {
symbol: "APLL",
Symbol: "APLL",
LastTradePriceOnly: "0.119",
LastTradeDate: "6/6/2017",
LastTradeTime: "11:13am",
Change: "+0.023",
Open: "0.119",
DaysHigh: "0.119",
DaysLow: "0.110",
Volume: "300"
}
}
}
}
for (var key in x) {
if (!x.hasOwnProperty(key)) continue;
var obj = x[key];
for (var prop in obj) {
if(!obj.hasOwnProperty(prop)) continue;
alert(prop + " = " + obj[prop]);
}
}
Is this what you want to achieve?
// let's assume this is your data
var data = {
query: {
count: 1,
created: "2017-06-07T12:34:44Z",
lang: "en-US",
results: {
quote: {
symbol: "APLL",
Symbol: "APLL",
LastTradePriceOnly: "0.119",
LastTradeDate: "6/6/2017",
LastTradeTime: "11:13am",
Change: "+0.023",
Open: "0.119",
DaysHigh: "0.119",
DaysLow: "0.110",
Volume: "300"
}
}
}
};
// prints one li with key and value
function printTree(key, value, container) {
var li = $('<li></li>');
if (typeof value === 'object') {
// value is a nested object, create a new <ul> element for it
li.append(key + ': ');
var ul = $('<ul></ul>');
for (var index in value) {
printTree(index, value[index], ul); // call the function recursively
}
li.append(ul);
} else {
li.text(key + ': ' + value);
}
container.append(li);
}
printTree('data', data, $('#container')); // call the function for the first time
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul id="container">
This is in literal notation so I assume you've already parsed it into an object. Let's call that object myObject
var myObject={
query : {
count : 1 ,
created : "2017-06-07T12:34:44Z" ,
lang : "en-US" ,
results : {
quote : {
symbol : "APLL" , Symbol : "APLL" , LastTradePriceOnly : "0.119" , LastTradeDate : "6/6/2017" , LastTradeTime : "11:13am" , Change : "+0.023" , Open : "0.119" , DaysHigh : "0.119" , DaysLow : "0.110" , Volume : "300" } } } }
You can access properties as follows:
var myCount = myObject.query.count
console.log(myCount) // logs 1
I am not very good with my javascript but recently needed to work with a library to output an aggregated table. Was using fin-hypergrid.
There was a part where I need to insert a sum function (rollups.sum(11) in this example)to an object so that it can compute an aggregated value in a table like so:
aggregates = {Value: rollups.sum(11)}
I would like to change this value to return 2 decimal places and tried:
rollups.sum(11).toFixed(2)
However, it gives the error : "rollups.sum(...).toFixed is not a function"
If I try something like:
parseFloat(rollups.sum(11)).toFixed(2)
it throws the error: "can't assign to properties of (new String("NaN")): not an object"
so it has to be a function object.
May I know if there is a way to alter the function rollups.sum(11) to return a function object with 2 decimal places?
(side info: rollups.sum(11) comes from a module which gives:
sum: function(columnIndex) {
return sum.bind(this, columnIndex);
}
)
Sorry I could not post sample output here due to data confidentiality issues.
However, here is the code from the example I follow. I basically need to change rollups.whatever to give decimal places. The "11" in sum(11) here refers to a "column index".
window.onload = function() {
var Hypergrid = fin.Hypergrid;
var drillDown = Hypergrid.drillDown;
var TreeView = Hypergrid.TreeView;
var GroupView = Hypergrid.GroupView;
var AggView = Hypergrid.AggregationsView;
// List of properties to show as checkboxes in this demo's "dashboard"
var toggleProps = [{
label: 'Grouping',
ctrls: [
{ name: 'treeview', checked: false, setter: toggleTreeview },
{ name: 'aggregates', checked: false, setter: toggleAggregates },
{ name: 'grouping', checked: false, setter: toggleGrouping}
]
}
];
function derivedPeopleSchema(columns) {
// create a hierarchical schema organized by alias
var factory = new Hypergrid.ColumnSchemaFactory(columns);
factory.organize(/^(one|two|three|four|five|six|seven|eight)/i, { key: 'alias' });
var columnSchema = factory.lookup('last_name');
if (columnSchema) {
columnSchema.defaultOp = 'IN';
}
//factory.lookup('birthState').opMenu = ['>', '<'];
return factory.schema;
}
var customSchema = [
{ name: 'last_name', type: 'number', opMenu: ['=', '<', '>'], opMustBeInMenu: true },
{ name: 'total_number_of_pets_owned', type: 'number' },
{ name: 'height', type: 'number' },
'birthDate',
'birthState',
'employed',
{ name: 'income', type: 'number' },
{ name: 'travel', type: 'number' }
];
var peopleSchema = customSchema; // or try setting to derivedPeopleSchema
var gridOptions = {
data: people1,
schema: peopleSchema,
margin: { bottom: '17px' }
},
grid = window.g = new Hypergrid('div#json-example', gridOptions),
behavior = window.b = grid.behavior,
dataModel = window.m = behavior.dataModel,
idx = behavior.columnEnum;
console.log('Fields:'); console.dir(behavior.dataModel.getFields());
console.log('Headers:'); console.dir(behavior.dataModel.getHeaders());
console.log('Indexes:'); console.dir(idx);
var treeView, dataset;
function setData(data, options) {
options = options || {};
if (data === people1 || data === people2) {
options.schema = peopleSchema;
}
dataset = data;
behavior.setData(data, options);
idx = behavior.columnEnum;
}
// Preset a default dialog options object. Used by call to toggleDialog('ColumnPicker') from features/ColumnPicker.js and by toggleDialog() defined herein.
grid.setDialogOptions({
//container: document.getElementById('dialog-container'),
settings: false
});
// add a column filter subexpression containing a single condition purely for demo purposes
if (false) { // eslint-disable-line no-constant-condition
grid.getGlobalFilter().columnFilters.add({
children: [{
column: 'total_number_of_pets_owned',
operator: '=',
operand: '3'
}],
type: 'columnFilter'
});
}
window.vent = false;
//functions for showing the grouping/rollup capabilities
var rollups = window.fin.Hypergrid.analytics.util.aggregations,
aggregates = {
totalPets: rollups.sum(2),
averagePets: rollups.avg(2),
maxPets: rollups.max(2),
minPets: rollups.min(2),
firstPet: rollups.first(2),
lastPet: rollups.last(2),
stdDevPets: rollups.stddev(2)
},
groups = [idx.BIRTH_STATE, idx.LAST_NAME, idx.FIRST_NAME];
var aggView, aggViewOn = false, doAggregates = false;
function toggleAggregates() {
if (!aggView){
aggView = new AggView(grid, {});
aggView.setPipeline({ includeSorter: true, includeFilter: true });
}
if (this.checked) {
grid.setAggregateGroups(aggregates, groups);
aggViewOn = true;
} else {
grid.setAggregateGroups([], []);
aggViewOn = false;
}
}
function toggleTreeview() {
if (this.checked) {
treeView = new TreeView(grid, { treeColumn: 'State' });
treeView.setPipeline({ includeSorter: true, includeFilter: true });
treeView.setRelation(true, true);
} else {
treeView.setRelation(false);
treeView = undefined;
delete dataModel.pipeline; // restore original (shared) pipeline
behavior.setData(); // reset with original pipeline
}
}
var groupView, groupViewOn = false;
function toggleGrouping(){
if (!groupView){
groupView = new GroupView(grid, {});
groupView.setPipeline({ includeSorter: true, includeFilter: true });
}
if (this.checked){
grid.setGroups(groups);
groupViewOn = true;
} else {
grid.setGroups([]);
groupViewOn = false;
}
}
you may try:
(rollups.sum(11)).toFixed(2)
enclosing number in parentheses seems to make browser bypass the limit that identifier cannot start immediately after numeric literal
edited #2:
//all formatting and rendering per cell can be overridden in here
dataModel.getCell = function(config, rendererName) {
if(aggViewOn)
{
if(config.columnName == "total_pets")
{
if(typeof(config.value) == 'number')
{
config.value = config.value.toFixed(2);
}
else if(config.value && config.value.length == 3 && typeof(config.value[1]) == 'number')
{
config.value = config.value[1].toFixed(2);
}
}
}
return grid.cellRenderers.get(rendererName);
};
I'm trying to use ES6 Classes to construct data models (from a MySQL database) in an API that I'm building. I prefer not using an ORM/ODM library, as this will be a very basic, simple API. But, I'm struggling to get my head around how to define these models.
My data entities are (these are just some simplified examples):
CUSTOMER
Data Model
id
name
groupId
status (enum of: active, suspended, closed)
Private Methods
_getState(status) {
var state = (status == 'active' ? 'good' : 'bad');
return state;
}
Requests
I want to be able to do:
findById: Providing a single customer.id, return the data for that specific customer, i.e. SELECT * FROM customers WHERE id = ?
findByGroupId: Providing a group.id, return the data for all the customers (in an array of objects), belonging to that group, i.e. SELECT * FROM customers WHERE groupId = ?
Response Payloads
For each customer object, I want to return JSON like this:
findById(1);:
[{
"id" : 1,
"name" : "John Doe",
"groupId" : 2,
"status" : "active",
"state" : "good"
}]
findByGroupId(2);:
[{
"id" : 1,
"name" : "John Doe",
"groupId" : 2,
"status" : "active",
"state" : "good"
},
{
"id" : 4,
"name" : "Pete Smith",
"groupId" : 2,
"status" : "suspended",
"state" : "bad"
}]
GROUP
Data Model
id
title
Requests
I want to be able to do:
findById: Providing a single group.id, return the data for that specific group, i.e. SELECT * FROM groups WHERE id = ?
Response Payloads
For each group object, I want to return JSON like this:
findById(2);:
{
"id" : 2,
"title" : "This is Group 2",
"customers" : [{
"id" : 1,
"name" : "John Doe",
"groupId" : 2,
"status" : "active",
"state" : "good"
},
{
"id" : 4,
"name" : "Pete Smith",
"groupId" : 2,
"status" : "suspended",
"state" : "bad"
}]
}
Requirements:
Must use ES6 Classes
Each model in its own file (e.g. customer.js) to be exported
Questions:
My main questions are:
Where would I define the data structure, including fields that require data transformation, using the private methods (e.g. _getState())
Should the findById, findByGroupId, etc by defined within the scope of the class? Or, should these by separate methods (in the same file as the class), that would instantiate the object?
How should I deal with the case where one object is a child of the other, e.g. returning the Customer objects that belongs to a Group object as an array of objects in the Group's findById?
Where should the SQL queries that will connect to the DB be defined? In the getById, getByGroupId, etc?
UPDATE!!
This is what I came up with - (would be awesome if someone could review, and comment):
CUSTOMER Model
'use strict';
class Cust {
constructor (custData) {
this.id = custData.id;
this.name = custData.name;
this.groupId = custData.groupId;
this.status = custData.status;
this.state = this._getState(custData.status);
}
_getState(status) {
let state = (status == 'active' ? 'good' : 'bad');
return state;
}
}
exports.findById = ((id) => {
return new Promise ((resolve, reject) => {
let custData = `do the MySQL query here`;
let cust = new Cust (custData);
let Group = require(appDir + process.env.PATH_API + process.env.PATH_MODELS + 'group');
Group.findById(cust.groupId).then(
(group) => {
cust.group = group;
resolve (cust)
},
(err) => {
resolve (cust);
}
);
});
});
GROUP Model
'use strict';
class Group {
constructor (groupData) {
this.id = groupData.id;
this.title = groupData.title;
}
}
exports.findById = ((id) => {
return new Promise ((resolve, reject) => {
let groupData = `do the MySQL query here`;
if (id != 2){
reject('group - no go');
};
let group = new Group (groupData);
resolve (group);
});
});
CUSTOMER Controller (where the Customer model is instantiated)
'use strict';
var Cust = require(appDir + process.env.PATH_API + process.env.PATH_MODELS + 'cust');
class CustController {
constructor () {
}
getCust (req, res) {
Cust.findById(req.params.id).then(
(cust) => {
res(cust);
},
(err) => {
res(err);
}
)
}
}
module.exports = CustController;
This seems to be working well, and I've been able to use Class, Promise and let to make it more ES6 friendly.
So, I'd like to get some input on my approach. Also, am I using the export and required features correctly in this context?
Here is another approach,
Where would I define the data structure, including fields that require data transformation, using the private methods (e.g. _getState())
You should define those fields, relationship in your model class extending the top model. Example:
class Group extends Model {
attributes() {
return {
id: {
type: 'integer',
primary: true
},
title: {
type: 'string'
}
};
}
relationships() {
return {
'Customer': {
type: 'hasMany',
foreignKey: 'groupId'
}
};
}
}
Should the findById, findByGroupId, etc by defined within the scope of the class? Or, should these by separate methods (in the same file as the class), that would instantiate the object?
Instead of having many functions use findByAttribute(attr) in Model Example:
static findByAttribute(attr) {
return new Promise((resolve, reject) => {
var query = this._convertObjectToQueriesArray(attr);
query = query.join(" and ");
let records = `SELECT * from ${this.getResourceName()} where ${query}`;
var result = this.run(records);
// Note: Only support 'equals' and 'and' operator
if (!result) {
reject('Could not found records');
} else {
var data = [];
result.forEach(function(record) {
data.push(new this(record));
});
resolve(data);
}
});
}
/**
* Convert Object of key value to sql filters
*
* #param {Object} Ex: {id:1, name: "John"}
* #return {Array of String} ['id=1', 'name=John']
*/
static _convertObjectToQueriesArray(attrs) {
var queryArray = [];
for (var key in attrs) {
queryArray.push(key + " = " + attrs[key]);
}
return queryArray;
}
/**
* Returns table name or resource name.
*
* #return {String}
*/
static getResourceName() {
if (this.resourceName) return this.resourceName();
if (this.constructor.name == "Model") {
throw new Error("Model is not initialized");
}
return this.constructor.name.toLowerCase();
}
How should I deal with the case where one object is a child of the other, e.g. returning the Customer objects that belongs to a Group object as an array of objects in the Group's findById?
In case of relationships, you should have methods like findRelations, getRelatedRecords.
var customer1 = new Customer({ id: 1, groupId: 3});
customer1.getRelatedRecords('Group');
class Model {
...
getRelatedRecords(reln) {
var targetRelationship = this.relationships()[reln];
if (!targetRelationship) {
throw new Error("No relationship found.");
}
var primaryKey = this._getPrimaryKey();
var relatedObject = eval(reln);
var attr = {};
if (targetRelationship.type == "hasOne") {
console.log(this.values);
attr[relatedObject.prototype._getPrimaryKey()] = this.values[targetRelationship.foreignKey];
} else if (targetRelationship.type == "hasMany") {
attr[targetRelationship.foreignKey] = this.values[this._getPrimaryKey()];
}
relatedObject.findByAttribute(attr).then(function(records) {
// this.values[reln] = records;
});
}
...
}
Where should the SQL queries that will connect to the DB be defined? In the getById, getByGroupId, etc?
This one is tricky, but since you want your solution to be simple put the queries inside your find methods. Ideal scenario will be to have their own QueryBuilder Class.
Check the following full code the solution is not fully functional but you get the idea. I've also added engine variable in the model which you can use to enhance fetching mechanism. All other design ideas are upto your imagination :)
FULL CODE:
var config = {
engine: 'db' // Ex: rest, db
};
class Model {
constructor(values) {
this.values = values;
this.engine = config.engine;
}
toObj() {
var data = {};
for (var key in this.values) {
if (this.values[key] instanceof Model) {
data[key] = this.values[key].toObj();
} else if (this.values[key] instanceof Array) {
data[key] = this.values[key].map(x => x.toObj());
} else {
data[key] = this.values[key];
}
}
return data;
}
static findByAttribute(attr) {
return new Promise((resolve, reject) => {
var query = this._convertObjectToQueriesArray(attr);
query = query.join(" and ");
let records = `SELECT * from ${this.getResourceName()} where ${query}`;
var result = this.run(records);
// Note: Only support 'equals' and 'and' operator
if (!result) {
reject('Could not found records');
} else {
var data = [];
result.forEach(function(record) {
data.push(new this(record));
});
resolve(data);
}
});
}
getRelatedRecords(reln) {
var targetRelationship = this.relationships()[reln];
if (!targetRelationship) {
throw new Error("No relationship found.");
}
var primaryKey = this._getPrimaryKey();
var relatedObject = eval(reln);
var attr = {};
if (targetRelationship.type == "hasOne") {
console.log(this.values);
attr[relatedObject.prototype._getPrimaryKey()] = this.values[targetRelationship.foreignKey];
} else if (targetRelationship.type == "hasMany") {
attr[targetRelationship.foreignKey] = this.values[this._getPrimaryKey()];
}
relatedObject.findByAttribute(attr).then(function(records) {
// this.values[reln] = records;
});
}
/**
* Test function to show what queries are being ran.
*/
static run(query) {
console.log(query);
return [];
}
_getPrimaryKey() {
for (var key in this.attributes()) {
if (this.attributes()[key].primary) {
return key;
}
}
}
/**
* Convert Object of key value to sql filters
*
* #param {Object} Ex: {id:1, name: "John"}
* #return {Array of String} ['id=1', 'name=John']
*/
static _convertObjectToQueriesArray(attrs) {
var queryArray = [];
for (var key in attrs) {
queryArray.push(key + " = " + attrs[key]);
}
return queryArray;
}
/**
* Returns table name or resource name.
*
* #return {String}
*/
static getResourceName() {
if (this.resourceName) return this.resourceName();
if (this.constructor.name == "Model") {
throw new Error("Model is not initialized");
}
return this.constructor.name.toLowerCase();
}
}
class Customer extends Model {
attributes() {
return {
id: {
type: 'integer',
primary: true
},
name: {
type: 'string'
},
groupId: {
type: 'integer'
},
status: {
type: 'string'
},
state: {
type: 'string'
}
};
}
relationships() {
return {
'Group': {
type: 'hasOne',
foreignKey: 'groupId'
}
};
}
}
class Group extends Model {
attributes() {
return {
id: {
type: 'integer',
primary: true
},
title: {
type: 'string'
}
};
}
relationships() {
return {
'Customer': {
type: 'hasMany',
foreignKey: 'groupId'
}
};
}
}
var cust = new Customer({
id: 1,
groupId: 3
});
cust.getRelatedRecords('Group');
var group = new Group({
id: 3,
title: "Awesome Group"
});
group.getRelatedRecords('Customer');
var groupData = new Group({
"id": 2,
"title": "This is Group 2",
"customers": [new Customer({
"id": 1,
"name": "John Doe",
"groupId": 2,
"status": "active",
"state": "good"
}),
new Customer({
"id": 4,
"name": "Pete Smith",
"groupId": 2,
"status": "suspended",
"state": "bad"
})
]
});
console.log(groupData.toObj());
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 }
});