我一直在网上搜索并搔头几个小时!
我有一个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
答案 0 :(得分:2)
问题是你有observableArray和jQuery竞争控制显示的元素。在这种情况下,您希望jQuery控制视图,并将其更改反映到数组中,而不会将中间更改反射回返回到视图中。
因此,不要对observableArray执行remove
和splice
操作,而是操作内容,然后告诉数组它已被更新。
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>