Search code examples
javascriptangularjspackery

Packery layout with draggable divs does not respect the item order


I'm using Packery & Draggabilly in an Angular.js application to create a dashboard-like setup where a user can dynamically create windows that are added to the Packery layout. The problem I'm having, I think, is related to fitting Packery and Angular.js properly together.

Overall, the layouting works okay'ish: the div's are added properly, their order can be changed by dragging on the canvas, and they can be removed. The problem is that if the div order is changed and a new div is added to the layout, the divs are relocated to the old positions. For example:

  1. Add div A, B and C. A is now the leftmost on the canvas.
  2. Drag A behind C, so the new order is B, C, A.
  3. Add new div, D.
  4. For some reason, the new order is now A, B, C, D for the divs. What I expected was that the dragged position of A would be respected after the new div in the layout.

Directive for Packery:

win.controller('PackeryController', ['$scope', '$rootScope', '$timeout',
  function($scope, $rootScope, $timeout) {
    console.log("packery controller");
    $scope.$onRootScope('packery.add', function(event, selection, type) {
      $scope.add(selection, type);
    });

    $scope.windows = [];
    $scope.windowRunningNumber = 0;

    // remove from grid
    $scope.remove = function(number, element) {
      $scope.windows = _.reject($scope.windows, function(obj) {
        return obj.number === number;
      });
      $scope.packery.remove(element[0]);
      $scope.packery.layout();
    };

    // adds window to grid
    $scope.add = function(selection, type) {
      $scope.windows.push({
        number: (++$scope.windowRunningNumber),
        type: type,
        variables: selection
      });
    };
  }
]);
win.directive('packery', [

  function() {
    return {
      restrict: 'C',
      templateUrl: 'vis/windowing/packery.tpl.html',
      replace: true,
      controller: 'PackeryController',
      scope: {},
      link: function(scope, elm, attrs, controller) {
        scope.packery = new Packery(elm[0], {
          isResizeBound: true,
          // see https://github.com/metafizzy/packery/issues/7
          rowHeight: 410,
          itemSelector: '.window',
          gutter: '.gutter-sizer',
          columnWidth: 500
        });
      }
    };
  }
]);

Directive for individual div/window on the packery canvas:

win.directive('window', ['$compile', '$injector',
 function($compile, $injector) {
  return {
   scope: false,
   require: '^packery',
   restrict: 'C',
   templateUrl: 'vis/windowing/window.tpl.html',
   replace: true,
   link: function($scope, ele, iAttrs, controller) {
    $scope.element = ele;
    var draggable = new Draggabilly($scope.element[0], {
     handle: '.handle'
    });
    $scope.$parent.packery.bindDraggabillyEvents(draggable);

    $scope.packery.reloadItems();
    $scope.packery.layout();

    // catch dragging operations
    $scope.element.find('.handle').mousedown(function() {
     $(window).mousemove(function() {
      $scope.isDragging = true;
      $(window).unbind("mousemove");
     });
    })
     .mouseup(function() {
      var wasDragging = $scope.isDragging;
      $scope.isDragging = false;
      $(window).unbind("mousemove");
      if (!wasDragging) { //was clicking
       return;
      }
      console.log("was dragging");
      setTimeout(function() {
       $scope.packery.reloadItems();
       // $scope.packery.layout();
      }, 900);
     });

    // catch window destroys
    $scope.$on('$destroy', function() {
     $scope.packery.reloadItems();
     // $scope.packery.layout();
    });
   }
  };
 }
]);

I suspect that either

a) I'm not catching all necessary DOM operations (are there others than add, remove and change order by dragging?) which causes the Packery instance to go off-sync, or

b) other Packery methods need to called on these DOM operations.

Any ideas? What am I doing wrong?


Solution

  • This was resolved with the help of the developer of Packery in Github.

    See this Codepen for the gist of it. Basically you want to avoid .reloadItems(), and there is no need to layout after drag events (and those could be handled better anyways, see Events in Draggabilly documentation).