Skip to content

Sortable v1.0 — New capabilities

Viktor Pele edited this page Jun 12, 2017 · 15 revisions

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 AngularJSMeteor, and other details.




Introduction


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


Advanced groups


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:https://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 have clone 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:https://jsbin.com/yexine/2/edit http:https://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']
}
});

Animation


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:https://jsbin.com/yexine/4/edit

Operation example (gif)


Smart scrolling of window and list


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 in px;


Examples:



Disabling sorting


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:https://jsbin.com/xizeh/3/edit?html,js,output




Methods for sorting acquisition and modification


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:https://jsbin.com/yexine/7/edit (change the order and refresh the page)





Filtering capability


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:https://jsbin.com/yexine/6/edit?html,js,output




AngularJS support


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);
     }
  };
}

Operation example

Full code of directive: https://github.com/RubaXa/Sortable/blob/master/ng-sortable.js





Integration with Meteor


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. 




Plans for the future

  • 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 ;]