基于 observable 变化的切换函数

Toggle function based on change of observable

Durandal 中,我正在寻找一种通过分配 viewOptions: ko.observable("small") 来快速初始化活动切换按钮的方法。在 viewOptions 发生变化的同时,我想打开相应 id 的按钮并关闭所有其他按钮。

在我尝试过的几种方法中,none 正是这样做的。以下代码中缺少什么?

HTML:

<div class="btn-group right" id="view-selector" data-toggle="buttons" data-bind="radio: viewOptions">
    <button type="button" id="small" class="btn btn-default" data-bind="css: {active: viewOptions() == 'small'}"><span class="glyphicon glyphicon-stop">Small</button>
    <button type="button" id="medium" class="btn btn-default" data-bind="css: {active: viewOptions() == 'medium'}"><span class="glyphicon glyphicon-th-large">Medium</button>
    <button type="button" id="large" class="btn btn-default" data-bind="css: {active: viewOptions() == 'large'}"><span class="glyphicon glyphicon-th">Large</button>
</div>

JS:

define(['jquery', 'jquery-ui', 'bootstrap', 'knockout', 'durandal/app', 'plugins/router'], function($, jqueryui, bootstrap, ko, app, router) {
    return {
        viewOptions: ko.observable("small"),
        bindingComplete: function() { // called immediately after databinding occurs.
            alert("start"); //called
            console.log("validate: this:", this); // Object {__moduleId__: "app/view"}
            console.log("validate: viewOptions:",this.viewOptions); // OK, non-empty
            this.viewOptions.subscribe(function(newViewOptions){
                alert("Hello there!"); //never gets called
                alert(newViewOptions); //
            });
            alert("end"); //called
        }
    }
});

http://jsfiddle.net/ccjnj/179/(简体)

您使用 the css binding,告诉它在 viewOptions() == 'small'(或中等或大)时应用 active class:

<button ... data-bind="css: {active: viewOptions() == 'small'}">...</button>

示例:

var viewModel = {
    viewOptions: ko.observable("small"),
};

ko.applyBindings(viewModel);

loop();

function loop() {
  viewModel.viewOptions("small");
  setTimeout(function() {
    viewModel.viewOptions("medium");
  }, 1000);
  setTimeout(function() {
    viewModel.viewOptions("large");
  }, 2000);
  setTimeout(loop, 3000);
}
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet"/>
<div class="btn-group right" id="view-selector" data-toggle="buttons" data-bind="radio: viewOptions">
    <button type="button" id="small" class="btn btn-default" data-bind="css: {active: viewOptions() == 'small'}"><span class="glyphicon glyphicon-stop">Small</button>
    <button type="button" id="medium" class="btn btn-default" data-bind="css: {active: viewOptions() == 'medium'}"><span class="glyphicon glyphicon-th-large">Medium</button>
    <button type="button" id="large" class="btn btn-default" data-bind="css: {active: viewOptions() == 'large'}"><span class="glyphicon glyphicon-th">Large</button>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>

我从上面删除了订阅,因为我认为你只是用它来设置 class。如果你想要订阅以及,只需将其添加回来(在浏览器控制台中查看):

var viewModel = {
    viewOptions: ko.observable("small"),
    attached: function() {
      this.viewOptions.subscribe(function(newViewOptions){
          console.log(newViewOptions);
      }, this); // <== No need for bind, note subscribe' 2nd arg
    }
};

ko.applyBindings(viewModel);
viewModel.attached();

var timeout = +new Date() + 10000;
loop();

function loop() {
  viewModel.viewOptions("small");
  setTimeout(function() {
    viewModel.viewOptions("medium");
  }, 1000);
  setTimeout(function() {
    viewModel.viewOptions("large");
  }, 2000);
  if (+new Date() < timeout) {
    setTimeout(loop, 3000);
  }
}
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet"/>
<div class="btn-group right" id="view-selector" data-toggle="buttons" data-bind="radio: viewOptions">
    <button type="button" id="small" class="btn btn-default" data-bind="css: {active: viewOptions() == 'small'}"><span class="glyphicon glyphicon-stop">Small</button>
    <button type="button" id="medium" class="btn btn-default" data-bind="css: {active: viewOptions() == 'medium'}"><span class="glyphicon glyphicon-th-large">Medium</button>
    <button type="button" id="large" class="btn btn-default" data-bind="css: {active: viewOptions() == 'large'}"><span class="glyphicon glyphicon-th">Large</button>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>

自定义绑定 "radio" 从数据属性 "data-value" 驱动每个切换按钮的值,因此您必须为每个选项指定该值,例如:

 <button type="button" id="small" class="btn btn-default" data-value="small"> ...

并且应该处理这个 fiddle http://jsfiddle.net/ccjnj/171/

中展示的问题

T.J。 Crowder 为您提供了一个很好的答案。我只是认为在您的视图模型中制作按钮项目并使用 foreach 绑定来呈现它们似乎是个好主意。节省重复 HTML.

function buttonData(label, value) {
  return {
    label: label,
    value: value
  };
}

var viewModel = {
  buttons: [
    buttonData('Small', 'small'),
    buttonData('Medium', 'medium'),
    buttonData('Large', 'large')
  ],
  selectedButton: ko.observable("small"),
  setOption: function(data) {
    console.debug("Set option", data.value);
    viewModel.selectedButton(data.value);

  }
};

ko.applyBindings(viewModel);

viewModel.selectedButton.subscribe(function(newViewOptions) {
  alert("Hello there!");
  alert(newViewOptions);
});
<link href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/2.1.1/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div class="btn-group right" id="view-selector" data-toggle="buttons" data-bind="foreach: buttons">
  <button type="button" class="btn btn-default" data-bind="click:$root.setOption, text:label, css: { active: value == $root.selectedButton()}"></button>
</div>

结合一些解决方案,我找到了答案。我意识到 Durandal 中的事件处理模型与 Knockout 略有不同,在我错过 ko.bindingHandlers.radiobindingComplete:

之前需要以下代码
define(['jquery', 'jquery-ui', 'bootstrap', 'knockout', 'durandal/app', 'plugins/router'], function($, jqueryui, bootstrap, ko, app, router) {
    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);
            }
            var elementBindings = allBindings();
            $buttons.each(function() {
                var $btn, btn, radioValue;
                btn = this;
                $btn = $(btn);
                radioValue = $btn.attr("id");
                $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 vm = {
        viewOptions: ko.observable(),
        bindingComplete: function() { 
            vm.viewOptions.subscribe(function(newViewOptions){
                alert("Hello there!"); 
                alert(newViewOptions);
            });
            vm.viewOptions("large");
        }
    }
    return vm;
});