knockout How to change the model binded to elements - javascript

I'm really just starting into knockout and so far it is pretty impressive. With that, there is also an issue I am running into.
I'm trying to take a list of binded objects and when on is clicked, I want it take that model and bind it to another view for further editing, no issues there. The problem I run into is that I get an error saying I cannot bind multiple times to the same element. I have read the ko.cleanNode([domElement]) can be used to circumvent it, but that is really meant to be used internally. I have done some searching and people mention you should use with but I don't think that applies to what I want to do.
Is there a way to apply the bindings but then just update the underlying model context?
Simplified jsfiddle example. My js code starts on like 119, above it the ko mapping plugin.

First, use the external resources to add external resources to your fiddle rather than pasting them into your fiddle.
Second, on your problem, the easiest way to do something like this is to have a SelectedItem property in your view model and bind the child view to it. Something like this:
var MyViewModel = function() {
var self = this;
self.MyItems = ko.observableArray();
self.SelectedItem = ko.observable();
}
var MyChildViewModel = function() {
var self = this;
self.SomeProperty = ko.observable();
}
Now populate MyItems with instances of MyChildViewModel and set SelectedItem to whichever item in MyItems you want to have as your child and bind the child view to SelectedItem:
<div id="theChildView" data-bind="with: SelectedItem">
<span data-bind="text: SomeProperty"></span>
</div>

Related

Having a separate VM class per knockoutjs template violates OOP?

I have seen many a times that a web page has multiple templates for a specific region of the page, one of which is loaded depending on the scenario. Typically a corresponding VM is instantiated at that point and bound to the template.
Does this design pattern not violate OOP principles in that the objects (VMs in this case) should be based on the functional entities that actually exist and not necessarily based on a specific area of the UI.
Lemme substantiate this with an example. Lets say we have a web page dealing with online sales of clothes, shoes and furniture. Once a selection in made among the three in a particular part of the page, a separate template is loaded in another part of the page which is dependent on the selection made. Should my object model be ItemVM<-ShoeVM,ClothesVM,FurntitureVM as per OOP rules, or should it be ParentVM containing ItemSelectorVM (for the UI to select the item type), shoeAdverisementVM (to bind to the shoe template loaded as a result), FurnitureAdvertisementVM and many more VMs for every part of the page?
When you say OOP principles, I'm assuming you are referring to SOLID.
View Models model the UI and do not have to be coupled to a single template. You may use a view model with a template that has been created to target desktops and then reuse that view model with a template that has been created to target mobiles.
Should my object model be ItemVM<-ShoeVM,ClothesVM,FurntitureVM as per
OOP rules, or should it be ParentVM containing ItemSelectorVM (for the
UI to select the item type), shoeAdverisementVM (to bind to the shoe
template loaded as a result), FurnitureAdvertisementVM and many more
VMs for every part of the page?
This depends on the complexity of the application and domain.
Let's assume that your UI is doing more than just iterating over an ObservableArray<T> where T could be of type Shoe or Furniture. And let's also assume that ShoeAdvertisementVM is not some decorator of ItemVM<T>.
Now, lets say you have FurnitureAdvertisementVM and ShoeAdverisementVM that look like this:
function FurnitureAdvertisementVM(furniture){
this.items = ko.observableArray(furniture);
this.doSomething = function(){
// doing something
}
}
function ShoeAdverisementVM(shoes){
this.items = ko.observableArray(shoes);
this.doSomething = function(){
// doing something
}
}
var shoeVM = new ShoeAdverisementVM(shoes);
var furnitureVM = new FurnitureAdverisementVM(furniture);
If the two view models are pretty much a copy and paste job and the only difference is the name of the view model and the type of the items that go into the collection then it's probably worth changing the implementation into something generic like this:
function ItemsVM(items){
this.items = ko.observableArray(items);
this.doSomething = function(){
// doing something
}
}
var shoeVM = new ItemsM(shoes);
var furnitureVM = new ItemsVM(furniture);
But if each view model has some very specific properties and functions, for example:
function FurnitureAdvertisementVM(furniture, someOtherDependency){
this.items = ko.observableArray(furniture);
this.doSomething = function(){
// doing something
}
this.propertyIsBoundToATextbox = ko.observable();
this.clickHandlerUsesDependency = function(ctx){
if(ctx.SomeCondition){
someOtherDependency.doSomething();
}
}
}
function ShoeAdverisementVM(shoes){
this.items = ko.observableArray(shoes);
this.doSomething = function(){
// doing something
}
this.propertyIsBoundToACheckbox = ko.observable();
}
Then it it is not as clear cut as making both view models generic.
Roy J commented:
your viewmodels are intended to model the view (the UI)
Which is so true, so if the views have different requirements, i.e. one has 3 checkboxes the other has loads of buttons etc, then you are likely going to need different view models.
If you want to factor out some of common functionality into separate classes then you can:
function ItemService(items){
this.items = ko.observableArray(items);
this.doSomething = function(){
// doing something
}
}
function FurnitureAdvertisementVM(itemService, someOtherDependency){
this.service = itemService;
this.propertyIsBoundToATextbox = ko.observable();
this.clickHandlerUsesDependency = function(ctx){
if(ctx.SomeCondition){
someOtherDependency.doSomething();
}
}
}
function ShoeAdverisementVM(itemService){
this.service = itemService;
this.propertyIsBoundToACheckbox = ko.observable();
}

Handlebars: More than one model inside template

I am starting with Handlebars, and was wondering:
Is there the possibility to pass more than one model to the view?
I am passing my model with $(template(model)) to the view:
var source = $('#template').html();
var template = Handlebars.compile(source);
var model = this.model.toJSON();
$(template(model)).appendTo(this.$parent);
So I can pass one variable with stored JSON data to the view. But what if I want to have two different variables/models in one template?
Is this possible? This would be much easier than generating another template and loading into the other.
A compiled Handlebars template just wants an object as its argument, you can build that object however you want. If you want two models, just add an extra level of indirection:
var html = template({
model: this.model.toJSON(),
other: this.other_model.toJSON()
});
and then inside your template you can say things like:
{{model.attribute}}
{{other.other_attribute}}
and the like.
As an aside, a Backbone view adding HTML to anything other than this.$el (i.e. this.$parent) is a bit dodgy. Events are bound to this.$el so events won't work without help. You'll probably an easier time if you turn that around a bit so that the parent places your view's $el somewhere so that your view can be self contained.

How to force knockoutjs to update UI (reevaluate bindings)

(I know there are other questions here asking the same thing; I've tried them and they don't apply here)
I have a collection being displayed by a Knockout JS foreach. For each item, the visible binding is set by call a method, based on something external to the item itself. When the externality changes, I need the UI to be redrawn.
A striped down version can be seen in this Fiddle: http://jsfiddle.net/JamesCurran/2us8m/2/
It starts with a list of four folder names, and displays the ones starting with 'S'.
<ul data-bind="foreach: folders">
<li data-bind="text: $data,
visible:$root.ShowFolder($data)"></li>
</ul>
<button data-bind="click:ToA">A Folders</button>
Clicking the button should display the ones starting with 'A' instead.
self.folders = ko.observableArray(['Active', 'Archive', 'Sent', 'Spam']);
self.letter = 'S';
// Behaviours
self.ShowFolder = function (folder)
{
return folder[0] === self.letter;
}
self.ToA = function ()
{
self.letter = 'A';
}
UPDATE:
After Loic showed me how easily this example could be fixed, I reviewed the differences between this example and my actual code. I'm using an empty object as a dictionary to toggle if an item is selected self.Selected()[item.Id] = !self.Selected()[item.Id];
The object being changed is already an observable. I assumed that Knockout didn't realize that the list is dependent on the external observable, but it does. What Knockout was missing was that the observable was in fact changing. So, the solution was simply:
self.Selected()[item.Id] = !self.Selected()[item.Id];
self.Selected.notifySubscribers();
Here's what I came up with:
What you have to understand is that Knockout is only "answering" to data changes in observables. If an observable changes, it will trigger every object that uses it. By making your self.letter an observable. You can simply change it's value and uses it somewhere like self.letter() and it will automagically redraw when needed.
http://jsfiddle.net/2us8m/3/
function WebmailViewModel() {
// Data
var self = this;
self.folders = ko.observableArray(['Active', 'Archive', 'Sent', 'Spam']);
self.letter = ko.observable('S');
// Behaviours
self.ShowFolder = function (folder)
{
return folder[0] === self.letter();
}
self.ToA = function ()
{
self.letter('A');
}
};
ko.applyBindings(new WebmailViewModel());
In case you have complex bindings, like storing an object inside an observable. If you want to modify that object you have multiple possible choices.
self.Selected()[item.Id] = !self.Selected()[item.Id];
You could change it to this by making everything "observables" but if my memory is right, it can become complicated.
self.Selected()[item.Id](!self.Selected()[item.Id]());
I remember I had one similar issue where I had dependency problem where I had to update a country, region, city. I ended up storing it as list inside an observable to prevent update on individual element change. I had something like this.
var path = PathToCity();
path[0] = 'all';
path[1] = 'all';
PathtoCity(path);
By doing this, the change would be atomic and there will be only one update. I haven't played a lot with knockout for a while. I'm not sure but I do believe that the last time I worked with knockout, it was able to "optimize" and prevent to redraw the whole thing. But be careful because if it is not able to guess that you didn't change many thing, it could redraw the whole observable tree (which could end up pretty bad in term of performance)
In your example, we could use the same behaviour with my modified example:
http://jsfiddle.net/2us8m/4/

In Backbone, how do I have an after_render() on all views?

I am maintaining a javascript application and I would like there to be a jquery function invoked on pretty much every view. It would go something like this:
SomeView = Backbone.Marionette.ItemView.extend
initialize: ->
#on( 'render', #after_render )
after_render: ->
this.$el.fadeOut().fadeIn()
Clearly there is a better way to do this than have an after_render() in each view? What is the better way to do it? If you can give an answer that includes jasmine tests, I'll <3 you ;)
The event you are looking for is onDomRefresh. See here for the documentation:
https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.view.md#view-domrefresh--ondomrefresh-event
Create your own base view class and put your afterRender code in it. When you create a view, inherit from this class.
var MyApp.ItemView = Backbone.Marionette.ItemView.extend({
afterRender: function() {
// This will be called after rendering every inheriting view.
}
});
var SpecificItemView = MyApp.ItemView.extend({
// this view will automatically inherit the afterRender code.
});
In general, it seems to be considered good practice to define your own base views for all 3 view types. It will enable you to easily add global functionality later.
There is a common pattern used across all Backbone frameworks, normally they have a render method which in turn calls beforeRender, renderTemplate and afterRender methods.
render:function(){
this.beforeRender();
this.renderTemplate();// method names are just indicative
this.afterRender();
return this;
}
In your Base view you can have these methods to be empty functions, and implement them wherever you want it. Not sure this answer applies to Marionette
Combining thibaut's and Robert Levy's answer, the correct solution would be:
var baseView = Backbone.Marionette.ItemView.extend({
onDomRefresh: function() {
// This will be triggered after the view has been rendered, has been shown in the DOM via a Marionette.Region, and has been re-rendered
// if you want to manipulate the dom element of the view, access it via this.$el or this.$('#some-child-selector')
}
});
var SpecificItemView = baseView.extend({
// this view will automatically inherit the onDomRefresh code.
});

KnockoutJS : How do I remove an item from a child array?

Issue:
I'm still learning knockoutJS, please guide me if my approach is wrong.
Here is my fiddle: http://jsfiddle.net/amitava82/wMH8J/25/
While onclick of edit, I receive the json model which is represented in the view and I want to remove certain items (child array) or actions (parent array) from the model (I removed add UI to add more Actions from the fiddle for simplicity) and then finally pass the model back to server.
Now, deleting from root level is easy. I'm stuck with deleting individual item which is ActionParamaters in ActionItems array.
Question:
How do I remove an item from a child array?
You can pass the clicked actionItem and the containing action array to deleteActionItem function as follows:
<!-- /ko -->
remove item
In your model you need to make every actionItem array observable using ko.mapping plugin (see edit function)
var viewModel = function() {
var self = this;
self.data = ko.observable();
self.edit = function() {
self.data ( ko.mapping.fromJS(editData) );
}
self.log = function() {
console.log(self.data())
}
self.deleteAction = function(data) {
//delete root node
self.data().remove(data)
}
self.deleteActionItem = function(data,actionItem) {
//delete items
data.ActionItems.remove(actionItem);
}
}
Then you will be able to remove the item from array in the deleteActionItem function and since the array is observable now, the result will reflect to binded dom element.
Sam, your fiddle data was too complicated. When asking questions, you will improve your chance of getting help if you distill your fiddle down to the relevant elements. I have cooked up a simple fiddle that illustrates nested arrays, and removal.
Here is the HTML, note that the remove function is inside the context of the array, so it calls a function on $parent instead of $root. This lets us target the context directly above, instead of the root.
<ul data-bind="foreach: editData">
<li>
<span data-bind="text: name"></span>
<button data-bind="click: $parent.removeParent">Remove Parent</button>
...
<!-- This line is on the child context -->
<button data-bind="click: $parent.removeChild">Remove Child</button>
</ul>​
Here is the parent model. Note the removal function here is for removing children. When the removeChild function is called, it is from the child context asking for $parent, which will call this remove.
var Parent = function(name, children) {
var self = this;
self.name = ko.observable(name);
self.children = ko.observableArray(children);
self.removeChild = function(child) {
self.children.remove(child);
};
};
Your fiddle also makes no use of models, which are an important aspect of MVVM development. You should consider going through the tutorials on the knockout site to get a better understanding of how to structure knockout applications. It will help you deal with problems like this much easier.

Categories