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);
Related
I would like to observe whenever a property of a third party object is changed. I'm taking the approach of assigning a custom setter but my console.log below is never invoked. Why is that? Is there a better approach?
const foo = { a: 1, b: 2 };
Object.assign(foo, {
set user(user) {
foo.user = user;
console.log(">>>>>> user was changed", user);
},
});
// Desired behaviour
foo.user = "asdf"; // >>>>>> user was changed asdf
delete foo.user; // >>>>>> user was changed undefined
foo.user = "asdf1" // >>>>>> user was changed asdf1
Please note, I need to mutate foo I cannot wrap a proxy around foo and return that because it is a third party library which mutates .user internally
I've found a way, pretty hacky as it is
const foo = { a: 1, b: 2 };
let underlyingValue = foo.user
Object.defineProperty(foo, "user", {
get() {
return underlyingValue
},
set(user) {
underlyingValue = user;
console.log(">>>>>> user was changed", user);
},
enumerable: true
});
foo.user = "asdf";
console.log(foo)
I've made this into a generic function below 👇
/** Intercepts writes to any property of an object */
function observeProperty(obj, property, onChanged) {
const oldDescriptor = Object.getOwnPropertyDescriptor(obj, property);
let val = obj[property];
Object.defineProperty(obj, property, {
get() {
return val;
},
set(newVal) {
val = newVal;
onChanged(newVal);
},
enumerable: oldDescriptor?.enumerable,
configurable: oldDescriptor?.configurable,
});
}
// example usage 👇
const foo = { a: 1 };
observeProperty(foo, "a", (a) => {
console.log("a was changed to", a);
});
foo.a = 2; // a was changed to 2
Also available in typescript
🚨 Edit: This will break if the property is deleted eg delete foo.user. The observer will be removed and the callback will stop firing. You will need to re-attach it.
#david_adler ... when I commented ...
"Is the latter a special case or does the OP need a somehow more generic observation approach?"
... I thought of the most generic solution one could come up with in terms of changing/mutating an existing object entirely into an observable variant of itself.
Such a solution also would be more close to what the OP did ask for ...
"I would like to observe whenever a property of a third party object is changed"
Thus the next provided approach keeps the objects appearance and behavior and also does not introduce additional (e.g. Symbol based) keys.
function mutateIntoObservableZombie(obj, handlePropertyChange) {
const propertyMap = new Map;
function createAccessors(keyOrSymbol, initialValue, handler) {
return {
set (value) {
propertyMap.set(keyOrSymbol, value);
handler(keyOrSymbol, value, this);
return value;
},
get () {
return propertyMap.has(keyOrSymbol)
? propertyMap.get(keyOrSymbol)
: initialValue;
},
};
}
function wrapSet(keyOrSymbol, proceed, handler) {
return function set (value) {
handler(keyOrSymbol, value, this);
return proceed.call(this, value);
};
}
function createAndAssignObservableDescriptor([keyOrSymbol, descriptor]) {
const { value, get, set, writable, ...descr } = descriptor;
if (isFunction(set)) {
descr.get = get;
descr.set = wrapSet(keyOrSymbol, set, handlePropertyChange);
}
if (descriptor.hasOwnProperty('value')) {
Object.assign(descr, createAccessors(keyOrSymbol, value, handlePropertyChange));
}
Object.defineProperty(obj, keyOrSymbol, descr);
}
const isFunction = value => (typeof value === 'function');
if (isFunction(handlePropertyChange)) {
const ownDescriptors = Object.getOwnPropertyDescriptors(obj);
const ownDescrSymbols = Object.getOwnPropertySymbols(ownDescriptors);
Object
.entries(ownDescriptors)
.forEach(createAndAssignObservableDescriptor);
ownDescrSymbols
.forEach(symbol =>
createAndAssignObservableDescriptor([symbol, ownDescriptors[symbol]])
);
}
return obj;
}
// third party object (closed/inaccessible code)
const foo = { a: 1, b: 2 };
// custom changes already.
foo.userName = '';
foo.userLoginName = '';
const userNick = Symbol('nickname');
foo[userNick] = null;
console.log('`foo` before descriptor change ...', { foo });
mutateIntoObservableZombie(foo, (key, value, target) => {
console.log('property change ...', { key, value, target });
});
console.log('`foo` after descriptor change ...', { foo });
foo.a = "foo bar";
foo.b = "baz biz";
console.log('`foo` after property change ...', { foo });
foo.userName = '****';
foo.userLoginName = '************#**********';
console.log('`foo` after property change ...', { foo });
foo[userNick] = 'superuser';
console.log('`foo` after symbol property change ...', { foo });
.as-console-wrapper { min-height: 100%!important; top: 0; }
Edit
Since the above approach already is implemented generic and modular it of cause easily can be refactored into a function which allows the exact definition of which property/ies, both string and symbol based, are going to be observed ...
function observePropertyChange(obj, keysAndSymbols, handlePropertyChange) {
const propertyMap = new Map;
function createAccessors(keyOrSymbol, initialValue, handler) {
return {
set (value) {
propertyMap.set(keyOrSymbol, value);
handler(keyOrSymbol, value, this);
return value;
},
get () {
return propertyMap.has(keyOrSymbol)
? propertyMap.get(keyOrSymbol)
: initialValue;
},
};
}
function wrapSet(keyOrSymbol, proceed, handler) {
return function set (value) {
handler(keyOrSymbol, value, this);
return proceed.call(this, value);
};
}
function createAndAssignObservableDescriptor(keyOrSymbol, descriptor) {
const { value, get, set, writable, ...descr } = descriptor;
if (isFunction(set)) {
descr.get = get;
descr.set = wrapSet(keyOrSymbol, set, handlePropertyChange);
}
if (descriptor.hasOwnProperty('value')) {
Object.assign(descr, createAccessors(keyOrSymbol, value, handlePropertyChange));
}
Object.defineProperty(obj, keyOrSymbol, descr);
}
const isString = value => (typeof value === 'string');
const isSymbol = value => (typeof value === 'symbol');
const isFunction = value => (typeof value === 'function');
if (isFunction(handlePropertyChange)) {
const ownDescriptors = Object.getOwnPropertyDescriptors(obj);
const identifierList = (Array
.isArray(keysAndSymbols) && keysAndSymbols || [keysAndSymbols])
.filter(identifier => isString(identifier) || isSymbol(identifier));
identifierList
.forEach(keyOrSymbol =>
createAndAssignObservableDescriptor(keyOrSymbol, ownDescriptors[keyOrSymbol])
);
}
return obj;
}
// third party object (closed/inaccessible code)
const foo = { a: 1, b: 2 };
// custom changes already.
foo.userName = '';
foo.userLoginName = '';
const userNick = Symbol('nickname');
foo[userNick] = null;
console.log('`foo` before descriptor change ...', { foo });
observePropertyChange(
foo,
['b', 'userLoginName', userNick],
(key, value, target) => { console.log('property change ...', { key, value, target }); },
);
console.log('`foo` after descriptor change ...', { foo });
foo.a = "foo bar";
foo.b = "baz biz";
console.log('`foo` after property change ...', { foo });
foo.userName = '****';
foo.userLoginName = '************#**********';
console.log('`foo` after property change ...', { foo });
foo[userNick] = 'superuser';
console.log('`foo` after symbol property change ...', { foo });
.as-console-wrapper { min-height: 100%!important; top: 0; }
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
}
});
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)();
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.
I'd like to create a Javascript object that can save and load its state (to local storage).
This is the basic pattern I'm using:
var obj = function () {
// private members
//
return {
// public members
load: function () {
this.state = JSON.parse(localStorage.getItem('obj'));
if (this.state === null) {
this.state = {
name: 'foo'
};
}
},
save: function () {
localStorage.setItem('obj', JSON.stringify(this.state));
}
};
}();
// load state
obj.load();
console.log(obj.state.name);
// save state
obj.state.name = 'bar';
obj.save();
But there's one thing that annoys me about this pattern: I have to access the object's persistent properties through the 'state' property.
How can I rewrite this so I can use the object in a more natural way, like:
// load state
obj.load();
console.log(obj.name);
// save state
obj.name = 'bar';
obj.save();
This is a very simple 'state', but the solution has to work for a complex state object with nested objects, arrays etc., so simply adding a 'name' property to my object is not what I'm after.
If you don't care which properties get loaded/saved then you can simply copy all from state into self. For example, after reading into var state (instead of this.state since you don't want state to be a part of this anymore): for(x in state) this[x] = state[x];
similarly, you'd save out: var state = {}; for(x in this) state[x] = this[x]
However, if you want to have a pre-defined list, then I'd recommend: var fields = ['name', 'zip', 'age'];
And then use for(x in fields) this[x] = state[x] to load and for(x in fields) state[x] = this[x]; to save.
Sorry it's a bit pieced together, but I hope you can follow what I mean :)
EDIT: Added full example per OPs request.
An example of a full solution using this technique is as follows:
var obj = function () {
// private members
//
return {
// public members
load: function () {
var state = JSON.parse(localStorage.getItem('obj'));
if(state == null) state = { name: 'foo' };
for(x in state) this[x] = state[x];
},
save: function ()
{
var state = {};
// check if it's a function. This version taken from underscorejs
var isFunction = function(obj) {
return !!(obj && obj.constructor && obj.call && obj.apply);
};
for(x in this)
{
if(isFunction(this[x])) continue; // skip functions
state[x] = this[x];
}
localStorage.setItem('obj', JSON.stringify(state));
}
};
};
You can also accomplish a direct save when a property changes,
by using ES5 getters/setters or by using Watch.js
Watch.js example:
var obj = (function () {
// private members
//
var self = {
// Some properties
name: '',
otherName: '',
// Try to load state or use "foo state"
state: JSON.parse(localStorage.getItem('obj')) || {
name: 'foo'
},
save: function () {
localStorage.setItem('obj', JSON.stringify(this.state));
}
};
// Watch the object and save it to local storage, when a property changes
// (Of course, you don't need to call the save method here...)
watch(self, function(property, value) {
console.log('saving state!');
self.state[property] = value;
self.save();
});
return self;
}());
// Update some properties and see that it is saved to local storage.
obj.name = "Some name";
obj.otherName = "Some other name";
console.log(JSON.parse(localStorage.getItem('obj')));
​
Example on JsFiddle.
You could make the state internal and surface getters and setters:
var obj = function () {
// private members
var state = {};
return {
// public members
load: function () {
var loadedState = JSON.parse(localStorage.getItem('obj'));
if (loadedState === null) {
state = {
name: 'foo'
};
} else {
state = loadedState;
}
},
save: function () {
localStorage.setItem('obj', JSON.stringify(state));
},
getState: function (key) {
return state[key];
},
setState: function (key, value) {
state[key] = value;
}
};
};
Using jQuery's extend():
var obj = (function () {
return {
load: function () {
var stored = localStorage.getItem("obj");
var state = stored ? JSON.parse(stored) : {
name: 'foo'
};
$.extend(this, state);
},
save: function () {
localStorage.setItem("obj", JSON.stringify(this));
}
};
})();
// load state
obj.load();
console.log(obj);
// save state
obj.name = 'bar';
obj.save();
jsfiddle
All credit to pimvdb.