The key implementation involves adding sortable bindings to menu containers. Here's the essential HTML structure:
<div class="row">
<div class="col-lg-12 full-width" id="leftMenus">
<div class="col-lg-12">
<div class="dd" id="ddMenus" data-bind="if:MenuItems">
<ol class="dd-list" data-bind="sortable:{template: 'menuTemplate', data: MenuItems, afterMove: $root.handleDrop }">
</ol>
</div>
</div>
</div>
</div>
<script id="menuTemplate" type="text/html">
<li class="dd-item lv1">
<div class="dd-handle">
<span class="pull-right">
<i class="fa fa-plus" data-bind="click:$root.addMenuItem"></i>
<i class="fa fa-times" data-bind="click:$root.deleteItem"></i>
<i class="fa fa-pencil" data-bind="click:$root.editItem"></i>
</span>
<span>
<span class="label label-info"><i class="fa" data-bind="css:$root.getIconClass(type)"></i></span>
<span data-bind="text:title,click:$root.editItem" style="margin-left:10px;"></span>
</span>
</div>
<!-- ko if:$data.children -->
<ol class="dd-list" data-bind="sortable:{template: 'submenuTemplate', data: $data.children, afterMove: $root.handleDrop }">
</ol>
<!-- /ko -->
</li>
</script>
Important implemantation details:
- sortable binding: Enables drag-and-drop functionality for the menu items
- afterMove event: Triggered when an item is dropped after dragging
The drop event handler manages data refresh:
this.handleDrop = function() {
self.updateMenuData();
};
this.updateMenuData = function(menuData) {
var menuItems = menuData || ko.mapping.toJS(self.MenuItems());
self.MenuItems([]);
self.MenuItems(menuItems);
};
This approach ensures the UI updates correctly after drag operations by refreshing the underlying data.
The knockout-sortable library automatically updates observableArrays during drag operations, making it ideal for implementing sortable interfaces. The library requires:
- Knockout 2.0+
- jQuery
- jQuery UI
Official examples demonstrate various use cases including simple lists, connected lists, draggable elements, and complex arrangements like seating charts.