ko.mapping can transform get, set property's (ES5) in single ko.computed ?
var people = {
get Name (){
return this._name;
},
set Name(value){
this._name = value;
}
};
var vm = ko.mapping(people, {/* mapping getset to computed */});
vm.Name instanceOf ko.computed === true.
ko.mapping support this or how do this ?
I'm sure you mean to use an observable, not a computed, as this would not depend on other observables.
I created a gist, including tests, of creating models with observable properties, and some utilty functions to create them. The core code is here:
var defineProperty = function(type, obj, prop, def) {
if (obj == null || typeof obj != 'object' || typeof prop != 'string') {
throw new Error('invalid arguments passed');
}
if (Object.prototype.toString.call(def) === '[object Array]' && type === 'observable') {
type = 'observableArray';
}
var obv = ko[type](def);
Object.defineProperty(obj, prop, {
set: function(value) { obv(value) },
get: function() { return obv() },
enumerable: true,
configurable: true
});
Object.defineProperty(obj, '_' + prop, {
get: function() { return obv },
enumerable: false
});
};
ko.utils.defineObservableProperty = defineProperty.bind(null, 'observable');
ko.utils.defineComputedProperty = defineProperty.bind(null, 'computed');
ko.observableModel = function(defaults) {
for (var prop in defaults) {
if (defaults.hasOwnProperty(prop)) {
if (defaults[prop] != null && typeof defaults[prop] == 'object' && Object.prototype.toString.call(defaults[prop]) !== '[object Array]') {
// should this also be an observable property?
this[prop] = new ko.observableModel(defaults[prop]);
} else if (!defaults[prop] || !ko.isSubscribable(defaults[prop])) {
ko.utils.defineObservableProperty(this, prop, defaults[prop]);
} else {
this[prop] = defaults[prop];
}
}
}
};
Related
Tell me, how correctly to check the existence of a key in associative arrays?
For example:
var mydata = {
key1: '',
key2: {
subkey1: {
subkey1_1: {
value1: ''
value2" '',
},
},
subkey2: '';
},
}
if ((mydata.key2 != undefined) && (mydata.key2.subkey1 != undefined) && (mydata.key2.subkey1.subkey1_1 != undefined))
mydata.key2.subkey1.subkey1_1.value1 = 'test';
Too long and confusing
((mydata.key2 != undefined) && (mydata.key2.subkey1 != undefined) && (mydata.key2.subkey1.subkey1_1 != undefined))
I would like to use a simpler function, like
safeSet(mydata.key2.subkey1.subkey1_1.value1, 'test');
or
if (is_undefined(mydata.key2.subkey1.subkey1_1.value1) == true)
mydata.key2.subkey1.subkey1_1.value1 = 'test'; // now - error if 'mydata.key2.subkey1.subkey1_1' not exist
You can create custom function using reduce() to test if nested property exists. You can just pass key as string.
var mydata = {
key1: '',
key2: {
subkey1: {
subkey1_1: {
value1: '',
value2: '',
},
},
subkey2: ''
},
}
function safeSet(key, data) {
return key.split('.').reduce(function(r, e) {
return r ? r[e] : undefined;
}, data) != undefined
}
console.log(safeSet('key2.subkey1.subkey1_1.value1', mydata))
You should use the in operator:
"key" in obj // true, regardless of the actual value
Or, if you want to particularly test for properties of the object instance (and not inherited properties), use hasOwnProperty:
obj.hasOwnProperty("key") // true
hope this would help you.
Source: http://www.advancesharp.com/questions/628/checking-if-an-associative-array-key-exists-in-javascript
Alternatively, you can make use of the .has() method of Lodash.
Then, you would only need to check:
if (_.has(mydata, 'key2.subkey1.subkey1_1.value1')
mydata.key2.subkey1.subkey1_1.value1 = 'test';
For trying to get something in a nested structure I'd do something like this:
function getPath(element, path) {
var handledSoFar = [];
for (var i = 0; i < path.length; i++) {
var property = path[i];
handledSoFar.push(property);
if (typeof element[property] === 'undefined') {
throw new Error('Path ' + handledSoFar.join('->') + ' is undefined');
}
element = object[property];
}
return element;
}
var mydata = {
key1: '',
key2: {
subkey1: {
subkey1_1: {
value1: '',
value2: 'hi'
}
},
subkey2: ''
}
};
// Prints 'hi'
console.log(getPath(mydata, ['key2', 'subkey1', 'subkey1_1', 'value2']));
// Throws error 'Path key2->subkey2->subkey1_1 is undefined'
console.log(getPath(mydata, ['key2', 'subkey1', 'subkey1_1', 'value2']));
Of course keeping track of the search in handledSoFar is optional but might be useful for development / debugging.
You can also use the lodash deep field selector: lodash.get (documentation)
const get = require('lodash.get');
const set = require('lodash.set');
if (!get(mydata, 'key2.subkey1.subkey1_1.value1')) {
set(mydata, 'key2.subkey1.subkey1_1.value1', 'test');
}
You could split the path and make a check if the following element exist. If not assign an object to the new property.
Return then the value of the property.
At the end assign the value.
function setValue(object, path, value) {
var fullPath = path.split('.'),
way = fullPath.slice(),
last = way.pop();
way.reduce(function (r, a) {
return r[a] = r[a] || {};
}, object)[last] = value;
}
var object = { key1: '', key2: { subkey1: { subkey1_1: { value1: '', value2: '' } }, subkey2: '' } };
setValue(object, 'key2.subkey1.subkey1_1.value1', 'test');
console.log(object);
The problem with the example function that you proposed:
safeSet(mydata.key2.subkey1.subkey1_1.value1, 'test');
or
is_undefined(mydata.key2.subkey1.subkey1_1.value1)
Is that the mydata.key2.subkey1... part is run before the function is called. So if one of the subkeys does not exist, an exception will be thrown before your code is reached.
You could get something similar using a callback though...
safeSet(function(val) { mydata.key2.subkey1.subkey1_1.value1 = val; }, 'test')
the implementation of safeSet would then be:
var safeSet = function(setterFunc, val) {
try {
setterFunc(val);
} catch (e) {
if (e instanceof TypeError) {
return false;
} else {
throw e;
}
}
return true;
}
safeSet returns true if the value was set, and false otherwise.
I'm writing a simple EventEmitter is ES5.
The objective is to ensure that all properties on EventEmitter instances are
non-writable and non-configurable."
After 6 hours of racking my brain I still can't figure out how to, increase the listenerCount, for example if the configurable descriptor is set to false.
Here's an example of what I have:
var eventEmitter = function(){
var listeners = listeners || 0;
var events = events || {};
Object.defineProperties(this, {
listeners: {
value : 0,
configurable: false,
writable: false
},
events: {
value: {},
configurable : false,
writable: false
}
});
return this;
};
eventEmmitter.prototype.on = function(ev, cb) {
if (typeof ev !== 'string') throw new TypeError("Event should be type string", "index.js", 6);
if (typeof cb !== 'function' || cb === null || cb === undefined) throw new TypeError("callback should be type function", "index.js", 7);
if (this.events[ev]){
this.events[ev].push(cb);
} else {
this.events[ev] = [cb];
}
this.listeners ++;
return this;
};
I would recommend the use of an IIFE (immediatly invoked function expression):
var coolObj=(function(){
var public={};
var nonpublic={};
nonpublic.a=0;
public.getA=function(){nonpublic.a++;return nonpublic.a;};
return public;
})();
Now you can do:
coolObj.getA();//1
coolObj.getA();//2
coolObj.a;//undefined
coolObj.nonpublic;//undefined
coolObj.nonpublic.a;//undefined
I know this is not the answer youve expected, but i think its the easiest way of doing sth like that.
You can use a proxy which requires a key in order to define properties:
function createObject() {
var key = {configurable: true};
return [new Proxy({}, {
defineProperty(target, prop, desc) {
if (desc.value === key) {
return Reflect.defineProperty(target, prop, key);
}
}
}), key];
}
function func() {
var [obj, key] = createObject();
key.value = 0;
Reflect.defineProperty(obj, "value", {value: key});
key.value = function() {
key.value = obj.value + 1;
Reflect.defineProperty(obj, "value", {value: key});
};
Reflect.defineProperty(obj, "increase", {value: key});
return obj;
}
var obj = func();
console.log(obj.value); // 0
try { obj.value = 123; } catch(err) {}
try { Object.defineProperty(obj, "value", {value: 123}); } catch(err) {}
console.log(obj.value); // 0
obj.increase();
console.log(obj.value); // 1
I read a lot about JS accessors here and figure out this gonna be good for me:
This is what I used for local fields:
TYPE_DEFAULT_VALUE= {
number: 0,
string: "",
array: [],
object: {},
};
typeOf = function (object) {
if (typeof object === "number" && isNaN(object))
return NaN;
try {
return Object.prototype.toString.call(object).slice(8, -1).toLowerCase();
}
catch(ex) {
return "N/A";
};
};
getAccessor = function(obj, key, type, defaultValue) {
if (defaultValue === undefined)
defaultValue = TYPE_DEFAULT_VALUE[type] === undefined ? null : TYPE_DEFAULT_VALUE[type];
return {
enumerable: true,
configurable: true,
get: function () {
if (obj[key] === undefined)
obj[key] = defaultValue;
return obj[key];
},
set: function (value) {
if (typeOf(value) === type)
obj[key] = value;
},
};
}
LocalFields = function (fields, object) {
/**
* field properties
* {
* type: [ required ] ( number | string | array | object | ... ),
* defaultValue: [ optional ]
* }
*/
if (! fields)
throw "Too few parameters ...";
if (! object)
object = this;
var obj = this;
var fieldsAccessor = {};
for(key in fields){
field = fields[key];
fieldHandler = key[0].toUpperCase() + key.substr(1);
if(! field.type)
throw "Type not set for field: " + key;
fieldsAccessor[fieldHandler] = getAccessor(obj, fieldHandler, field.type, field.defaultValue)
}
Object.defineProperties(object, fieldsAccessor);
}
Now for each Class I can just call something like:
Person = function(){
new LocalFields({
id: { type: "number" },
name: { type: "string" },
phone: { type: "array" },
}, this);
}
And then like VS getter and setter you'll call:
var alex = new Person();
alex.Name = "Alex Ramsi";
console.clear();
console.info(alex.Name);
this works for all types but there is a problem because getter and setter is the basic operation and what if I want to add an array field and call this append method or even prepend is there anyhow anyway to do that?
For example How can I call:
alex.Phone.append('+1234567890');
That's a good effort but you forgot that there is no append function for array list!
You can use push and any other array functionality. Check it again;
I'm trying to rock a more functional style, and would like to set all properties of an object (and if possible sub-objects) to a specific value, e.g. false inplace. Is there a shortcut or do I have to iterate over the properties?
var obj = {
a: true,
b: true,
c: true,
...
z: true
}
Transforms into:
var obj = {
a: false,
b: false,
c: false,
...
z: false
}
You can use underscore for the more functional style.
You can iterate over your object if missing you can change or if it sub-object reiterate and change every missing sub-object properties.
function remap(object, missingValue, suppliedValue){
var keys= _.keys(object);
return _.reduce(keys, function(memo, key){
memo[key] = object[key];
if(memo[key] === missingValue){
memo[key] = suppliedValue;
}
if(_.isObject(memo[key])){
memo[key] = remap(memo[key],missingValue,suppliedValue);
}
return memo;
}, {});
}
var h = {val : 3, b : undefined, d : undefined , k : {
a: false, b: undefined
}, c: function(){ console.log(a);}};
console.log(remap(h,undefined,false));
If you need more complex check for comparing values then use the below function.
function remap(object, complexCheck){
var keys= _.keys(object);
return _.reduce(keys, function(memo, key){
memo[key] = object[key];
memo[key] = complexCheck(memo[key]);
if(_.isObject(memo[key])){
memo[key] = remap(memo[key],complexCheck);
}
return memo;
}, {});
}
I've written something similar for performing a regex replace on fields nested within my object which matched a given pattern. I then mixed it into the underscore/lodash object so I could use it like you're wanting to do.
Modified for your purposes it could look something like this:
function(obj) {
var $this = this,
checkField = function(field) {
if (typeof field === "undefined" || field === null || typeof field === "boolean") {
return false;
} else {
return field;
}
},
checkObject = function(obj) {
if (obj instanceof Object) {
Object.getOwnPropertyNames(obj).forEach(function (val) {
if (obj[val] instanceof Array) {
obj[val] = checkArray(obj[val]);
} else if (obj[val] instanceof Object) {
obj[val] = checkObject(obj[val]);
} else {
obj[val] = checkField(obj[val]);
}
});
return obj;
} else if (obj instanceof Array) {
return checkArray(obj);
} else {
return checkField(obj);
}
},
checkArray = function(arr) {
if (arr instanceof Array) {
arr.forEach(function(val) {
if (val instanceof Object) {
obj[val] = checkObject(val);
} else {
obj[val] = checkField(val);
}
});
return arr;
} else {
return arr;
}
};
obj = checkObject(obj);
}
To add it as a mixin:
window._.mixin({
setBoolsAndSuchToFalse: function(obj) {
. . . . // The contents of the function from above
}
});
I'm looking for a way to merge two configuration objects together, something like:
var developmentConfig = {
url: "localhost",
port: 80
};
var productionConfig = {
url: "example.com"
};
var config = isDevelopment ? developmentConfig : jQuery.extend(developmentConfig, productionConfig);
However, this is a Node.js app and I don't want to include jQuery and am looking for something similar but standalone. I know I could write something similar myself but I'd rather use something tested and proven (there are edge cases, complications when the config objects use richer hierarchies etc.)
Edit: Simple iteration is not enough because that does not handle hierarchical structures. Neither does Underscore's extend.
If all you need is extend, then it's pretty simple to write that in a couple of lines. If you want recursive extension, it's tricky to do that completely generically if you want have circular structures, objects with complex prototype chains, etc. If it's just some nested plain objects, then this should work:
function extend (target, source) {
target = target || {};
for (var prop in source) {
if (typeof source[prop] === 'object') {
target[prop] = extend(target[prop], source[prop]);
} else {
target[prop] = source[prop];
}
}
return target;
}
If you're looking for a lightweight library that does this (minus the recursion, for the reasons listed above) and other similar functions not provided by javascript, look at Underscore which is available via NPM for node too.
One more example of simple standalone function for future pilgrims across this question with protection from merge of the properties of different types:
function extend(obj) {
Array.prototype.slice.call(arguments, 1).forEach(function(source) {
if (source) {
for (var prop in source) {
if (source[prop].constructor === Object) {
if (!obj[prop] || obj[prop].constructor === Object) {
obj[prop] = obj[prop] || {};
extend(obj[prop], source[prop]);
} else {
obj[prop] = source[prop];
}
} else {
obj[prop] = source[prop];
}
}
}
});
return obj;
}
Usage:
extend({ name:'Maria', address:{ city:'Moscow', street:'Lenina str, 52' } }, { name:'Marianna', address:{ zip:1200003 }})
=> { name:'Marianna', address:{ city:'Moscow', street:'Lenina str, 52', zip:1200003 } }
This solution creates a new object and is able to handle multiple objects.
Furthermore, it is recursive and you can chose weather you want to overwrite Values and Objects.
function extendObjects() {
var newObject = {};
var overwriteValues = false;
var overwriteObjects = false;
for ( var indexArgument = 0; indexArgument < arguments.length; indexArgument++ ) {
if ( typeof arguments[indexArgument] !== 'object' ) {
if ( arguments[indexArgument] == 'overwriteValues_True' ) {
overwriteValues = true;
} else if ( arguments[indexArgument] == 'overwriteValues_False' ) {
overwriteValues = false;
} else if ( arguments[indexArgument] == 'overwriteObjects_True' ) {
overwriteObjects = true;
} else if ( arguments[indexArgument] == 'overwriteObjects_False' ) {
overwriteObjects = false;
}
} else {
extendObject( arguments[indexArgument], newObject, overwriteValues, overwriteObjects );
}
}
function extendObject( object, extendedObject, overwriteValues, overwriteObjects ) {
for ( var indexObject in object ) {
if ( typeof object[indexObject] === 'object' ) {
if ( typeof extendedObject[indexObject] === "undefined" || overwriteObjects ) {
extendedObject[indexObject] = object[indexObject];
}
extendObject( object[indexObject], extendedObject[indexObject], overwriteValues, overwriteObjects );
} else {
if ( typeof extendedObject[indexObject] === "undefined" || overwriteValues ) {
extendedObject[indexObject] = object[indexObject];
}
}
}
return extendedObject;
}
return newObject;
}
var object1 = { a : 1, b : 2, testArr : [888, { innArr : 1 }, 777 ], data : { e : 12, c : { lol : 1 }, rofl : { O : 3 } } };
var object2 = { a : 6, b : 9, data : { a : 17, b : 18, e : 13, rofl : { O : 99, copter : { mao : 1 } } }, hexa : { tetra : 66 } };
var object3 = { f : 13, g : 666, a : 333, data : { c : { xD : 45 } }, testArr : [888, { innArr : 3 }, 555 ] };
var newExtendedObject = extendObjects( 'overwriteValues_False', 'overwriteObjects_False', object1, object2, object3 );
Contents of newExtendedObject:
{"a":1,"b":2,"testArr":[888,{"innArr":1},777],"data":{"e":12,"c":{"lol":1,"xD":45},"rofl":{"O":3,"copter":{"mao":1}},"a":17,"b":18},"hexa":{"tetra":66},"f":13,"g":666}
Fiddle: http://jsfiddle.net/o0gb2umb/