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:
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?
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).