Nick Retallack
Nick Retallack

Reputation: 19571

Drag and drop sortable ng:repeats in AngularJS?

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?

Upvotes: 63

Views: 85200

Answers (5)

Guillaume86
Guillaume86

Reputation: 14384

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

Upvotes: 79

Manuel Woelker
Manuel Woelker

Reputation: 121

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/

Upvotes: 12

Dheeraj Nalawade
Dheeraj Nalawade

Reputation: 404

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

Upvotes: 0

Sebastien Chartier
Sebastien Chartier

Reputation: 130

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

Upvotes: 6

respectTheCode
respectTheCode

Reputation: 43116

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

Upvotes: 10

Related Questions