I'm using trying improve the readability of some code i'm writing. I pass an array of values to a function and want to be able to extract each. I'd like to check that the array is the expected size.
function useData(data) {
if (data.length != Data.Size) {
// ERROR
}
var name = data[Data.Indexes.NAME];
var age = data[Data.Indexes.AGE];
var weight = data[Data.Indexes.WEIGHT];
}
I want to know how many properties are in Data.Indexes, i've tried the following two methods,
var Data = {
Indexes : {
NAME : 0,
AGE : 1,
WEIGHT : 2
},
Size : Objects.keys(Indexes).length // Doesn't work.
};
Data.Size2 = Object.keys(Data.Indexes).length; // Works.
Can I find the size of Indexes whilst still inside Data?
You can make Size-property a function returning the number of properties in Indexes like so:
var Data = {
Indexes : {
NAME : 0,
AGE : 1,
WEIGHT : 2
},
Size : function() {return Object.keys(this.Indexes).length}
};
Now you can call it inside function useData():
function useData(data) {
if (data.length != Data.Size()) {/* ERROR */}
/* ... */
}
A second way makes Data.Size a getter-function. That way it's protected against overwriting:
var Data = {
Indexes : { /* ... */ },
get Size() {return Object.keys(this.Indexes).length
};
The getter is executed each time you refer to it:
function useData(data) {
if (data.length != Data.Size) {/* ERROR */}
/* ... */
}
Data.Size = 55; // nothing happens
Update: These ways doesn't protect Data.Indexes against changes. If you need that, have a look at Object.seal() and Object.freeze().
Related
Function Description
This function is supposed to simply replace an item in my observable array.
I am accepting 3 parameters:
findBy = Whether to locate the source index by "id" or "name" property.
cmpVal = The value we are searching the array for
objNewItem = The new item. This case ex, { id: "12" , name: "NewName" }
Then I am deleting that index from the array, and finally pushing the new object into the array.
I had no luck using the replace function from Knockout, so I had to write my own.
I realize this may be the ugliest code on the internet. That's why I am deferring to you professionals here. :)
/* Update A Station
*
* #param findBy string Property to find ( "id" or "name")
* #param cmpVal string Value to compare against
* #param objNewItem object The new object replacing old
* */
self.modifyStation = function(findBy, cmpVal, objNewItem){
var sourceIndex;
var oldId;
/* Find Index Of Old Station */
var c = -1;
ko.utils.arrayForEach(this.station(), function(item) {
c++;
switch (findBy) {
case "id":
var value = item.id();
if(value == cmpVal){
sourceIndex = c;
oldId = item.id();
}
break;
case "name":
var value = item.name();
if(value == cmpVal){
sourceIndex = c;
oldId = item.id();
}
break;
}
});
/* Remove Old */
self.station().splice(sourceIndex,1);
/* Insert New Station
* [For Now] not allowing updating of ID. Only
* can update the other properties (yes, I realize that
* only leaves "name", but more will be added )
*/
objNewItem.id = oldId; // Put old ID back in
self.station.push(objNewItem);
}
Note: I am not allowing them to edit the ID for now.
Can anyone help me clean this up? I am smart enough to know its not efficient, but I don't know how else to optimize it.
Any advice would be appreciated. Thank you!
John
Ok so...
First of we will create a function that will contain all the logic, from this example on you can do anything with it. The most proper way would be to extend your 'observableArray' directly on knockout, but i am not going to get this one this far now :P
function ReplaceInObservableArray(obsArray, prop, cmpVal, newItem){
// use the fact that you can get the value of the property by treating the object as the dictionary that it is
// so you do not need to program for specific properties for different array model types
var foundItems = obsArray().filter(function(i){
return ko.utils.unwrapObservable(i[prop]) == cmpVal;
});
if(foundItems.length > 1)
{
// handle what happens when the property you are comparing is not unique on your list. More than one items exists with this comparison run
// you should throw or improve this sample further with this case implementation.
} else if(foundItems.length == 0)
{
// handle what happens when there is nothing found with what you are searching with.
} else {
// replace at the same index rather than pushing, so you dont move any other items on the array
// use the frameworks built in method to make the replacement
obsArray.replace(foundItems[0], newItem);
}
}
var demoArray = ko.observableArray([{ id : 1, name : 'test1' },{id : 2, name : 'test2' },{ id : 3, name : 'test3'},{id : 4, name : 'test4'}]);
ReplaceInObservableArray(demoArray,'id', 1, {id : 1, name : 'test111'});
ReplaceInObservableArray(demoArray,'name', 'test3', {id : 3, name : 'test3333'});
console.log(demoArray());
I am having a little trouble trying to achieve something. So I have some data
let data = [
{
"ID": 123456,
"Date": "2012-01-01",
"Irrelevant_Column_1": 123,
"Irrelevant_Column_2": 234,
"Irrelevant_Column_3": 345,
"Irrelevant_Column_4": 456
},
...
]
And I wanted to remove the irrelevant columns. So someone suggested using map
data = data.map(element => ({ID: element.ID, Date: element.Date}))
The problem is, I dont want to define the columns. I have the user select the columns to keep, and assign them to a variable. I can then do something like
let selectedId = this.selectedIdCol;
The issue is, I am unable to now use this within the map. I am trying
let selectedId = this.selectedIdCol;
this.parsed_csv = data.map(element => (
{ID: element.selectedId, Date: element.Date}
));
But that does not seem to work, just returns the date. Also, my IDE is saying that the variable is unused. So how can I use the selectedId variable as part of the map function?
Thanks
You can do using Bracket notation notation and helper function
Whenever you want to use variable to access property you need to use [] notation.
let data = [{"ID": 123456,"Date": "2012-01-01","column_1": 123,"column_2": 234,"column_3": 345,"column_4": 456},{"ID": 123456,"Date": "2018-10-01", "column_1": 123,"column_2": 234,"column_3": 345,"column_4": 46},]
function selectDesired(data,propName1,propName2){
return data.map(e=> ({[propName1]: e[propName1], [propName2]: e[propName2]}))
}
console.log(selectDesired(data, 'Date', 'column_4'))
The basic technique is illustrated here, assuming that the user's selected column_name is "ID"
let data = [
{
"ID": 123456,
"Date": "2012-01-01",
"Irrelevant_Column_1": 123,
"Irrelevant_Column_2": 234,
"Irrelevant_Column_3": 345,
"Irrelevant_Column_4": 456
}
];
let column_name = "ID";
let curated = data.map(element=>({[column_name]: element[column_name]}));
console.log(curated)
If you are wanting the user to be able to multi-select their columns,(assuming data from above is still in scope)
let user_selection = ["ID","Date"];
let curated = data.map(
(element)=>
{
let item = {};
user_selection.forEach(
(property)=>
{
item[property] = element[property];
}
return item;
}
);
To set up a function that can handle multiple calling situations without having a monstrously hack-and-patched source history, set up the function's signature to receive a spread list of properties.
If you wish to extend the capabilities to accept
a csv property list
an array of property names delivered directly
an array of property names
you can assume the properties argument in the signature to be an iterable of property groupings, having the most basic grouping be a singleton.
Commentary embedded within the sample code to expound in more detail
var getProjection = (data,...properties) =>
{
//+=================================================+
// Initialize the projection which will be returned
//+=================================================+
let projection = {};
//+=================================================+
// Set up the property mapping func
//+=================================================+
let safe_assign = (source, target ,propertyDesignator)=>
{
if(source[propertyDesignator])
{
target[propertyDesignator] = source[propertyDesignator];
}
};
//+=====================================================+
// Iterate the properties list, assuming each element to
// be a property grouping
//+=====================================================+
properties.forEach(
(propertyGroup)=>
{
//+-----------------------------------------------+
// If the propertyGroup is not an array, perform
// direct assignment
//+-----------------------------------------------+
if(!Array.isArray(propertyGroup))
{
//+-------------------------------------------+
//Only map the requested property if it exists
//+-------------------------------------------+
safe_assign(data,projection,propertyGroup);
}
//+-----------------------------------------------+
// If the propertyGroup *is* an array, iterate it
// This technique obviously assumes that your
// property groupings are only allowed to be one
// level deep. This is for accommodating distinct
// calling conventions, not for supporting a deeply
// nested object graph. For a deeper object graph,
// the technique would largely be the same, but
// you would need to recurse.
//+-----------------------------------------------+
if( Array.isArray(propertyGroup))
{
propertyGroup.forEach(
(property)=>
{
safe_assign(data,projection,property);
}
}
}
);
//+===================================+
// Return your projection
//+===================================+
return projection;
};
//+--------------------------------------+
//Now let's test
//+--------------------------------------+
let data = [
{ID:1,Foo:"Foo1",Bar:"Bar1",Baz:"Inga"},
{ID:2,Foo:"Foo2",Bar:"Bar2",Baz:"Ooka"},
{ID:3,Foo:"Foo3",Bar:"Bar3",Baz:"oinga",Floppy:"Floop"},
{ID:4,Foo:"Foo4",Good:"Boi",Bar:"Bar3"Baz:"Baz"}
];
//***************************************
//tests
//***************************************
var projection1 = getProjection(data.find(first=>first),"ID","Baz"));//=>{ID:1,Baz:"Inga"}
var projection2 = getProjection(data[0],["ID","Baz"]);//=>{ID:1,Baz:"Inga"}
var projection3 = getProjection(data[0],...["ID","Baz"]);//=>{ID:1,Baz:"Inga"}
var user_selected_properties = ["ID","Good","Baz"];
var projections = data.map(element=>getProjection(element,user_selected_properties));
//+=====================================+
// projections =
// [
// {ID:1,Baz:"Inga"},
// {ID:2,Baz:"Ooka"},
// {ID:3,Baz:"oinga"},
// {ID:4,Good:"Boi",Baz:"Baz"}
// ];
//+=====================================+
I have an object as follows:
var obj = {
parentKey : {
nestedKey1 : value,
nestedKey2 : function () {
return parentKey + nestedKey2;
}
}
};
Is there a way to use the values within the actual name of the parentKey and nestedKey1 and/or nestedKey2 in a function held in one of the nested key-value pairs as the one above?
In my actual scenario the keys are all numerical values I wish to use as criteria for a for loop.
Updated Scenario To Question
As an exercise to learn JavaScript I decided to code the logic of an elevator system. To do this I have made an object which represents the building and this object contains each floor, nested within each floor are the data of the number of people wishing to travel to another floor as follows:
var floorData = {
<floor number> : {
<people going to floor X> : <number of people>,
<people going to floor Y> : <number of people>,
peopleGoingUp : <formula that sums amount of people going up>,
peopleGoingDown : <formula that sums amount of people going down>
}
};
I want to include within each <floor number> object a property that sums the amount of peopleGoingUp and peopleGoingDown by means of a formula. For this formula to work as I intend I need to access the <floor number> name which is a numeric value from within the peopleGoingUp and peopleGoingDown formulas.
Here is my working example thus far and I have included peopleGoingUp and peopleGoingDown formulas in floor theBuilding[2]. What I wish for is to change the hand entered value of theParentKey to equal the name of the parent object.
// Total number of floors in building global object
var totalFloors = 3;
// Building object including amount of people waiting to take lift to other floors on each level of the building
var theBuilding = {
0 : {
1 : 2,
2 : 0,
3 : 1,
},
1 : {
0 : 1,
2 : 3,
3 : 2,
},
2: {
0 : 2,
1 : 1,
3 : 4,
goingUp : function () {
var sum = 0;
var theParentKey = 2; // this is supposed to be a direct reference to the parent object name to this nested object and thus equal to 2
for (i = 0; i <= totalFloors; i++) { // loop for total floors
if (i !== theParentKey && i >= theParentKey) {//sum if i isn't = this floor number and that i is greater than this floor number
sum += this[i]
}
};
return sum;
},
goingDown : function () {
var sum = 0;
var theParentKey = 2; // this is supposed to be a direct reference to the parent object name to this nested object and thus equal to 2
for (i = 0; i <= totalFloors; i++) { // loop for total floors from lowest
if (i !== theParentKey && i <= theParentKey) { //sum if i isn't = this floor number and that i is less than this floor number
sum += this[i]
}
};
return sum;
},
3 : {
0 : 0,
1 : 1,
2 : 4
}
};
console.log(theBuilding[2].goingUp()); // 4
console.log(theBuilding[2].goingDown()); // 3
Is there a way to use the values within the actual name of the
parentKey and nestedKey1 and/or nestedKey2 in a function held in one
of the nested key-value pairs as the one above?
You can do it in two ways.
Using lexical scope
The function inside nestedKey2 has access to the global scope. You can therefore reach every property inside obj using .:
obj.Parentkey
Using this
This is a bit more tricky as this inside a function in a multilevel object will point to the "same level" of the object as the function resides in. You can use this together with . to reach the lower levels of the object but there is no way to reach the higher levels.
You can, however, use a workaround implementing a circular reference:
var myObj = {
levelAccess: function() {
this.two.__ = this;
return this;
},
one: 'one',
two:
{
__: [],
myFunction: function () {return this.__.one}
}
}.levelAccess();
This solution requires that you add a __ property for each level that needs access to a higher level and then initialises all the __ properties via the levelAccess function. The solution should not cause any memory leaks, see this.
If I understood your question, you want to get names of the keys. You can use Object.keys() while passing the current object this.
var obj = {
parentKey : {
nestedKey1: 3,
nestedKey2: function () {
return Object.keys(this);
}
}
};
console.log(obj.parentKey.nestedKey2()); // ['nestedKey1', 'nestedKey2']
See this is an example
var obj = {
parentKey : {
nestedKey1 : "value",
nestedKey2 : function () {
var c = obj.parentKey;
//console.log("we get "+ c.nestedKey1 +" & "+ c.nestedKey3);
//you can update values
//obj.parentKey.nestedKey1 = "new value";
console.log("we get "+ c.nestedKey1 +" & "+ c.nestedKey3);
return "we get "+ c.nestedKey1 +" & "+ c.nestedKey3;
},
nestedKey3 : "another value"
}
};
obj.parentKey.nestedKey2();
var an = window["obj"]["parentKey"]["nestedKey3"];
console.log(an);
Some question about ability of writing hybrid getters/setters... For my chess app I have an object diagram much more similar to a graph than a tree. So I decide to pool the various kind of objects in a data structure holding both object instances and relations between them.
Something like:
// in js/app/base/pools.js
var Pool = function() {
this.items = {};
this.relations = {};
};
/**
* store an object into the pool
* and augment it with relation/pooling capabilities
* #param string key
* #param Object obj
*/
Pool.prototype.store = function(key, obj) {
var self = this;
Object.defineProperty(obj.constructor.prototype, "pool", {
set : undefined,
get : function() {
return self;
}
});
this.items[key] = obj;
};
/**
* looks for an object in the pool
* #param string
*/
Pool.prototype.find = function(key) {
return this.items[key];
};
Relations are stored as pairs [obj1, obj2] in the "relations" property
of the Pool instance. I have basically two kinds of relationship:
One-to-one: unary correspondences like Chessman <--> Location, or interface implementation like Chessman <--> Pawn | ... | King
One-to-many like Board [x1] <--> [x64] Tile
Those relations are designed (by the pool way) to be bi-directional and have to be set atomically (e.g. transactions) since object cross-references need to be "ACID", for the above example, 1 Board contain 64 Tiles, and each tile knows about its standing board.
For 1-to-1 relations, maybe no problem, since I can set:
chessman.location = location;
// AND AT THE SAME TIME :
location.chessman = chessman;
// with two Object.defineProperty(...) combined
The trouble comes with 1-N relations since I would be able to write:
// 1st : defining relation
// ... (see below)
// 2nd setting a relation
board1.tiles.add(tile_63);
// and
tile_63.board = board1;
// 3rd getting values
board1.tiles --> a collection of tiles (array)
tile_63.board --> board1 object
In the main program the relation is given to the Pool instance by passing a parameter object:
pool.defineRelation("board-contains-tiles", {
tiles : { subject : boards.Board, multiple : true },
board : { subject : tiles.Tile, multiple : false }
});
To define the relation, 1-side is a normal getter/setter, but N-side is more a getter-adder since we have to populate (the board with tiles). So this doesn't work:
Pool.prototype.defineRelation = function(alias, definition) {
this.relations[alias] = [];
var self = this, linker;
var relation = self.relations[alias];
var subject, multiple;
// iterate through relation short names
for(name in definition) {
subject = definition[name].subject;
multiple = definition[name].multiple;
console.log("loop with name : " + name + " subject is : " + subject);
var getter = function() {
var result = [];
for(r = 0; r < relation.length; r++) {
// [x,y] storing
if(relation[r][0] == this)
result.push( relation[r][1]);
// [y,x] storing
if(relation[r][1] == this)
result.push( relation[r][0]);
return result;
};
var setter;
if(multiple) {
setter = function() {};
setter.add = function(x) { relation.push([this, x]); };
} else {
setter = function(x) { relation.push([this, x]); };
}
Object.defineProperty(subject.prototype, name, {
set : setter,
get : getter
});
}
};
Question:
I'm figuring it's possible to do that, but how? Or in a better way, like Delphi's TComponent, or like the DOM tree?
SEE ALSO:
The old, ugly, and messy codebase can be found on my website:
www.eozine.fr --> Jet d'echecs --> ColorChess
If you want to see a previous result (2009)
I need the following two codes
1) A code to select all variables that begin with "example"
2) A code to select all variables that have "true" as value for "available"
example1= {price:1000, size: 1000, available:true}
example2= {price:2000, size: 2000, available:false}
example3= {price:3000, size: 3000, available:true}
example4= {price:4000, size: 4000, available=true}
This is what I want to achieve with code one. As there are a lot of variables I need a quick way of doing it:
var allexampleprices=[example1.price, example2.price, example3.price, example4.price]
With the second code I want to get an array with all the names of the variables that contain the value "false"
Any help appreciated!
All of these are the exact same thing, assuming you're not in a function:
var myVar = 7;
window.myVar = 7;
window["myVar"] = 7;
Therefore, you can access any global variable (a variable defined outside a function) by using the window[ insertString ] method. If you wanted to search through every property on the window object to find one called example, you'd do:
for( var k in window ){
if(/example/.test(k)){
var myExample = window[k];
// Do stuff
}
}
I would HIGHLY recommend against this method, though, for many reasons. To start, it's a horribly bad practice to put anything in the global scope. Variables will start colliding all over the place on big projects. Also, the window object has soooooo many properties that searching through all of them is a horrible performance drain.
Having said all of that, I've devised an example of what you should do, including the helper functions to do it:
var objects =
{
example1:
{
price: 1000,
size: 1000,
available: true
},
example2:
{
price: 2000,
size: 2000,
available: false
},
example3:
{
price: 3000,
size: 3000,
available: true
},
example4:
{
price: 4000,
size: 4000,
available: true
}
}
function filter(obj, comparator){
var list = [];
for(var k in obj){
if(obj.hasOwnProperty(k)){ // fix for IE
if(comparator(obj[k], k, obj)) list.push(obj[k]);
}
}
return list;
}
function isExample(obj, key){
if(/^example/.test( key.toLowerCase() )) return true;
}
function isAvailable(obj){
if(obj.available) return true;
}
/**
* And here's how you use it
*/
var examples = filter(objects, isExample);
var available = filter(objects, isAvailable);
var availableExample = filter(examples, isAvailable);
The filter function returns an array of all of the matching objects.
--- EDIT ---
You want it to say the names of the objects in the console. I'm assuming what you mean is that the console currently shows [object, object, object, object]. There are two ways to do this:
(1) Put the name in the object itself
example1:
{
name: "example1",
price: 1000,
size: 1000,
available: true
}
(2) Capture the name in the filter operation
var names = [];
var examples = filter(objects, function(obj, name){
if(/^example/.test( name.toLowerCase() )){
names.push(name);
return true;
}
});
console.log(names);
I do like below if all variables are in global scope
var passedElements = [];
for(var i = 1, l = 100 /* Maximum number of variable */ ; i < l; i++){
if(window['example' + i]){
var temp = window['example' + i];
if(temp.available === true){
passedElements.push(temp);
}
}/*
else{
// Dont break the loop here, if any variable is missing in between
two variables it will fail. Eg : Example1, Example3.. 2 is missing.
}*/
}
console.log(passedElements);
I hope it will help.
It's seems like follwing line is generated by some logical code:
var example1= {price:1000, size: 1000, available:true}
Why dont you simply store the vaiable names in another array that should give you the solution of Q-1.
Then you can easily travers through all of the vaiables (array) to find the vairables that have "true" as value for "available"