Reputation: 12131
I'm building a mobile app which requires a bunch of open/close tab. I'm trying to find a way to use bindingHandlers to reduce the amount of code. But I seem to miss something. Here's my fiddle.
http://jsfiddle.net/noppanit/4zRrZ/
And this is what I have
<a href="javascript:void(0)" data-bind="click: expandCommentsRatings">Rating
<div style="display:none" data-bind="visible: productCommentsRatingsVisiblity">
<div class="rating" style="width: 85%">3.5 Stars Rating</div>
</div>
</a>
<br/>
<a href="javascript:void(0)" data-bind="click: expandsReviews">Reviews
<div style="display:none" data-bind="visible: productReviewsVisiblity">
<div class="reviews">Reviews</div>
</div>
</a>
var Model = function () {
var productCommentsRatingsVisiblity = ko.observable(false);
var productReviewsVisiblity = ko.observable(false);
var expandCommentsRatings = function (item, event) {
productCommentsRatingsVisiblity(!productCommentsRatingsVisiblity());
if (productCommentsRatingsVisiblity() === false) {
$(event.target).removeClass('expanded');
} else {
$(event.target).addClass('expanded');
}
};
var expandsReviews = function (item, event) {
productReviewsVisiblity(!productReviewsVisiblity());
if (productReviewsVisiblity() === false) {
$(event.target).removeClass('expanded');
} else {
$(event.target).addClass('expanded');
}
};
return {
productCommentsRatingsVisiblity: productCommentsRatingsVisiblity,
productReviewsVisiblity: productReviewsVisiblity,
expandCommentsRatings: expandCommentsRatings,
expandsReviews: expandsReviews
}
};
ko.applyBindings(Model());
How do I reduce the duplication so I can reuse this code to other ViewModel as well. The reason I'm struggling is because I don't know how to pass productCommentsRatingsVisiblity
or productReviewsVisiblity
to allBindings
dynamically. You need to know the name in order to get it.
Thanks.
Upvotes: 0
Views: 317
Reputation: 1502
Sorry for the late reply on this, but I have a solution using bindingHandlers.
The fiddle is here: http://jsfiddle.net/u3m7m/1/
I followed a strategy of creating a toggle bindingHandler which adds the specified class if it's not present on the element, or removes the class if it is. The only state needed to make this happen is the class list on the element, meaning you can delete all those state tracking observables from the model. In fact, this was the model I used:
var Model = function () {
// stuff
};
ko.applyBindings(Model());
The toggle bindingHandler looks like this:
ko.bindingHandlers['toggle'] = {
init: function (element, valueAccessor) {
var value = ko.unwrap(valueAccessor()),
clickHandler = function (e) {
if (!e) {
e = window.event;
}
e.cancelBubble = true;
if (e.stopPropagation) {
e.stopPropagation();
}
var classes = (this.className||'').split(' '),
index = classes.indexOf(value);
if (index >= 0) {
classes.splice(index, 1);
} else {
classes.push(value);
}
element.className = classes.join(' ');
};
element.onclick = clickHandler;
if (element.captureEvents) {
element.captureEvents(Event.CLICK);
}
}
};
Which is hopefully not too complicated, the weird looking stuff with the e
object is from here: http://www.quirksmode.org/js/introevents.html
Because I'm using the strategy of using classes only, I had to add to your CSS:
.expandable > div
{
display: none;
}
.expandable.expanded > div
{
display: block;
}
The state tracking is now removed from the html, and the data-bind
is modified to use the toggle
bindingHandler:
<a class="expandable" href="javascript:void(0)" data-bind="toggle: 'expanded'">Rating
<div>
<div class="rating" style="width: 85%">3.5 Stars Rating</div>
</div>
</a>
<br/>
<a class="expandable" href="javascript:void(0)" data-bind="toggle: 'expanded'">Reviews
<div>
<div class="reviews">Reviews</div>
</div>
</a>
Hopefully this is of some help to you.
Upvotes: 1
Reputation: 22733
You can do this simply by using an observableArray
to hold your menu system, with properties for:
itemName
- to hold top level menu itemsexpanded
- to control the expansion of a submenu with child itemssubMenu
- to hold child itemsOn top of this, you need a simple function to toggle the visibility of each sub-menu when the parent is clicked. Then you can utilise the knockout visible
attribute in your data-binding, which would be bound to the expanded
property.
Here's a working JSFiddle and below is the code used:
JS view model:
var Model = function () {
var self = this;
self.tabs = ko.observableArray([
{ itemName: "Ratings",
expanded: ko.observable(false),
subMenu: ["option 1","option 2"]},
{ itemName: "Review",
expanded: ko.observable(false),
subMenu: ["option 1","option 2"]}
]);
self.toggleExpanded = function (item) {
item.expanded(!item.expanded());
}
};
ko.applyBindings(Model());
HTML Mark Up:
<ul data-bind="foreach: tabs">
<li><span data-bind="text: itemName, click: toggleExpanded"></span>:
<ul data-bind="foreach: subMenu">
<li data-bind="text: $data, visible: $parent.expanded">
</li>
</ul>
</li>
</ul>
Upvotes: 0
Reputation: 723
I'm not sure this would help you,
I've reconstruct and optimize your code based on what you need.
This might give you some idea. You don't need custom binding handler to implement this.
here the working jsFiddle: http://jsfiddle.net/farizazmi/6E4Wz/2/
so, what do you need is to include property to control visibility of the item:
var data = [
{
'name' : 'test1',
'rateIsExpanded' : ko.observable(false),
'rating': 3.5,
'review': 'blabla1',
'reviewIsExpanded': ko.observable(false)
},
{
'name' : 'test2',
'rateIsExpanded' : ko.observable(false),
'rating': 1.5,
'review': 'blabla2',
'reviewIsExpanded': ko.observable(false)
}
];
and create a function will use to change state of visibility each data:
var Model = function () {
var self = this;
self.data = ko.observableArray(data);
self.expandRate = function(item)
{
console.log(ko.toJSON(item));
item.rateIsExpanded( ! item.rateIsExpanded() );
};
self.expandReview = function(item)
{
item.reviewIsExpanded( ! item.reviewIsExpanded() );
};
};
ko.applyBindings(Model());
Upvotes: 0