是否可以使用knockout-sortable与knockout-ui树进行排序,以便可以将项目从一棵树拖到另一棵树?
我一直在努力调整RP Niemeyer中的示例,但我无法让它发挥作用。
我可以从两棵树上拖动物品,但它不会让我放下它们。我将connectWith属性作为参数添加到可排序插件中,但它无效。
这是我到目前为止所做的:
JS:
$(function () {
$(".availableItemsContainer .node").sortable({ connectWith: ".groupedItemsContainer" });
$(".groupedItemsContainer .node").sortable({ connectWith: ".availableItemsContainer" });
});
ko.bindingHandlers.flash = {
init: function (element) {
$(element).hide();
},
update: function (element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
if (value) {
$(element).stop().hide().text(value).fadeIn(function () {
clearTimeout($(element).data("timeout"));
$(element).data("timeout", setTimeout(function () {
$(element).fadeOut();
valueAccessor()(null);
}, 3000));
});
}
},
timeout: null
};
ko.bindingHandlers.droppable = {
init: function(element, valueAccessor) {
var dropHandler = valueAccessor() || {};
$(element).droppable({
drop: function(event, ui) {
var item = ko.utils.domData.get(ui.draggable[0], "ko_dragItem");
if (item) {
item = item.clone ? item.clone() : item;
dropHandler.call(this, item, event, ui);
}
}
});
}
};
var DGViewModel = function () {
var self = this;
self.tree1 = {
id: 'groupedItems',
remember: true,
children: [
{
name: "Annabelle",
id: '1',
isOpen: true,
children: [
{ name: "Arnie", cssClass: 'page', id: '5', children: [] },
{ name: "Anders", cssClass: 'page', id: '6', children: [] },
{ name: "Apple", cssClass: 'page', id: '7', children: [] }
]
},
{
name: "Bertie",
id: '2',
children: [
{ name: "Boutros-Boutros", cssClass: 'page', id: '8', children: [] },
{ name: "Brianna", cssClass: 'page', id: '9', children: [] },
{ name: "Barbie", cssClass: 'page', id: '10', children: [] },
{ name: "Bee-bop", id: '4', children: [] }
]
},
{ name: "Charles", id: '3', children: [] }
],
dragHolder: ko.observable(undefined),
handlers: {
addNode: function(parent, type, name, onSuccess) {
nextId = nextId + 1;
onSuccess({ id: nextId, parent: parent, name: name, cssClass: type });
}
},
logTo: '#log1',
defaults: {
'folder': {
name: 'New Folder',
childType: 'page'
},
'page': {
name: 'New Page',
isDraggable: true,
isDropTarget: false,
canAddChildren: false
}
},
contextMenu: {
contextMenus:
[
{
name: 'foldercontext',
width: 190,
items: [
{
text: 'Delete',
run: function(dataItem) {
dataItem.deleteSelf();
}
},
{
text: 'Rename',
run: function(dataItem) {
dataItem.isRenaming(true);
}
},
{ separator: true },
{
text: 'New',
items: [
{
text: 'Page',
iconCssClass: 'page',
run: function(dataItem) {
dataItem.addChild({ type: 'page' });
}
},
{
text: 'Folder',
iconCssClass: 'folder',
run: function(dataItem) {
dataItem.addChild({ type: 'folder' });
}
}
]
}
]
},
{
name: 'pagecontext',
width: 190,
items: [
{
text: 'Delete',
run: function(dataItem) {
dataItem.deleteSelf();
}
},
{
text: 'Rename',
run: function(dataItem) {
dataItem.isRenaming(true);
}
}
]
}
],
build: function(event, dataItem) {
dataItem.selectNode();
var type = dataItem.type();
if (type == 'folder') {
return { name: 'foldercontext' };
} else {
return { name: 'pagecontext' };
}
}
}
};
self.tree2 = {
id: 'availableItems',
remember: true,
children: [
{ name: "Test1", id: '300', cssClass: 'page', children: [] },
{ name: "Test2", id: '301', cssClass: 'page', children: [] },
{ name: "Test3", id: '302', cssClass: 'page', children: [] },
{ name: "Test4", id: '303', cssClass: 'page', children: [] }
],
dragHolder: ko.observable(undefined),
handlers: {
addNode: function(parent, type, name, onSuccess) {
nextId = nextId + 1;
onSuccess({ id: nextId, parent: parent, name: name, cssClass: type });
}
},
logTo: '#log1',
defaults: {
'folder': {
name: 'New Folder',
childType: 'page'
},
'page': {
name: 'New Page',
isDraggable: true,
isDropTarget: false,
canAddChildren: false
}
},
contextMenu: {
contextMenus:
[
{
name: 'foldercontext',
width: 190,
items: [
{
text: 'Delete',
run: function(dataItem) {
dataItem.deleteSelf();
}
},
{
text: 'Rename',
run: function(dataItem) {
dataItem.isRenaming(true);
}
},
{ separator: true },
{
text: 'New',
items: [
{
text: 'Page',
iconCssClass: 'page',
run: function(dataItem) {
dataItem.addChild({ type: 'page' });
}
},
{
text: 'Folder',
iconCssClass: 'folder',
run: function(dataItem) {
dataItem.addChild({ type: 'folder' });
}
}
]
}
]
},
{
name: 'pagecontext',
width: 190,
items: [
{
text: 'Delete',
run: function(dataItem) {
dataItem.deleteSelf();
}
},
{
text: 'Rename',
run: function(dataItem) {
dataItem.isRenaming(true);
}
}
]
}
],
build: function(event, dataItem) {
dataItem.selectNode();
var type = dataItem.type();
if (type == 'folder') {
return { name: 'foldercontext' };
} else {
return { name: 'pagecontext' };
}
}
}
};
};
var vm = new DGViewModel();
var nextId = 20;
vm.treeViewModel1 = new ko.tree.viewModel(vm.tree1);
vm.treeViewModel2 = new ko.tree.viewModel(vm.tree2);
ko.bindingHandlers.sortable.beforeMove = vm.verifyAssignments;
ko.bindingHandlers.sortable.afterMove = vm.updateLastAction;
ko.applyBindings(vm);
HTML:
<div id="availableItemsTreeViewWrapper">
<div class="container">
<div class="availableItemsContainer">
<div data-bind="tree : treeViewModel2"></div>
</div>
<div class="availableItemsContextMenuContainer">
<div style="padding: 10px;">
<a href="#" id="add" data-bind="click : function() { treeViewModel2.addNode(); }">Add</a>
<a href="#" id="delete" data-bind="click : treeViewModel2.deleteNode">Delete</a>
<a href="#" id="delete" data-bind="click : treeViewModel2.renameNode">Rename</a>
</div>
<div id="log2" class="logger">
</div>
</div>
</div>
</div>
<div class="mainWrapper">
<div id="groupedItemsTreeViewWrapper">
<div class="container">
<div class="groupedItemsContainer">
<div data-bind="tree : treeViewModel1"></div>
</div>
<div class="groupedItemsContextMenuContainer">
<div style="padding: 10px;">
<a href="#" id="add" data-bind="click : function() { treeViewModel1.addNode(); }">Add</a>
<a href="#" id="delete" data-bind="click : treeViewModel1.deleteNode">Delete</a>
<a href="#" id="delete" data-bind="click : treeViewModel1.renameNode">Rename</a>
</div>
<div id="log1" class="logger">
</div>
</div>
</div>
</div>
</div>
我有什么问题吗?
提前多多感谢!
答案 0 :(得分:0)
我没有使用你正在做的解决方案就完成了这项工作。我所做的是保持所有的ui交互相同,然后处理事件中的所有内容。所以我有一个可排序的绑定(ko.bindingHandlers.sortable),值访问器保留我的选项。在可排序的绑定中,我会有类似的东西:
ko.bindingHandlers.sortable = {
init: function (element, valueAccessor) {
options = valueAccessor();
$(element).sortable({
connectWith: options.connectWith || {},
items: options.items || {},
update: options.update || {}
});
}
};
然后在更新中我处理从一个列表添加到另一个列表。所以你把它放到你的列表中(此时它只在DOM中)并且更新捕获它(或停止,取决于你需要的。更新第一个列表和第二个列表,停止只是第二个列表。)这里你从第一个列表中删除它(显式或在dom节点上使用ko.contextFor),然后将它拼接到第二个列表中。此时你必须从ui中删除该项,这就像ui.item.remove()一样简单。如果您将这些信息存储在数据库中(无论哪种方式),您都必须在其上保留一个序列,因为它们只是按照它们创建的顺序返回,无论如何。
这种方式是让它与jquery ui一起工作的好方法,但它并没有真正集成。你让jquery ui做它的事情,然后根据最终结果更新模型。
-
所以在html中你要做的就是使用我上面提到的绑定。为此,您将绑定两个列表,如下所示:
<div id="list1" data-bind="sortable: { connectWith: '#list2', update: updateListFunction, items: '.myItems' }, foreach: list1.items">
<div class="myItems"></div>
</div>
<div id="list2" data-bind="sortable: { connectWith: '#list1', update: updateListFunction, items: '.myItems' }, foreach: list2.items">
<div class="myItems"></div>
</div>
然后updateListFunction
会在您的上下文视图模型中出现如下内容:
self.updateListFuntion = function() {
//makes sure it only updates on the list to which the sorting applied, since it fires twice
if (this === ui.item.parent()[0]){
//do your updating here, using the method in the second paragraph
//ko.contextFor(ui.item) will give you the original item to get it out of the list and move it into the next
//a lot of the stuff that's going to happen here is array math and is a whole 'nother can of worms!
}
}