angular-ui / ui-sortable

jQuery UI Sortable for AngularJS
http://angular-ui.github.io/ui-sortable/
MIT License
1.26k stars 444 forks source link

UI.Sortable directive

npm npm Build Status Coverage Status debugInfoEnabled(false) Ready Badge Join the chat at https://gitter.im/angular-ui/ui-sortable

This directive allows you to sort an array with drag & drop.

Requirements

Single minified cdn link ~245kB and example with JQuery v1.x, required parts of JQueryUI v1.10, AngularJS v1.2 & latest angular-ui-sortable.

Notes:

Installation

Usage

Load the script file: sortable.js in your application:

<script type="text/javascript" src="https://github.com/angular-ui/ui-sortable/raw/master/modules/directives/sortable/src/sortable.js"></script>

Add the sortable module as a dependency to your application module:

var myAppModule = angular.module('MyApp', ['ui.sortable'])

Apply the directive to your form elements:

<ul ui-sortable ng-model="items">
  <li ng-repeat="item in items">{{ item }}</li>
</ul>

Developing Notes:

Options

All the jQueryUI Sortable options can be passed through the directive.
Additionally, the ui argument of the available callbacks gets enriched with some extra properties as specified to the API.md file. Any model changes that happen inside the available callbacks, are applied right after the stop event. We are not wrapping callbacks like start/change/... with $apply, in order to minimize the number of digest loops and avoid possible modifications of the model (eg: by watchers) before the drop takes place.

myAppModule.controller('MyController', function($scope) {
  $scope.items = ["One", "Two", "Three"];

  $scope.sortableOptions = {
    update: function(e, ui) { ... },
    axis: 'x'
  };
});
<ul ui-sortable="sortableOptions" ng-model="items">
  <li ng-repeat="item in items">{{ item }}</li>
</ul>

When using event callbacks (start/update/stop...), avoid manipulating DOM elements (especially the one with the ng-repeat attached). The suggested pattern is to use callbacks for emmiting events and altering the scope (inside the 'Angular world').

ui-floating

ui-floating (default: undefined)
Description: Enables a workaround for smooth horizontal sorting.
Type: Boolean/String/undefined

To have a smooth horizontal-list reordering, jquery.ui.sortable needs to detect the orientation of the list. This detection takes place during the initialization of the plugin (and some of the checks include: whether the first item is floating left/right or if 'axis' parameter is 'x', etc). There is also a known issue about initially empty horizontal lists.

To provide a solution/workaround (till jquery.ui.sortable.refresh() also tests the orientation or a more appropriate method is provided), ui-sortable directive provides a ui-floating option as an extra to the jquery.ui.sortable options.

<ul ui-sortable="{ 'ui-floating': true }" ng-model="items">
  <li ng-repeat="item in items">{{ item }}</li>
</ul>

OR

$scope.sortableOptions = {
  'ui-floating': true
};
<ul ui-sortable="sortableOptions" ng-model="items">
  <li ng-repeat="item in items">{{ item }}</li>
</ul>

ui-model-items

ui-model-items (default: > [ng-repeat],> [data-ng-repeat],> [x-ng-repeat])
Description: Defines which elements should be considered as part of your model.
Type: CSS selector/String

This is the model related counterpart option of jQuery's items option.

ui-preserve-size

ui-preserve-size (default: undefined)
Description: Set's the size of the sorting helper to the size of the original element before the sorting.
Type: Boolean/undefined

This is useful for elements that their size is dependent to other page characteristics. A representative example of such cases are <table> <tr>s and <td>s.

Attributes For Event Handling

To handle events with html bindings just define any expression to listed event attributes. If you defined an attribute for this events and defined callback function in sortableOptions at the same time, the attribute based callback will be called first.

Expression works on update event.

<ul ui-sortable ng-model="items" ui-sortable-update="expression" >
  <li ng-repeat="item in items">{{ item }}</li>
</ul>

On update event callBackFunction1 if called before callBackFunction2.

$scope.sortableOptions = {
  'update': callBackFunction2
};
<ul ui-sortable="sortableOptions" ng-model="items" ui-sortable-update="callBackFunction1" >
  <li ng-repeat="item in items">{{ item }}</li>
</ul>

Canceling

Inside the update callback, you can check the item that is dragged and cancel the sorting.

$scope.sortableOptions = {
  update: function(e, ui) {
    if (ui.item.sortable.model == "can't be moved") {
      ui.item.sortable.cancel();
    }
  }
};

Notes:

jQueryUI Sortable Event order

Single sortable demo

create

/* dragging starts */
helper
start
activate

/* multiple: sort/change/over/out */

beforeStop
update    <= call cancel() here if needed
deactivate
stop

Connected sortables demo

list A: create
list B: create

/* dragging starts from sortable A to B */
list A: helper
list A: start
list B: activate
list A: activate

/* both lists multiple: sort/change/over/out */
list A: sort
list A: change
list B: change
list B: over
list A: sort
list B: out
list A: sort

list A: beforeStop
list A: update    <= call cancel() here if needed
list A: remove
list B: receive
list B: update
list B: deactivate
list A: deactivate
list A: stop

For more details about the events check the jQueryUI API documentation.

Integrating with directives doing transclusion

Wrap the transclusion directive element with the ui-sortable directive and set the items to target your ng-repeated elements. Following best practices, it is also highly recommended that you add a track by expression to your ng-repeat. Angular Material example.

myAppModule.controller('MyController', function($scope) {
  $scope.items = ["One", "Two", "Three"];

  $scope.sortableOptions = {
    items: '.sortable-item'
    // It is suggested to use the most specific cssselector you can,
    // after analyzing the DOM elements generated by the transclusion directive
    // eg: items: '> .transclusionLvl1 > .transclusionLvl2 > .sortable-item'
  };
});
<div ui-sortable="sortableOptions" ng-model="items">
  <a-transclusion-directive>
    <div ng-repeat="item in items" class="sortable-item">{{ item }}</div>
  </a-transclusion-directive>
</div>

Examples

Integrations

Reporting Issues

The above pen's are provided as a good starting point to demonstrate issues, proposals and use cases. Feel free to edit any of them for your needs (don't forget to also update the libraries used to your version).

Testing

We use Karma and JSHint to ensure the quality of the code. The easiest way to run these checks is to use grunt:

npm install -g grunt-cli
npm install && bower install
grunt

The karma task will try to open Firefox and Chrome as browser in which to run the tests. Make sure this is available or change the configuration in test\karma.conf.js.

Grunt Serve

We have one task to serve them all!

grunt serve

It's equal to run separately: