Reputation: 3052
I am trying to toggle visibility of nested items using Knockout. On the initial display of the page, I don't want the nested items to be visible. If the user clicks a link to show the nested items, then I want those nested items to show (not all nested items).
For example, I have a list of products, and for each product I have a list of star ratings. By default or for initial page load, the ratings are not shown. If the user clicks "Ratings", then the ratings for that item are shown. If the user clicks "Ratings" for the other item, then that product's ratings are shown as well as the other one.
To illustrate, I have two products:
Samsung Ratings
iPhone Ratings
If I click iPhone's Ratings, then iPhones ratings are shown:
Samsung Ratings
iPhone Ratings
1203: 3
1204: 2
If I click Samsung's Ratings, then Samsung's ratings are shown also:
Samsung Ratings
1201: 5
1202: 4
iPhone Ratings
1203: 3
1204: 2
Then if I click the Ratings of either, the ratings for that product becomes non visible.
My sample HTML is as follows:
<ul data-bind="foreach: products">
<li><span data-bind="text: ProductName"></span>
<a href="#" data-bind="click: $parent.toggleVisibility">Ratings</a>
<ul data-bind="foreach: StarRatings, visible: $parent.shouldShowRatings">
<li><span data-bind="text: RatingId"></span>: <span data-bind="text: RatingValue"></span></li>
</ul>
</li>
</ul>
The sample javascript using Knockout.js 3.0.0 is as follows:
var initialProducts = [{
ProductName: "Samsung",
StarRatings: [{
RatingId: 1201,
RatingValue: 5
},
{
RatingId: 1202,
RatingValue: 4
}
]
}, {
ProductName: "iPhone",
StarRatings: [{
RatingId: 1203,
RatingValue: 3
},
{
RatingId: 1204,
RatingValue: 2
}
]
}];
(function (ko) {
function products(data) {
var self = this;
data = data || {};
// Persisted properties
self.ProductId = data.ProductId;
self.StarRatings = data.StarRatings;
}
})(ko);
var viewModel = (function(ko){
var products = ko.observableArray(initialProducts),
showRatings = ko.observableArray(),
toggleVisibility = function(item) {
if(showRatings.indexOf(item) < 0){
showRatings.push(item);
}
else{
showRatings.remove(item);
}
},
shouldShowRatings = function(item) {
if( showRatings.indexOf(item) >= 0){
return true;
}
{
return false;
}
};
return {
products: products,
showRatings: showRatings,
toggleVisibility: toggleVisibility,
shouldShowRatings: shouldShowRatings
};
})(ko);
ko.applyBindings(viewModel);
I've been able to show all ratings or show none. I've also been able to set it up to where only one is shown at a time, but I think this code is the closest to the solution I need.
It looks like after the click event, shouldShowRatings for the visibility is not called for the StarRatings visibility.
The sample code is also on jsFiddle at http://jsfiddle.net/justinnafe/866my/4/
Upvotes: 0
Views: 919
Reputation:
The better way to fix the initial evaluation is to use standard ko way: http://jsfiddle.net/866my/10/
change
<a href="#" data-bind="click: $parent.toggleVisibility($data)">Ratings</a>
to
<a href="#" data-bind="click: $parent.toggleVisibility">Ratings</a>
now ko gets a function object ( the toggleVisibility function ), it would not call the function until you click it.
ko by default passes current context $data as first parameter to the target function, so explicit call($data) is unnecessary.
Upvotes: 1
Reputation: 7356
Just update your bindings to call the functions with the $data as the first parameter: http://jsfiddle.net/866my/6/
<ul data-bind="foreach: products">
<li><span data-bind="text: ProductName"></span>
<a href="#" data-bind="click: $parent.toggleVisibility($data)">Ratings</a>
<ul data-bind="foreach: StarRatings, visible: $parent.shouldShowRatings($data)">
<li><span data-bind="text: RatingId"></span>: <span data-bind="text: RatingValue"></span></li>
</ul>
</li>
</ul>
The important part is that you use the function notation in order to get the binding to check for updates.
Upvotes: 2