Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / All-Topics

Replacing Radio Buttons with a Button Group using Bootstrap and #KnockoutJS

3.00/5 (3 votes)
27 Apr 2015CPOL3 min read 14.6K  
Replacing Radio Buttons with a Button Group using Bootstrap and #KnockoutJS

Problem

Radio buttons are hard-to-see, not easy to select, and let's face it, quite mundane. You would like to replace these radio buttons with a group of buttons that represent the same functionality, e.g., only one of the options may be selected at any given time.

Solution

Leveraging Bootstrap which provides many incredibly styled components for buttons, alert boxes, tables, forms, etc., regular radio buttons will be replaced by a button group (see screenshot below). Knockout.js will be used to create a custom data binding that will make the group of buttons act like regular radio buttons (with a nicer look of course).

radiobuttongroup

This example assumes you have a basic understanding of both Bootstrap and Knockout.js.

The versions used for this example are 3.3.4 for Bootstrap and 3.3 for Knockout.js. This example should be compatible with older versions of these frameworks.

Discussion

Before getting started, the frameworks required must be setup. Bootstrap can be installed either via their CDN or downloaded. This example will use the CDN; however, I suggest you use the option that bests suits your needs. Knockout.js has been downloaded and saved into the same location as where the example lives. Ensure you update this location based on your project's location. And finally, a little bit of jQuery is used within the Knockout Custom Binding. Once again, this example is leveraging the CDN; however, you may download and include it if you like.

For this example to work, it requires two things. A group of buttons that will contain what would be the options if they were radio buttons and a Knockout observable value that will be used in the data-binding. This observable will contain the selected option. Below contains the HTML markup and the extremely basic Knockout ViewModel.

HTML
<html>
<head>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
</head>
<body>
<div class="btn-group">
<button type="button" class="btn btn-default" 
data-bind="radioButtonGroup: selectedOption, radioValue: 'option1'">Option 1</button>
<button type="button" class="btn btn-default" 
data-bind="radioButtonGroup: selectedOption, radioValue: 'option2'">Option 2</button>
<button type="button" class="btn btn-default" 
data-bind="radioButtonGroup: selectedOption, radioValue: 'option3'">Option 3</button>
</div>

<script src="knockout.js"></script>
<script src="http://code.jquery.com/jquery-2.1.3.min.js"></script>
<script>
function ViewModel() {
var self = this;

self.selectedOption = ko.observable();
};

var viewModel = new ViewModel();
ko.applyBindings(viewModel);
</script>
</body>
</html>

This example won't work just yet. If you look closely, each of the HTML buttons contain a data-binding called radioButtonGroup (this will be defined momentarily). This is the custom data binding that will accomplish our fancy button group. Supplied to the new data binding is the observable variable, aptly named, selectedOption. A second value is supplied as well named radioValue. This should be set to the value you wish the selectedOption variable to contain when the user presses that button.

To complete this example, the custom binding needs to be created. Custom bindings are added under the ko.bindingHandlers namespace. A binding handler is defined by creating an init and update function. Either are optional, but at least one must be defined. The init function is called when Knockout is first data bound to the page. This is where you would create event listeners, etc. The update function is called every time the value changes (including on first load). This function would be used if you wished to perform specific actions each time the value changed.

The radioButtonGroup custom binding only requires the init function because event listeners for the click event will be used to track all changes. The following JavaScript code should be placed (or included from a separate file) after the Knockout framework is included, but before your ViewModel is defined and the Knockout bindings are applied.

HTML
<script>
ko.bindingHandlers.radioButtonGroup = {
init: function (element, valueAccessor, allBindings, viewModel, context) {
var $buttons, $element, observable;
observable = valueAccessor();
if (!ko.isWriteableObservable(observable)) {
throw "You must pass an observable or writeable computed";
}
$element = $(element);
if ($element.hasClass("btn")) {
$buttons = $element;
} else {
$buttons = $(".btn", $element);
}
elementBindings = allBindings();
$buttons.each(function () {
var $btn, btn, radioValue;
btn = this;
$btn = $(btn);
radioValue = elementBindings.radioValue || 
$btn.attr("data-value") || $btn.attr("value") || $btn.text();
$btn.on("click", function () {
observable(ko.utils.unwrapObservable(radioValue));
});
return ko.computed({
disposeWhenNodeIsRemoved: btn,
read: function () {
$btn.toggleClass("active", observable() === ko.utils.unwrapObservable(radioValue));
$btn.toggleClass("btn-info", observable() === ko.utils.unwrapObservable(radioValue));
}
});
});
}
};
</script>

The init function accepts 5 parameters. The element being data bound, the variable it is data bound too, all other bindings on this element, the entire ViewModel (however this is being deprecated), and the bindingContext, this is the new way proposed to access the ViewModel by using bindingContext.$data.

Inside the init function, it first verifies it is dealing with an observable property. Using the HTML element with the data binding (and some jQuery), the related buttons are found and stored in a variable. These buttons are then looped through and the click event is listened for. When it occurs, the observable property associated to the data binding's value is updated with the option selected. And finally a computed variable is defined, that if the observable's value is equal the value of the button, the class active and btn-info are added to identify which button is currently selected.

That completes this example, as always you can find the full source code on GitHub.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)