I'm intending to write a module that can be instantiated with default configuration and then overridden with custom configuration when initialized. The configuration object has nested objects, so I need to traverse over these nested objects if they are included in the custom configuration. I am attempting to do so by calling customize recursively. This works for the first nested object but the traversal ends after that object. Why is this and what can I do to fully traverse an object containing nested objects?
function Config(options) {
function customize(conf) {
if (conf && conf.constructor === Object) {
for (var prop in conf) {
if(conf[prop].constructor === Object) {
return customize.call(this[prop], conf[prop]);
} else {
if (this.hasOwnProperty(prop)){
this[prop] = conf[prop];
}
}
}
} else {
console.error('The first argument must be an object.');
return;
}
}
//Default config values
this.foo = 'default';
this.bar = {
foo: 'default'
};
this.baz = {
foo: 'default'
};
//Overide default config with custom config
if (options && options.constructor === Object) {
customize.call(this, options);
}
}
function TestModule(){
this.init = function(options){
this.config = (options && options.constructor === Object) ? new Config(options) : new Config();
return this;
};
}
console.log(
new TestModule().init({
foo: 'custom',
bar: {foo: 'custom'},
baz: {foo: 'custom'}
}).config
);
//RESULT
// {
// foo: 'custom',
// bar: {foo: 'custom'},
// baz: {foo: 'default'}
// }
This line:
return customize.call(this[prop], conf[prop]);
occurs inside a for loop, so you are returning before each item has been iterated over. Your return statement should be outside the loop.
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
}
});
Say I have some object:
Org.prototype = {
constructor : Org,
get id(){ return this._id; },
some_method: function(){},
etc...
How to retrieve getters of the object?
Loop through all the property names, and filter down to only those whose property descriptor has a get property.
function Foo() { }
Foo.prototype = {
get id() { return this._id; },
otherfunc() { }
};
function getGetters(obj) {
var proto = obj.prototype;
return Object.getOwnPropertyNames(proto)
.filter(name => Object.getOwnPropertyDescriptor(proto, name).get);
}
console.log(getGetters(Foo));
To get the list of properties which have "getter" function use the following approch with Object.keys, Object.getOwnPropertyDescriptor and Array.filter functions:
function Org(){};
Org.prototype = {
constructor : Org,
get id(){ return this._id; },
some_method: function(){}
};
propList = Object.keys(Org.prototype).filter(function (p) {
return typeof Object.getOwnPropertyDescriptor(Org.prototype, p)['get'] === "function"
});
console.log(propList);
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor
How do I get access to the properties or method of the main object, from sub-obiect level two (sub3). If possible I would like to avoid solutions chaining return this.
Obj = function () {};
Obj.prototype = {
name: 'name',
main: function(){
console.log(this.name);
},
subobject: {
sub2: function () {
console.log(this);
},
sub3: function () {
console.log(this.name); // How access to Obj.name ??
}
}
}
o = new Obj();
o.main(); // return name
o.subobject.sub2(); // return subobject
o.subobject.sub3(); // return undefined
With your current syntax, you can't. Because for sub2 and sub3, the this variable is Obj.prototype.subobject.
You have multiple choice:
The obvious one: don't use a suboject.
Create subobject, sub2 and sub3 in the constructor
Obj = function() {
var self = this;
this.subobject = {
sub1: function() { console.log(self); }
}
}
Use bind at each call:
o.subobject.sub2.bind(o)();
How can I just update one part of a mapped model?
var model = { foo: { bar: "hello" }, moo: { la: "world" }};
var mapped = ko.mapping.fromJS(model);
Mapping results in:
mapped =
{
__ko_mapping_object__ : object,
foo: {
bar : function c()
},
moo: {
la: function c()
},
__proto__ : object
}
Since foo and moo aren't observable, if I do:
mapped.foo = { "bar" : "changed" };
or
mapped.foo = ko.mapping.fromJS({ "bar" : "changed" });
The object is updated but rebinding is not triggered.
Any ideas? I need binding to happen on updating a one part of the model.
One idea I had was to crawl the partial model and then force rebind.
triggerObjectRebind(mapped.foo);
function triggerObjectRebind(model) {
for (var property in model) {
if (model.hasOwnProperty(property)) {
if (typeof model[property] === "object") {
triggerObjectRebind(model[property]);
} else if (typeof model[property] === "function") {
model[property].valueHasMutated();
}
}
}
}
When you do updates, you need to pass in the mapped object as a parameter. Corresponding properties will be updated.
var model = {
foo: { bar: "hello" },
moo: { la: "world" }
};
var mapped = ko.mapping.fromJS(model);
var newFoo = { bar: "changed" };
// do the update on the foo object
ko.mapping.fromJS(newFoo, {}, mapped.foo);
If I have a nested set of plain old javascript objects (for example, having been returned from JSON), how do I them into Ember.js objects (or at least getting the binding functionality working)?
For example, if I have an object like:
var x = {
bar: {
baz: "quux"
}
}
Then I turn that into an Ember object:
var y = Ember.Object.create(x);
Then setting the value of "baz" won't update any views I have, because it is just a normal js object, not an Ember object.
I know I can just recursively go over the object keys, and do Ember.Object.create all the way down, but is there an alternative approach?
I'm not sure how you're attempting to set the value of baz, after you've created the Ember.Object, but you should make sure you use an observer-aware setter function. For this example, I'd suggest using setPath().
For example:
var x = {
bar: {
baz: "quux"
}
};
var y = Ember.Object.create(x);
y.setPath('bar.baz', 'foo');
jsFiddle example, showing a view update after setting: http://jsfiddle.net/ebryn/kv3cU/
Here's my version:
import { typeOf } from '#ember/utils'
import EmberObject from '#ember/object'
export default function deepEmberObject(anything) {
if (typeOf(anything) === 'array') {
return anything.map(a => deepEmberObject(a))
} else if (typeOf(anything) === 'object') {
let converted = Object.keys(anything).reduce((acc, k) => {
acc[k] = deepEmberObject(anything[k])
return acc
}, {})
return EmberObject.create(converted)
} else {
return anything
}
}
test:
import deepEmberObject from 'zipbooks/utils/deep-ember-object'
import { module, test } from 'qunit'
module('Unit | Utility | deep-ember-object', function() {
test('it works', function(assert) {
let result = deepEmberObject({ pandas: [{ id: 3, children: [{ name: 'Bobby', features: { weight: 3 } }] }] })
assert.equal(
result
.get('pandas')[0]
.get('children')[0]
.get('features')
.get('weight'),
3
)
})
})
For some reason I had to define nested objects independently in order to ensure the computed works properly (even the enumerated ones).
For that I end up crafting these 2 utility functions:
import EmberObject from '#ember/object';
import { A } from '#ember/array';
function fromArrayToEmberArray(array) {
const emberArray = A();
array.forEach(function(item) {
if (Array.isArray(item)) {
emberArray.push(fromArrayToEmberArray(item));
} else if (item && typeof item === 'object') {
emberArray.push(fromObjectToEmberObject(item));
} else {
emberArray.push(item);
}
});
return emberArray;
}
function fromObjectToEmberObject(pojo) {
const emberObject = EmberObject.create();
for (const key in pojo) {
const keyObject = pojo[key];
if (Array.isArray(keyObject)) {
emberObject.set(key, fromArrayToEmberArray(keyObject))
} else if (keyObject && typeof keyObject === 'object') {
emberObject.set(key, fromObjectToEmberObject(keyObject))
} else {
emberObject.set(key, keyObject);
}
}
return emberObject;
}
export default {fromObjectToEmberObject};