Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 425
Nick Retallack Avatar asked Sep 08 '11 21:09

Nick Retallack


3 Answers

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')) }
like image 173
Guillaume86 Avatar answered Oct 20 '22 00:10

Guillaume86


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/

like image 12
Manuel Woelker Avatar answered Oct 20 '22 01:10

Manuel Woelker


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();
            }
        });
    };
});
like image 10
respectTheCode Avatar answered Oct 19 '22 23:10

respectTheCode