orszaczky
orszaczky

Reputation: 15645

AngularJS - draggable and multiple connected sortables (jQuery UI + angular-common)

I'm trying to extend angular-common's excellent dragdrop module which can handle a draggable connected to a single sortable. (Original SO thread behind angular-common's dragdrop module is here.)

I managed to connect two sortables, and dropping a draggable to each of them, and rearranging within the sortables work fine (scope item arrays get updated as expected). In fact the UI part works fine, but I can't seem figure out how to update the item arrays in the Angular scope when I drag a list item from one sortable to another.

Factory:

.factory('DragDropHandler', [function() {
    return {
        dragObject: undefined,
        addObject: function(object, objects, to) {
            objects.splice(to, 0, object);
        },
        moveObject: function(objects, from, to) {
            objects.splice(to, 0, objects.splice(from, 1)[0]);
        }
    };
}])

Droppable (sortable) directive:

.directive('droppable', ['DragDropHandler', function(DragDropHandler) {
    return {
        scope: {
            droppable: '=',
            ngUpdate: '&',
            ngCreate: '&'
        },
        link: function(scope, element, attrs){
            element.sortable({
              connectWith: ['.draggable','.sortable'],
            });
            element.disableSelection();
            var list = element.attr('id');
            element.on("sortdeactivate", function(event, ui) {

                var from = angular.element(ui.item).scope().$index;
                var to = element.children().index(ui.item);

                //console.log('from: ' + from + ', to: ' +to);

                if (to >= 0 ){
                    scope.$apply(function(){
                        if (from >= 0) {
                            DragDropHandler.moveObject(scope.droppable, from, to, list);
                            scope.ngUpdate({
                                from: from,
                                to: to,
                                list: list
                            });
                        } else {

                            scope.ngCreate({
                                object: DragDropHandler.dragObject,
                                to: to,
                                list: list
                            });

                            ui.item.remove();
                        }
                    });
                }
            });

            element.on("sortremove", function(event, ui) {
              //console.log(element);
              //console.log('a sort item is removed from a connected list and is dragged into another.');
            });
            element.on("sortreceive", function(event, ui) {
              //console.log(element);
              //console.log('item from a connected sortable list has been dropped.');
            });
        }
    };
}]);

Controller functions:

$scope.updateObjects = function(from, to, list) {
    var itemIds = _.pluck($scope.items[list], 'id');
    //console.log(itemIds);
};

$scope.createObject = function(object, to, list) {
    console.log(list);
    console.log($scope.items[list]);
    var newItem = angular.copy(object);
    newItem.id = Math.ceil(Math.random() * 1000);
    DragDropHandler.addObject(newItem, $scope.items[list], to);
};

$scope.deleteItem = function(itemId) {
    $scope.items = _.reject($scope.items, function(item) {
        return item.id == itemId; 
    });
};

And the view:

<h3>sortable</h3>
<ul
    droppable='items.list1'
    ng-update='updateObjects(from, to)'
    ng-create='createObject(object, to, list)'
    id="list1" class="sortable">
    <li
        class="ui-state-default"
        ng-repeat="item in items.list1 track by item.id">
        {{ $index }}: {{ item.id }} - {{ item.name }}
        <button
            ng-click='deleteItem(item.id)'
            class='btn btn-xs pull-right btn-danger'>X</button>
    </li>
</ul>
<h3>sortable</h3>
<ul
    droppable='items.list2'
    ng-update='updateObjects(from, to)'
    ng-create='createObject(object, to, list)'
    id="list2" class="sortable">
    <li
        class="ui-state-default"
        ng-repeat="item in items.list2 track by item.id">
        {{ $index }}: {{ item.id }} - {{ item.name }}
        <button
            ng-click='deleteItem(item.id)'
            class='btn btn-xs pull-right btn-danger'>X</button>
    </li>
</ul>

Working example on Plunker.

Any help would be greatly appreciated.

Upvotes: 4

Views: 2653

Answers (1)

orszaczky
orszaczky

Reputation: 15645

Ok, I finally solved it. I created a fork on GitHub, and PLUNKER UPDATED!

Explanation: The key was to check if ui.sender was set for the dragged object by another sortable list. If it was set, the object was coming from another sortable, otherwise not.

The extended droppable (sortable) directive:

.directive('droppable', ['DragDropHandler', function(DragDropHandler) {
    return {
        scope: {
            droppable: '=',
            ngMove: '&',
            ngCreate: '&'
        },
        link: function(scope, element, attrs){
            element.sortable({
              connectWith: ['.draggable','.sortable'],
            });
            element.disableSelection();
            var list = element.attr('id');
            element.on("sortupdate", function(event, ui) {

                var from = angular.element(ui.item).scope().$index;
                var to = element.children().index(ui.item);

                if (to >= 0 ){
                  //item is moved to this list
                    scope.$apply(function(){
                        if (from >= 0) {
                          //item is coming from a sortable

                          if (!ui.sender) {
                            //item is coming from this sortable
                              DragDropHandler.moveObject(scope.droppable, from, to);

                          } else {
                            //item is coming from another sortable
                            scope.ngMove({
                                from: from,
                                to: to,
                                fromList: ui.sender.attr('id'),
                                toList: list
                            });
                            ui.item.remove();
                          }
                        } else {
                          //item is coming from a draggable
                            scope.ngCreate({
                                object: DragDropHandler.dragObject,
                                to: to,
                                list: list
                            });

                            ui.item.remove();
                        }
                    });
                }
            });

        }
    };
}]);

In the controller I added a moveObject function that is responsible for moving the object from the old array to the new one:

$scope.moveObject = function(from, to, fromList, toList) {
    var item = $scope.items[fromList][from];
    DragDropHandler.addObject(item, $scope.items[toList], to);
    $scope.items[fromList].splice(0, 1);
}

And the deleteItem function had to be updated to handle multiple arrays of the multiple sortables (just to keep the demo fully working):

$scope.deleteItem = function(itemId) {
  for (var list in $scope.items) {
    if ($scope.items.hasOwnProperty(list)) {
      $scope.items[list] = _.reject($scope.items[list], function(item) {
        return item.id == itemId; 
      });
    }
  }
};

And the view:

<h3>sortable</h3>
<ul
    droppable='items.list2'
    ng-move='moveObject(from, to, fromList, toList)'
    ng-create='createObject(object, to, list)'
    id="list2" class="sortable">
    <li
        class="ui-state-default"
        ng-repeat="item in items.list2 track by item.id">
        {{ $index }}: {{ item.id }} - {{ item.name }}
        <button
            ng-click='deleteItem(item.id)'
            class='btn btn-xs pull-right btn-danger'>X</button>
    </li>
</ul>

I removed ngUpdate, as far as I could tell, it didn't have any actual functionality.

Upvotes: 3

Related Questions