Passing KnockoutJS viewmodels in BoilerplateJS - javascript

Nested components presuppose nested view models also.
However, in the sample components, I don't see this kinda of dependency to appear (except the BackboneJS TODO app which is not very clear for a KO user).
Could you elaborate on how to do such a design, e.g. for a collection:
ItemViewModel with properties Name and IsSelected
CollectionViewModel with has an Items property that contains a collection of ItemViewModel and SelectedCount which is computed by counting how many items are selected. (I know this can be done with KO in a simpler way, but for the sake of illustration.

Viewmodels (VMs) are just objects (that get bound using ko.applyBindings()) - which means you can arbitrarily nest VMs into a parent object (what #Hasith said). You only need to pass one parent object back to Boilerplate. Bear with some ultra commented code:
// Let's assume you have your items nicely formatted in an array
// data source (and technically the objects in this array can be
// considered 'dumb' viewmodels)
var items = [
{Name:'a',isSelected:false},
{Name:'b',isSelected:true}
]
// We can apply some bindings to make 'smarter' viewmodels
// One approach is just to map using rocking KO mapping plugin
var koMapItems = ko.mapping.fromJS( items )
// You could skip the mapping plugin and go for broke
// creating your own VM mappings manually
// (you could do this using the mapping plugin with less work)
var goforbrokeVM = function( item )
{
var _name = ko.observable( item.Name )
var _dance = function() { return _name+' is space monkey' }
return {
Name: _name,
isSelected: ko.observable( item.isSelected ),
spaceMonkey: _dance
}
}
// Don't forget to assign and create your goforbrokeVMs
var epicItemVMs = []
for (var i=0;i<items.length;i++)
epicItemVMs.push( new goforbrokeVM( items[i]) )
// Now the interesting part, lets create a 'child' VM that
// we can embed in another VM. Notice this VM has as a
// property an array of VMs itself.
var childVM = {
elements: epicItemVMs,
// A sub method, because we can
otherMethod: function() { return 'wat' }
}
// And ultimately our 'master' VM with its own properties
// including the nested child VM we setup above (which you'll
// remember contains its own sub VMs)
var ParentVM = {
// And its own property
parentPropA: ko.observable('whatever'),
// Oooow look, a list of alternative ko.mapping VMs
simpleMappedItems: koMapItems,
// And a nested VM with its own nested goforbrokeVMs
nested: childVM
}
// Apply your master viewmodel to the appropriate DOM el.
ko.applyBindings( ParentVM, document.getElementById('items'))
And your HTML:
<div id="items">
<span data-bind="text: parentPropA"></span>
<!-- Step through one set of items in ParentVM -->
<div data-bind="foreach: simpleMappedItems">
<input type="checkbox" data-bind="checked: isSelected">
<span data-bind="text: Name"></span>
</div>
<!-- Or jump into the nested models and go crazy -->
<!-- ko with: nested -->
<div data-bind="foreach:elements">
<div data-bind="text: spaceMonkey"></div>
</div>
<div data-bind="text: otherMethod"></div>
<!-- /ko -->
</div>
In this way you can pass a single object (in this case ParentVM) to Boilerplate with as many nested viewmodels as you need.
Info for mapping plugin for knockout lives here: http://knockoutjs.com/documentation/plugins-mapping.html

The 'todo' sample is done by adopting Addy Osmani's implementation.
There is an implementation of knockoutjs also here.

Related

Knockout components within foreach and templates

So with the introduction of components and custom elements it seems a lot easier to encapsulate your logic and markup, however I am a bit unsure how to make use of components within foreach sections when you need to pass in a viewmodel.
So my current scenario is that I have a view model which looks something like:
function SomePartialViewModel()
{
this.Name = ko.observable();
this.Score = ko.observable();
this.SomeDate = ko.observable();
}
function SomeViewModel()
{
this.DataFromWebServiceCall = ko.observableArray();
this.GetDataFromServer = function(){
// get some data from service and populate DataFromWebSeriviceCall with instances of SomePartialViewModel
}
}
Now in the above we have a POJO to contain partial data, and we have a main view model for the view which will contact a web service or something, and populate its array with instances of the partial. Then this would currently be used like so:
<div id="partial-data" data-bind="template: { name: 'partial-view', foreach: DataFromWebServiceCall }"></div>
<div class="partial-view">
<label data-bind="text: Name"></label>
<label data-bind="text: Score"></label>
<label data-bind="text: SomeDate"></label>
</div>
Now assume the .partial-view is in a script tag with correct template name etc and is a correct template, then the #partial-data is in the main view and wants to display all the instances on the page. Now currently it all works, but I would like to move to a more component based model, and currently we can see that the template relies upon the SomePartialViewModel data, so we have our template and our viewmodel for that component, however the problem is around getting the viewmodel into the component, as currently you register the component at setup time, then you use params to populate chunks of it. However in this case I want to pass in the viewmodel to the component at binding time...
So I was wondering how I can go about doing this, as I imagine I could register the component with a template but no viewmodel, but is there the notion of a data style binding where I can set the $data property and move to a foreach from a template binding on the view?
Hopefully the problem I am trying to solve can be seen and any info would be great.
There are loads of ways to pass values and/or viewmodels to components using the params.
If you use the createViewModel method, you can just pass in the viewmodel via the params and use the partial viewmodel as the component viewmodel:
ko.components.register("partial-view", {
viewModel: {
createViewModel: function (params) {
return params.value;
}
},
template: "<div>Partial View for <b data-bind='text: Name'></b></div>"
});
You can see a working example in this fiddle: http://jsfiddle.net/Quango/fn1ymf9w/
You can define viewModels under viewModels :)
just like defining an observable you can define another viewModel and using "with" binding you can create a component based model you desire.
First you create your components and sub-Components and sub-sub-sub-Components etc viewModels seperately.
var SomePartialViewModel = function()
{
this.Name = ko.observable();
this.Score = ko.observable();
this.SomeDate = ko.observable();
}
var SomeViewModel = function()
{
this.DataFromWebServiceCall = ko.observableArray();
this.GetDataFromServer = function(){
// get some data from service and populate DataFromWebSeriviceCall with instances of SomePartialViewModel
}
this.SPM = new SomePartialViewModel(); // partial-1
}
And then you create a MainViewModel and bind all the main elements here.
var MainViewModel = function() {
var self = this;
self.SVM = new SomeViewModel();
self.SPM = new SomePartialViewModel(); // partial-2
}
ko.applyBindings(new MainViewModel());
then in your html you can create your components obeying the context you created on knockout entities
...
<body>
<div data-bind="with: SVM">
....
<div data-bind="with: SPM">
<!-- partial-1 data -->
</div>
...
</div>
<div data-bind="with: SPM">
<!-- partial-2 data -->
</div>
</body>
...
You may want to create seperate files for your component models and using a modular script loader like Require js you can bind all together to a complete component based knockout web application

KO ObservableArray update breaks when binding with .sort

I'm sorting my ObservableArray in the markup thusly, but for some reason, when I push a new object to the pages ObservableArray, this does not update.
//store.get('pages') returns an ObservableArray
<!-- ko foreach:store.get('pages').sort(function (l , r ) { return l.pageNumber() < r.pageNumber() ? -1 : 1}) -->
<markup/>
<!--/ko-->
However, when I remove the sort call, it catches the array change just fine.
Like so,
//works fine, updates when item pushed to observableArray
<!-- ko foreach:store.get('pages') -->
<markup/>
<!--/ko-->
Any idea for a simple workaround?
Edits:
Tried using valueHasMutated(), doesn't force an update either.
Workaround:
I ended up moving the sort call into a subscription on the observableArray, and it seems to be working fine now, not sure why though.
store.get returns an observableArray but store.get(...).sort(...) returns an array. You're effectively making a one way binding like this:
<div data-bind="foreach: ['foo', 'bar']"></div>
Also, although you bind a returned value of a function call, to me it has a code smell that you're coupling your business logic with your view logic. You have something like this:
// View
<div data-bind="foreach: $root.get()"></div>
// Javascript
function ViewModel () {
var self = this;
self.get = function () {
return ko.observableArray();
};
}
And it works, but from your view, it's unclear what you're doing. I think a better solution would be:
// View
<div data-bind="foreach: stores"></div>
// Javascript
function ViewModel () {
var self = this;
self.stores = ko.observableArray();
self.get = function () {
var arr = ["foo", "bar"];
stores(arr.sort(...));// When you do this, KO updates the foreach binding this is bound to
return stores;// not that you need to, you can access it from the viewModel.
};
}
Glad you found a workaround but take a minute and plan out your HTML binding structure. It's the first place I start when I'm creating a new view and it drives how I structure my ViewModel.

Knockout.js: Separating ViewModel logic from the Model

In a video tutorial on the Knockout.js homepage, the presenter sets up a simple example in which the ViewModel contains an observableArray property containing instances of a custom object (a "friend" object, in this case):
function friend(name) {
return {
name: ko.observable(name),
remove: function () {
viewModel.friends.remove(this);
}
};
}
var viewModel = {
friends: ko.observableArray()
}
ko.applyBindings(viewModel);
This is then rendered using a KO template:
<script id="friendsTemplate" type="text/html">
<li>
<input data-bind="value: name" />
<button data-bind="click: remove">Remove</button>
</li>
</script>
It seems odd to me that the logic for removing a friend object (the Model?) from the ViewModel's friends collection is implemented in the friend object itself. Doesn't this create an undesirable coupling between the friend object and the ViewModel instance? Is there a more consistent design approach that would allow the removeFriend functionality to be defined in the ViewModel rather than in the friend object?
A more common pattern would be to put a removeFriend function one level higher and the bind to it using:
<button data-bind="click: $parent.removeFriend">Remove</button>
When Knockout calls the handler from the click or event binding it will pass the current data item as the first argument. It will also set the context (this) equal to the current data as well. Depending on your structure, you may have to bind the function to ensure that it is called with the appropriate context or use a strategy like var self = this;.
Since, viewModel is an object literal, in this case removeFriend could look like:
friends: ko.observableArray(),
removeFriend: function(friend) {
viewModel.friends.remove(friend);
}

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.

Knockout JS ObservableArray with many-to-many relationships

I am creating a guest list app using Knockout.js, and so far things are going swimmingly. However I have a best-practices question. My app has several different types of objects: guests and tags among them. Guests can have multiple tags, and tags can have multiple guests. At different points in the app, I need to display both arrays individually. For example, I have a "Guests" view where one can see all the guests along with their associated tags, and I also have a "Tags" view where one can see all tags and their associated guests. At present, the code for me to add a tag to a guest looks something like this:
var tag = function(opts) {
this.guests = ko.observableArray()
// Other tag code here...
}
var guest = function(opts) {
this.tags = ko.observableArray()
// Other guest code here...
var self = this
this.addTag = function(tag) {
self.tags.push(tag)
tag.guests.push(self)
}
}
I know there must be a better way of doing this kind of many-to-many relationship in Knockout other than updating each observableArray independently. This also leads to a kind of recursive app structure where a guest has a tags property/array which contains a tag, which has a guest property/array which contains a guest, which has a tags property... you get the picture.
Right now, the ViewModel structure is like so:
- Parent Object
- Guests ObservableArray
- Guest Object
- Tag Object as property of Guest
- Tags ObservableArray
- Tag Object
- Guest Object as property of Tag
So I guess my question is twofold: 1) Is there a better way to structure my ViewModel to avoid recursive arrays? and 2) how can I better use Knockout.js to update my ViewModel in a DRY manner, rather than updating both the tags array AND the guests array individually? Thanks!
There are probably other ways to do this, but this method has pretty minimal duplication, without sacrificing proper modeling. A server should have no trouble generating the data in this format.
Here it is in a (crudely styled) fiddle. Note, clicking a tag or guest will cause the selections below it to update (the first is selected by default).
Basically, store the relationships by id, and use computed array's to represent associations. Here is a basic viewmodel:
var ViewModel = function(guests, tags) {
var self = this;
self.guests = ko.observableArray(
ko.utils.arrayMap(guests, function(i){ return new Guest(i); }
));
self.tags= ko.observableArray(
ko.utils.arrayMap(tags, function(i){ return new Tag(i); }
));
self.selectedGuest = ko.observable(self.guests()[0]);
self.selectedTag = ko.observable(self.tags()[0]);
self.guestTags = ko.computed(function() {
return ko.utils.arrayFilter(self.tags(), function(t) {
return self.selectedGuest().tags().indexOf(t.id()) > -1;
});
});
self.tagGuests = ko.computed(function() {
return ko.utils.arrayFilter(self.guests (), function(g) {
return self.selectedTag().guests().indexOf(g.id()) > -1;
});
});
};
UPDATE
So I have made a new fiddle to demonstrate a different kind of mapping, but this code could easily co-exist with the above viewmodel; its only seperate for demonstration. Instead of working off selection, it offers a general lookup, so that any code can consume it. Below is the HTML from Tags (guests is symmetrical), and the guestMap function that was added to the viewmodel.
You will note that the names are inputs now, so you can change the names and watch all the bindings stay up to date. Let me know what you think:
<div>Tags
<ul data-bind="foreach: tags">
<li>
<input data-bind="value: name, valueUpdate: 'afterkeydown'" />
</br><span>Tags</span>
<ul data-bind="foreach: guests">
<li><span data-bind="text: $parents[1].guestMap($data).name()"></span></li>
</ul>
</li>
</ul>
</div>
self.guestMap = function(id) {
return ko.utils.arrayFirst(self.guests(), function(g) {
return id == g.id();
});
};
I had the same kind of problem. Displaying many to many related stuff. I had to do 'grid' style display and have an update mechanism in it.
I ended up replicating the structure I had in the backend DB. Two tables of items with join table in between. Pulled the data out from those in three arrays and kept updating the 'join' array. The test data and fiddle of my testings with it below.
var items = [{name: "T1",id: 1}, {name: "T2",id: 2}, {name: "T3",id: 3}, {name: "T4",id: 4}];
var cats = [{catname: 'C1', catid: 1}, {catname: 'C2',catid: 2}, {catname: 'C3',catid: 3}, {catname: 'C4',catid: 4}];
var catsItems = [{catid:1,itemid:2},{catid:2,itemid:1},{catid:4,itemid:2},{catid:3,itemid:4},{catid:3,itemid:1},{catid:1,itemid:3},{catid:2,itemid:4}];
Join table fiddle
I'm not really sure how efficient this method is with lots of data but did the stuff I needed it to do.

Categories