jQuery Sortable
A flexible, opinionated sorting plugin for jQuery
View on GitHub Download (v0.9.13)
Download minified version (9.7 kb, gzipped ~3.2 kb)
Features
So what does it do?
- Sorts any items in any container
- Fully supports nested containers
- Connect lists
- Callbacks and events (see docs)
- Pure drag/drop lists
- Vertical and horizontal sorting
Why another sortable plugin?
you might ask. Aren't there many others?
The answer is: nested lists. None of the other solutions had a decent support for nested lists. nestedSortable relies on a fixed width hierarchy. Others mimic the way jQuery UI does sortables and therefore require ugly hacks that suffer from sudden jumps.
The opinionated part
This plugin does one and only one thing: sorting . If you need animations or autoscrolling, do them yourself .
Moreover this plugin assumes that the placeholder has zero height/width. As a result, the item dimensions may be cached. This might change in the future, if need be.
Compatibility
jquery-sortable.js
has been tested with the following browsers
- Firefox >= 3.5
- Chrome
- IE > 7
- Safari >= 6
- Opera
- Konqueror
If you confirmed, that it works on other browsers please tell me .
Show it to me! With default options.
Heads Up! There is no on-the-fly creation of sublists. Only list items that contain a sublist are drop targets.
-
First
-
Second
-
Third
- First
- Second
-
Third
- First
- Second
- First
- Second
- Fourth
- Fifth
- Sixth
Getting started
Making a list sortable consists of 3 easy steps
Add styles
body.dragging, body.dragging * { cursor: move !important; } .dragged { position: absolute; opacity: 0.5; z-index: 2000; } ol.example li.placeholder { position: relative; /** More li styles **/ } ol.example li.placeholder:before { position: absolute; /** Define arrowhead **/ }
Look here for a complete example
Define your HTML
<ol class='example'> <li>First</li> <li>Second</li> <li>Third</li> </ol> <script src='js/jquery-sortable.js'></script>
Call the initializer
$(function () { $("ol.example").sortable(); });
Examples
Connected lists with drop animation
var adjustment; $("ol.simple_with_animation").sortable({ group: 'simple_with_animation', pullPlaceholder: false, // animation on drop onDrop: function ($item, container, _super) { var $clonedItem = $('<li/>').css({height: 0}); $item.before($clonedItem); $clonedItem.animate({'height': $item.height()}); $item.animate($clonedItem.position(), function () { $clonedItem.detach(); _super($item, container); }); }, // set $item relative to cursor position onDragStart: function ($item, container, _super) { var offset = $item.offset(), pointer = container.rootGroup.pointer; adjustment = { left: pointer.left - offset.left, top: pointer.top - offset.top }; _super($item, container); }, onDrag: function ($item, position) { $item.css({ left: position.left - adjustment.left, top: position.top - adjustment.top }); } });
- Define your own drop animation
-
Connect lists by placing them in the same
group - Only sort if the cursor is above a container
- Item 1
- Item 2
- Item 3
- Item 4
- Item 5
- Item 6
- Item 1
- Item 2
- Item 3
- Item 4
- Item 5
- Item 6
Sort handle and limited drag/drop
$("ol.simple_with_drop").sortable({ group: 'no-drop', handle: 'i.icon-move', onDragStart: function ($item, container, _super) { // Duplicate items of the no drop area if(!container.options.drop) $item.clone().insertAfter($item); _super($item, container); } }); $("ol.simple_with_no_drop").sortable({ group: 'no-drop', drop: false }); $("ol.simple_with_no_drag").sortable({ group: 'no-drop', drag: false });
- Drag the items by a handle
- Pure drag/drop container
- Clone items on drag
I'm draggable and droppable
- Item 1
- Item 2
- Item 3
- Item 4
- Item 5
- Item 6
I'm only draggable
- Item 1
- Item 2
- Item 3
I'm only droppable
- Item 1
- Item 2
- Item 3
Toggable nested lists
var oldContainer; $("ol.nested_with_switch").sortable({ group: 'nested', afterMove: function (placeholder, container) { if(oldContainer != container){ if(oldContainer) oldContainer.el.removeClass("active"); container.el.addClass("active"); oldContainer = container; } }, onDrop: function ($item, container, _super) { container.el.removeClass("active"); _super($item, container); } }); $(".switch-container").on("click", ".switch", function (e) { var method = $(this).hasClass("active") ? "enable" : "disable"; $(e.delegateTarget).next().sortable(method); });
- Nest lists of arbitrary depth
- Highlight the current container
- Enable/disable lists
- Item 1
- Item 2
- Item 3
-
Item 4
- Item 3.1
- Item 3.2
- Item 3.3
- Item 3.4
- Item 3.5
- Item 3.6
- Item 5
- Item 6
- Item 1
- Item 2
- Item 3
-
Item 4
- Item 3.1
- Item 3.2
- Item 3.3
- Item 3.4
- Item 3.5
- Item 3.6
- Item 5
- Item 6
Connected lists with limited drop targets
var group = $("ol.limited_drop_targets").sortable({ group: 'limited_drop_targets', isValidTarget: function ($item, container) { if($item.is(".highlight")) return true; else return $item.parent("ol")[0] == container.el[0]; }, onDrop: function ($item, container, _super) { $('#serialize_output').text( group.sortable("serialize").get().join("\n")); _super($item, container); }, serialize: function (parent, children, isContainer) { return isContainer ? children.join() : parent.text(); }, tolerance: 6, distance: 10 });
- Limit the drop targets of the dragged item
- Customize serialization of the lists
- Decrease sort sensitivity
- Start dragging after a distance has been met
Serialize result
- Item 1
- Item 2
- Item 3
- Item 4
- Item 5
- Item 6
- Item 1
- Item 2
- Item 3
- Item 4
- Item 5
- Item 6
Sorting a bootstrap menu
- Sort vertically
- Define the nested containers differently
- Exclude some items from being sortable
Heads Up!
The
itemSelector
should always match every sibling of any item.
If you want to exclude some items, use the
exclude
option.
See the first example
here
why this is a good idea.
$("ol.nav").sortable({ group: 'nav', nested: false, vertical: false, exclude: '.divider-vertical', onDragStart: function($item, container, _super) { $item.find('ol.dropdown-menu').sortable('disable'); _super($item, container); }, onDrop: function($item, container, _super) { $item.find('ol.dropdown-menu').sortable('enable'); _super($item, container); } }); $("ol.dropdown-menu").sortable({ group: 'nav' });
Serialization and delay
var group = $("ol.serialization").sortable({ group: 'serialization', delay: 500, onDrop: function ($item, container, _super) { var data = group.sortable("serialize").get(); var jsonString = JSON.stringify(data, null, ' '); $('#serialize_output2').text(jsonString); _super($item, container); } });
- Uses the default serialize implementation, that reads out the data attributes
Serialize result
- Item 1
- Item 2
- Item 3
-
Item 4
- Item 3.1
- Item 3.2
- Item 3.3
- Item 3.4
- Item 3.5
- Item 3.6
- Item 5
- Item 6
- Item 1
- Item 2
- Item 3
- Item 4
- Item 5
- Item 6
Sort tables (doesn't work well in Konqueror and IE)
// Sortable rows $('.sorted_table').sortable({ containerSelector: 'table', itemPath: '> tbody', itemSelector: 'tr', placeholder: '<tr class="placeholder"/>' }); // Sortable column heads var oldIndex; $('.sorted_head tr').sortable({ containerSelector: 'tr', itemSelector: 'th', placeholder: '<th class="placeholder"/>', vertical: false, onDragStart: function ($item, container, _super) { oldIndex = $item.index(); $item.appendTo($item.parent()); _super($item, container); }, onDrop: function ($item, container, _super) { var field, newIndex = $item.index(); if(newIndex != oldIndex) { $item.closest('table').find('tbody tr').each(function (i, row) { row = $(row); if(newIndex < oldIndex) { row.children().eq(newIndex).before(row.children()[oldIndex]); } else if (newIndex > oldIndex) { row.children().eq(newIndex).after(row.children()[oldIndex]); } }); } _super($item, container); } });
- Sort tables
-
Specify custom
placeholder - Provide custom callbacks, to change the sorting behaviour
Sortable Rows
Sortable column heads
| A Column | B Column | C Column |
|---|---|---|
| A Item 1 | B Item 1 | C Item 1 |
| A Item 2 | B Item 2 | C Item 2 |
| A Item 3 | B Item 3 | C Item 3 |
| A Item 4 | B Item 4 | C Item 4 |
| A Item 5 | B Item 5 | C Item 5 |
| A Item 6 | B Item 6 | C Item 6 |
External examples
- SoraMame Block - program by drag and drop
Documentation
jQuery API
The
sortable()
method must be invoked on valid containers,
meaning they must match the
containerSelector
option.
.sortable([options])
Instantiate sortable on each matched element. The available options are divided into group options and container options .
Group options are shared between all member containers and are set on the first instantiation of a member container. Subsequent instantiations of further containers in the same group do not change the group options.
Container options can be set seperately for each member of a group.
.sortable("enable")
Enable all instantiated sortables in the set of matched elements
.sortable("disable")
Disable all instantiated sortables in the set of matched elements
.sortable("refresh")
Reset all cached element dimensions
.sortable("destroy")
Remove the sortable plugin from the set of matched elements
.sortable("serialize")
Serialize all selected containers. Returns a
jQuery
object . Use
.get()
to retrieve the array, if needed.
Group options
| Option | Default | Description |
|---|---|---|
afterMove
|
function ($placeholder, container, $closestItemOrContainer) { } |
This is executed after the placeholder has been moved. $closestItemOrContainer contains the closest item, the placeholder has been put at or the closest empty Container, the placeholder has been appended to. |
containerPath
|
"" |
The exact css path between the container and its items, e.g. "> tbody" |
containerSelector
|
"ol, ul" |
The css selector of the containers |
distance
|
0 |
Distance the mouse has to travel to start dragging |
delay
|
0 |
Time in milliseconds after mousedown until dragging should start. This option can be used to prevent unwanted drags when clicking on an element. |
handle
|
"" |
The css selector of the drag handle |
itemPath
|
"" |
The exact css path between the item and its subcontainers. It should only match the immediate items of a container. No item of a subcontainer should be matched. E.g. for ol>div>li the itemPath is "> div" |
itemSelector
|
"li" |
The css selector of the items |
bodyClass
|
"dragging" |
The class given to "body" while an item is being dragged |
draggedClass
|
"dragged" |
The class giving to an item while being dragged |
isValidTarget
|
function ($item, container) { return true } |
Check if the dragged item may be inside the container. Use with care, since the search for a valid container entails a depth first search and may be quite expensive. |
onCancel
|
function ($item, container, _super, event) { } |
Executed before onDrop if placeholder is detached. This happens if pullPlaceholder is set to false and the drop occurs outside a container. |
onDrag
|
function ($item, position, _super, event) { $item.css(position) } |
Executed at the beginning of a mouse move event. The Placeholder has not been moved yet. |
onDragStart
|
function ($item, container, _super, event) { $item.css({ height: $item.outerHeight(), width: $item.outerWidth() }) $item.addClass(container.group.options.draggedClass) $("body").addClass(container.group.options.bodyClass) } |
Called after the drag has been started, that is the mouse button is being held down and the mouse is moving. The container is the closest initialized container. Therefore it might not be the container, that actually contains the item. |
onDrop
|
function ($item, container, _super, event) { $item.removeClass(container.group.options.draggedClass).removeAttr("style") $("body").removeClass(container.group.options.bodyClass) } |
Called when the mouse button is being released |
onMousedown
|
function ($item, _super, event) { if (!event.target.nodeName.match(/^(input|select|textarea)$/i)) { event.preventDefault() return true } |
Called on mousedown. If falsy value is returned, the dragging will not start. Ignore if element clicked is input, select or textarea |
placeholderClass
|
"placeholder" |
The class of the placeholder (must match placeholder option markup) |
placeholder
|
'<li class="placeholder"></li>' |
Template for the placeholder. Can be any valid jQuery input e.g. a string, a DOM element. The placeholder must have the class "placeholder" |
pullPlaceholder
|
true |
If true, the position of the placeholder is calculated on every mousemove. If false, it is only calculated when the mouse is above a container. |
serialize
|
function ($parent, $children, parentIsContainer) { var result = $.extend({}, $parent.data()) if(parentIsContainer) return [$children] else if ($children[0]){ result.children = $children } |
Specifies serialization of the container group. The pair $parent/$children is either container/items or item/subcontainers. |
tolerance
|
0 |
Set tolerance while dragging. Positive values decrease sensitivity, negative values increase it. |
Container options
Alternatives
Listed in alphabetical order
- HTML5 Sortable (uses native dnd events, supports IE5.5+)
- jQuery UI sortable
- Nestable (requires no jQuery UI)
- nestedSortable (an extension of jQuery UI)
- RubaXa's Sortable (supports touch devices and AngularJS)
- YUI sortable
