Bootstrap + Knockout: Toggle Button Bindings

October 17, 2012

I am in the middle of an overhaul of the TrendWeight user dashboards, and as part of that project, I found myself looking for ways to bring Bootstrap button groups and Knockout together in peace and harmony. In other words, when someone clicks on one of the buttons in the radio button group, I need the associated observable on the ViewModel to be updated with the value associated with that particular radio button.

My first attempt worked fine, but left something to be desired. Here is the markup I had:

That's pretty verbose and repetitive. This seems like the perfect opportunity to use a custom binding, but custom bindings have always seemed intimidating for some reason. Ryan Niemeyer has been selling me on the idea that custom bindings are really not that scary and that I should embrace them instead of fearing them. After receiving a small pep talk this week, I decided to take the plunge.

My goal is to enable markup that looks like this instead of the markup above:

Here, there is a binding on the btn-group element that says, "Hey, these buttons should act like radio buttons and be bound to the 'range' observable, please." Additionally, each button in the group has a data-value attribute that tells the binding what value should be associated with each button.

In the binding I ended up writing, the data-value attribute is actually optional. If you are ok with the value of your observable being the same as the captions on your buttons, you can leave the attribute off and the binding will use the inner text of the button instead.

While I was at it, I also made the binding handle the alternate markup below where the binding is on each individual button instead of on the button group. This may be useful if you want radio button behavior without putting the buttons in a button group. And for variety, this markup specifies the value for each radio button with radioValue property in the binding itself (although you the data-value attribute still would work as well):

After all that, I felt empowered and thought I might as well make a binding for checkbox behavior as well. In other words, each button would be bound to a boolean observable and would be toggled when that observable was true. So markup like this:

Before I get into the code for the bindings, you can check out this working jsFiddle demo that shows the user interface behavior I'm talking about.

Ok, so the code... Of course, since I am on a CoffeeScript kick at the moment, I wrote these bindings in CoffeeScript. If CoffeeScript isn't your thing, you can hit the jsFiddle link above to see the JavaScript version of the bindings.

One caveat: while Knockout does not depend on jQuery, these binding do. If you want to use them, you either have to have jQuery on your page, or you have to rewrite these appropriately. Since jQuery is a dependency of Bootstrap's JavaScript plugins, I didn't feel like this was an unreasonable dependency here.

The radio button binding is the more interesting of the two bindings. The first thing it does in the 'init' function (besides some checking for error conditions) is determine if the binding is directly on a button or not. If it is on a button, it's going to wire up the button directly. If it is not, it's going to look for any descendant buttons and wire each of them up.

Next, I loop through each of the buttons (which might just be the single button if the binding is directly on a button). For each button, I first figure out what the radioButton value should be for that button. Then I wire up a click event handler that sets the observable to the right value. Finally, I create a computed observable that will fire any time the main observable changes. The computed observable adds or removes the 'active' css class depending on if the value of the observable matches the button's assigned value.

Normally, you'd put the code to respond to changes from the main observable in a custom binding's 'update' function. However in this case, I did it with an explicit computed observable because I needed access to the radioValue variable, and there is no convenient way to pass state between the 'init' function and the 'update' function. Having an explicit computed observable accomplishes the same thing as using an 'update' function because 'update' functions essentially get turned into computed observables behind the scenes anyway.

The checkbox binding is similar but a bit simpler. It is always used directly on a button, so the part about looping through descendant buttons is not there. The binding just directly wires up a similar click event handler and creates a similar computed observable to toggle the right css class. In this case, I could have used an 'update' function, but chose not to just to keep it consistent with how I structured the radio button binding.

I think I am mostly over my fear of custom bindings, and I expect that I'll now be looking for opportunities to use them even where they aren't really needed :)

I'm Erv Walter, a father and husband, a software developer, a computer geek, and a board game addict living in Sun Prairie, Wisconsin. My interests include Azure, ReactJS, TypeScript, and web development in general. And I'm a sucker for any kind of gadget.