62

Is it at all easy to use jQuery.sortable on ng-repeat elements in AngularJS?


It would be awesome if re-ordering the items automatically propagated that ordering back into the source array. I'm afraid the two systems would fight though. Is there a better way to do this?

Graham
  • 7,431
  • 18
  • 59
  • 84
Nick Retallack
  • 18,986
  • 17
  • 92
  • 114
  • Is it possible that you update the js fiddle codes for angular js current version i.e. angular js 1.0.1. I tried to run the examples on jsfiddle by changing the version of angular js to 1.0.1 as per my requirements but then the code breaks and doesnot work. Kindly help.' Thanks ! – Basmah Jul 25 '12 at 10:25
  • Im on board with that. Also looking for a working example for 1.0.1 :-) – Marco Johannesen Jul 25 '12 at 16:08
  • Found this, which works in 1.0.1 - http://jsfiddle.net/007design/KHqbM/ – Marco Johannesen Jul 27 '12 at 11:59
  • 2
    I've created a tutorial on how to do this in combination with JQuery UI. Very easy to do with a single directive. Explanation and examples can be found here: http://www.smartjava.org/content/drag-and-drop-angularjs-using-jquery-ui – Jos Dirksen Dec 05 '12 at 21:00

5 Answers5

79

Angular UI has a sortable directive,Click Here for Demo

Code located at ui-sortable, usage:

<ul ui-sortable ng-model="items" ui-sortable-update="sorted">
  <li ng-repeat="item in items track by $index" id="{{$index}}">{{ item }}</li>
</ul>
$scope.sorted = (event, ui) => { console.log(ui.item[0].getAttribute('id')) }
Raz Buchnik
  • 7,753
  • 14
  • 53
  • 96
Guillaume86
  • 14,341
  • 4
  • 53
  • 53
12

I tried to do the same and came up with the following solution:

angular.directive("my:sortable", function(expression, compiledElement){
    return function(linkElement){
        var scope = this;
        linkElement.sortable(
        {
            placeholder: "ui-state-highlight",
            opacity: 0.8,
            update: function(event, ui) {
                var model = scope.$tryEval(expression);
                var newModel = [];
                var items = [];
                linkElement.children().each(function() {
                    var item = $(this);
                    // get old item index
                    var oldIndex = item.attr("ng:repeat-index");
                    if(oldIndex) {
                        // new model in new order
                        newModel.push(model[oldIndex]);
                        // items in original order
                        items[oldIndex] = item;
                        // and remove
                        item.detach();
                    }
                });
                // restore original dom order, so angular does not get confused
                linkElement.append.apply(linkElement,items);

                // clear old list
                model.length = 0;
                // add elements in new order
                model.push.apply(model, newModel);

                // presto
                scope.$eval();

                // Notify event handler
                var onSortExpression = linkElement.attr("my:onsort");
                if(onSortExpression) {
                    scope.$tryEval(onSortExpression, linkElement);
                }
            }
        });
    };
});

Used like this:

<ol id="todoList" my:sortable="todos" my:onsort="onSort()">

It seems to work fairly well. The trick is to undo the DOM manipulation made by sortable before updating the model, otherwise angular gets desynchronized from the DOM.

Notification of the changes works via the my:onsort expression which can call the controller methods.

I created a JsFiddle based on the angular todo tutorial to shows how it works: http://jsfiddle.net/M8YnR/180/

danronmoon
  • 3,814
  • 5
  • 34
  • 56
Manuel Woelker
  • 121
  • 1
  • 5
10

This is how I am doing it with angular v0.10.6. Here is the jsfiddle

angular.directive("my:sortable", function(expression, compiledElement){
    // add my:sortable-index to children so we know the index in the model
    compiledElement.children().attr("my:sortable-index","{{$index}}");

    return function(linkElement){
        var scope = this;            

        linkElement.sortable({
            placeholder: "placeholder",
            opacity: 0.8,
            axis: "y",
            update: function(event, ui) {
                // get model
                var model = scope.$apply(expression);
                // remember its length
                var modelLength = model.length;
                // rember html nodes
                var items = [];

                // loop through items in new order
                linkElement.children().each(function(index) {
                    var item = $(this);

                    // get old item index
                    var oldIndex = parseInt(item.attr("my:sortable-index"), 10);

                    // add item to the end of model
                    model.push(model[oldIndex]);

                    if(item.attr("my:sortable-index")) {
                        // items in original order to restore dom
                        items[oldIndex] = item;
                        // and remove item from dom
                        item.detach();
                    }
                });

                model.splice(0, modelLength);

                // restore original dom order, so angular does not get confused
                linkElement.append.apply(linkElement,items);

                // notify angular of the change
                scope.$digest();
            }
        });
    };
});
respectTheCode
  • 42,348
  • 18
  • 73
  • 86
  • I ended up just using SlickGrid for this, but I'll take your word that this works. – Nick Retallack Jul 31 '12 at 20:43
  • One thing to note with this solution is that it will most likely not work as is with v1.x. It shouldn't be to hard to tweak this, but I haven't gotten around to updating all of my code yet. – respectTheCode Aug 01 '12 at 15:15
  • @respectTheCode Have you re-factored the code to work with the latest AngularJS version (v1.0.1)? I can't for the life of me find a reliable working version for 1.0.1.... – Greg Aug 12 '12 at 18:01
  • 1
    @Greg we are using Angular-UI's UI-Sortable now and it works great. https://github.com/angular-ui/ui-sortable – respectTheCode Oct 11 '13 at 13:17
6

Here's my implementation of sortable Angular.js directive without jquery.ui :

Brad Rem
  • 6,036
  • 2
  • 25
  • 50
0

you can go for ng-sortable directive which is lightweight and it does not uses jquery. here is link ng-sortable drag and drop elements

Demo for ng-sortable