Most elegant way to wire "toggle buttons" with Knockout?

3,383 views
Skip to first unread message

Steph K

unread,
Jun 25, 2011, 5:30:11 PM6/25/11
to KnockoutJS
Hi,

I'm totally new to knockout and the first thing I'm trying to do is
implement a "toggle button" behavior. Let's say we have 3 buttons,
when you click one it gets selected with a "selected" class, and the
two others get deselected. It's just something I use a lot in my
applications.

Here's how I would do it in jQuery:

HTML

<ul id="toggleButtons">
<li class="selected"><a href="#">Option 1</a></li>
<li><a href="#">Option 2</a></li>
<li><a href="#">Option 3</a></li>
</ul>

JS

$(document).ready(function() {

$('#toggleButtons li').click(function() {
// Update 'selected' class
updateToggleBtnSelection($(this));
// Do something when a button is clicked
respondToToggleBtnClick($(this).index());
});

function updateToggleBtnSelection($toggleBtn) {
$toggleBtn.siblings('li.selected').removeClass('selected');
$toggleBtn.addClass('selected');
}

function respondToToggleBtnClick(buttonIndex) {
console.log('You clicked button number ' + buttonIndex);
}

});

What's an elegant way to achieve this with Knockout? I managed to make
it work with the code below. While I really like the clean object-
oriented way to define and access the toggle buttons, the syntax is a
little heavy for such a simple behavior. Could anyone think of a more
lightweight solution that would still be elegant?

Two things I don't really like with my current solution:
• Having to create a template that sits outside the <ul> just so we
can use {{each}} adds some clutter. Ideally the {{each}} would be
inside the <ul>. I would also be fine manually listing all the li
tags, as long as there isn't a lot of code in each (I don't want lots
of duplicated code).
• With my jQuery solution, because I've defined a
updateToggleBtnSelection() function, I can easily reuse that behavior
for multiple instances of the toggle buttons, and still have each
group of toggle buttons perform different actions when clicked. Not
sure how we would do that with the Knockout solution I proposed here.
I'm sure it's possible but it looks like it would be a lot more
complex. Any advice would appreciated!

HTML

<ul data-bind="template: 'toggleBtnTemplate'">
</ul>

<script type="text/html" id="toggleBtnTemplate">
{{each(i, toggleButton) toggleButtons}}
<li data-bind="click: click, css: {selected: selectedToggleBtn() ==
key}"><a>${label}</a></li>
{{/each}}
</script>

JS

$(document).ready(function() {

function toggleButton(btnKey, btnLabel) {
this.key = btnKey;
this.label = btnLabel;
this.click = function() {
viewModel.selectedToggleBtn(this.key);
console.log('You clicked the button: ' + this.key);
}.bind(this);
}

var viewModel = {
toggleButtons: [
new toggleButton('toggleBtn0', 'Option 1'),
new toggleButton('toggleBtn1', 'Option 2'),
new toggleButton('toggleBtn2', 'Option 3')
],
selectedToggleBtn: ko.observable('toggleBtn0')
};

ko.applyBindings(viewModel); // This makes Knockout get to work

});

Andrew Booth

unread,
Jun 25, 2011, 8:17:12 PM6/25/11
to knock...@googlegroups.com
Hi Steph


Perhaps html label and input[type=radio] elements, with the checked binding, are a good choice for a list of items that are toggled. The jsFiddle shows some CSS to make the radio inputs look a bit less like the standard browser rendering, if required. Then a simple jQuery behavior to add the additional css classes, may be a pragmatic approach, rather than using the css binding.

Andy


<label><input type="radio" name="choice" value="1" data-bind='checked: choice'/> Option 1</label>
<label><input type="radio" name="choice" value="2" data-bind='checked: choice'/> Option 2</label>
<label><input type="radio" name="choice" value="3" data-bind='checked: choice'/> Option 3</label>
<p>Selected option: <span data-bind='text: choice'></span></p>

$('input[type=radio]').change(function({
    var label $(this).closest('label');
    var name $(this).attr('name');
    
    $(document.getElementsByName(name))
        .closest('label')
        .removeClass('selected');
    
    label.addClass('selected');
});

var viewModel {
    choiceko.observable(3)    
};

ko.applyBindings(viewModel);

$('input:checked').trigger('change');


label {
    displayblock;   
    border1px solid #ececec
    padding0.2em;
    margin0.2em;
}

input[type=radio{
    displaynone;   


.selected {
    font-weightbold
    background-color#ececec;    
}

Reply all
Reply to author
Forward
0 new messages