toy
toy

Reputation: 12131

How to use bindingHandlers for this in KnockoutJs

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

Answers (3)

Paul
Paul

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

Tanner
Tanner

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 items
  • expanded - to control the expansion of a submenu with child items
  • subMenu - to hold child items

On 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

Fariz Azmi
Fariz Azmi

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

Related Questions