-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Sortable v1.0 — New capabilities
Hello, %username%! As we approach the New Year, I want to share my joy with you – the release of Sortable v1.0. This very same day, one year ago, I presented my small tool for list sorting using drag’n’drop. During all this time, I have been thoroughly collecting feedback, adding new capabilities and fixing bugs. Under the cut, I will tell you about new capabilities, integration with AngularJS, Meteor, and other details.
Sortable is a minimalistic instrument for sorting elements within a list or between lists. Its library doesn’t depend on jQuery or other libraries; it uses native HTML5 Drag’n’Drop API, and works on both desktop and touch devices. It has a simple API, can be easily integrated with any project and represents an excellent replacement of jQueryUI/Sortable ;]
New features in this release:
- Advanced groups (flexible adjustment of movement and some other things)
- Animation during movement
- Smart scrolling of browser window and list
- Disabling of sorting (allows to imitate "draggable" and "droppable")
- Methods for sorting acquisition and modification
- Filtering capability
- Supporting AngularJS and Meteor
From the very beginning, the library has had ability of movement between groups. All we had to do was to assign the same name to such groups:
Code | Operation example (gif) |
---|---|
http://jsbin.com/yexine/1/edit | |
// “foo” and “bar” are links to HTMLElement
Sortable.create(foo, { group: 'shared' });
Sortable.create(bar, { group: 'shared' }); |
Over time, it became obvious that this was not enough. For example, it was impossible to make one element donor list, while another list would only receive elements. Besides that, the main drawback was the absence of capability to organise interaction among several groups. We needed to develop a solution, which would allow for implementation of the tasks arisen, and retain some space for future enhancement without losing the current interface.
Now, we can set group
option as an object with the following properties:
-
name
— group name; -
pull
— an ability to “pull out” elements during their movement between lists; also this property may haveclone
value; -
put
— an ability to accept an element from another group, or an array of permitted groups.
It is easier to explain how it works using the following example:
- You have three lists: «A», «B» and «C»;
- We need to move elements from «A» and «B» to «C», movement between «A» and «B» is impossible;
- When dragging from «A», a
clone
must appear in the element’s place.
In order to show all abilities at once, I will solve this task in two different ways.
Common group | Several groups |
---|---|
http://jsbin.com/yexine/2/edit | http://jsbin.com/yexine/8/edit |
Sortable.create(listA, {
group: {
name: 'shared',
pull: 'clone',
put: false
}
});
Sortable.create(listB, {
group: {
name: 'shared',
put: false
}
});
Sortable.create(listC, {
group: 'shared'
}); |
Sortable.create(listA, {
group: {
name: 'A',
pull: 'clone'
}
});
Sortable.create(listB, {
group: 'B'
});
Sortable.create(listC, {
group: {
put: ['A', 'B']
}
}); |
There’s not much to tell. The animation was implemented very simply, by means of CSS3 transition. It can be enabled by setting animation
option in ms
. Unfortunately, it has its own downsides that are not resolved yet, but I hope that we will find a way to correct them “at a low price”.
http://jsbin.com/yexine/4/edit
Just recently, a new task arose: to implement scrolling of window after reaching one of its borders. In theory, it would have worked by default, since we use Native Drag’n’Drop API, but in practice, the browser scrolled the window quite unwillingly. The problem, when the list is in overflow
, also remained unsolved. So after thinking for a while, we have managed to implement smart scrolling, which scrolled a list first (if it was in overflow
) and/or the window (if we have reached the browser’s window border). Three additional options were introduced for finer adjustment:
-
scroll
— enable auto-scrolling; -
scrollSensitivity
— how close we must be to the border for scrolling activation; -
scrollSpeed
— scrolling speed inpx
;
Examples:
- Long list: http://jsbin.com/boqugumiqi/1/edit?html,js,output
- overflow: http://jsbin.com/kohamakiwi/1/edit?html,js,output
Yes, that’s it. It may seem strange, but with this parameter, it is possible to disable the function, which was the main purpose of this tool ;]. For example, this may be used to imitate “draggable” and “droppable”: http://jsbin.com/xizeh/3/edit?html,js,output
As this library was created as the result of exploring Drag’n’Drop API capabilities, we were unable to implement even commonplace methods for acquiring an order or changing it. When I looked at jQueryUI API, I noticed that we can only acquire the order of elements, but it can’t be changed, because it’s not an order ;]. In order to solve all these problems, store
property was added. It accepts an object of two parameters get
and set
in order to acquire and save the sorting. Also, we’ve added two methods toArray
and sort
.
For example, order saving by means of localStorage
looks like this:
Sortable.create(users, {
store: {
// Sorting acquisition (called during initialization)
get: function (sortable) {
var order = localStorage.getItem(sortable.options.group);
return order ? order.split('|') : [];
},
// Saving the acquired sorting (called each time upon sorting modification)
set: function (sortable) {
var order = sortable.toArray();
localStorage.setItem(sortable.options.group, order.join('|'));
}
}
});
http://jsbin.com/yexine/7/edit (change the order and refresh the page)
Let’s assume that you need to make a sortable list with possibility of editing and deleting elements. Before now, you would have to hang the needed handlers by your own. Now, you can solve this kind of task by the means of the library itself, without any additional tools: http://jsbin.com/yexine/6/edit?html,js,output
Angular continues to conquer the market, so in order to make it easier for people to use Sortable, it was decided to create a directive for fast integration with a project. When I was looking through the analogues, I noticed a strange thing that everybody make the following:
<ul ui-sortable="sortableOptions" ng-model="items">
<li ng-repeat="item in items">{{ item }}</li>
</ul>
Why? In fact, it’s just a copy-paste thing, and to be completely honest, it is only a gimp stick. In my opinion, the following record (without ng-model
) would make sense and be correct:
<ul ng-sortable="sortableOptions">
<li ng-repeat="item in items">{{ item }}</li>
</ul>
The data that we’re interested in are already contained in ng-repeat
. To obtain them, we will need to use $parse
function and a little trick. The trick is about the fact that the data from ng-repeat
can be obtained only by finding a special comment left by Angular itself:
<ul ng-sortable="{ animation: 150 }">
<!-- ngRepeat: item in items -->
<!-- end ngRepeat: item in items -->
</ul>
Now, we can create a method for working with data related to ng-repeat
:
/**
* Obtaining an object for working with data in `ng-repeat`
* @param {HTMLElement} el
* @returns {object}
*/
function getNgRepeat(el) {
// Obtaining the current `scope` related to the element
var scope = angular.element(el).scope();
// Find the needed comment
var ngRepeat = [].filter.call(el.childNodes, function (node) {
return (
(node.nodeType === 8) &&
(node.nodeValue.indexOf('ngRepeat:') !== -1)
);
})[0];
// Parsing the name of variables of element and array
ngRepeat = ngRepeat.nodeValue.match(/ngRepeat:\s*([^\s]+)\s+in\s+([^\s|]+)/);
// Converting the names of variables to `expression` in order to obtain their values from `scope`
var itemExpr = $parse(ngRepeat[1]);
var itemsExpr = $parse(ngRepeat[2]);
return {
// Obtaining list element model
item: function (el) {
return itemExpr(angular.element(el).scope());
},
// Obtaining an array related to `ng-repeat`
items: function () {
return itemsExpr(scope);
}
};
}
Full code of directive: https://github.com/RubaXa/Sortable/blob/master/ng-sortable.js
This is a completely new ability, which has appeared thanks to Dan Dascalescu, so if you use Meteor, then the library is already added to the athmosphere, and Dan has already added a detailed user manual and an example. Just in case you need it, place the task with “meteor” mark on it, it will be pleased to help ;]
To this end, I want to thank everyone, who has taken part in testing and development of the library; although it has put on some “weight”, it is still a simple and flexible tool. Thanks for your attention.
- Covering with texts (it is still unplumbed how to cover Drag’n’Drop, but I have some ideas)
- Improved animation
- Extension system (for example, nested lists or combining two elements in one)
- Limiting by axes (unfortunately, may be we will have to put Drag’n’Drop API aside)
- Your suggestion ;]