Reputation: 8034
I'm looking for a way in KnockoutJS to bind toggle button to a function that gets called when a specific value (button) is selected.
I tried several solutions and got it running with just KnockoutJS, but when I add Boostrap to it, it still doesn't work.
My code shown here does not trigger the handler function associated with checked
event. For the latest working example, use the JSFiddle.
HTML:
<div id="access-rights-control" class="btn-group pull-right" data-toggle="buttons">
<label class="btn btn-xs">
<input type="radio" name="status" value="1" id="status-published" data-bind="checked: $root.allowAccessHandler" />Allow access
</label>
<label class="btn btn-xs active">
<input type="radio" name="status" value="0" id="status-draft" checked /> No change
</label>
</div>
JS:
var viewModel = function() {
this.allowAccessHandler = function() {
alert("Works!");
}
};
ko.applyBindings(new viewModel());
JSFiddle: http://jsfiddle.net/p8nSe/191/
Upvotes: 1
Views: 7297
Reputation: 8034
Finally I found an approach that works natively and gets the task done. I'm not sure if it is the most generic one, since I have to override ko.bindingHandlers.radio
, but it works.
ko.bindingHandlers.radio = {
init: function(element, valueAccessor, allBindings, data, 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));
}
});
});
}
};
var viewModel = function() {
this.radioSelected = ko.observable('0');
this.radioSelected.subscribe(function(newValue) {
console.debug("Comparing", newValue, '1');
if (newValue === '1') {
alert("Works!");
}
});
};
ko.applyBindings(new viewModel());
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css" rel="stylesheet"/>
<link href="https://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div class="btn-group">
<button type="button" class="btn" data-bind="radio: radioSelected, radioValue: '1'">Allow access</button>
<button type="button" class="btn" data-bind="radio: radioSelected, radioValue: '0'">No change</button>
</div>
JSFiddle: http://jsfiddle.net/p8nSe/192/
Upvotes: 0
Reputation: 4222
Maybe what you want is something like what the snippet below do. please see and gimme a feedback.
ko.bindingHandlers["bsChecked"] ={
init: function(element, valueAccessor){
ko.utils.registerEventHandler(element,"change",function(event){
var check = $(event.target);
valueAccessor()(check.val());
});
}
};
var viewModel = function() {
this.checkedButton = ko.observable("2").extend({ notify: 'always' });
this.checkedButton.subscribe( function(checkedVal) {
// here you can decide what to do depends on the new checkedVal
alert("Works! : "+ checkedVal);
},this);
};
ko.applyBindings(new viewModel());
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css" rel="stylesheet"/>
<link href="https://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div id="access-rights-control" class="btn-group pull-left" data-toggle="buttons">
<label class="btn btn-xs btn-default">
<input type="radio" name="status" value="1" data-bind="bsChecked: checkedButton" />Allow access
</label>
<label class="btn btn-xs btn-default">
<input type="radio" name="status" value="2" data-bind="bsChecked: checkedButton" />No change
</label>
</div>
Created a binding handler to take care about the prevent changes caused by bootstrap, so this is a workaround and I suggest to you read about KO custom bindings and implement the
update
function of the binding handler.
Upvotes: 4
Reputation: 43881
I think you have two issues:
checked
binding isn't going to workchecked
binding correctly.I can help you with the 2nd. In a radio group where all the elements have the same name
attribute and different value
attributes, the variable bound to the checked
observable will get the value of the selected radio item.
You can subscribe to the observable and take action when it gets a particular value.
var viewModel = function() {
this.radioSelected = ko.observable();
this.radioSelected.subscribe(function(newValue) {
console.debug("Comparing", newValue, '1');
if (newValue === '1') {
alert("Works!");
}
});
this.radioSelected('0');
this.output = ko.observableArray();
};
ko.applyBindings(new viewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div id="access-rights-control" class="btn-group pull-right" data-toggle="buttons">
<label class="btn btn-xs btn-default">
<input type="radio" name="status" value="1" data-bind="checked: radioSelected" />Allow access
</label>
<label class="btn btn-xs btn-default active">
<input type="radio" name="status" value="0" data-bind="checked: radioSelected" />No change
</label>
</div>
<div data-bind="foreach:output">
<div data-bind="text:$data">
</div>
</div>
Again, this won't work if you have Bootstrap-styled radio buttons, because Bootstrap fiddles with the DOM in ways that Knockout isn't set up to detect. So you need a binding handler that works with Bootstrap radio magic. That may require that your approach be slightly different (ie, you may not use the checked
binding) I don't know.
Upvotes: 1
Reputation: 45135
If you want something to run when you click on the input
, then you need to use an event binding not the checked binding (which just binds the checked state to an observable, it's not an event handler). Something like:
var viewModel = function() {
this.allowAccessHandler = function() {
alert("Works!");
}
};
ko.applyBindings(new viewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div id="access-rights-control" class="btn-group pull-right" data-toggle="buttons">
<label class="btn btn-xs">
<input type="radio" name="status" value="1" id="status-published" data-bind="event: { change: $root.allowAccessHandler }" />Allow access
</label>
<label class="btn btn-xs active">
<input type="radio" name="status" value="0" id="status-draft" /> No change
</label>
</div>
Now if you have the checked
state bound to a property in your view model, it would probably be better to just subscribe
to that property instead of attaching an event handler to the DOM as that would be more inline with separating your view and view model.
Upvotes: 1