可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Related to: Bootstrap Radio Button Group
HTML:
<div class="btn-group" data-toggle="buttons">
<label class="btn btn-primary">
<input type="radio" name="options" id="option1" value="1" data-bind="checked: optionsValue"> Option 1
</label>
<label class="btn btn-primary">
<input type="radio" name="options" id="option2" value="2" data-bind="checked: optionsValue"> Option 2
</label>
<label class="btn btn-primary">
<input type="radio" name="options" id="option3" value="3" data-bind="checked: optionsValue"> Option 3
</label>
</div>
<br />
<span data-bind="text: optionsValue"></span>
Javascript:
var ViewModel = function() {
this.optionsValue = ko.observable()
};
ko.applyBindings(new ViewModel());
JsFiddle:
- Without data-toggle: http://jsfiddle.net/fDMM2/
- With data-toggle: http://jsfiddle.net/Kf3tj/1/
I have the above code which I'm trying to get working as I expect it to. The problem is that when data-toggle="buttons"
is added to the btn-group div (as in the Bootstrap 3 example) the knockout binding stops working. If I leave the data-toggle off of the buttons group then the binding works as expected but the button group looks awful. I know that this didn't work in Bootstrap 2 because they didn't actually use the radio input for their radio styling. How come it refuses to work now even though they do?
回答1:
The bootstrap buttons and the knockout checked
binding are still not playing nice:
- knockout uses the
click
event inside the checked
binding to tigger the underlaying observable to change
- bootstrap subscribes on the click event to do the toggling but calls
e.preventDefault()
so KO won't be notified about the click.
One possible solution is to create a custom binding handler where you subscribe on the change
event (this is fired by bootstrap on toogle) and set your observables value there:
ko.bindingHandlers.bsChecked = {
init: function (element, valueAccessor, allBindingsAccessor,
viewModel, bindingContext) {
var value = valueAccessor();
var newValueAccessor = function () {
return {
change: function () {
value(element.value);
}
}
};
ko.bindingHandlers.event.init(element, newValueAccessor,
allBindingsAccessor, viewModel, bindingContext);
},
update: function (element, valueAccessor, allBindingsAccessor,
viewModel, bindingContext) {
if ($(element).val() == ko.unwrap(valueAccessor())) {
setTimeout(function () {
$(element).closest('.btn').button('toggle');
}, 1);
}
}
}
And use it in your view with:
<label class="btn btn-primary">
<input type="radio" name="options" id="option1" value="1"
data-bind="bsChecked: optionsValue"> Option 1
</label>
Original demo using Bootstrap 3.0.2 JSFiddle.
Updated demo using Bootstrap 3.2.0 JSFiddle.
回答2:
I can't take credit for this since once on my coworkers came up with it but it works really well.
<div class="btn-group" data-toggle="buttons">
<label data-bind="css: { active: !HideInLeaderboards() },
click: function () { HideInLeaderboards(false); },
clickBubble: false"
class="btn btn-default">
Show Name
</label>
<label data-bind="css: { active: HideInLeaderboards() },
click: function () { HideInLeaderboards(true); },
clickBubble: false" class="btn btn-default">
Hide Name
</label>
</div>
回答3:
change the handler @nemesv proposed to be this: and it worked in my app just fine.
ko.bindingHandlers.bsChecked = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var value = valueAccessor();
var newValueAccessor = function () {
return {
change: function () {
value(element.value);
}
}
};
if ($(element).val() == ko.unwrap(valueAccessor())) {
$(element).closest('.btn').button('toggle');
}
ko.bindingHandlers.event.init(element, newValueAccessor, allBindingsAccessor, viewModel, bindingContext);
}
}
回答4:
Knockstrap appears to be a binding between Bootstrap and Knockout, and it appears to be kept pretty well up to date. Concerning radio buttons in specific, they utilize this code:
// Knockout checked binding doesn't work with Bootstrap radio-buttons
ko.bindingHandlers.radio = {
init: function (element, valueAccessor) {
if (!ko.isObservable(valueAccessor())) {
throw new Error('radio binding should be used only with observable values');
}
$(element).on('change', 'input:radio', function (e) {
// we need to handle change event after bootsrap will handle its event
// to prevent incorrect changing of radio button styles
setTimeout(function() {
var radio = $(e.target),
value = valueAccessor(),
newValue = radio.val();
value(newValue);
}, 0);
});
},
update: function (element, valueAccessor) {
var $radioButton = $(element).find('input[value="' + ko.unwrap(valueAccessor()) + '"]'),
$radioButtonWrapper;
if ($radioButton.length) {
$radioButtonWrapper = $radioButton.parent();
$radioButtonWrapper.siblings().removeClass('active');
$radioButtonWrapper.addClass('active');
$radioButton.prop('checked', true);
} else {
$radioButtonWrapper = $(element).find('.active');
$radioButtonWrapper.removeClass('active');
$radioButtonWrapper.find('input').prop('checked', false);
}
}
};
回答5:
There is a much easier approach found here.
We can just not use the data-toggle
attribute and try to achieve the same behavior using knockout's data binding.
It's almost as simple as the native html radios.
var ViewModel = function() {
this.optionsValue = ko.observable("1");
};
ko.applyBindings(new ViewModel());
input[type="radio"] {
/* Make native radio act the same as bootstrap's radio. */
display: none;
}
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div class="btn-group">
<label class="btn btn-primary" data-bind="css: {active: optionsValue() == '1'}">
<input type="radio" name="options" id="option1" value="1" data-bind="checked: optionsValue">Option 1
</label>
<label class="btn btn-primary" data-bind="css: {active: optionsValue() == '2'}">
<input type="radio" name="options" id="option2" value="2" data-bind="checked: optionsValue">Option 2
</label>
<label class="btn btn-primary" data-bind="css: {active: optionsValue() == '3'}">
<input type="radio" name="options" id="option3" value="3" data-bind="checked: optionsValue">Option 3
</label>
</div>
<br/>
<span data-bind="text: optionsValue"></span>
回答6:
There's a simple solution here that I'm surprised hasn't been mentioned yet.
1) Remove the data-toggle="buttons"
from the btn-group
Now knockout should be working
2) Bootstrap applies custom css for these inputs to hide them, that we just lost by removing the data-toggle, so apply the following css to the radio inputs:
position: absolute;
clip: rect(0,0,0,0);
pointer-events: none;
3) Last thing we need is for the selected option's button to have the active class. Add the following to the label buttons:
data-bind="css: {active: optionsValue() == valueOfThisInput}"