Merge objects dynamically - Object Property Name Issue - javascript

I'm trying to merge objects together, or add new objects to existing objects.
I've already got it working as for merge using jQuery, but it's the name of the property that wont work dynamically.
The parameter filters is an object, such as {test:123}.
When invoking filter({test:123});, I want the filter function to dynamically add objects to a global object. (and of course can't use push() since its not an array)
this.filter = function(filters) {
for (var key in filters) {
$.extend( settings.filter, {key:filters[key]} );
}
};
The problem is that "key" turns into "key" as the name of the property. When it should be "test" as the property name; I can not get the property name to be created dynamically.
The goal would be to allow the user to fire the function like this:
filter({test:123,test2:1321,test55:4})
and dynamically add objects to the global settings object without the user meddling with the object itself.

Your code does not work because key is not being interpreted as a variable when being directly set in the object.
$.extend( settings.filter, {key:filters[key]} );
Considering:
var keyName = "Test";
var badObj = { keyName: "Hello World" };
You would get undefined when calling newObj.Test because it is actually newObj.keyName.
In order to use a variable to create a property name, you need to use different syntax.
var keyName = "Test";
var newObj = {};
newObj[keyName] = "Hello World";
You could then reference and use newObj.Test to get "Hello World"
To fix the method you provided, you can adjust it to:
this.filter = function(filters) {
for (var key in filters) {
if (filters.hasOwnProperty(key)) {
var newObj = {};
newObj[key] = filters[key];
$.extend(settings.filter, newObj);
}
}
};
Keep in mind you can simplify this and just use the extend method. This would be better, unless you are looking to do your own filtering as the method name suggests.
this.filter = function(filters) {
$.extend(settings.filter, filters);
};
Demos

You should create temp obj before extend :
this.filter = function(filters) {
for (var key in filters) {
var obj = {};
obj[key] = filters[key];
$.extend( settings.filter, obj );
}
};

Related

Javascript Unintentionally Changing Multiple Objects' Array Attribute

I am running into a weird problem where I am trying to change the value of an attribute in a specific object it's changing that attribute value in all objects.
Below is my code the creates these objects "nodes"
function processNode(nodeID, name, fields){
this.id = nodeID;
this.fields = fields;
this.name = name;
}
var nodeFuncs = {table:
function(nodeID){
function tableNode() {}
tableNode.prototype = Object.create(new terminalNode(nodeID, 'table'));
return new tableNode(nodeID, 'table');
}
, source:
function(nodeID){
function sourceNode() {}
sourceNode.prototype = Object.create(new processNode(nodeID, 'source'));
sourceNode.prototype.table = '';
sourceNode.prototype.selectors = '';
sourceNode.prototype.include = 1;
return new sourceNode(nodeID, 'source', []);
}
, sort:
function(nodeID){
function sortNode() {}
sortNode.prototype = Object.create(new processNode(nodeID, 'sort'));
sortNode.prototype.order = [];
return new sortNode(nodeID, 'sort', [])
}
, filter:
function(nodeID){
function filterNode() {}
filterNode.prototype = Object.create(new processNode(nodeID, 'filter'));
filterNode.prototype.names = [];
return new filterNode(nodeID, 'filter', [])
}
}
function createNode(nodeID, name){
return nodeFuncs[name](nodeID);
}
idToNodeObjectMap['abc123'] = createNode('abc123', 'source')
idToNodeObjectMap['abc124'] = createNode('abc124', 'sort')
idToNodeObjectMap['abc125'] = createNode('abc125', 'filter')
idToNodeObjectMap['abc126'] = createNode('abc126', 'filter')
Here is a sample jquery event that is triggering the re-valuing of an object's attribute:
$('body').on('click', "div button.ok", function(){
var nodeID = $(this).parent('div.nodeForm').attr('id')
var objectNode = idToNodeObjectMap[nodeID];
objectNode.fields.splice(1,1);
});
For example I will have 4 objects and reference one of them as var objectNode whose .fields value is ['tid', 'gid', 'tname']. Then when I call objectNode.fields.splice(1,1) the .fields value will be ['tid', 'tname'] FOR ALL 4 OBJECTS not just the one that is reference in the objectNode variable.
BUT, when I change the line...
objectNode.fields.splice(1,1);
to...
objectNode.fields = ['hi'];
then it only changes the .fields attribute of the object that I referenced as var objectNode.
Does anyone have any idea why this would happen? I thought it might be a referencing issue where all .fields were referencing and changing each others values but that doesn't seem to make sense to me because when I set the .fields attribute equal to ['hi'] it only changes one object's .fields attribute.
Any help would be appreciated.
I notice in your code that you have this line and I wonder if its part of the problem.
filterNode.prototype = Object.create(new processNode(nodeID, 'filter'));
Object.create() returns an object that has the prototype of processNode but not the prototype object itself. So you have a prototype equal to an object with another prototype inside of it.
filterNode {
__proto__: processNode {
__proto__: processNode.prototype
}
}
This might be attributing to your reference issues.

Assign value in javascript

I have two objects : $scope.objectA and $scope.objectB.
I assign value from $scope.objectA to $scope.objectB like this:
$scope.objectB.value1 = $scope.objectA.value1;
then I make value1 of $scope.objectB to null;
$scope.objectB.value1 = null;
My question is why when I assign $scope.objectB.value1 to null, $scope.objectA.value1 is null too. How can I keep value of $scope.objectA.value1 while changing value of $scope.objectB.value1?
Make copy of object B and assign it to the object A. Use angular.copy function. It will creates a deep copy of source.
For more information Visit Angular copy doc
$scope.objectA.value1 = angular.copy($scope.objectB.value1);
Because this is how it works. You make these two variables "bound" together.
If you want to keep value of objectA, then use
$scope.objectB.value1 = angular.copy($scope.objectA.value1);
I think this can happen only if ObjectA and ObjectB refer to the same object on the heap, i.e. it ObjectA and ObjectB are the same objects
$scope['object1'] = {};
$scope['object1']['val'] = {};
$scope['object2'] = {};
$timeout(() => {
this.$scope['object2']['val'] = this.$scope['object1']['val'];
$timeout(() => {
this.$scope['object2']['val'] = null;
console.log(this.$scope['object1']['val']); // Object {}
})
});
-
$scope['object1'] = {};
$scope['object1']['val'] = {};
$scope['object2'] = {};
$timeout(() => {
this.$scope['object2']['val'] = this.$scope['object1']['val'];
$timeout(() => {
this.$scope['object2']['val'] = null;
console.log(this.$scope['object1']['val']); // null
})
});
The reason is when you assign object to a variable, the assignment will be by reference, so the old and new will be a reference to the original object
So when you edit an object, you're actually editing the original object.
The solution
Angular: use object = angular.copy(myObject1)
jQuery: object = $.extend({}, myObject1);

JavaScript class to populate object

I am trying to populate an object by using a JavaScript class. I am not even sure if I am doing it correctly, I am very new to JavaScript OOP.
var ImagesViewModel = {}; // Global object
function ImagesClass() {
this.addImage = function (iUrl) {
ImagesViewModel.push({ "ImageUrl": iUrl }) //< Error is here
}
}
var k = new ImagesClass()
k.addImage("http://www.yahoo.com")
k.addImage("http://www.xbox.com")
Basically I need an easy way to populate ImagesViewModel with multiple properties. Do I need to specify properties within ImagesViewModel? Maybe I can do all of this without having to specify a global variable?
I am getting the error
Object has no method PUSH
What you want is an array and not an object, push is a method on Array prototype and you are trying to use it on object.
Change:
var ImagesViewModel = {};
To:
var ImagesViewModel = [];
You can do it this way as well so that each instance of ImagesClass has its own set of images.
function ImagesClass() {
var _images = [];
this.addImage = function (iUrl) {
_images.push({ "ImageUrl": iUrl }) //< Error is here
}
this.getImages = function(){
return _images;
}
}
and use it as:
var k = new ImagesClass();
k.addImage("http://www.yahoo.com");
k.addImage("http://www.xbox.com");
var ImagesViewModel = k.getImages(); //You can either set it directly or as a property of object
console.log(ImagesViewModel);
Demo
the push method is only for Arrays, here you are trying to push() to an object, which is why it isn't working.
You will need to change var ImagesViewModel = {}; to var ImagesViewModel = [];
From a design perspective, you probably don't want your viewmodel to just be a flat array (even though you declared it as an object, as other posters pointed out).
I'd suggest declaring an array declaration to hold the images inside of your ImagesViewModel object.
var ImagesViewModel = { // ViewModel generic OBJECT
this.Images = new Array(); // prototype ARRAY object
};
function ImagesClass() {
this.addImage = function (iUrl) {
ImagesViewModel.Images.push({ "ImageUrl": iUrl })
}
}

Is there some way to add meta-data to JavaScript objects?

I would like to add key-value pairs of metadata to arbitrary JavaScript objects. This metadata should not affect code that is not aware of the metadata, that means for example
JSON.stringify(obj) === JSON.stringify(obj.WithMetaData('key', 'value'))
MetaData aware code should be able to retrieve the data by key, i.e.
obj.WithMetaData('key', 'value').GetMetaData('key') === 'value'
Is there any way to do it - in node.js? If so, does it work with builtin types such as String and even Number? (Edit Thinking about it, I don't care about real primitives like numbers, but having that for string instances would be nice).
Some Background: What I'm trying to do is cache values that are derived from an object with the object itself, so that
to meta data unaware code, the meta data enriched object will look the same as the original object w/o meta
code that needs the derived values can get it out of the meta-data if already cached
the cache will get garbage collected alongside the object
Another way would be to store a hash table with the caches somewhere, but you'd never know when the object gets garbage collected. Every object instance would have to be taken care of manually, so that the caches don't leak.
(btw clojure has this feature: http://clojure.org/metadata)
You can use ECMA5's new object properties API to store properties on objects that will not show up in enumeration but are nonetheless retrievable.
var myObj = {};
myObj.real_property = 'hello';
Object.defineProperty(myObj, 'meta_property', {value: 'some meta value'});
for (var i in myObj)
alert(i+' = '+myObj[i]); //only one property - #real_property
alert(myObj.meta_property); //"some meta value"
More information here: link
However you're not going to be able to do this on primitive types such as strings or numbers, only on complex types.
[EDIT]
Another approach might be to utilise a data type's prototype to store meta. (Warning, hack ahead). So for strings:
String.prototype.meta = {};
String.prototype.addMeta = function(name, val) { this.meta[name] = val; }
String.prototype.getMeta = function(name) { return this.meta[name]; };
var str = 'some string value';
str.addMeta('meta', 'val');
alert(str.getMeta('meta'));
However this is clearly not ideal. For one thing, if the string was collected or aliased (since simple data types are copied by value, not reference) you would lose this meta. Only the first approach has any mileage in a real-world environment, to be honest.
ES6 spec introduces Map and WeakMap. You can enable these in node by running node --harmony and by enabling the experimental javascript flag in Chrome, (it's also in Firefox by default). Maps and WeakMaps allow objects to be used as keys which can be be used to store metadata about objects that isn't visible to anyone without access to the specific map/weakmap. This is a pattern I now use a lot:
function createStorage(creator){
creator = creator || Object.create.bind(null, null, {});
var map = new Map;
return function storage(o, v){
if (1 in arguments) {
map.set(o, v);
} else {
v = map.get(o);
if (v == null) {
v = creator(o);
map.set(o, v);
}
}
return v;
};
}
Use is simple and powerful:
var _ = createStorage();
_(someObject).meta= 'secret';
_(5).meta = [5];
var five = new Number(5);
_(five).meta = 'five';
console.log(_(someObject).name);
console.log(_(5).meta);
console.log(_(five).meta);
It also facilitates some interesting uses for separating implementation from interface:
var _ = createStorage(function(o){ return new Backing(o) });
function Backing(o){
this.facade = o;
}
Backing.prototype.doesStuff = function(){
return 'real value';
}
function Facade(){
_(this);
}
Facade.prototype.doSomething = function doSomething(){
return _(this).doesStuff();
}
There is no "comment" system in JSON. The best you can hope for is to add a property with an unlikely name, and add that key contaning the metadata. You can then read the metadata back out if you know it's metadata, but other setups will just see it as another property. And if someone uses for..in...
You could just add the Metadata as a "private" variable!?
var Obj = function (meta) {
var meta = meta;
this.getMetaData = function (key) {
//do something with the meta object
return meta;
};
};
var ins_ob = new Obj({meta:'meta'});
var ins_ob2 = new Obj();
if(JSON.stringify(ins_ob) === JSON.stringify(ins_ob2)) {
console.log('hoorai');
};
If you want object-level metadata, you could create a class that extends Object. Getters and setters are not enumerable and, obviously, neither are private fields.
class MetadataObject extends Object {
#metadata = undefined;
get metadata() { return this.#metadata; }
set metadata(value) { this.#metadata; }
}
var obj = new MetadataObject();
obj.a = 1;
obj.b = 2;
obj.metadata = { test: 123 };
console.log(obj); // { a: 1, b: 2 }
console.log(obj.metadata); // { test: 123 }
console.log(JSON.stringify(obj)); // '{"a":1,"b":2}'
You can even simplify the implementation using a Map. Without a setter on metadata, you have to use Map methods to modify it.
class MetadataObject extends Object {
#metadata = new Map();
get metadata() { return this.#metadata; }
}
var obj = new MetadataObject();
obj.a = 1;
obj.b = 2;
obj.metadata.set('test', 123);
console.log(obj); // { a: 1, b: 2 }
console.log(obj.metadata.get('test')); // 123
console.log(JSON.stringify(obj)); // '{"a":1,"b":2}'
I ran into a situation where I needed property level metadata, and used the latter implementation.
obj.id = 1;
obj.metadata.set('id', 'metadata for the id property');

Can you use custom objects as properties of an object in javascript?

Suppose I create a custom object/javascript "class" (airquotes) as follows:
// Constructor
function CustomObject(stringParam) {
var privateProperty = stringParam;
// Accessor
this.privilegedGetMethod = function() {
return privateProperty;
}
// Mutator
this.privilegedSetMethod = function(newStringParam) {
privateProperty = newStringParam;
}
}
Then I want to make a list of those custom objects where I can easily add or remove things from that list. I decide to use objects as a way to store the list of custom objects, so I can add custom objects to the list with
var customObjectInstance1 = new CustomObject('someString');
var customObjectInstance2 = new CustomObject('someOtherString');
var customObjectInstance3 = new CustomObject('yetAnotherString');
myListOfCustomObjects[customObjectInstance1] = true;
myListOfCustomObjects[customObjectInstance2] = true;
myListOfCustomObjects[customObjectInstance3] = true;
and remove custom objects from the list with
delete myListOfCustomObjects[customObjectInstance1];
but if i try to iterate through the list with
for (i in myListOfCustomObjects) {
alert(i.privilegedGetMethod());
}
I would get an error in the FireBug console that says "i.privilegedGetMethod() is not a function". Is there a way to fix this problem or an idiom in javascript to do what I want? Sorry if this is a dumb question, but I'm new to javascript and have scoured the internet for solutions to my problem with no avail. Any help would be appreciated!
P.S. I realize that my example is super simplified, and I can just make the privateProperty public using this.property or something, but then i would still get undefined in the alert, and I would like to keep it encapsulated.
i won't be the original object as you were expecting:
for (i in myListOfCustomObjects) {
alert(typeof i); // "string"
}
This is because all keys in JavaScript are Strings. Any attempt to use another type as a key will first be serialized by toString().
If the result of toString() isn't somehow unique for each instance, they will all be the same key:
function MyClass() { }
var obj = {};
var k1 = new MyClass();
var k2 = new MyClass();
obj[k1] = {};
obj[k2] = {};
// only 1 "[object Object]" key was created, not 2 object keys
for (var key in obj) {
alert(key);
}
To make them unique, define a custom toString:
function CustomObject(stringParam) {
/* snip */
this.toString = function () {
return 'CustomObject ' + stringParam;
};
}
var obj = {};
var k1 = new CustomObject('key1');
var k2 = new CustomObject('key2');
obj[k1] = {};
obj[k2] = {};
// "CustomObject key1" then "CustomObject key2"
for (var key in obj) {
alert(key);
}
[Edit]
With a custom toString, you can set the object as the serialized key and the value to keep them organized and still continue to access them:
var customObjectInstance1 = new CustomObject('someString');
var customObjectInstance2 = new CustomObject('someOtherString');
var customObjectInstance3 = new CustomObject('yetAnotherString');
myListOfCustomObjects[customObjectInstance1] = customObjectInstance1;
myListOfCustomObjects[customObjectInstance2] = customObjectInstance2;
myListOfCustomObjects[customObjectInstance3] = customObjectInstance3;
for (i in myListOfCustomObjects) {
alert(myListOfCustomObjects[i].privilegedGetMethod());
}
The for iteration variable is just the index, not the object itself. So use:
for (i in myListOfCustomObjects) {
alert(myListOfCustomObjects[i].privilegedGetMethod());
}
and, in my opinion, if you use an Object as an array index / hash, it just would be converted to the string "Object", which ends up in a list with a single entry, because all the keys are the same ("Object").
myListOfCustomObjects =[
new CustomObject('someString'),
new CustomObject('someOtherString'),
new CustomObject('yetAnotherString')
]
you will get access to any element by index of array.

Categories

Resources