Bootstrap 多选 selectAll 不适用于 Knockout.js 除非使用 setTimeout

Bootstrap multiselect selectAll doesn't work with Knockout.js unless setTimeout is used

显然,Bootstrap 多选在使用 jQuery multiselect() 和 Knockout.js 时有限制,因此如果在 Knockout 事件期间通过代码修改多选下拉列表 (单击事件,在以下示例中),则不会应用代码。

下面的例子演示了它:

  1. 首先,点击左边的按钮。您会看到虽然创建了选项,但未选择它们。您需要再次单击才能选中它们。
  2. 然后,点击右边的按钮。您会看到已创建并选择了选项。我使用了 1000 毫秒的超时,但它也只适用于 1 毫秒的超时。

我的问题:有没有比超时更好的方法来使 selectAll() 工作?

var selectorVM = function () {
 var self = this;
  self.available = ko.observableArray([]);
  self.selected = ko.observableArray([]);
  self.init = function () {
    self.initOptions();
    self.selectAll();
  };
  self.initWithTimeout = function () {
    self.initOptions();
    self.selectAllWithTimeout();
  };
  self.initOptions = function () {
    self.available([]);
    self.available([
      { name: "option 1", value: 1}, 
      { name: "option 2", value: 2}
    ]);  
  };
  self.selectAll = function () {
      var $selector = $("#selector");
      $selector.multiselect('selectAll', false);
      $selector.multiselect('updateButtonText');
  };
  self.selectAllWithTimeout = function () {
    setTimeout(self.selectAll, 1000);
  };
}
var selectorVM = new selectorVM();
ko.applyBindings(selectorVM);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-multiselect/0.9.15/js/bootstrap-multiselect.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-multiselect/0.9.15/css/bootstrap-multiselect.css" rel="stylesheet"/>

<div> 
  <button class="btn btn-default" data-bind="click: init">Click to init dropdown, no timeout</button>
  <button class="btn btn-default" data-bind="click: initWithTimeout">Click to init dropdown, with timeout</button>
</div>
<div>
  <select id="selector" 
        class="form-control"
        multiple="multiple" 
        data-bind="options: available,
                   optionsText: 'name',
                   optionsValue: 'value',
                   selectedOptions: selected,
                   multiselect: { includeSelectAllOption: true }">
  </select>    
</div>

您的问题出在代码执行顺序上。内置 KO options 绑定处理程序和 jQuery Multiselect 插件都在修改 DOM,并且 options 处理程序可能执行 multiselect 函数之后,所以当您构建 multiselect 菜单时,还没有任何选项可供选择。当您使用 setTimeout 时,即使有 1 毫秒的超时,该函数也会被推送到调用堆栈的末尾,并将在当前事件循环完成时执行(有关更多详细信息,请参见 ),所以现在它在 options 绑定完成后执行。因此,有效。

然而,这不是很漂亮。在视图模型中执行 DOM 操作从来都不是一个好主意。这就是为什么 custom binding handlers 是一回事;轻松与 DOM 元素交互并保持您的视图模型干净。 您的代码引用了一个 multiselect 绑定处理程序,但我在您的 post 中没有看到它。如果我们创建它(并不复杂),我们将看到我们可以使菜单按预期工作。 您最初的问题是您使用提供的绑定处理程序以及手动操作 DOM .您应该坚持使用绑定处理程序,它会正常工作。无需超时。

对于像 KO 这样的框架,作为一般经验法则,您应该始终只需要操作视图模型中的数据。 KO 应该承担更新 UI 的繁重工作。因此,如果您想要 select 所有项目,请考虑其工作原理:有一个名为 selected 的可观察数组,其中存储了 selected 项目的 ID。因此,如果您想要 select 所有项目,您只需遍历所有可用项目,并将 ID 推送到 selected 数组。 KO 会负责为您更新 UI。请参阅更新的代码片段。我已经创建了一个调用 selectAll 函数的单独按钮,但如果你愿意,你当然可以在你的 init 函数中调用 selectAll

var selectorVM = function () {
  var self = this;
  
  self.available = ko.observableArray([]);
  self.selected = ko.observableArray([]);
  
  self.init = function () {
    self.initOptions();
  };
  
  self.initOptions = function () {
    self.available([
      { name: "option 1", value: 1 }, 
      { name: "option 2", value: 2 }
    ]);  
  };

  self.selectAll = function () {
    self.available().forEach(function (opt) {
      if (self.selected.indexOf(opt.value) === -1) {
        self.selected.push(opt.value);
      }
    });
  }
}

var selectorVM = new selectorVM();
ko.applyBindings(selectorVM);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-multiselect/0.9.15/js/bootstrap-multiselect.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-multiselect/0.9.15/css/bootstrap-multiselect.css" rel="stylesheet"/>

<div> 
  <button class="btn btn-default" data-bind="click: init">Click to init dropdown, no timeout</button>
  <button class="btn btn-default" data-bind="click: selectAll">Select all</button>
</div>
<div>
  <select id="selector" 
        class="form-control"
        multiple="multiple" 
        data-bind="options: available,
                   optionsText: 'name',
                   optionsValue: 'value',
                   selectedOptions: selected,
                   multiselect: { includeSelectAllOption: true }">
  </select>    
</div>

<p>Selected options in observable array:</p>
<ul data-bind="foreach: selected"><li data-bind="text: $data"></li></ul>