Lightweight vanillajs micro-library for creating sortable lists and grids using native HTML5 drag and drop API.
- Only 2KB (minified and gzipped).
- Built using native HTML5 drag and drop API. No dependencies.
- Supports both list and grid style layouts.
- Supported Browsers: Current versions of all major browsers (Chrome, Firefox, Safari, Opera, Edge), IE11+ (Polyfill required)
- Available as ES6 Module, AMD, CommonJS and iffe with
sortable
global
Demo: Check out the examples
If you would like to add an adapter to the list, please create an issue with the link to your adapter.
We recommend installing the package via npm.
npm install html5sortable --save
Once you install the package using npm
or downloading the latest release (don't use the master branch), load file you need from the dist/
directory, e.g. dist/html.sortable.min.js
for the minified iife version.
- iffe (loading file via script tag):
dist/html5sortable.js
ordist/html5sortable.min.js
- ES6 Module:
dist/html5sortable.es.js
- CommonJS Module:
dist/html5sortable.cjs.js
- AMD Module:
dist/html5sortable.amd.js
Still using bower? bower install https://github.com/lukasoppermann/html5sortable.git
You can find the examples online or test locally. Warning: the online demo is just to show off the features and is most likely not up to date. Please study this readme file for the current way of implementing and using html5sortable
.
Use sortable
method to create a sortable list:
sortable('.sortable');
Use .sortable-placeholder
CSS selectors to change the styles of the placeholder. You may change the class by setting the placeholderClass
option in the config object.
sortable('.sortable', {
placeholderClass: 'my-placeholder fade'
});
You can nest sortables inside each other. However, take care to add a wrapper around the items, a sortable-item can not at the same time be a sortable
.
<div class="list"><!-- Sortable -->
<div class="item"> Item 1
<div class="sublist"><!-- Nested Sortable; Wrapping container needed -->
<div class="subitem">Subitem 1</div>
<div class="subitem">Subitem 2</div>
</div>
</div>
<div class="item"> Item 2 </div>
</div>
NOTE: Events can be listened on any element from the group (when using connectWith
), since the same event will be dispatched on all of them.
Use sortstart
event if you want to do something when sorting starts:
sortable('.sortable')[0].addEventListener('sortstart', function(e) {
/*
This event is triggered when the user starts sorting and the DOM position has not yet changed.
e.detail.item - {HTMLElement} dragged element
Origin Container Data
e.detail.origin.index - {Integer} Index of the element within Sortable Items Only
e.detail.origin.elementIndex - {Integer} Index of the element in all elements in the Sortable Container
e.detail.origin.container - {HTMLElement} Sortable Container that element was moved out of (or copied from)
*/
});
Use the sortstop
event if you want to do something when sorting stops:
sortable('.sortable')[0].addEventListener('sortstop', function(e) {
/*
This event is triggered when the user stops sorting and the DOM position has not yet changed.
e.detail.item - {HTMLElement} dragged element
Origin Container Data
e.detail.origin.index - {Integer} Index of the element within Sortable Items Only
e.detail.origin.elementIndex - {Integer} Index of the element in all elements in the Sortable Container
e.detail.origin.container - {HTMLElement} Sortable Container that element was moved out of (or copied from)
*/
});
Use sortupdate
event if you want to do something when the order changes (e.g. storing the new order):
sortable('.sortable')[0].addEventListener('sortupdate', function(e) {
console.log(e.detail);
/*
This event is triggered when the user stopped sorting and the DOM position has changed.
e.detail.item - {HTMLElement} dragged element
Origin Container Data
e.detail.origin.index - {Integer} Index of the element within Sortable Items Only
e.detail.origin.elementIndex - {Integer} Index of the element in all elements in the Sortable Container
e.detail.origin.container - {HTMLElement} Sortable Container that element was moved out of (or copied from)
e.detail.origin.itemsBeforeUpdate - {Array} Sortable Items before the move
e.detail.origin.items - {Array} Sortable Items after the move
Destination Container Data
e.detail.destination.index - {Integer} Index of the element within Sortable Items Only
e.detail.destination.elementIndex - {Integer} Index of the element in all elements in the Sortable Container
e.detail.destination.container - {HTMLElement} Sortable Container that element was moved out of (or copied from)
e.detail.destination.itemsBeforeUpdate - {Array} Sortable Items before the move
e.detail.destination.items - {Array} Sortable Items after the move
*/
});
Use the items
option to specify which items inside the element should be sortable:
sortable('.sortable', {
items: ':not(.disabled)'
});
Use the handle
option to restrict drag start to the specified element:
sortable('.sortable', {
handle: 'h2'
});
Setting the forcePlaceholderSize
option to true, forces the placeholder to have a height:
sortable('.sortable', {
forcePlaceholderSize: true
});
Use acceptFrom
instead. The connectWith
option allows you to create a connected lists:
sortable('.js-sortable, .js-second-sortable', {
connectWith: 'connected' // unique string, which is not used for other connectWith sortables
});
Use the acceptFrom
option to restrict which sortable's items will be accepted by this sortable. acceptFrom
accepts a space separated list of selectors or false
to disabling accepting items. This is an alternative to connectWith and should not be used together.
sortable('.sortable', {
acceptFrom: '.selector,.anotherSortable' // Defaults to null
});
If you want to be able to move items between to sortables, the acceptFrom
option must be present on both of them.
Use the placeholder
option to specify the markup of the placeholder:
sortable('.sortable', {
items: 'tr' ,
placeholder: '<tr><td colspan="7"> </td></tr>'
});
Use the hoverClass
option to apply css classes to the hovered element rather than relying on :hover
. This can eliminate some potential drag and drop issues where another element thinks it is being hovered over.
sortable('.sortable', {
hoverClass: 'is-hovered is-hovered-class' // Defaults to false
});
Use the maxItems
option to restrict the number of items that can be added to a sortable from a connected sortable. maxItems
should always be combined with the items
option. Make sure items
does not match placeholder and other options, so that they are not counted.
sortable('.sortable', {
maxItems: 3 // Defaults to 0 (no limit)
});
Use the copy
option to duplicate the element on drag. The original element will remain in the same position.
sortable('.sortable', {
copy: true // Defaults to false
});
You can provide a function
that will be applied to every item in the items
array (see serialize). The function receives two arguments: serializedItem: object
, sortableContainer: Element
. This function can be used to change the output for the items. Defaults to undefined
.
sortable('.sortable', {
itemSerializer: (serializedItem, sortableContainer) => {
return {
position: serializedItem.index + 1,
html: serializedItem.html
}
}
});
You can provide a function
that will be applied to the container
object (see serialize). The function receives one argument: serializedContainer: object
. This function can be used to change the output for the container. Defaults to undefined
.
sortable('.sortable', {
containerSerializer: (serializedContainer) => {
return {
length: container.itemCount
}
}
});
You can provide a function as a customDragImage
property on the options object which will be used to create the item and position of the drag image (the half transparent item you see when dragging an element).
The function gets three parameters, the dragged element, an offset object with the offset values for the offset of the item and the dragstart
event. The function MUST return an object with an element
property with an html element as well as a posX
and posY
property with has the x and y offset for the dragImage.
sortable('.sortable', {
customDragImage: (draggedElement, elementOffset, event) => {
return {
element: draggedElement,
posX: event.pageX - elementOffset.left,
posY: event.pageY - elementOffset.top
}
}
});
// elementOffset object that is received in the customDragImage function
{
left: rect.left + window.scrollX,
right: rect.right + window.scrollX,
top: rect.top + window.scrollY,
bottom: rect.bottom + window.scrollY
}
To remove the sortable functionality completely:
sortable('.sortable', 'destroy');
To disable the sortable temporarily:
sortable('.sortable', 'disable');
To enable a disabled sortable:
sortable('.sortable', 'enable');
You can easily serialize a sortable using the serialize
command. If you provided an itemSerializer
or containerSerializer
function in the options object, they will be applied to the container
object and the items
objects before they are returned.
sortable('.sortable', 'serialize');
// You will receive an object in the following format
[{
container: {
node: sortableContainer,
itemCount: items.length
}
items: [{
parent: sortableContainer,
node: item,
html: item.outerHTML,
index: index(item, items)
}, …]
}, …]
When you add a new item to a sortable, it will not automatically be a draggable item, so you will need to reinit the sortable. Your previously added options will be preserved.
sortable('.sortable');
- Initialize plugin on
tbody
element (browsers automatically addtbody
if you don't) - Keep in mind that different browsers may display different drag images of the row during the drag action. Webkit browsers seem to hide entire contents of
td
cell if there are any inline elements inside thetd
. This may or may not be fixed by setting thetd
to beposition: relative;
- If you add a custom
placeholder
you must use atr
e.g.placeholder: "<tr><td colspan="3">The row will appear here</td></tr>"
, otherwise you will only be able to drop items when hovering the first column.
This version is maintained by Lukas Oppermann and many other contributors. Thanks for your help! 👍
Contributions are always welcome. Please check out the contribution guidelines to make it fast & easy for us to merge your PR.
This project is focusing on modern, evergreen browsers to deliver a fast and small package. While many projects try build features so that it runs in the oldest browser (looking at you IE9), we try to create a fast and pleasant development experience using the language capabilities that the current version of Javascript offers.
While a backwards facing approach penalises modern browsers by making them download huge files, we prefer to ship a small package and have outdated browser bear the penalty of the polyfill. An additional benefit is that you might polyfill those features in any case so you don't have any additional load.
We try to encourage people to help shape the future of this package and contribute in small or big ways. By removing hard to understand hacks we make it easier for people new to the code base or even Javascript to contribute.
Browser try to performance optimise language features as much as possible. Working around the language to make code work in outdated browser may actually work against this.
We recommend using the Financial Times Polyfill Service which will polyfill only the necessary features for browsers that need a polyfill. It is basically a no-config, easy solution.
<script src="https://cdn.polyfill.io/v2/polyfill.min.js"></script>
- Dragstart not working on buttons
Dragstart event does not fire onbutton
elements. This effectively disables drag and drop for button elements. See https://caniuse.com/#feat=dragndrop in the known issues section.