Using setters functions in knockoutjs bindings - javascript

It is end of day and my brain is down for the night, but I am working on learning how to use setters when dynamically binding to Html elements. Of the many examples I have read so far, it seems the Urls below are the most helpful on the subject of using setters with knockoutjs bindings but I still do not get the idea or yet understand how it ought to be done.
knockoutjs-data-bind-setter
conditionally-bind-a-function-in-knockoutjs
knockout-data-bind-on-dynamically-generated-elements
easy-two-way-data-binding-in-javascript
For instance my viewmodel below (see fiddle) would like to protect the private variables and probably do so by adding some sort of validation code later on.However, right now, it simply needs to get at the parameter or text value entered into the text box by user. What exactly is the best syntax for this kind of operation?
<!-- related bindings: valueUpdate is a parameter for value -->
Your value: <input data-bind="value: someValue, valueUpdate: 'afterkeydown'"/>

If your trying to achieve two way binding along with the validation, then knockouts computed functions should be the best approach.
In your view model have a private variable and and expose ko.computed function for the binding, then in either read/write part of the computed you can do the validation.

Technically you could do that, but it is not the way Knockout is meant to be used. For example, let's say our viewmodel has one <select> and one text <input> binding. Using the private vars to hold the actual values, means we need a writable computed observable to update it, because Knockout only binds properties to your view, not private vars.
function appWithPrivateVars() {
var selectedItem = ko.observable('Option 3'), //our private vars
textVal = ko.observable('Haha');
this.selected = ko.computed({
read: function() { return selectedItem(); },
write: function(value) { /* add validation code here */ selectedItem(value); }
});
this.textVal = ko.computed({
read: function() { return textVal(); },
write: function(value) { /* add validation code here */ textVal(value); }
});
this.listItems = ['Option 1','Option 2','Option 3'];
this.get = function() { return selectedItem(); }; //getter
}
Now compare with the code needed for the same viewmodel without caring about private vars (also notice you don't need an explicit getter/setter) :
function appWithProperties() {
var self = this;
this.textVal = ko.observable('Haha');
// example of computed
this.textValInput = ko.computed({
read: function() { return self.textVal(); },
write: function(value) { /* add validation code here */ textVal(value); }
this.selected = ko.observable('Option 3');
this.listItems = ['Option 1','Option 2','Option 3'];
}
The thing is you don't need to 'protect' your otherwise accessible model properties because if they are not bound to the view, they will not be able to be modified. Furthermore, you will get yourself in trouble if you use var's at the moment you want to easily serialize your data to JSON with the ko.toJSON function (unless you're willing to rewrite an entire parsing function). Compare the outputs of ko.toJSON for both viewmodels:
Sample data for appWithPrivateVars
// no private vars included, eg. 'selectedItem'
{"selected":"Option 1", // computed prop; not what we need
"textVal":"Haha",
"listItems":["Option 1","Option 2","Option 3"]}
See how the 'actual' values are not included in the mapping (which is logical because ko.toJSON doesn't have access to them). Now check out the JSON output for appWithProperties:
{"textVal":"Haha", // actual value
"textValInput: "Haha", // value filter
"selected":"Option 1",
"listItems":["Option 1","Option 2","Option 3"]
}
Check out the fiddle

Related

Knockout subscribe scope

Is there any possibility to change the scope of the subscribe in Knockout?
I have something like this:
element =
{
type: ko.observable()
name: ko.observable()
content: ko.observable()
}
element.type.subscribe(this._typeChanged.bind(element))
Basically I want to have an access to the object which property I am subscribed to. Binding like in my code does nto work since it binds to the whole VeiwModel and not the object.
Maybe the knockout handle that when you subscribe an observable you can pass 2 parameters the first is the callback and the second is the scope/context, try something like this:
element.type.subscribe(this._typeChanged, element)
The subscribe function accepts three parameters: callback is the function that is called whenever the notification happens, target (optional) defines the value of this in the callback function, and event (optional; default is "change") is the name of the event to receive notification for.
Ref. http://knockoutjs.com/documentation/observables.html
The problem is the way in which you're creating your view model. The view model shuld be self-contained, including the functions that operate on it. It should be something like this:
var ViewModel = function() {
var self = this;
self.type = ko.observable();
self.name = ko.observable();
self.content = ko.observable();
self.type.subscribe(function(newVal) {
// here you have access to all the viewmodel properties through self
});
return self;
};
This is a constructor using the var self=this; pattern.To use the view model you need to instantiate it, i.e. var vm = new ViewModel(). (You can omit the new).
Of course, you can also define a function, and bind it to self, or receive a callback in the constructor, and bind it to self. In that case, the function implementation will have the view model accesible via this, and not self, which will be undefined inside the function body.
var doSomethignWithVm = function(newVal) {
// acces viewmodel via this
// you can also use newVal
};
You modify the constructor to receive this as a callback:
var ViewModel = function(doSomethingCallback) {
self.type.subscribe(callback.bind(self));
};
This pattern doesn't make much sense because your callback is supposed to have knowledge of your view model. In that case it makes more sense to include the subscription functionality directly inside the model.
EDIT
Note: as I've mentioned in a comment to Joel Ramos Michaliszen's answer, both of this codes are equivalent:
self.type.subscribe(callback.bind(self));
self.type.subscribe(callback.bind, self);
You can check that by seeing the source code of subscribable in knockout's gitbhub, in the file knockout/src/subscribales/subscribable.js. If you look for subscribe implementation you'll see this:
subscribe: function (callback, callbackTarget, event) {
// ...
boundCallback = callbackTarget ? callback.bind(callbackTarget) : callback;
I.e. if you provide a second argument, it's used tob bind the function passed in the firt argument to it.
Although I get that I may have the wrong approach top this I am also in a stage where I will not be able to do any breaking changes to the app.
I figured out that I could use lodash to help me with this.
I ended up using partial function to append the element as a parameter in the subscribe callback:
element.type.subscribe(_.partial(this.typeChanged, element))
or in coffeescript
element.type.subscribe $_.partial #typeChanged, element
Now the chartTypeChanged has 2 parameters on the input instead of one.

Ember properties with multiple dependencies dont update as expected

I have following issue concerning understanding ember properties:
If i have a propertyA:
propertyA: function() {
return this.get("propertyB.someObject.someValue");
}.property("propertyB")
and a propertyB:
propertyB: function() {
return this.get("propertyX.someObject");
}.property("propertyX", "propertyY", "propertyZ")
And i have a binding for propertyA in some template like:
{{propertyA}}
Then in 90% of the cases in my code it happens that propertyA does not get updated properly when i set i.e. propertyX.
If i understand it correctly, then propertyB should become dirty as soon as one of the dependent properties (like propertyX) changes. This should automatically make propertyA dirty and thus update it automatically since it has a binding.
What happens in my code is, that propertyA remains the old cached value even when i called it in the console, but when i call propertyB it revaluates and returns the updated code, since it was dirty.
The question is, why does propertyA not automatically become dirty when propertyB does? Is it because propertyB has no binding in a template? I thought it is not necessary if propertyA has the dependence.
I also figured out that this problem does not occur when propertyB just depends on propertyX, so the multi-dependency must somehow mess things up.
Sorry for this quite complicated explanation but i tried to abstract my actual code as simple as possible.
UPDATE:
Ok here some actual code:
Controller:
styling: function() {
var clipValues = this.get("clip.styling") || {};
var infoValues = this.get("clip.info.styling") || {};
return Ember.Object.create(jQuery.extend({}, clipValues, infos));
}.property("clip.styling", "clip.info.styling"),
showBottombar: function() {
return (!!this.get("bottombarSrc") || !!this.get("styling.bottombar.fancyStuff"));
}.property("styling"),
Somewhere else the clip gets set for this controller. And later its info gets updated in the clip model which is a simple Ember.Object:
getInfo: function(url) {
var self = this;
return App.ajax(url).then(function(response) {
self.set("info", response);
});
}
Now after getInfo gets called, the {{showBottombar}} in the template shows "false" even if "bottombarSrc" and "...fancyStuff" is true. When i call "styling" from the console, it reevaluates the styling code which indicates that it was marked as dirty after clip.getInfo happened (which sets the "info"). But this does not effect the showBottombar. It just does not get called afterwards.
UPDATE 2
There are two strange ways of making it work, but i dont understand why:
First one is adding a styling binding to a template:
{{styling}}
That causes showBottombar to get called after the styling changes.
Second one is removing other dependencies from the styling property:
styling: function() {
var clipValues = this.get("clip.styling") || {};
var infoValues = this.get("clip.info.styling") || {};
return Ember.Object.create(jQuery.extend({}, clipValues, infos));
}.property("clip.info.styling"),
(no more "clip.styling" dependency). Which also causes the showBottombar property to work properly. Both ways work individually.
propertyA: function() {
return this.get("propertyB.someObject.someValue");
}.property("propertyB").volatile()
http://emberjs.com/api/classes/Ember.ComputedProperty.html#method_volatile

How to display a property in JavaScript object using Knockoutjs?

I am new to Knockoutjs and JavaScript, and need some advice.
In my HTML (View) page, I am trying to show a text property from my Javascript object (Model), which looks something like:
var object = function() {
this.text = "blah blah blah";
}
In my Object's ViewModel, I have this:
var objectViewModel= function (object) {
var content = ko.observable(object); // or is it object.text() ?
self.section = function() {
return content.text; //or is it content ?
}
}
And in my view, I have this:
<span data-bind="text:section"></span>
My main question is how do I make the HTML show a model's property (the text) via viewmodel? I commented in my other questions, and would like some help.
Thanks in advance!
So I'd recommend this post as a good read.
To answer both of the additional commented questions: It all depends on what you passed as the parameter. That important bit of information would be provided when you instantiate your viewmodel, which you left out.
As specified, you'll need to ko.applyBindings(new objectViewModel(new object())).
Secondly, you have self, where did it come from? Make it this or declare var self = this; or provide the rest of the code from which that variable is coming from.
Next, from within the section function you need to "read" your content observable:
return content().text
Finally, in your view you need to execute section:
<span data-bind="text:section()"></span>
As an additional consideration, you could make section a computed observable with a dependency on content.
this.section = ko.computed(function() {
return content().text;
})
which removes the need to execute in the view. Check out this fiddle with two different scenarios depicted.

knockout computed dependency is empty initially

I have this viewmodel, on my web I have a dropdown that updates the sortedallemployees option. It works fine except my table is empty initially. Once I sort the first time I get data. Seems like when the vm is created it doesn't wait for allemployees to be populated.
var vm = {
activate: activate,
allemployees: allemployees,
sortedallemployees:ko.computed( {
return allemployees.sort(function(f,s) {
var ID = SelectedOptionID();
var name = options[ ID - 1].OptionText;
if (f[name] == s[name]) {
return f[name] > s[name] ? 1 : f[name] < s[name] ? -1 : 0;
}
return f[name] > s[name] ? 1 : -1;
});
}
Without the rest of your code, its difficult to tell exactly how this will behave. That being said, you are doing several very odd things that I would recommend you avoid.
First, defining all but the simplest viewmodels as object literals will cause you pain. Anything with a function or a computed will almost certainly behave oddly, or more likely not at all, when defined this way.
I would recommend using a constructor function for your viewmodels.
var Viewmodel = function(activate, allEmployees) {
var self = this;
self.activate = activate;
self.allEmployees = ko.observableArray(allEmployees);
self.sortedEmployees = ko.computed(function() {
return self.allEmployees().sort(function(f,s) {
//your sort function
});
});
};
var vm = new Viewmodel(activate, allemployees);
This method has several advantages. First, it is reusable. Second, you can reference its properties properly during construction, such as during the computed definition. It is necessary for a computed to reference at least one observable property during definition for it to be reactive.
Your next problem is that your computed definition is not a function, but an object. It isn't even a legal object, it has a return in it. This code shouldn't even compile. This is just wrong. The Knockout Documentation is clear on this point: computed's are defined with a function.
Your last problem is that your sort function is referencing things outside the viewmodel: SelectedOptionID(). This won't necessarily stop it from working, but its generally bad practice.

Dealing with non-saveable values in Backbone

Is there a standard way to deal with non-saveable values in Backbone.
e.g.
MyModel = Backbone.extend(Backbone.Model, {
initialize: function () {
this.set({'inches': this.get('mm') / 25});
}
})
If I call save() on this model it will throw an error as there is no corresponding database field for inches. I can think of a few ways to fix this, but am wondering if there's a tried and tested approach generally best used for this?
At the moment my preferred solution is to extend Backbone's toJSON method and to allow passing of a boolean parameter dontCleanup to allow for it to still return all the model's values (including the non saveable ones) when it's needed e.g. for passing to a template.
I like Peter Lyon's idea. I've thought about that a few times, but never actually put it in place. For all the ways that I have handled this, though, here are my two favorites:
Non-"attribute" values
View Models
Non-Attribute Values
This one is simple: don't store the values you need in the model's standard attributes. Instead, attach it directly to the object:
myModel.someValue = "some value";
The big problem here is that you don't get all of the events associated with calling set on the model. So I tend to wrap this up in a method that does everything for me. For example, a common method I put on models is select to say that this model has been selected:
MyModel = Backbone.Model.extend({
select: function(){
if (!this.selected){
this.selected = true;
this.trigger("change:selected", this, this.selected);
}
}
});
In your case, I'm not sure this would be a good approach. You have data that needs to be calculated based on the values that are in your attributes already.
For that, I tend to use view models.
View models.
The basic idea is that you create a backbone model that is persist-able, as you normally would. But the you come along and create another model that inherits from your original one and adds all the data that you need.
There are a very large number of ways that you can do this. Here's what might be a very simple version:
MyModel = Backbone.Model.Extend({ ... });
MyViewModel = function(model){
var viewModel = Object.create(model);
viewModel.toJSON = function(){
var json = model.toJSON();
json.inches = json.mm / 25;
return json;
};
return viewModel;
});
The big benefit of wrapping this with Object.create is that you now have a prototypal inheritance situation, so all of your standard functionality from the model is still in place. We've just overridden the toJSON method on the view model, so that it returns the JSON object with the inches attribute.
Then in a view that needs this, you would wrap your model in the initialize function:
MyView = Backbone.View.extend({
initialize: function(){
this.model = MyViewModel(this.model);
},
render: function(){
var data = this.model.toJSON(); // returns with inches
}
});
You could call new MyViewModel(this.model) if you want, but that's not going to do anything different, in the end, because we're explicitly returning an object instance from the MyViewModel function.
When your view's render method calls toJSON, you'll get the inches attribute with it.
Of course, there are some potential memory concerns and performance concerns with this implementation, but those can be solved easily with some better code for the view model. This quick and dirty example should get you down the path, though.
I think this should do it. Define your Model defaults as your valid schema and then return only the subset of this.attributes that is valid during toJSON.
var Model = Backbone.Model.extend({
defaults: {
foo: 42,
bar: "bar"
},
toJSON: function () {
var schemaKeys = _.keys(this.defaults);
var allowedAttributes = {};
_.each(this.attributes, function (value, key) {
if (_.include(schemaKeys, key)) {
allowedAttributes[key] = value;
}
return allowedAttributes;
}
});
Note that _.pick would make the code a bit shorter once you have underscore 1.3.3 available. I haven't seen a "tried and tested" convention in my travels through the backbone community, and since backbone leaves so many options open, sometimes conventions don't emerge, but we'll see what this stackoverflow question yields.
Dealing with non-persisted attributes in Backbone.js has been doing my head in for a while, particularly since I've started using ember/ember-data, which handles the various situations through computed properties, ember-data attributes, or controllers.
Many solutions suggest customising the toJSON method. However, some popular Backbone plugins (particularly those that deal with nested models), implement their own toJSON method, and make a call to Backbone.Model.prototype.toJSON to obtain an object representation of a model's attributes. So by overwriting the toJSON method in a model definition, you'll lose some (potentially crucial) features of those plugins.
The best I've come up with is to include an excludeFromJSON array of keys in the model definition, and overwrite the toJSON method on Backbone.Model.prototype itself:
Backbone.Model.prototype.toJSON = function() {
var json = _.clone(this.attributes),
excludeFromJSON = this.excludeFromJSON;
if(excludeFromJSON) {
_.each(excludeFromJSON, function(key) {
delete json[key];
});
}
return json;
};
MyModel = Backbone.Model.extend({
excludeFromJSON: [
'inches'
]
});
In this way, you'll only have to define the non-persisted keys (if you forget to do so, you'll soon be reminded when your server throws an error!). toJSON will behave as normal if no excludeFromJSON property is present.
In your case, inches is a computed property, derived from mm, so it makes sense to implement this as a method on your model (ensuring that the value for inches is correct when mm is changed):
MyModel = Backbone.Model.extend({
inches: function() {
return this.get('mm') / 25;
}
});
However, this has the downside of being accessed differently to everyother attribute. Ideally you'll want to keep it consistent with accessing other attributes. This can be achieved by extending the default get method:
var getMixin = {
get: function(attr) {
if(typeof this[attr] == 'function') {
return this[attr]();
}
return Backbone.Model.prototype.get.call(this, attr);
}
};
MyModel = Backbone.Model.extend({
inches: function() {
return this.get('mm') / 25;
}
});
_.extend(MyModel.prototype, getMixin);
Which will let you do:
new MyModel().get('inches');
This approach doesn't touch the underlying attributes hash, meaning that inches will not appear in the toJSON representation, unless you set the value of inches later on, in which case you'll need something like the excludeFromJSON array.
If you have the need to set the inches value, you may also want to listen for changes and adjust the value of mm:
MyModel = Backbone.Model.extend({
initialize: function() {
this.on('change:inches', this.changeInches, this);
},
inches: function() {
return this.get('mm') / 25;
},
changeInches: function() {
this.set('mm', this.attributes.inches * 25);
}
});
_.extend(MyModel.prototype, getMixin);
See the complete example on JSBin.
It's also worth noting that the (official?) purpose of the toJSON method has recently been redefined as preparing a model for syncing with a server. For this reason, calling toJSON should always return only the "persistable" (or "saveable") attributes.

Categories

Resources