Knockout + Bootstrap 3 单选按钮

32

相关链接:Bootstrap 单选按钮组

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:


我有上面的代码,我正在尝试让它按照我的预期工作。问题是,当将data-toggle="buttons"添加到按钮组div中时(就像Bootstrap 3示例中一样),knockout绑定停止工作。如果我在按钮组中不加入data-toggle,则绑定按预期工作,但按钮组看起来很糟糕。我知道在Bootstrap 2中这不起作用,因为他们实际上没有使用单选输入来进行单选框样式设置。为什么现在它拒绝工作,即使他们这样做了呢?


Bootstrap 切换按钮仍然与 KO 不兼容,因此您需要一个自定义绑定处理程序,类似于这样:http://jsfiddle.net/hd89y/ - nemesv
请注意,使用此代码的单选按钮组在Bootstrap 2.x中确实可以正常工作(尽管它们也会在“按钮”内部显示单选按钮本身,除非您自己隐藏它)。这是因为Bootstrap 2.x代码没有尝试拦截点击事件-它更喜欢使用实际的button元素来模拟相同的行为。 - Moshe Katz
6个回答

50

Bootstrap按钮Knockout的checked绑定仍然存在问题:

  • Knockout在checked绑定中使用click事件来触发底层可观察对象的更改。
  • Bootstrap订阅点击事件来进行切换,但是调用了e.preventDefault(),因此KO不会被通知到点击事件。

一种可能的解决方案是创建一个自定义绑定处理程序,在其中订阅change事件(这是Bootstrap在切换时触发的),并在那里设置您的可观察值:

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); 
        }
    }
}

并在您的视图中使用它:

<label class="btn btn-primary">
    <input type="radio" name="options" id="option1" value="1" 
           data-bind="bsChecked: optionsValue"> Option 1
</label>

使用Bootstrap 3.0.2的原始演示JSFiddle
使用Bootstrap 3.2.0 更新后的演示 JSFiddle


@Kittoes 你应该向作者询问这个问题... - nemesv
抱歉,伙计。我只是想知道是否有一些明显的原因,因为我对JavaScript还很陌生。你的答案非常有效,只是可惜需要这样做。 - Kittoes0124
我很好奇如何使这个工作在单个复选框上,除了单选按钮。 - Kittoes0124
5
非常棒的帖子!这是复选框按钮组的一种适应方法:http://jsfiddle.net/MEA33/1/ - mrtig
5
这里有一个类似的问题。数据没问题,但是 BS 和 KO 会切换数值。在更新时添加 && !$(element).prop('checked')) 条件可以解决这个问题。 - RockResolve
显示剩余7条评论

15

我不能为此而拍自己的胸脯,因为这是我的一个同事想出来的,但它确实非常有效。

<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>

谢谢,这对于使用相同可观察对象的多个链接切换非常有效! - John

4

在这里找到了一种更简单的方法 (点击此处).

我们可以不使用data-toggle属性,尝试使用Knockout的数据绑定来实现相同的行为。

这几乎和原生的html单选框一样简单。

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>


3

Knockstrap 看起来是 Bootstrap 和 Knockout 之间的绑定,而且它似乎一直被很好地更新。关于特定的单选按钮,它们使用以下代码:

// 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);
        }
    }
};

1

将@nemesv提出的处理程序更改为以下内容:在我的应用程序中正常工作。

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);
    }
}

1
这里有一个简单的解决方案,我很惊讶还没有被提到。
1)从btn-group中删除"data-toggle =“ buttons”"
现在knockout应该可以工作了。
2)Bootstrap为这些输入应用自定义css以隐藏它们,我们刚刚通过删除data-toggle而失去了它们,因此将以下css应用于radio输入:
position: absolute;
clip: rect(0,0,0,0);
pointer-events: none;

3) 我们需要的最后一件事是为所选选项的按钮添加活动类。将以下内容添加到标签按钮中:

data-bind="css: {active: optionsValue() == valueOfThisInput}"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接