Knockout + JQuery sortable = list items消失

时间:2016-01-26 09:42:11

标签: jquery jquery-ui knockout.js

我一直在网上搜索并搔头几个小时!

我有一个knockout observable数组,我可以通过绑定到jQuery Sortable重新排列。一切都工作得很好,除了一个恼人的小虫子。

如果我将第二个列表项拖到第一个列表项之上(不丢弃),然后将其放回原来的位置,然后拖动第一个列表项并将其放在第二个和第三个列表项之间...第二个列表项消失了!

HTML:

<ul data-bind="foreach: headers, uiSortableList: headers" class="dropdown-menu">
    <li>
        <span data-bind="text: title"></span>
    </li>
</ul>

使用Javascript:

ko.bindingHandlers.uiSortableList = {
    init: function (element, valueAccessor, allBindingsAccesor, context) {
        var list = valueAccessor();

        $(element).sortable({
            axis: 'y',
            update: function (event, ui) {
                var item = ko.dataFor(ui.item[0]);
                var newIndex = ui.item.index();

                if (list.indexOf(item) != newIndex) {
                    ui.item.remove();
                    list.remove(item);
                    list.splice(newIndex, 0, item);
                }
            }
        });
    }
};

function ViewModel() {
    var self = this;

    self.headers = ko.observableArray([
        {title: 'One'},
        {title: 'Two'},
        {title: 'Three'},
        {title: 'Four'}
    ]);
}

var vm = new ViewModel();
ko.applyBindings(vm);

外部脚本:

https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js
https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js
https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js

JSFiddle:https://jsfiddle.net/connyake/w1Lgqtug/

1 个答案:

答案 0 :(得分:2)

问题是你有observableArray和jQuery竞争控制显示的元素。在这种情况下,您希望jQuery控制视图,并将其更改反映到数组中,而不会将中间更改反射回返回到视图中。

因此,不要对observableArray执行removesplice操作,而是操作内容,然后告诉数组它已被更新。

        update: function (event, ui) {
            var item = ko.dataFor(ui.item[0]);
            var newIndex = ui.item.index();
            var oldIndex = list.indexOf(item);

            if (oldIndex != newIndex) {
                list().splice(oldIndex, 1);
                list().splice(newIndex, 0, item);
                list.valueHasMutated();
            }
        }

在下面的示例中,我添加了第二个不可排序的列表,因此您可以看到observableArray确实反映了相应的更改。我还添加了一个按钮来创建更多项目,这似乎按预期工作。

重要提示:在测试中,我曾经多次将两个数组不同步。如果你将Two移到一个以上,然后将One移到最后一个位置,则jQuery列表会被加扰。所以这不是一个完美的解决方案,但它确实找出了你所看到的问题(并且它可能仍然是observableArray和jQuery争夺控制权的问题)。如果我重新排列jQuery列表,直到它与其他列表匹配,那么它们会再次同步。

更新如果重置observableArray的内容,它似乎就会停止混淆。如果您的列表很长,这不是一个很好的解决方案,因为重绘会很明显,但它看起来确实很强大。我已将其替换为片段。

    if (oldIndex != newIndex) {
      var c = list();
      list([]);
      c.splice(oldIndex, 1);
      c.splice(newIndex, 0, item);
      list(c);
    }

ko.bindingHandlers.uiSortableList = {
  init: function(element, valueAccessor, allBindingsAccesor, context) {
    var list = valueAccessor();

    $(element).sortable({
      axis: 'y',
      update: function(event, ui) {
        var item = ko.dataFor(ui.item[0]);
        var newIndex = ui.item.index();
        var oldIndex = list.indexOf(item);

        if (oldIndex != newIndex) {
          var c = list();
          list([]);
          c.splice(oldIndex, 1);
          c.splice(newIndex, 0, item);
          list(c);
        }
      }
    });
  }
};

function ViewModel() {
  var self = this;

  self.headers = ko.observableArray([{
    title: 'One'
  }, {
    title: 'Two'
  }, {
    title: 'Three'
  }, {
    title: 'Four'
  }]);
  self.addItem = function() {
    self.headers.push({
      title: 'Item' + self.headers().length
    });
  };
}

var vm = new ViewModel();
console.clear();
ko.applyBindings(vm);
ul {
  width: 100px;
}
li {
  margin: 2px;
  padding: .2em;
  background-color: #777;
  color: #fff;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<ul data-bind="foreach: headers, uiSortableList: headers" class="dropdown-menu">
  <li>
    <span data-bind="text: title"></span>
  </li>
</ul>
<ul data-bind="foreach: headers">
  <li>
    <span data-bind="text: title"></span>
  </li>
</ul>
<button data-bind="click: addItem">Add Item
</button>