使用2个KnockoutJS树进行排序

时间:2014-04-15 20:18:13

标签: jquery jquery-ui knockout.js treeview knockout-sortable

是否可以使用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>

我有什么问题吗?

提前多多感谢!

1 个答案:

答案 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!
    }
}