0

Attached is a snippet for a custom binding handler for some buttons designed to act as a radio group.

The problem I have is that when I click on a button it fires the update 6 times.

I guess I need to add either a throttle or bind differently but I am new to knockout so any help would be appreciated.

Or as it fires the same update 6 times is it not actually affecting knockout performance wise?

Just a pointer in the right direction would be fine!

ko.bindingHandlers.buttonGroupChecked = {
    init: function (element, valueAccessor, allBindingsAccessor,
    viewModel, bindingContext) {
        if (typeof globalLog !== 'undefined' && globalLog === true){
            console.log("buttonGroupInit");
        }
        var value = valueAccessor();
        var newValueAccessor = function () {
            return {
                click: function () {
                    value($(element).data('value'));
                }
            }
        };
        ko.bindingHandlers.event.init(element, newValueAccessor,
        allBindingsAccessor, viewModel, bindingContext);
    },
    update: function (element, valueAccessor, allBindingsAccessor,
    viewModel, bindingContext) {          
        if ($(element).data('value') == ko.unwrap(valueAccessor())) {
           //$(element).closest('.btn').button('toggle');
           $(element).siblings().removeClass('btn-success').addClass('btn-info');
           $(element).removeClass('btn-info').addClass('btn-success');
        }
        if (typeof globalLog !== 'undefined' && globalLog === true){
            console.log("buttonGroupUpdate" + ko.unwrap(valueAccessor()));
        }
        
        
    }
}

var ViewModel = function () {
    this.optionsValue = ko.observable(2);
};
globalLog = true;
var vm = new ViewModel();
ko.applyBindings(vm);
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>

<div class="btn-group btn-group-justified">
            
            <a href="javascript:void(0)" class="btn btn-lg btn-info" data-value="1" data-bind="buttonGroupChecked: optionsValue">1</a>
            <a href="javascript:void(0)" class="btn btn-lg btn-info" data-value="2" data-bind="buttonGroupChecked: optionsValue">2</a>
            <a href="javascript:void(0)" class="btn btn-lg btn-info" data-value="3" data-bind="buttonGroupChecked: optionsValue">3</a>
            <a href="javascript:void(0)" class="btn btn-lg btn-info" data-value="4" data-bind="buttonGroupChecked: optionsValue">4</a>
            <a href="javascript:void(0)" class="btn btn-lg btn-info" data-value="5" data-bind="buttonGroupChecked: optionsValue">5</a>
            <a href="javascript:void(0)" class="btn btn-lg btn-info" data-value="5+" data-bind="buttonGroupChecked: optionsValue">5+</a>
        </div>
        <span data-bind="text: optionsValue"></span>
GrahamTheDev
  • 22,724
  • 2
  • 32
  • 64
  • Ignore me I am an idiot - put my log in the wrong place :-P will leave this up as it is a useful (heopfully) snippet – GrahamTheDev Mar 18 '15 at 10:37

2 Answers2

0

I see you've already solved this yourself, but I was just going to mention that you don't need a custom binding to achieve this. Here is an example...

var ViewModel = function () {
    this.group1 = ['1','2','3','4','5','5+'];
    this.optionsValue = ko.observable('2');
};
globalLog = true;
var vm = new ViewModel();
ko.applyBindings(vm);
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>

<div class="btn-group btn-group-justified" data-bind="foreach:group1">
            
            <a href="javascript:void(0)" class="btn btn-lg" data-bind="css: { 'btn-success': $parent.optionsValue() == $data, 'btn-info': $parent.optionsValue() != $data}, click: $parent.optionsValue, text: $data"></a>
            
        </div>
        <span data-bind="text: optionsValue"></span>
Dean North
  • 3,741
  • 2
  • 29
  • 30
  • and that is exactly why I wrote a custom binding - takes that ugly code out of the DOM and makes it a lot more reusable. Encouraging people to do this way is not good, could you remove the answer, dont want to down vote when you being helpful!!!! – GrahamTheDev Mar 18 '15 at 11:31
  • I actually would encourage people to do it this way, rather than writing a custom binding that manipulates the dom using jquery. But if you would like me to remove my answer, I will. – Dean North Mar 18 '15 at 11:38
  • I agree with the jQuery part, but manipulating the DOM within a custom binding is their purpose, I am being lazy as this is an application that I know will use jQuery throughout its lifetime - perhaps re-write my binding handler without jQuery as it is indeed a valid point, but putting this code in the DOM is not good long-term (I have 7/8 of these 'radio' groups per page) – GrahamTheDev Mar 18 '15 at 11:46
  • If that is the case, I would put the options in the model. See updated answer. – Dean North Mar 18 '15 at 11:59
  • I actually like that as a concept, unfortunately given that some values will be different to their displayed text that would require a bit more work that I am prepared for at this stage - but nice tip, still think a custom binding is the way to go, especially as you could just write your own siblings selector if you wanted to make this vanilla – GrahamTheDev Mar 18 '15 at 12:09
0

Just incase anyone stumbles across this, this is the way I have ended up doing this, taking into account what Dean said, it still needs some improvement but should offer a good starting point for someone.

I just want to get rid of the $parent, $data etc. from the DOM if I can (I remember seeing a way to do this somewhere) but overall not a bad first attempt.

//used to abstract out the jQuery dependancies - just in case at some point in the future jQUery is replaced with another library.
function activeClassSingle(element, standardClass, activeClass){
    $(element).siblings().removeClass(activeClass).addClass(standardClass);
    $(element).removeClass(standardClass).addClass(activeClass);
}




ko.bindingHandlers.buttonGroupChecked = {
    init: function (element, valueAccessor, allBindingsAccessor,
    viewModel, bindingContext) {
        var value = valueAccessor();
        var newValueAccessor = function () {
            return {
                click: function () {
                    value(allBindingsAccessor.get('val'));
                }
            }
        };
        ko.bindingHandlers.event.init(element, newValueAccessor,
        allBindingsAccessor, viewModel, bindingContext);
    },
    update: function (element, valueAccessor, allBindingsAccessor,
    viewModel, bindingContext) {          
        if (allBindingsAccessor.get("val") == ko.unwrap(valueAccessor())) {
           activeClassSingle(element, "btn-info", "btn-success");
        }        
    }
}

 var adults = [
    {val: 1, text: "1"},
    {val: 2, text: "2"},
    {val: 3, text: "3"},
    {val: 4, text: "4"},
    {val: 5, text: "5"},
    {val: 6, text: "5+"}
    ];


var ViewModel = function () {
    this.adultsNo = ko.observable(2);
};

var vm = new ViewModel();
ko.applyBindings(vm);
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>

<div class="btn-group btn-group-justified" data-bind="foreach: adults">
            <div class="btn btn-lg btn-info" data-bind="buttonGroupChecked: $parent.adultsNo, val: $data.val, text: $data.text"></div>
        </div>
        <span data-bind="text: adultsNo"></span>
GrahamTheDev
  • 22,724
  • 2
  • 32
  • 64