Determine if an object property is ko.observable - javascript

I'm using KnockoutJS version 2.0.0
If I'm looping through all properties of an object, how can I test whether each property is a ko.observable? Here's what I've tried so far:
var vm = {
prop: ko.observable(''),
arr: ko.observableArray([]),
func: ko.computed(function(){
return this.prop + " computed";
}, vm)
};
for (var key in vm) {
console.log(key,
vm[key].constructor === ko.observable,
vm[key] instanceof ko.observable);
}
But so far everything is false.

Knockout includes a function called ko.isObservable(). You can call it like ko.isObservable(vm[key]).
Update from comment:
Here is a function to determine if something is a computed observable:
ko.isComputed = function (instance) {
if ((instance === null) || (instance === undefined) || (instance.__ko_proto__ === undefined)) return false;
if (instance.__ko_proto__ === ko.dependentObservable) return true;
return ko.isComputed(instance.__ko_proto__); // Walk the prototype chain
};
UPDATE: If you are using KO 2.1+ - then you can use ko.isComputed directly.

Knockout has the following function which I think is what you are looking for:
ko.isObservable(vm[key])

To tack on to RP Niemeyer's answer, if you're simply looking to determine if something is "subscribable" (which is most often the case). Then ko.isSubscribable is also available.

I'm using
ko.utils.unwrapObservable(vm.key)
Update: As of version 2.3.0, ko.unwrap was added as substitute for ko.utils.unwrapObservable

Related

Angular services with default values for non-existing attributes

Working on an Ionic application that performs both in Android and Windows.
There are services, such as Ionic's $ionicLoading, which we override functionality in order to work properly in windows:
angular.factory('$ionicLoading', function(){
return {
show: function (){...} // custom implementation
hide: function (){...} // custom implementation
}
});
But there are other services which we have to override only to not break the app.
In this cases it would be really useful to provide a service that won't do anything. For example:
angular.factory('$ionicExampleService', function(){
return {
*foo*: angular.noop // for operations
*bar*: promise // returns promise
}
});
Note: I know that a better way of doing this would be with a service that chooses between Ionic's implementation or a made one, but this is just for the sake of learning.
The ideal would be going even further, it would be magnificent to be able to return something even more bulletproof. Something like a generic flexible services:
angular.factory('$ionicPopup', function(){
return /*magic*/;
});
$ionicPopup.show({...}) // show was not defined
.then(foo); // won't break and will execute foo()
It is possible?
From what I understood you need to override implementation of existing services. You can do that with an angular service decorator.
A service decorator intercepts the creation of a service, allowing it to override or modify the behaviour of the service. The object returned by the decorator may be the original service, or a new service object which replaces or wraps and delegates to the original service.
For more information you can check angular documentation. One simple example would be:
app.factory('someService', function () {
return {
method1: function () { return '1'; }
method2: function () { return '2'; }
};
});
app.decorator('someService', function ($delegate) {
// NOTE: $delegate is the original service
// override method2
$delegate.method2 = function () { return '^2'; };
// add new method
$delegate.method3 = function () { return '3'; };
return $delegate;
});
// usage
app.controller('SomeController', function(someService) {
console.log(someService.method1());
console.log(someService.method2());
console.log(someService.method3());
});
EDIT: Question - How to override every method in the service?
var dummyMethod = angular.noop;
for(var prop in $delegate) {
if (angular.isFunction($delegate[prop])) {
$delegate[prop] = dummyMethod;
}
}
I hope that this helps you.
Using an evaluation for each assignment based on an object property, similar to this:
myVar = myObj.myPropVar === undefined ? "default replacement" : myObj.myPropVar;
Basically you're using a check for if the property has been defined, substituting a default value if it hasn't, and assigning it if it has.
Alternatively, you can use a modified version of the global function in Sunny's linkback to define defaults for all those properties you might assume to be undefined at specific points in your code.
function getProperty(o, prop) {
if (o[prop] !== undefined) return o[prop];
else if(prop == "foo") return "default value for foo";
else if(prop == "bar") return "default value for bar";
/* etc */
else return "default for missing prop";
}
Hope that helps,
C§
use var a = {}; to declare new variable.

angular.isDefined() vs obj.hasOwnProperty()

I have an object that may or may not have a status. When using the angular.js framework which would be more appropriate. What are the advantages and disadvantages of both.
var checkStatus = function(item){
if(angular.isDefined(item.status){
//do something
}
//VS.
if(item.hasOwnProperty('status')){
//do something
}
}
checkStatus(item);
angular.isDefined only test if the value is undefined :
function isDefined(value){return typeof value !== 'undefined';}
Object.hasOwnProperty test if the value is a direct one and not an inherited one.
For example :
var test = {};
angular.isDefined(test.toString); // true
test.hasOwnProperty('toString'); // false
info : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty

Checking if parameter is a contructor or an instance in javascript

I wanna do the following, if the parameter passed is a contructor, then do new 'constructor' if not, just use the instance. How can I do that?
This is what I've done so far, but it doesn't work. I think something is wrong with my code:
JS
var showList = function (view, options) {
// checking if view is a conctructor or not
if (view instanceof view) {
app.getRegion('main').show(view(options));
} else {
app.getRegion('main').show(new view(options));
}
}
so the above function can be used as:
var listView = new ListView;
showList(listView);
or straight:
showList(new ListView);
I think you're going to want to test whether the argument is an object or a function:
if (typeof view === "function")
will tell you it's a function (a constructor function in your context)
if (typeof view === "object")
will tell you that it's an already constructed object.
var showScreen = function (view, options) {
// check if view is already an object
if (typeof view === "object") {
app.getRegion('main').show(view(options));
} else {
app.getRegion('main').show(new view(options));
}
}
One thing I'm confused about in your code is if view is already an object, then why do you do view(options). That doesn't make sense to me. Doing new view(options) when view is a function makes sense, but not the other option so I think something also needs to be corrected with that line of code. Do you perhaps mean to call a method on that object?
FYI, I tend to avoid using instanceof as a general practice if there is another option because instanceof can have issues with cross frame code whereas typeof does not have those issues.
var showScreen = function (view, options) {
// checking if view is a conctructor or not
if (view instanceof Function) {
app.getRegion('main').show(new view(options));
} else {
app.getRegion('main').show(view(options));
}
}
Maybe not the best way but well.
function A(){}
var a = new A();
a instanceof A // true
a instanceof Function // false
A instanceof Function // true
This seems like a code smell to me. I think it's better pass an instance instead of a constructor function.
In this case you can do:
showScreen(new ListView(options))
If it's hard to construct a ListView you should wonder why that is.

How can I make Ember.js handlebars #each iterate over objects?

I'm trying to make the {{#each}} helper to iterate over an object, like in vanilla handlebars. Unfortunately if I use #each on an object, Ember.js version gives me this error:
Assertion failed: The value that #each loops over must be an Array. You passed [object Object]
I wrote this helper in attempt to remedy this:
Ember.Handlebars.helper('every', function (context, options) {
var oArray = [];
for (var k in context) {
oArray.push({
key : k,
value : context[k]
})
}
return Ember.Handlebars.helpers.each(oArray, options);
});
Now, when I attempt to use {{#every}}, I get the following error:
Assertion failed: registerBoundHelper-generated helpers do not support use with Handlebars blocks.
This seems like a basic feature, and I know I'm probably missing something obvious. Can anyone help?
Edit:
Here's a fiddle: http://jsfiddle.net/CbV8X/
Use {{each-in}} helper. You can use it like like {{each}} helper.
Example:
{{#each-in modelWhichIsObject as |key value|}}
`{{key}}`:`{{value}}`
{{/each-in}}
JS Bin demo.
After fiddling with it for a few hours, I came up with this hacky way:
Ember.Handlebars.registerHelper('every', function(context, options) {
var oArray = [], actualData = this.get(context);
for (var k in actualData) {
oArray.push({
key: k,
value: actualData[k]
})
}
this.set(context, oArray);
return Ember.Handlebars.helpers.each.apply(this,
Array.prototype.slice.call(arguments));
});
I don't know what repercussions this.set has, but this seems to work!
Here's a fiddle: http://jsfiddle.net/CbV8X/1/
I've been after similar functionality, and since we're sharing our hacky ways, here's my fiddle for the impatient: http://jsfiddle.net/L6axcob8/1/
This fiddle is based on the one provided by #lxe, with updates by #Kingpin2k, and then myself.
Ember: 1.9.1, Handlebars: 2.0.0, jQuery 2.1.3
Here we are adding a helper called every which can iterate over objects and arrays.
For example this model:
model: function() {
return {
properties: {
foo: 'bar',
zoo: 'zar'
}
};
}
can be iterated with the following handlebars template:
<ul class="properties">
{{#every p in properties}}
<li>{{p.key}} : {{p.value}}</li>
{{/every}}
</ul>
every helper works by creating an array from the objects keys, and then coordinating changes to Ember by way of an ArrayController. Yeah, hacky. This does however, let us add/remove properties to/from an object provided that object supports observation of the [] property.
In my use case I have an Ember.Object derived class which notifies [] when properties are added/removed. I'd recommend looking at Ember.Set for this functionality, although I see that Set been recently deprecated. As this is slightly out of this questions scope I'll leave it as an exercise for the reader. Here's a tip: setUnknownProperty
To be notified of property changes we wrap non-object values in what I've called a DataValueObserver which sets up (currently one way) bindings. These bindings provide a bridge between the values held by our internal ArrayController and the object we are observing.
When dealing with objects; we wrap those in ObjectProxy's so that we can introduce a 'key' member without the need to modify the object itself. Why yes, this does imply that you could use #every recursively. Another exercise for the reader ;-)
I'd recommend having your model be based around Ember.Object to be consistent with the rest of Ember, allowing you to manipulate your model via its get & set handlers. Alternatively, as demonstrated in the fiddle, you can use Em.Get/Em.set to access models, as long as you are consistent in doing so. If you touch your model directly (no get/set), then every won't be notified of your change.
Em.set(model.properties, 'foo', 'asdfsdf');
For completeness here's my every helper:
var DataValueObserver = Ember.Object.extend({
init: function() {
this._super();
// one way binding (for now)
Em.addObserver(this.parent, this.key, this, 'valueChanged');
},
value: function() {
return Em.get(this.parent, this.key);
}.property(),
valueChanged: function() {
this.notifyPropertyChange('value');
}
});
Handlebars.registerHelper("every", function() {
var args = [].slice.call(arguments);
var options = args.pop();
var context = (options.contexts && options.contexts[0]) || this;
Ember.assert("Must be in the form #every foo in bar ", 3 == args.length && args[1] === "in");
options.hash.keyword = args[0];
var property = args[2];
// if we're dealing with an array we can just forward onto the collection helper directly
var p = this.get(property);
if (Ember.Array.detect(p)) {
options.hash.dataSource = p;
return Ember.Handlebars.helpers.collection.call(this, Ember.Handlebars.EachView, options);
}
// create an array that we will manage with content
var array = Em.ArrayController.create();
options.hash.dataSource = array;
Ember.Handlebars.helpers.collection.call(this, Ember.Handlebars.EachView, options);
//
var update_array = function(result) {
if (!result) {
array.clear();
return;
}
// check for proxy object
var result = (result.isProxy && result.content) ? result.content : result;
var items = result;
var keys = Ember.keys(items).sort();
// iterate through sorted array, inserting & removing any mismatches
var i = 0;
for ( ; i < keys.length; ++i) {
var key = keys[i];
var value = items[key];
while (true) {
var old_obj = array.objectAt(i);
if (old_obj) {
Ember.assert("Assume that all objects in our array have a key", undefined !== old_obj.key);
var c = key.localeCompare(old_obj.key);
if (0 === c) break; // already exists
if (c < 0) {
array.removeAt(i); // remove as no longer exists
continue;
}
}
// insert
if (typeof value === 'object') {
// wrap object so we can give it a key
value = Ember.ObjectProxy.create({
content: value,
isProxy: true,
key: key
});
array.insertAt(i, value);
} else {
// wrap raw value so we can give it a key and observe when it changes
value = DataValueObserver.create({
parent: result,
key: key,
});
array.insertAt(i, value);
}
break;
}
}
// remove any trailing items
while (array.objectAt(i)) array.removeAt(i);
};
var should_display = function() {
return true;
};
// use bind helper to call update_array if the contents of property changes
var child_properties = ["[]"];
var preserve_context = true;
return Ember.Handlebars.bind.call(context, property, options, preserve_context, should_display, update_array, child_properties);
});
Inspired by:
How can I make Ember.js handlebars #each iterate over objects?
http://mozmonkey.com/2014/03/ember-getting-the-index-in-each-loops/
https://github.com/emberjs/ember.js/issues/4365
https://gist.github.com/strathmeyer/1371586
Here's that fiddle again if you missed it:
http://jsfiddle.net/L6axcob8/1/

KnockoutJS - computed abservable and js object

I am trying to return a property of an observable but seem to be missing something.
self.SelectedAccountTypeID = ko.computed(function () {
return self.selectedAccountType.AccountTypeID();
});
I am trying to return the AccountTypeID property of selectedAccountType but this is not working
when I try
self.SelectedAccountTypeID = ko.computed(function () {
return self.selectedAccountType();
});
it works but returns a javascript object
Here is a fiddle with the code
http://jsfiddle.net/qafrD/
You are on the right track, because your selectedAccountType is an observable you need to access its value with selectedAccountType()
So the correct syntax: self.selectedAccountType().AccountTypeID;
However because the self.selectedAccountType() can be null you need to check that first before accessing the AccountTypeID on it:
self.SelectedAccountTypeID = ko.computed(function () {
if (self.selectedAccountType())
return self.selectedAccountType().AccountTypeID;
});
Demo Fiddle
This is because self.selectedAccountType is an observable meaning that you need to invoke it like a function to retrieve its current value. The property "AccountTypeID" however is not an observable therefore you do not need parenthesis here.
self.SelectedAccountTypeID = ko.computed(function () {
// Retrieve the value of the observable
var selectedAccountType = self.selectedAccountType();
// The value may be "undefined" or "null" if there has not yet been
// anything stored in the observable
if (selectedAccountType && typeof selectedAccountType.AccountTypeID != "undefined") {
return selectedAccountType.AccountTypeID;
}
// Return a default value otherwise
return null;
});
Demo: http://jsfiddle.net/qafrD/1/

Categories

Resources