From 2ea4f9e73671cd72fa6b4f7b69e2ea265a9fe570 Mon Sep 17 00:00:00 2001 From: jespinosa Date: Mon, 28 Sep 2015 19:07:56 -0300 Subject: [PATCH 01/16] Add collbacks to resize and initialize --- src/angular-gridster.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/angular-gridster.js b/src/angular-gridster.js index 174f49c5..23d0db19 100755 --- a/src/angular-gridster.js +++ b/src/angular-gridster.js @@ -1987,6 +1987,11 @@ controller: 'GridsterItemCtrl', controllerAs: 'gridsterItem', require: ['^gridster', 'gridsterItem'], + bindToController:{ + gridsterItemInitialized:'&', + gridsterItemResized:'&' + }, + link: function(scope, $el, attrs, controllers) { var optionsKey = attrs.gridsterItem, options; @@ -2100,6 +2105,7 @@ if (changedX || changedY) { item.gridster.moveOverlappingItems(item); gridster.layoutChanged(); + scope.gridsterItem.gridsterItemResized({item}); scope.$broadcast('gridster-item-resized', item); } } @@ -2152,6 +2158,7 @@ $el.on(whichTransitionEvent(), debouncedTransitionEndPublisher); + scope.gridsterItem.gridsterItemInitialized({item}); scope.$broadcast('gridster-item-initialized', item); return scope.$on('$destroy', function() { From f9d65f3b64ec214e0c39e228b4009ec0d394f1b3 Mon Sep 17 00:00:00 2001 From: jespinosa Date: Mon, 28 Sep 2015 19:11:02 -0300 Subject: [PATCH 02/16] change repository url --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 9663fa50..ccb1b896 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,8 @@ "version": "0.13.5", "description": "This directive gives you gridster behavior", "license": "MIT", - "homepage": "http://manifestwebdesign.github.io/angular-gridster", - "authors": "https://github.com/ManifestWebDesign/angular-gridster/graphs/contributors", + "homepage": "http://jocluz.github.io/proteus-angular-gridster", + "authors": "https://github.com/jocluz/proteus-angular-gridster/graphs/contributors", "devDependencies": { "karma-chrome-launcher": "^0.1.3", "karma-script-launcher": "^0.1.0", @@ -38,7 +38,7 @@ }, "repository": { "type": "git", - "url": "git@github.com:ManifestWebDesign/angular-gridster.git" + "url": "git@github.com:jocluz/proteus-angular-gridster.git" }, "main": "dist/angular-gridster.min.js" } From e79fad5caafd2f5fefdb522d60e892187788910a Mon Sep 17 00:00:00 2001 From: jocluz Date: Tue, 29 Sep 2015 09:03:29 -0300 Subject: [PATCH 03/16] update name and version --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ccb1b896..39675b01 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "angular-gridster", - "version": "0.13.5", + "name": "proteus-angular-gridster", + "version": "0.13.6", "description": "This directive gives you gridster behavior", "license": "MIT", "homepage": "http://jocluz.github.io/proteus-angular-gridster", From a3f567c1cf2f8bc272446d0328ab7907f7414749 Mon Sep 17 00:00:00 2001 From: jocluz Date: Fri, 2 Oct 2015 16:52:21 -0300 Subject: [PATCH 04/16] Added events New events fired when: - item moves down - item floats up - item resized stop - item drag stop --- src/angular-gridster.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/angular-gridster.js b/src/angular-gridster.js index 23d0db19..488eaeff 100755 --- a/src/angular-gridster.js +++ b/src/angular-gridster.js @@ -455,6 +455,7 @@ this.moveOverlappingItems(item, ignoreItems); } this.putItem(item, item.row, item.col, ignoreItems); + item.gridsterItemMovedDown({item}); }; /** @@ -506,6 +507,8 @@ if (bestRow !== null) { this.putItem(item, bestRow, bestColumn); } + + item.gridsterItemFloatUp({item}); }; /** @@ -618,6 +621,7 @@ restrict: 'EAC', controller: 'GridsterCtrl', controllerAs: 'gridster', + compile: function($tplElem) { $tplElem.prepend('
'); @@ -1560,6 +1564,8 @@ gridster.draggable.stop(event, $el, itemOptions); } }); + + item.gridsterItemDragStop({item}) } var enabled = null; @@ -1848,6 +1854,10 @@ } var isChanged = item.row !== oldRow || item.col !== oldCol || item.sizeX !== oldSizeX || item.sizeY !== oldSizeY; + if (isChanged){ + item.gridsterItemResizedStop({item}); + } + if (hasCallback || isChanged) { scope.$apply(function() { if (hasCallback) { @@ -1989,7 +1999,11 @@ require: ['^gridster', 'gridsterItem'], bindToController:{ gridsterItemInitialized:'&', - gridsterItemResized:'&' + gridsterItemResized:'&', + gridsterItemDragStop : '&', + gridsterItemResizedStop: '&', + gridsterItemMovedDown: '&', + gridsterItemFloatUp: '&' }, link: function(scope, $el, attrs, controllers) { From 6fd6044c2afa1f237737bda1d71c1f2e08f8e1d6 Mon Sep 17 00:00:00 2001 From: jocluz Date: Tue, 6 Oct 2015 10:00:42 -0300 Subject: [PATCH 05/16] Changed events. Added resizing callback. Changed resized stop callback call. --- src/angular-gridster.js | 3135 ++++++++++++++++++++------------------- 1 file changed, 1570 insertions(+), 1565 deletions(-) diff --git a/src/angular-gridster.js b/src/angular-gridster.js index 488eaeff..109791e7 100755 --- a/src/angular-gridster.js +++ b/src/angular-gridster.js @@ -55,988 +55,988 @@ }) .controller('GridsterCtrl', ['gridsterConfig', '$timeout', - function(gridsterConfig, $timeout) { + function(gridsterConfig, $timeout) { - var gridster = this; + var gridster = this; - /** - * Create options from gridsterConfig constant - */ - angular.extend(this, gridsterConfig); + /** + * Create options from gridsterConfig constant + */ + angular.extend(this, gridsterConfig); - this.resizable = angular.extend({}, gridsterConfig.resizable || {}); - this.draggable = angular.extend({}, gridsterConfig.draggable || {}); + this.resizable = angular.extend({}, gridsterConfig.resizable || {}); + this.draggable = angular.extend({}, gridsterConfig.draggable || {}); - var flag = false; - this.layoutChanged = function() { - if (flag) { - return; - } - flag = true; - $timeout(function() { - flag = false; - if (gridster.loaded) { - gridster.floatItemsUp(); - } - gridster.updateHeight(gridster.movingItem ? gridster.movingItem.sizeY : 0); - }, 30); - }; + var flag = false; + this.layoutChanged = function() { + if (flag) { + return; + } + flag = true; + $timeout(function() { + flag = false; + if (gridster.loaded) { + gridster.floatItemsUp(); + } + gridster.updateHeight(gridster.movingItem ? gridster.movingItem.sizeY : 0); + }, 30); + }; - /** - * A positional array of the items in the grid - */ - this.grid = []; + /** + * A positional array of the items in the grid + */ + this.grid = []; - /** - * Clean up after yourself - */ - this.destroy = function() { - // empty the grid to cut back on the possibility - // of circular references - if (this.grid) { - this.grid = []; - } - this.$element = null; - }; + /** + * Clean up after yourself + */ + this.destroy = function() { + // empty the grid to cut back on the possibility + // of circular references + if (this.grid) { + this.grid = []; + } + this.$element = null; + }; - /** - * Overrides default options - * - * @param {Object} options The options to override - */ - this.setOptions = function(options) { - if (!options) { - return; - } + /** + * Overrides default options + * + * @param {Object} options The options to override + */ + this.setOptions = function(options) { + if (!options) { + return; + } - options = angular.extend({}, options); + options = angular.extend({}, options); - // all this to avoid using jQuery... - if (options.draggable) { - angular.extend(this.draggable, options.draggable); - delete(options.draggable); - } - if (options.resizable) { - angular.extend(this.resizable, options.resizable); - delete(options.resizable); - } + // all this to avoid using jQuery... + if (options.draggable) { + angular.extend(this.draggable, options.draggable); + delete(options.draggable); + } + if (options.resizable) { + angular.extend(this.resizable, options.resizable); + delete(options.resizable); + } - angular.extend(this, options); + angular.extend(this, options); - if (!this.margins || this.margins.length !== 2) { - this.margins = [0, 0]; - } else { - for (var x = 0, l = this.margins.length; x < l; ++x) { - this.margins[x] = parseInt(this.margins[x], 10); - if (isNaN(this.margins[x])) { - this.margins[x] = 0; - } + if (!this.margins || this.margins.length !== 2) { + this.margins = [0, 0]; + } else { + for (var x = 0, l = this.margins.length; x < l; ++x) { + this.margins[x] = parseInt(this.margins[x], 10); + if (isNaN(this.margins[x])) { + this.margins[x] = 0; } } - }; + } + }; - /** - * Check if item can occupy a specified position in the grid - * - * @param {Object} item The item in question - * @param {Number} row The row index - * @param {Number} column The column index - * @returns {Boolean} True if if item fits - */ - this.canItemOccupy = function(item, row, column) { - return row > -1 && column > -1 && item.sizeX + column <= this.columns && item.sizeY + row <= this.maxRows; - }; + /** + * Check if item can occupy a specified position in the grid + * + * @param {Object} item The item in question + * @param {Number} row The row index + * @param {Number} column The column index + * @returns {Boolean} True if if item fits + */ + this.canItemOccupy = function(item, row, column) { + return row > -1 && column > -1 && item.sizeX + column <= this.columns && item.sizeY + row <= this.maxRows; + }; - /** - * Set the item in the first suitable position - * - * @param {Object} item The item to insert - */ - this.autoSetItemPosition = function(item) { - // walk through each row and column looking for a place it will fit - for (var rowIndex = 0; rowIndex < this.maxRows; ++rowIndex) { - for (var colIndex = 0; colIndex < this.columns; ++colIndex) { - // only insert if position is not already taken and it can fit - var items = this.getItems(rowIndex, colIndex, item.sizeX, item.sizeY, item); - if (items.length === 0 && this.canItemOccupy(item, rowIndex, colIndex)) { - this.putItem(item, rowIndex, colIndex); - return; - } + /** + * Set the item in the first suitable position + * + * @param {Object} item The item to insert + */ + this.autoSetItemPosition = function(item) { + // walk through each row and column looking for a place it will fit + for (var rowIndex = 0; rowIndex < this.maxRows; ++rowIndex) { + for (var colIndex = 0; colIndex < this.columns; ++colIndex) { + // only insert if position is not already taken and it can fit + var items = this.getItems(rowIndex, colIndex, item.sizeX, item.sizeY, item); + if (items.length === 0 && this.canItemOccupy(item, rowIndex, colIndex)) { + this.putItem(item, rowIndex, colIndex); + return; } } - throw new Error('Unable to place item!'); - }; + } + throw new Error('Unable to place item!'); + }; - /** - * Gets items at a specific coordinate - * - * @param {Number} row - * @param {Number} column - * @param {Number} sizeX - * @param {Number} sizeY - * @param {Array} excludeItems An array of items to exclude from selection - * @returns {Array} Items that match the criteria - */ - this.getItems = function(row, column, sizeX, sizeY, excludeItems) { - var items = []; - if (!sizeX || !sizeY) { - sizeX = sizeY = 1; - } - if (excludeItems && !(excludeItems instanceof Array)) { - excludeItems = [excludeItems]; - } - for (var h = 0; h < sizeY; ++h) { - for (var w = 0; w < sizeX; ++w) { - var item = this.getItem(row + h, column + w, excludeItems); - if (item && (!excludeItems || excludeItems.indexOf(item) === -1) && items.indexOf(item) === -1) { - items.push(item); - } + /** + * Gets items at a specific coordinate + * + * @param {Number} row + * @param {Number} column + * @param {Number} sizeX + * @param {Number} sizeY + * @param {Array} excludeItems An array of items to exclude from selection + * @returns {Array} Items that match the criteria + */ + this.getItems = function(row, column, sizeX, sizeY, excludeItems) { + var items = []; + if (!sizeX || !sizeY) { + sizeX = sizeY = 1; + } + if (excludeItems && !(excludeItems instanceof Array)) { + excludeItems = [excludeItems]; + } + for (var h = 0; h < sizeY; ++h) { + for (var w = 0; w < sizeX; ++w) { + var item = this.getItem(row + h, column + w, excludeItems); + if (item && (!excludeItems || excludeItems.indexOf(item) === -1) && items.indexOf(item) === -1) { + items.push(item); } } - return items; - }; - - /** - * @param {Array} items - * @returns {Object} An item that represents the bounding box of the items - */ - this.getBoundingBox = function(items) { - - if (items.length === 0) { - return null; - } - if (items.length === 1) { - return { - row: items[0].row, - col: items[0].col, - sizeY: items[0].sizeY, - sizeX: items[0].sizeX - }; - } - - var maxRow = 0; - var maxCol = 0; - var minRow = 9999; - var minCol = 9999; + } + return items; + }; - for (var i = 0, l = items.length; i < l; ++i) { - var item = items[i]; - minRow = Math.min(item.row, minRow); - minCol = Math.min(item.col, minCol); - maxRow = Math.max(item.row + item.sizeY, maxRow); - maxCol = Math.max(item.col + item.sizeX, maxCol); - } + /** + * @param {Array} items + * @returns {Object} An item that represents the bounding box of the items + */ + this.getBoundingBox = function(items) { + if (items.length === 0) { + return null; + } + if (items.length === 1) { return { - row: minRow, - col: minCol, - sizeY: maxRow - minRow, - sizeX: maxCol - minCol + row: items[0].row, + col: items[0].col, + sizeY: items[0].sizeY, + sizeX: items[0].sizeX }; + } + + var maxRow = 0; + var maxCol = 0; + var minRow = 9999; + var minCol = 9999; + + for (var i = 0, l = items.length; i < l; ++i) { + var item = items[i]; + minRow = Math.min(item.row, minRow); + minCol = Math.min(item.col, minCol); + maxRow = Math.max(item.row + item.sizeY, maxRow); + maxCol = Math.max(item.col + item.sizeX, maxCol); + } + + return { + row: minRow, + col: minCol, + sizeY: maxRow - minRow, + sizeX: maxCol - minCol }; + }; - /** - * Removes an item from the grid - * - * @param {Object} item - */ - this.removeItem = function(item) { - for (var rowIndex = 0, l = this.grid.length; rowIndex < l; ++rowIndex) { - var columns = this.grid[rowIndex]; - if (!columns) { - continue; - } - var index = columns.indexOf(item); - if (index !== -1) { - columns[index] = null; - break; - } + /** + * Removes an item from the grid + * + * @param {Object} item + */ + this.removeItem = function(item) { + for (var rowIndex = 0, l = this.grid.length; rowIndex < l; ++rowIndex) { + var columns = this.grid[rowIndex]; + if (!columns) { + continue; + } + var index = columns.indexOf(item); + if (index !== -1) { + columns[index] = null; + break; } - this.layoutChanged(); - }; + } + this.layoutChanged(); + }; - /** - * Returns the item at a specified coordinate - * - * @param {Number} row - * @param {Number} column - * @param {Array} excludeItems Items to exclude from selection - * @returns {Object} The matched item or null - */ - this.getItem = function(row, column, excludeItems) { - if (excludeItems && !(excludeItems instanceof Array)) { - excludeItems = [excludeItems]; - } - var sizeY = 1; - while (row > -1) { - var sizeX = 1, - col = column; - while (col > -1) { - var items = this.grid[row]; - if (items) { - var item = items[col]; - if (item && (!excludeItems || excludeItems.indexOf(item) === -1) && item.sizeX >= sizeX && item.sizeY >= sizeY) { - return item; - } + /** + * Returns the item at a specified coordinate + * + * @param {Number} row + * @param {Number} column + * @param {Array} excludeItems Items to exclude from selection + * @returns {Object} The matched item or null + */ + this.getItem = function(row, column, excludeItems) { + if (excludeItems && !(excludeItems instanceof Array)) { + excludeItems = [excludeItems]; + } + var sizeY = 1; + while (row > -1) { + var sizeX = 1, + col = column; + while (col > -1) { + var items = this.grid[row]; + if (items) { + var item = items[col]; + if (item && (!excludeItems || excludeItems.indexOf(item) === -1) && item.sizeX >= sizeX && item.sizeY >= sizeY) { + return item; } - ++sizeX; - --col; } - --row; - ++sizeY; + ++sizeX; + --col; } - return null; - }; + --row; + ++sizeY; + } + return null; + }; - /** - * Insert an array of items into the grid - * - * @param {Array} items An array of items to insert - */ - this.putItems = function(items) { - for (var i = 0, l = items.length; i < l; ++i) { - this.putItem(items[i]); - } - }; + /** + * Insert an array of items into the grid + * + * @param {Array} items An array of items to insert + */ + this.putItems = function(items) { + for (var i = 0, l = items.length; i < l; ++i) { + this.putItem(items[i]); + } + }; - /** - * Insert a single item into the grid - * - * @param {Object} item The item to insert - * @param {Number} row (Optional) Specifies the items row index - * @param {Number} column (Optional) Specifies the items column index - * @param {Array} ignoreItems - */ - this.putItem = function(item, row, column, ignoreItems) { - // auto place item if no row specified + /** + * Insert a single item into the grid + * + * @param {Object} item The item to insert + * @param {Number} row (Optional) Specifies the items row index + * @param {Number} column (Optional) Specifies the items column index + * @param {Array} ignoreItems + */ + this.putItem = function(item, row, column, ignoreItems) { + // auto place item if no row specified + if (typeof row === 'undefined' || row === null) { + row = item.row; + column = item.col; if (typeof row === 'undefined' || row === null) { - row = item.row; - column = item.col; - if (typeof row === 'undefined' || row === null) { - this.autoSetItemPosition(item); - return; - } + this.autoSetItemPosition(item); + return; } + } - // keep item within allowed bounds - if (!this.canItemOccupy(item, row, column)) { - column = Math.min(this.columns - item.sizeX, Math.max(0, column)); - row = Math.min(this.maxRows - item.sizeY, Math.max(0, row)); - } + // keep item within allowed bounds + if (!this.canItemOccupy(item, row, column)) { + column = Math.min(this.columns - item.sizeX, Math.max(0, column)); + row = Math.min(this.maxRows - item.sizeY, Math.max(0, row)); + } - // check if item is already in grid - if (item.oldRow !== null && typeof item.oldRow !== 'undefined') { - var samePosition = item.oldRow === row && item.oldColumn === column; - var inGrid = this.grid[row] && this.grid[row][column] === item; - if (samePosition && inGrid) { - item.row = row; - item.col = column; - return; - } else { - // remove from old position - var oldRow = this.grid[item.oldRow]; - if (oldRow && oldRow[item.oldColumn] === item) { - delete oldRow[item.oldColumn]; - } + // check if item is already in grid + if (item.oldRow !== null && typeof item.oldRow !== 'undefined') { + var samePosition = item.oldRow === row && item.oldColumn === column; + var inGrid = this.grid[row] && this.grid[row][column] === item; + if (samePosition && inGrid) { + item.row = row; + item.col = column; + return; + } else { + // remove from old position + var oldRow = this.grid[item.oldRow]; + if (oldRow && oldRow[item.oldColumn] === item) { + delete oldRow[item.oldColumn]; } } + } - item.oldRow = item.row = row; - item.oldColumn = item.col = column; + item.oldRow = item.row = row; + item.oldColumn = item.col = column; - this.moveOverlappingItems(item, ignoreItems); + this.moveOverlappingItems(item, ignoreItems); - if (!this.grid[row]) { - this.grid[row] = []; - } - this.grid[row][column] = item; + if (!this.grid[row]) { + this.grid[row] = []; + } + this.grid[row][column] = item; - if (this.movingItem === item) { - this.floatItemUp(item); - } - this.layoutChanged(); - }; + if (this.movingItem === item) { + this.floatItemUp(item); + } + this.layoutChanged(); + }; - /** - * Trade row and column if item1 with item2 - * - * @param {Object} item1 - * @param {Object} item2 - */ - this.swapItems = function(item1, item2) { - this.grid[item1.row][item1.col] = item2; - this.grid[item2.row][item2.col] = item1; - - var item1Row = item1.row; - var item1Col = item1.col; - item1.row = item2.row; - item1.col = item2.col; - item2.row = item1Row; - item2.col = item1Col; - }; + /** + * Trade row and column if item1 with item2 + * + * @param {Object} item1 + * @param {Object} item2 + */ + this.swapItems = function(item1, item2) { + this.grid[item1.row][item1.col] = item2; + this.grid[item2.row][item2.col] = item1; + + var item1Row = item1.row; + var item1Col = item1.col; + item1.row = item2.row; + item1.col = item2.col; + item2.row = item1Row; + item2.col = item1Col; + }; - /** - * Prevents items from being overlapped - * - * @param {Object} item The item that should remain - * @param {Array} ignoreItems - */ - this.moveOverlappingItems = function(item, ignoreItems) { - // don't move item, so ignore it - if (!ignoreItems) { - ignoreItems = [item]; - } else if (ignoreItems.indexOf(item) === -1) { - ignoreItems = ignoreItems.slice(0); - ignoreItems.push(item); - } - - // get the items in the space occupied by the item's coordinates - var overlappingItems = this.getItems( - item.row, - item.col, - item.sizeX, - item.sizeY, - ignoreItems - ); - this.moveItemsDown(overlappingItems, item.row + item.sizeY, ignoreItems); - }; + /** + * Prevents items from being overlapped + * + * @param {Object} item The item that should remain + * @param {Array} ignoreItems + */ + this.moveOverlappingItems = function(item, ignoreItems) { + // don't move item, so ignore it + if (!ignoreItems) { + ignoreItems = [item]; + } else if (ignoreItems.indexOf(item) === -1) { + ignoreItems = ignoreItems.slice(0); + ignoreItems.push(item); + } - /** - * Moves an array of items to a specified row - * - * @param {Array} items The items to move - * @param {Number} newRow The target row - * @param {Array} ignoreItems - */ - this.moveItemsDown = function(items, newRow, ignoreItems) { - if (!items || items.length === 0) { - return; - } - items.sort(function(a, b) { - return a.row - b.row; - }); + // get the items in the space occupied by the item's coordinates + var overlappingItems = this.getItems( + item.row, + item.col, + item.sizeX, + item.sizeY, + ignoreItems + ); + this.moveItemsDown(overlappingItems, item.row + item.sizeY, ignoreItems); + }; - ignoreItems = ignoreItems ? ignoreItems.slice(0) : []; - var topRows = {}, - item, i, l; + /** + * Moves an array of items to a specified row + * + * @param {Array} items The items to move + * @param {Number} newRow The target row + * @param {Array} ignoreItems + */ + this.moveItemsDown = function(items, newRow, ignoreItems) { + if (!items || items.length === 0) { + return; + } + items.sort(function(a, b) { + return a.row - b.row; + }); - // calculate the top rows in each column - for (i = 0, l = items.length; i < l; ++i) { - item = items[i]; - var topRow = topRows[item.col]; - if (typeof topRow === 'undefined' || item.row < topRow) { - topRows[item.col] = item.row; - } - } + ignoreItems = ignoreItems ? ignoreItems.slice(0) : []; + var topRows = {}, + item, i, l; - // move each item down from the top row in its column to the row - for (i = 0, l = items.length; i < l; ++i) { - item = items[i]; - var rowsToMove = newRow - topRows[item.col]; - this.moveItemDown(item, item.row + rowsToMove, ignoreItems); - ignoreItems.push(item); + // calculate the top rows in each column + for (i = 0, l = items.length; i < l; ++i) { + item = items[i]; + var topRow = topRows[item.col]; + if (typeof topRow === 'undefined' || item.row < topRow) { + topRows[item.col] = item.row; } - }; + } - /** - * Moves an item down to a specified row - * - * @param {Object} item The item to move - * @param {Number} newRow The target row - * @param {Array} ignoreItems - */ - this.moveItemDown = function(item, newRow, ignoreItems) { - if (item.row >= newRow) { - return; - } - while (item.row < newRow) { - ++item.row; - this.moveOverlappingItems(item, ignoreItems); - } - this.putItem(item, item.row, item.col, ignoreItems); - item.gridsterItemMovedDown({item}); - }; + // move each item down from the top row in its column to the row + for (i = 0, l = items.length; i < l; ++i) { + item = items[i]; + var rowsToMove = newRow - topRows[item.col]; + this.moveItemDown(item, item.row + rowsToMove, ignoreItems); + ignoreItems.push(item); + } + }; - /** - * Moves all items up as much as possible - */ - this.floatItemsUp = function() { - if (this.floating === false) { - return; - } - for (var rowIndex = 0, l = this.grid.length; rowIndex < l; ++rowIndex) { - var columns = this.grid[rowIndex]; - if (!columns) { - continue; - } - for (var colIndex = 0, len = columns.length; colIndex < len; ++colIndex) { - var item = columns[colIndex]; - if (item) { - this.floatItemUp(item); - } - } - } - }; + /** + * Moves an item down to a specified row + * + * @param {Object} item The item to move + * @param {Number} newRow The target row + * @param {Array} ignoreItems + */ + this.moveItemDown = function(item, newRow, ignoreItems) { + if (item.row >= newRow) { + return; + } + while (item.row < newRow) { + ++item.row; + this.moveOverlappingItems(item, ignoreItems); + } + this.putItem(item, item.row, item.col, ignoreItems); + item.gridsterItemMovedDown({item}); + }; - /** - * Float an item up to the most suitable row - * - * @param {Object} item The item to move - */ - this.floatItemUp = function(item) { - if (this.floating === false) { - return; + /** + * Moves all items up as much as possible + */ + this.floatItemsUp = function() { + if (this.floating === false) { + return; + } + for (var rowIndex = 0, l = this.grid.length; rowIndex < l; ++rowIndex) { + var columns = this.grid[rowIndex]; + if (!columns) { + continue; } - var colIndex = item.col, - sizeY = item.sizeY, - sizeX = item.sizeX, - bestRow = null, - bestColumn = null, - rowIndex = item.row - 1; - - while (rowIndex > -1) { - var items = this.getItems(rowIndex, colIndex, sizeX, sizeY, item); - if (items.length !== 0) { - break; + for (var colIndex = 0, len = columns.length; colIndex < len; ++colIndex) { + var item = columns[colIndex]; + if (item) { + this.floatItemUp(item); } - bestRow = rowIndex; - bestColumn = colIndex; - --rowIndex; - } - if (bestRow !== null) { - this.putItem(item, bestRow, bestColumn); } + } + }; - item.gridsterItemFloatUp({item}); - }; + /** + * Float an item up to the most suitable row + * + * @param {Object} item The item to move + */ + this.floatItemUp = function(item) { + if (this.floating === false) { + return; + } + var colIndex = item.col, + sizeY = item.sizeY, + sizeX = item.sizeX, + bestRow = null, + bestColumn = null, + rowIndex = item.row - 1; + + while (rowIndex > -1) { + var items = this.getItems(rowIndex, colIndex, sizeX, sizeY, item); + if (items.length !== 0) { + break; + } + bestRow = rowIndex; + bestColumn = colIndex; + --rowIndex; + } + if (bestRow !== null) { + this.putItem(item, bestRow, bestColumn); + } - /** - * Update gridsters height - * - * @param {Number} plus (Optional) Additional height to add - */ - this.updateHeight = function(plus) { - var maxHeight = this.minRows; - plus = plus || 0; - for (var rowIndex = this.grid.length; rowIndex >= 0; --rowIndex) { - var columns = this.grid[rowIndex]; - if (!columns) { - continue; - } - for (var colIndex = 0, len = columns.length; colIndex < len; ++colIndex) { - if (columns[colIndex]) { - maxHeight = Math.max(maxHeight, rowIndex + plus + columns[colIndex].sizeY); - } + item.gridsterItemFloatUp({item}); + }; + + /** + * Update gridsters height + * + * @param {Number} plus (Optional) Additional height to add + */ + this.updateHeight = function(plus) { + var maxHeight = this.minRows; + plus = plus || 0; + for (var rowIndex = this.grid.length; rowIndex >= 0; --rowIndex) { + var columns = this.grid[rowIndex]; + if (!columns) { + continue; + } + for (var colIndex = 0, len = columns.length; colIndex < len; ++colIndex) { + if (columns[colIndex]) { + maxHeight = Math.max(maxHeight, rowIndex + plus + columns[colIndex].sizeY); } } - this.gridHeight = this.maxRows - maxHeight > 0 ? Math.min(this.maxRows, maxHeight) : Math.max(this.maxRows, maxHeight); - }; + } + this.gridHeight = this.maxRows - maxHeight > 0 ? Math.min(this.maxRows, maxHeight) : Math.max(this.maxRows, maxHeight); + }; - /** - * Returns the number of rows that will fit in given amount of pixels - * - * @param {Number} pixels - * @param {Boolean} ceilOrFloor (Optional) Determines rounding method - */ - this.pixelsToRows = function(pixels, ceilOrFloor) { - if (!this.outerMargin) { - pixels += this.margins[0] / 2; - } + /** + * Returns the number of rows that will fit in given amount of pixels + * + * @param {Number} pixels + * @param {Boolean} ceilOrFloor (Optional) Determines rounding method + */ + this.pixelsToRows = function(pixels, ceilOrFloor) { + if (!this.outerMargin) { + pixels += this.margins[0] / 2; + } - if (ceilOrFloor === true) { - return Math.ceil(pixels / this.curRowHeight); - } else if (ceilOrFloor === false) { - return Math.floor(pixels / this.curRowHeight); - } + if (ceilOrFloor === true) { + return Math.ceil(pixels / this.curRowHeight); + } else if (ceilOrFloor === false) { + return Math.floor(pixels / this.curRowHeight); + } - return Math.round(pixels / this.curRowHeight); - }; + return Math.round(pixels / this.curRowHeight); + }; - /** - * Returns the number of columns that will fit in a given amount of pixels - * - * @param {Number} pixels - * @param {Boolean} ceilOrFloor (Optional) Determines rounding method - * @returns {Number} The number of columns - */ - this.pixelsToColumns = function(pixels, ceilOrFloor) { - if (!this.outerMargin) { - pixels += this.margins[1] / 2; - } + /** + * Returns the number of columns that will fit in a given amount of pixels + * + * @param {Number} pixels + * @param {Boolean} ceilOrFloor (Optional) Determines rounding method + * @returns {Number} The number of columns + */ + this.pixelsToColumns = function(pixels, ceilOrFloor) { + if (!this.outerMargin) { + pixels += this.margins[1] / 2; + } - if (ceilOrFloor === true) { - return Math.ceil(pixels / this.curColWidth); - } else if (ceilOrFloor === false) { - return Math.floor(pixels / this.curColWidth); - } + if (ceilOrFloor === true) { + return Math.ceil(pixels / this.curColWidth); + } else if (ceilOrFloor === false) { + return Math.floor(pixels / this.curColWidth); + } - return Math.round(pixels / this.curColWidth); - }; - } - ]) + return Math.round(pixels / this.curColWidth); + }; + } +]) - .directive('gridsterPreview', function() { - return { - replace: true, - scope: true, - require: '^gridster', - template: '
', - link: function(scope, $el, attrs, gridster) { - - /** - * @returns {Object} style object for preview element - */ - scope.previewStyle = function() { - if (!gridster.movingItem) { - return { - display: 'none' - }; - } +.directive('gridsterPreview', function() { + return { + replace: true, + scope: true, + require: '^gridster', + template: '
', + link: function(scope, $el, attrs, gridster) { + /** + * @returns {Object} style object for preview element + */ + scope.previewStyle = function() { + if (!gridster.movingItem) { return { - display: 'block', - height: (gridster.movingItem.sizeY * gridster.curRowHeight - gridster.margins[0]) + 'px', - width: (gridster.movingItem.sizeX * gridster.curColWidth - gridster.margins[1]) + 'px', - top: (gridster.movingItem.row * gridster.curRowHeight + (gridster.outerMargin ? gridster.margins[0] : 0)) + 'px', - left: (gridster.movingItem.col * gridster.curColWidth + (gridster.outerMargin ? gridster.margins[1] : 0)) + 'px' + display: 'none' }; + } + + return { + display: 'block', + height: (gridster.movingItem.sizeY * gridster.curRowHeight - gridster.margins[0]) + 'px', + width: (gridster.movingItem.sizeX * gridster.curColWidth - gridster.margins[1]) + 'px', + top: (gridster.movingItem.row * gridster.curRowHeight + (gridster.outerMargin ? gridster.margins[0] : 0)) + 'px', + left: (gridster.movingItem.col * gridster.curColWidth + (gridster.outerMargin ? gridster.margins[1] : 0)) + 'px' }; - } - }; - }) + }; + } + }; +}) - /** - * The gridster directive - * - * @param {Function} $timeout - * @param {Object} $window - * @param {Object} $rootScope - * @param {Function} gridsterDebounce - */ - .directive('gridster', ['$timeout', '$window', '$rootScope', 'gridsterDebounce', - function($timeout, $window, $rootScope, gridsterDebounce) { - return { - scope: true, - restrict: 'EAC', - controller: 'GridsterCtrl', - controllerAs: 'gridster', +/** +* The gridster directive +* +* @param {Function} $timeout +* @param {Object} $window +* @param {Object} $rootScope +* @param {Function} gridsterDebounce +*/ +.directive('gridster', ['$timeout', '$window', '$rootScope', 'gridsterDebounce', +function($timeout, $window, $rootScope, gridsterDebounce) { + return { + scope: true, + restrict: 'EAC', + controller: 'GridsterCtrl', + controllerAs: 'gridster', - compile: function($tplElem) { + compile: function($tplElem) { - $tplElem.prepend('
'); + $tplElem.prepend('
'); - return function(scope, $elem, attrs, gridster) { - gridster.loaded = false; + return function(scope, $elem, attrs, gridster) { + gridster.loaded = false; - gridster.$element = $elem; + gridster.$element = $elem; - scope.gridster = gridster; + scope.gridster = gridster; - $elem.addClass('gridster'); + $elem.addClass('gridster'); - var isVisible = function(ele) { - return ele.style.visibility !== 'hidden' && ele.style.display !== 'none'; - }; + var isVisible = function(ele) { + return ele.style.visibility !== 'hidden' && ele.style.display !== 'none'; + }; - function refresh(config) { - gridster.setOptions(config); + function refresh(config) { + gridster.setOptions(config); - if (!isVisible($elem[0])) { - return; - } + if (!isVisible($elem[0])) { + return; + } - // resolve "auto" & "match" values - if (gridster.width === 'auto') { - gridster.curWidth = $elem[0].offsetWidth || parseInt($elem.css('width'), 10); - } else { - gridster.curWidth = gridster.width; - } + // resolve "auto" & "match" values + if (gridster.width === 'auto') { + gridster.curWidth = $elem[0].offsetWidth || parseInt($elem.css('width'), 10); + } else { + gridster.curWidth = gridster.width; + } - if (gridster.colWidth === 'auto') { - gridster.curColWidth = (gridster.curWidth + (gridster.outerMargin ? -gridster.margins[1] : gridster.margins[1])) / gridster.columns; - } else { - gridster.curColWidth = gridster.colWidth; - } + if (gridster.colWidth === 'auto') { + gridster.curColWidth = (gridster.curWidth + (gridster.outerMargin ? -gridster.margins[1] : gridster.margins[1])) / gridster.columns; + } else { + gridster.curColWidth = gridster.colWidth; + } - gridster.curRowHeight = gridster.rowHeight; - if (typeof gridster.rowHeight === 'string') { - if (gridster.rowHeight === 'match') { - gridster.curRowHeight = Math.round(gridster.curColWidth); - } else if (gridster.rowHeight.indexOf('*') !== -1) { - gridster.curRowHeight = Math.round(gridster.curColWidth * gridster.rowHeight.replace('*', '').replace(' ', '')); - } else if (gridster.rowHeight.indexOf('/') !== -1) { - gridster.curRowHeight = Math.round(gridster.curColWidth / gridster.rowHeight.replace('/', '').replace(' ', '')); - } - } + gridster.curRowHeight = gridster.rowHeight; + if (typeof gridster.rowHeight === 'string') { + if (gridster.rowHeight === 'match') { + gridster.curRowHeight = Math.round(gridster.curColWidth); + } else if (gridster.rowHeight.indexOf('*') !== -1) { + gridster.curRowHeight = Math.round(gridster.curColWidth * gridster.rowHeight.replace('*', '').replace(' ', '')); + } else if (gridster.rowHeight.indexOf('/') !== -1) { + gridster.curRowHeight = Math.round(gridster.curColWidth / gridster.rowHeight.replace('/', '').replace(' ', '')); + } + } - gridster.isMobile = gridster.mobileModeEnabled && gridster.curWidth <= gridster.mobileBreakPoint; - - // loop through all items and reset their CSS - for (var rowIndex = 0, l = gridster.grid.length; rowIndex < l; ++rowIndex) { - var columns = gridster.grid[rowIndex]; - if (!columns) { - continue; - } - - for (var colIndex = 0, len = columns.length; colIndex < len; ++colIndex) { - if (columns[colIndex]) { - var item = columns[colIndex]; - item.setElementPosition(); - item.setElementSizeY(); - item.setElementSizeX(); - } - } - } + gridster.isMobile = gridster.mobileModeEnabled && gridster.curWidth <= gridster.mobileBreakPoint; - updateHeight(); + // loop through all items and reset their CSS + for (var rowIndex = 0, l = gridster.grid.length; rowIndex < l; ++rowIndex) { + var columns = gridster.grid[rowIndex]; + if (!columns) { + continue; } - var optionsKey = attrs.gridster; - if (optionsKey) { - scope.$parent.$watch(optionsKey, function(newConfig) { - refresh(newConfig); - }, true); - } else { - refresh({}); + for (var colIndex = 0, len = columns.length; colIndex < len; ++colIndex) { + if (columns[colIndex]) { + var item = columns[colIndex]; + item.setElementPosition(); + item.setElementSizeY(); + item.setElementSizeX(); + } } + } - scope.$watch(function() { - return gridster.loaded; - }, function() { - if (gridster.loaded) { - $elem.addClass('gridster-loaded'); - } else { - $elem.removeClass('gridster-loaded'); - } - }); + updateHeight(); + } - scope.$watch(function() { - return gridster.isMobile; - }, function() { - if (gridster.isMobile) { - $elem.addClass('gridster-mobile').removeClass('gridster-desktop'); - } else { - $elem.removeClass('gridster-mobile').addClass('gridster-desktop'); - } - $rootScope.$broadcast('gridster-mobile-changed', gridster); - }); - - scope.$watch(function() { - return gridster.draggable; - }, function() { - $rootScope.$broadcast('gridster-draggable-changed', gridster); - }, true); - - scope.$watch(function() { - return gridster.resizable; - }, function() { - $rootScope.$broadcast('gridster-resizable-changed', gridster); - }, true); - - function updateHeight() { - $elem.css('height', (gridster.gridHeight * gridster.curRowHeight) + (gridster.outerMargin ? gridster.margins[0] : -gridster.margins[0]) + 'px'); - } + var optionsKey = attrs.gridster; + if (optionsKey) { + scope.$parent.$watch(optionsKey, function(newConfig) { + refresh(newConfig); + }, true); + } else { + refresh({}); + } - scope.$watch(function() { - return gridster.gridHeight; - }, updateHeight); + scope.$watch(function() { + return gridster.loaded; + }, function() { + if (gridster.loaded) { + $elem.addClass('gridster-loaded'); + } else { + $elem.removeClass('gridster-loaded'); + } + }); - scope.$watch(function() { - return gridster.movingItem; - }, function() { - gridster.updateHeight(gridster.movingItem ? gridster.movingItem.sizeY : 0); - }); + scope.$watch(function() { + return gridster.isMobile; + }, function() { + if (gridster.isMobile) { + $elem.addClass('gridster-mobile').removeClass('gridster-desktop'); + } else { + $elem.removeClass('gridster-mobile').addClass('gridster-desktop'); + } + $rootScope.$broadcast('gridster-mobile-changed', gridster); + }); - var prevWidth = $elem[0].offsetWidth || parseInt($elem.css('width'), 10); + scope.$watch(function() { + return gridster.draggable; + }, function() { + $rootScope.$broadcast('gridster-draggable-changed', gridster); + }, true); - var resize = function() { - var width = $elem[0].offsetWidth || parseInt($elem.css('width'), 10); + scope.$watch(function() { + return gridster.resizable; + }, function() { + $rootScope.$broadcast('gridster-resizable-changed', gridster); + }, true); - if (!width || width === prevWidth || gridster.movingItem) { - return; - } - prevWidth = width; + function updateHeight() { + $elem.css('height', (gridster.gridHeight * gridster.curRowHeight) + (gridster.outerMargin ? gridster.margins[0] : -gridster.margins[0]) + 'px'); + } - if (gridster.loaded) { - $elem.removeClass('gridster-loaded'); - } + scope.$watch(function() { + return gridster.gridHeight; + }, updateHeight); - refresh(); + scope.$watch(function() { + return gridster.movingItem; + }, function() { + gridster.updateHeight(gridster.movingItem ? gridster.movingItem.sizeY : 0); + }); - if (gridster.loaded) { - $elem.addClass('gridster-loaded'); - } + var prevWidth = $elem[0].offsetWidth || parseInt($elem.css('width'), 10); - $rootScope.$broadcast('gridster-resized', [width, $elem[0].offsetHeight], gridster); - }; + var resize = function() { + var width = $elem[0].offsetWidth || parseInt($elem.css('width'), 10); - // track element width changes any way we can - var onResize = gridsterDebounce(function onResize() { - resize(); - $timeout(function() { - scope.$apply(); - }); - }, 100); - - scope.$watch(function() { - return isVisible($elem[0]); - }, onResize); - - // see https://github.com/sdecima/javascript-detect-element-resize - if (typeof window.addResizeListener === 'function') { - window.addResizeListener($elem[0], onResize); - } else { - scope.$watch(function() { - return $elem[0].offsetWidth || parseInt($elem.css('width'), 10); - }, resize); - } - var $win = angular.element($window); - $win.on('resize', onResize); - - // be sure to cleanup - scope.$on('$destroy', function() { - gridster.destroy(); - $win.off('resize', onResize); - if (typeof window.removeResizeListener === 'function') { - window.removeResizeListener($elem[0], onResize); - } - }); + if (!width || width === prevWidth || gridster.movingItem) { + return; + } + prevWidth = width; - // allow a little time to place items before floating up - $timeout(function() { - scope.$watch('gridster.floating', function() { - gridster.floatItemsUp(); - }); - gridster.loaded = true; - }, 100); - }; + if (gridster.loaded) { + $elem.removeClass('gridster-loaded'); + } + + refresh(); + + if (gridster.loaded) { + $elem.addClass('gridster-loaded'); + } + + $rootScope.$broadcast('gridster-resized', [width, $elem[0].offsetHeight], gridster); + }; + + // track element width changes any way we can + var onResize = gridsterDebounce(function onResize() { + resize(); + $timeout(function() { + scope.$apply(); + }); + }, 100); + + scope.$watch(function() { + return isVisible($elem[0]); + }, onResize); + + // see https://github.com/sdecima/javascript-detect-element-resize + if (typeof window.addResizeListener === 'function') { + window.addResizeListener($elem[0], onResize); + } else { + scope.$watch(function() { + return $elem[0].offsetWidth || parseInt($elem.css('width'), 10); + }, resize); } + var $win = angular.element($window); + $win.on('resize', onResize); + + // be sure to cleanup + scope.$on('$destroy', function() { + gridster.destroy(); + $win.off('resize', onResize); + if (typeof window.removeResizeListener === 'function') { + window.removeResizeListener($elem[0], onResize); + } + }); + + // allow a little time to place items before floating up + $timeout(function() { + scope.$watch('gridster.floating', function() { + gridster.floatItemsUp(); + }); + gridster.loaded = true; + }, 100); }; } - ]) - - .controller('GridsterItemCtrl', function() { - this.$element = null; + }; +} +]) + +.controller('GridsterItemCtrl', function() { + this.$element = null; + this.gridster = null; + this.row = null; + this.col = null; + this.sizeX = null; + this.sizeY = null; + this.minSizeX = 0; + this.minSizeY = 0; + this.maxSizeX = null; + this.maxSizeY = null; + + this.init = function($element, gridster) { + this.$element = $element; + this.gridster = gridster; + this.sizeX = gridster.defaultSizeX; + this.sizeY = gridster.defaultSizeY; + }; + + this.destroy = function() { + // set these to null to avoid the possibility of circular references this.gridster = null; - this.row = null; - this.col = null; - this.sizeX = null; - this.sizeY = null; - this.minSizeX = 0; - this.minSizeY = 0; - this.maxSizeX = null; - this.maxSizeY = null; - - this.init = function($element, gridster) { - this.$element = $element; - this.gridster = gridster; - this.sizeX = gridster.defaultSizeX; - this.sizeY = gridster.defaultSizeY; - }; + this.$element = null; + }; - this.destroy = function() { - // set these to null to avoid the possibility of circular references - this.gridster = null; - this.$element = null; + /** + * Returns the items most important attributes + */ + this.toJSON = function() { + return { + row: this.row, + col: this.col, + sizeY: this.sizeY, + sizeX: this.sizeX }; + }; - /** - * Returns the items most important attributes - */ - this.toJSON = function() { - return { - row: this.row, - col: this.col, - sizeY: this.sizeY, - sizeX: this.sizeX - }; - }; + this.isMoving = function() { + return this.gridster.movingItem === this; + }; - this.isMoving = function() { - return this.gridster.movingItem === this; - }; + /** + * Set the items position + * + * @param {Number} row + * @param {Number} column + */ + this.setPosition = function(row, column) { + this.gridster.putItem(this, row, column); + + if (!this.isMoving()) { + this.setElementPosition(); + } + }; - /** - * Set the items position - * - * @param {Number} row - * @param {Number} column - */ - this.setPosition = function(row, column) { - this.gridster.putItem(this, row, column); + /** + * Sets a specified size property + * + * @param {String} key Can be either "x" or "y" + * @param {Number} value The size amount + * @param {Boolean} preventMove + */ + this.setSize = function(key, value, preventMove) { + key = key.toUpperCase(); + var camelCase = 'size' + key, + titleCase = 'Size' + key; + if (value === '') { + return; + } + value = parseInt(value, 10); + if (isNaN(value) || value === 0) { + value = this.gridster['default' + titleCase]; + } + var max = key === 'X' ? this.gridster.columns : this.gridster.maxRows; + if (this['max' + titleCase]) { + max = Math.min(this['max' + titleCase], max); + } + if (this.gridster['max' + titleCase]) { + max = Math.min(this.gridster['max' + titleCase], max); + } + if (key === 'X' && this.cols) { + max -= this.cols; + } else if (key === 'Y' && this.rows) { + max -= this.rows; + } - if (!this.isMoving()) { - this.setElementPosition(); - } - }; + var min = 0; + if (this['min' + titleCase]) { + min = Math.max(this['min' + titleCase], min); + } + if (this.gridster['min' + titleCase]) { + min = Math.max(this.gridster['min' + titleCase], min); + } - /** - * Sets a specified size property - * - * @param {String} key Can be either "x" or "y" - * @param {Number} value The size amount - * @param {Boolean} preventMove - */ - this.setSize = function(key, value, preventMove) { - key = key.toUpperCase(); - var camelCase = 'size' + key, - titleCase = 'Size' + key; - if (value === '') { - return; - } - value = parseInt(value, 10); - if (isNaN(value) || value === 0) { - value = this.gridster['default' + titleCase]; - } - var max = key === 'X' ? this.gridster.columns : this.gridster.maxRows; - if (this['max' + titleCase]) { - max = Math.min(this['max' + titleCase], max); - } - if (this.gridster['max' + titleCase]) { - max = Math.min(this.gridster['max' + titleCase], max); - } - if (key === 'X' && this.cols) { - max -= this.cols; - } else if (key === 'Y' && this.rows) { - max -= this.rows; - } + value = Math.max(Math.min(value, max), min); - var min = 0; - if (this['min' + titleCase]) { - min = Math.max(this['min' + titleCase], min); - } - if (this.gridster['min' + titleCase]) { - min = Math.max(this.gridster['min' + titleCase], min); - } + var changed = (this[camelCase] !== value || (this['old' + titleCase] && this['old' + titleCase] !== value)); + this['old' + titleCase] = this[camelCase] = value; - value = Math.max(Math.min(value, max), min); + if (!this.isMoving()) { + this['setElement' + titleCase](); + } + if (!preventMove && changed) { + this.gridster.moveOverlappingItems(this); + this.gridster.layoutChanged(); + } - var changed = (this[camelCase] !== value || (this['old' + titleCase] && this['old' + titleCase] !== value)); - this['old' + titleCase] = this[camelCase] = value; + return changed; + }; - if (!this.isMoving()) { - this['setElement' + titleCase](); - } - if (!preventMove && changed) { - this.gridster.moveOverlappingItems(this); - this.gridster.layoutChanged(); - } + /** + * Sets the items sizeY property + * + * @param {Number} rows + * @param {Boolean} preventMove + */ + this.setSizeY = function(rows, preventMove) { + return this.setSize('Y', rows, preventMove); + }; - return changed; - }; + /** + * Sets the items sizeX property + * + * @param {Number} columns + * @param {Boolean} preventMove + */ + this.setSizeX = function(columns, preventMove) { + return this.setSize('X', columns, preventMove); + }; - /** - * Sets the items sizeY property - * - * @param {Number} rows - * @param {Boolean} preventMove - */ - this.setSizeY = function(rows, preventMove) { - return this.setSize('Y', rows, preventMove); - }; + /** + * Sets an elements position on the page + */ + this.setElementPosition = function() { + if (this.gridster.isMobile) { + this.$element.css({ + marginLeft: this.gridster.margins[0] + 'px', + marginRight: this.gridster.margins[0] + 'px', + marginTop: this.gridster.margins[1] + 'px', + marginBottom: this.gridster.margins[1] + 'px', + top: '', + left: '' + }); + } else { + this.$element.css({ + margin: 0, + top: (this.row * this.gridster.curRowHeight + (this.gridster.outerMargin ? this.gridster.margins[0] : 0)) + 'px', + left: (this.col * this.gridster.curColWidth + (this.gridster.outerMargin ? this.gridster.margins[1] : 0)) + 'px' + }); + } + }; - /** - * Sets the items sizeX property - * - * @param {Number} columns - * @param {Boolean} preventMove - */ - this.setSizeX = function(columns, preventMove) { - return this.setSize('X', columns, preventMove); - }; + /** + * Sets an elements height + */ + this.setElementSizeY = function() { + if (this.gridster.isMobile && !this.gridster.saveGridItemCalculatedHeightInMobile) { + this.$element.css('height', ''); + } else { + this.$element.css('height', (this.sizeY * this.gridster.curRowHeight - this.gridster.margins[0]) + 'px'); + } + }; - /** - * Sets an elements position on the page - */ - this.setElementPosition = function() { - if (this.gridster.isMobile) { - this.$element.css({ - marginLeft: this.gridster.margins[0] + 'px', - marginRight: this.gridster.margins[0] + 'px', - marginTop: this.gridster.margins[1] + 'px', - marginBottom: this.gridster.margins[1] + 'px', - top: '', - left: '' - }); - } else { - this.$element.css({ - margin: 0, - top: (this.row * this.gridster.curRowHeight + (this.gridster.outerMargin ? this.gridster.margins[0] : 0)) + 'px', - left: (this.col * this.gridster.curColWidth + (this.gridster.outerMargin ? this.gridster.margins[1] : 0)) + 'px' - }); - } - }; + /** + * Sets an elements width + */ + this.setElementSizeX = function() { + if (this.gridster.isMobile) { + this.$element.css('width', ''); + } else { + this.$element.css('width', (this.sizeX * this.gridster.curColWidth - this.gridster.margins[1]) + 'px'); + } + }; - /** - * Sets an elements height - */ - this.setElementSizeY = function() { - if (this.gridster.isMobile && !this.gridster.saveGridItemCalculatedHeightInMobile) { - this.$element.css('height', ''); - } else { - this.$element.css('height', (this.sizeY * this.gridster.curRowHeight - this.gridster.margins[0]) + 'px'); - } - }; + /** + * Gets an element's width + */ + this.getElementSizeX = function() { + return (this.sizeX * this.gridster.curColWidth - this.gridster.margins[1]); + }; - /** - * Sets an elements width - */ - this.setElementSizeX = function() { - if (this.gridster.isMobile) { - this.$element.css('width', ''); - } else { - this.$element.css('width', (this.sizeX * this.gridster.curColWidth - this.gridster.margins[1]) + 'px'); + /** + * Gets an element's height + */ + this.getElementSizeY = function() { + return (this.sizeY * this.gridster.curRowHeight - this.gridster.margins[0]); + }; + +}) + +.factory('GridsterTouch', [function() { + return function GridsterTouch(target, startEvent, moveEvent, endEvent) { + var lastXYById = {}; + + // Opera doesn't have Object.keys so we use this wrapper + var numberOfKeys = function(theObject) { + if (Object.keys) { + return Object.keys(theObject).length; } - }; - /** - * Gets an element's width - */ - this.getElementSizeX = function() { - return (this.sizeX * this.gridster.curColWidth - this.gridster.margins[1]); - }; + var n = 0, + key; + for (key in theObject) { + ++n; + } - /** - * Gets an element's height - */ - this.getElementSizeY = function() { - return (this.sizeY * this.gridster.curRowHeight - this.gridster.margins[0]); + return n; }; - }) - - .factory('GridsterTouch', [function() { - return function GridsterTouch(target, startEvent, moveEvent, endEvent) { - var lastXYById = {}; - - // Opera doesn't have Object.keys so we use this wrapper - var numberOfKeys = function(theObject) { - if (Object.keys) { - return Object.keys(theObject).length; - } - - var n = 0, - key; - for (key in theObject) { - ++n; - } - - return n; - }; - - // this calculates the delta needed to convert pageX/Y to offsetX/Y because offsetX/Y don't exist in the TouchEvent object or in Firefox's MouseEvent object - var computeDocumentToElementDelta = function(theElement) { - var elementLeft = 0; - var elementTop = 0; - var oldIEUserAgent = navigator.userAgent.match(/\bMSIE\b/); - - for (var offsetElement = theElement; offsetElement != null; offsetElement = offsetElement.offsetParent) { - // the following is a major hack for versions of IE less than 8 to avoid an apparent problem on the IEBlog with double-counting the offsets - // this may not be a general solution to IE7's problem with offsetLeft/offsetParent - if (oldIEUserAgent && - (!document.documentMode || document.documentMode < 8) && - offsetElement.currentStyle.position === 'relative' && offsetElement.offsetParent && offsetElement.offsetParent.currentStyle.position === 'relative' && offsetElement.offsetLeft === offsetElement.offsetParent.offsetLeft) { + // this calculates the delta needed to convert pageX/Y to offsetX/Y because offsetX/Y don't exist in the TouchEvent object or in Firefox's MouseEvent object + var computeDocumentToElementDelta = function(theElement) { + var elementLeft = 0; + var elementTop = 0; + var oldIEUserAgent = navigator.userAgent.match(/\bMSIE\b/); + + for (var offsetElement = theElement; offsetElement != null; offsetElement = offsetElement.offsetParent) { + // the following is a major hack for versions of IE less than 8 to avoid an apparent problem on the IEBlog with double-counting the offsets + // this may not be a general solution to IE7's problem with offsetLeft/offsetParent + if (oldIEUserAgent && + (!document.documentMode || document.documentMode < 8) && + offsetElement.currentStyle.position === 'relative' && offsetElement.offsetParent && offsetElement.offsetParent.currentStyle.position === 'relative' && offsetElement.offsetLeft === offsetElement.offsetParent.offsetLeft) { // add only the top elementTop += offsetElement.offsetTop; } else { @@ -1083,7 +1083,7 @@ // the offsetX/Y values are unpredictable so use the clientX/Y values and adjust by the scroll offsets of its parents // to get the document-relative coordinates (the same as pageX/Y) var sx = -2, - sy = -2; // adjust for old IE's 2-pixel border + sy = -2; // adjust for old IE's 2-pixel border for (var scrollElement = pointerObj.srcElement; scrollElement !== null; scrollElement = scrollElement.parentNode) { sx += scrollElement.scrollLeft ? scrollElement.scrollLeft : 0; sy += scrollElement.scrollTop ? scrollElement.scrollTop : 0; @@ -1328,881 +1328,886 @@ }]) .factory('GridsterDraggable', ['$document', '$window', 'GridsterTouch', - function($document, $window, GridsterTouch) { - function GridsterDraggable($el, scope, gridster, item, itemOptions) { - - var elmX, elmY, elmW, elmH, + function($document, $window, GridsterTouch) { + function GridsterDraggable($el, scope, gridster, item, itemOptions) { - mouseX = 0, - mouseY = 0, - lastMouseX = 0, - lastMouseY = 0, - mOffX = 0, - mOffY = 0, + var elmX, elmY, elmW, elmH, - minTop = 0, - maxTop = 9999, - minLeft = 0, - realdocument = $document[0]; + mouseX = 0, + mouseY = 0, + lastMouseX = 0, + lastMouseY = 0, + mOffX = 0, + mOffY = 0, - var originalCol, originalRow; - var inputTags = ['select', 'input', 'textarea', 'button']; - - function mouseDown(e) { - if (inputTags.indexOf(e.target.nodeName.toLowerCase()) !== -1) { - return false; - } + minTop = 0, + maxTop = 9999, + minLeft = 0, + realdocument = $document[0]; - var $target = angular.element(e.target); + var originalCol, originalRow; + var inputTags = ['select', 'input', 'textarea', 'button']; - // exit, if a resize handle was hit - if ($target.hasClass('gridster-item-resizable-handler')) { - return false; - } - - // exit, if the target has it's own click event - if ($target.attr('onclick') || $target.attr('ng-click')) { - return false; - } + function mouseDown(e) { + if (inputTags.indexOf(e.target.nodeName.toLowerCase()) !== -1) { + return false; + } - // only works if you have jQuery - if ($target.closest && $target.closest('.gridster-no-drag').length) { - return false; - } + var $target = angular.element(e.target); - switch (e.which) { - case 1: - // left mouse button - break; - case 2: - case 3: - // right or middle mouse button - return; - } + // exit, if a resize handle was hit + if ($target.hasClass('gridster-item-resizable-handler')) { + return false; + } - lastMouseX = e.pageX; - lastMouseY = e.pageY; + // exit, if the target has it's own click event + if ($target.attr('onclick') || $target.attr('ng-click')) { + return false; + } - elmX = parseInt($el.css('left'), 10); - elmY = parseInt($el.css('top'), 10); - elmW = $el[0].offsetWidth; - elmH = $el[0].offsetHeight; + // only works if you have jQuery + if ($target.closest && $target.closest('.gridster-no-drag').length) { + return false; + } - originalCol = item.col; - originalRow = item.row; + switch (e.which) { + case 1: + // left mouse button + break; + case 2: + case 3: + // right or middle mouse button + return; + } - dragStart(e); + lastMouseX = e.pageX; + lastMouseY = e.pageY; - return true; - } + elmX = parseInt($el.css('left'), 10); + elmY = parseInt($el.css('top'), 10); + elmW = $el[0].offsetWidth; + elmH = $el[0].offsetHeight; - function mouseMove(e) { - if (!$el.hasClass('gridster-item-moving') || $el.hasClass('gridster-item-resizing')) { - return false; - } + originalCol = item.col; + originalRow = item.row; - var maxLeft = gridster.curWidth - 1; + dragStart(e); - // Get the current mouse position. - mouseX = e.pageX; - mouseY = e.pageY; + return true; + } - // Get the deltas - var diffX = mouseX - lastMouseX + mOffX; - var diffY = mouseY - lastMouseY + mOffY; - mOffX = mOffY = 0; + function mouseMove(e) { + if (!$el.hasClass('gridster-item-moving') || $el.hasClass('gridster-item-resizing')) { + return false; + } - // Update last processed mouse positions. - lastMouseX = mouseX; - lastMouseY = mouseY; + var maxLeft = gridster.curWidth - 1; - var dX = diffX, - dY = diffY; - if (elmX + dX < minLeft) { - diffX = minLeft - elmX; - mOffX = dX - diffX; - } else if (elmX + elmW + dX > maxLeft) { - diffX = maxLeft - elmX - elmW; - mOffX = dX - diffX; - } + // Get the current mouse position. + mouseX = e.pageX; + mouseY = e.pageY; - if (elmY + dY < minTop) { - diffY = minTop - elmY; - mOffY = dY - diffY; - } else if (elmY + elmH + dY > maxTop) { - diffY = maxTop - elmY - elmH; - mOffY = dY - diffY; - } - elmX += diffX; - elmY += diffY; + // Get the deltas + var diffX = mouseX - lastMouseX + mOffX; + var diffY = mouseY - lastMouseY + mOffY; + mOffX = mOffY = 0; - // set new position - $el.css({ - 'top': elmY + 'px', - 'left': elmX + 'px' - }); + // Update last processed mouse positions. + lastMouseX = mouseX; + lastMouseY = mouseY; - drag(e); + var dX = diffX, + dY = diffY; + if (elmX + dX < minLeft) { + diffX = minLeft - elmX; + mOffX = dX - diffX; + } else if (elmX + elmW + dX > maxLeft) { + diffX = maxLeft - elmX - elmW; + mOffX = dX - diffX; + } - return true; + if (elmY + dY < minTop) { + diffY = minTop - elmY; + mOffY = dY - diffY; + } else if (elmY + elmH + dY > maxTop) { + diffY = maxTop - elmY - elmH; + mOffY = dY - diffY; } + elmX += diffX; + elmY += diffY; - function mouseUp(e) { - if (!$el.hasClass('gridster-item-moving') || $el.hasClass('gridster-item-resizing')) { - return false; - } + // set new position + $el.css({ + 'top': elmY + 'px', + 'left': elmX + 'px' + }); - mOffX = mOffY = 0; + drag(e); - dragStop(e); + return true; + } - return true; + function mouseUp(e) { + if (!$el.hasClass('gridster-item-moving') || $el.hasClass('gridster-item-resizing')) { + return false; } - function dragStart(event) { - $el.addClass('gridster-item-moving'); - gridster.movingItem = item; - - gridster.updateHeight(item.sizeY); - scope.$apply(function() { - if (gridster.draggable && gridster.draggable.start) { - gridster.draggable.start(event, $el, itemOptions); - } - }); - } + mOffX = mOffY = 0; - function drag(event) { - var oldRow = item.row, - oldCol = item.col, - hasCallback = gridster.draggable && gridster.draggable.drag, - scrollSensitivity = gridster.draggable.scrollSensitivity, - scrollSpeed = gridster.draggable.scrollSpeed; - - var row = gridster.pixelsToRows(elmY); - var col = gridster.pixelsToColumns(elmX); - - var itemsInTheWay = gridster.getItems(row, col, item.sizeX, item.sizeY, item); - var hasItemsInTheWay = itemsInTheWay.length !== 0; - - if (gridster.swapping === true && hasItemsInTheWay) { - var boundingBoxItem = gridster.getBoundingBox(itemsInTheWay), - sameSize = boundingBoxItem.sizeX === item.sizeX && boundingBoxItem.sizeY === item.sizeY, - sameRow = boundingBoxItem.row === oldRow, - sameCol = boundingBoxItem.col === oldCol, - samePosition = boundingBoxItem.row === row && boundingBoxItem.col === col, - inline = sameRow || sameCol; - - if (sameSize && itemsInTheWay.length === 1) { - if (samePosition) { - gridster.swapItems(item, itemsInTheWay[0]); - } else if (inline) { - return; - } - } else if (boundingBoxItem.sizeX <= item.sizeX && boundingBoxItem.sizeY <= item.sizeY && inline) { - var emptyRow = item.row <= row ? item.row : row + item.sizeY, - emptyCol = item.col <= col ? item.col : col + item.sizeX, - rowOffset = emptyRow - boundingBoxItem.row, - colOffset = emptyCol - boundingBoxItem.col; - - for (var i = 0, l = itemsInTheWay.length; i < l; ++i) { - var itemInTheWay = itemsInTheWay[i]; - - var itemsInFreeSpace = gridster.getItems( - itemInTheWay.row + rowOffset, - itemInTheWay.col + colOffset, - itemInTheWay.sizeX, - itemInTheWay.sizeY, - item - ); - - if (itemsInFreeSpace.length === 0) { - gridster.putItem(itemInTheWay, itemInTheWay.row + rowOffset, itemInTheWay.col + colOffset); - } - } - } - } + dragStop(e); - if (gridster.pushing !== false || !hasItemsInTheWay) { - item.row = row; - item.col = col; - } + return true; + } - if (event.pageY - realdocument.body.scrollTop < scrollSensitivity) { - realdocument.body.scrollTop = realdocument.body.scrollTop - scrollSpeed; - } else if ($window.innerHeight - (event.pageY - realdocument.body.scrollTop) < scrollSensitivity) { - realdocument.body.scrollTop = realdocument.body.scrollTop + scrollSpeed; - } + function dragStart(event) { + $el.addClass('gridster-item-moving'); + gridster.movingItem = item; - if (event.pageX - realdocument.body.scrollLeft < scrollSensitivity) { - realdocument.body.scrollLeft = realdocument.body.scrollLeft - scrollSpeed; - } else if ($window.innerWidth - (event.pageX - realdocument.body.scrollLeft) < scrollSensitivity) { - realdocument.body.scrollLeft = realdocument.body.scrollLeft + scrollSpeed; + gridster.updateHeight(item.sizeY); + scope.$apply(function() { + if (gridster.draggable && gridster.draggable.start) { + gridster.draggable.start(event, $el, itemOptions); } + }); + } - if (hasCallback || oldRow !== item.row || oldCol !== item.col) { - scope.$apply(function() { - if (hasCallback) { - gridster.draggable.drag(event, $el, itemOptions); + function drag(event) { + var oldRow = item.row, + oldCol = item.col, + hasCallback = gridster.draggable && gridster.draggable.drag, + scrollSensitivity = gridster.draggable.scrollSensitivity, + scrollSpeed = gridster.draggable.scrollSpeed; + + var row = gridster.pixelsToRows(elmY); + var col = gridster.pixelsToColumns(elmX); + + var itemsInTheWay = gridster.getItems(row, col, item.sizeX, item.sizeY, item); + var hasItemsInTheWay = itemsInTheWay.length !== 0; + + if (gridster.swapping === true && hasItemsInTheWay) { + var boundingBoxItem = gridster.getBoundingBox(itemsInTheWay), + sameSize = boundingBoxItem.sizeX === item.sizeX && boundingBoxItem.sizeY === item.sizeY, + sameRow = boundingBoxItem.row === oldRow, + sameCol = boundingBoxItem.col === oldCol, + samePosition = boundingBoxItem.row === row && boundingBoxItem.col === col, + inline = sameRow || sameCol; + + if (sameSize && itemsInTheWay.length === 1) { + if (samePosition) { + gridster.swapItems(item, itemsInTheWay[0]); + } else if (inline) { + return; + } + } else if (boundingBoxItem.sizeX <= item.sizeX && boundingBoxItem.sizeY <= item.sizeY && inline) { + var emptyRow = item.row <= row ? item.row : row + item.sizeY, + emptyCol = item.col <= col ? item.col : col + item.sizeX, + rowOffset = emptyRow - boundingBoxItem.row, + colOffset = emptyCol - boundingBoxItem.col; + + for (var i = 0, l = itemsInTheWay.length; i < l; ++i) { + var itemInTheWay = itemsInTheWay[i]; + + var itemsInFreeSpace = gridster.getItems( + itemInTheWay.row + rowOffset, + itemInTheWay.col + colOffset, + itemInTheWay.sizeX, + itemInTheWay.sizeY, + item + ); + + if (itemsInFreeSpace.length === 0) { + gridster.putItem(itemInTheWay, itemInTheWay.row + rowOffset, itemInTheWay.col + colOffset); } - }); + } } } - function dragStop(event) { - $el.removeClass('gridster-item-moving'); - var row = gridster.pixelsToRows(elmY); - var col = gridster.pixelsToColumns(elmX); - if (gridster.pushing !== false || gridster.getItems(row, col, item.sizeX, item.sizeY, item).length === 0) { - item.row = row; - item.col = col; - } - gridster.movingItem = null; - item.setPosition(item.row, item.col); + if (gridster.pushing !== false || !hasItemsInTheWay) { + item.row = row; + item.col = col; + } + + if (event.pageY - realdocument.body.scrollTop < scrollSensitivity) { + realdocument.body.scrollTop = realdocument.body.scrollTop - scrollSpeed; + } else if ($window.innerHeight - (event.pageY - realdocument.body.scrollTop) < scrollSensitivity) { + realdocument.body.scrollTop = realdocument.body.scrollTop + scrollSpeed; + } + if (event.pageX - realdocument.body.scrollLeft < scrollSensitivity) { + realdocument.body.scrollLeft = realdocument.body.scrollLeft - scrollSpeed; + } else if ($window.innerWidth - (event.pageX - realdocument.body.scrollLeft) < scrollSensitivity) { + realdocument.body.scrollLeft = realdocument.body.scrollLeft + scrollSpeed; + } + + if (hasCallback || oldRow !== item.row || oldCol !== item.col) { scope.$apply(function() { - if (gridster.draggable && gridster.draggable.stop) { - gridster.draggable.stop(event, $el, itemOptions); + if (hasCallback) { + gridster.draggable.drag(event, $el, itemOptions); } }); - - item.gridsterItemDragStop({item}) } + } - var enabled = null; - var $dragHandles = null; - var unifiedInputs = []; + function dragStop(event) { + $el.removeClass('gridster-item-moving'); + var row = gridster.pixelsToRows(elmY); + var col = gridster.pixelsToColumns(elmX); + if (gridster.pushing !== false || gridster.getItems(row, col, item.sizeX, item.sizeY, item).length === 0) { + item.row = row; + item.col = col; + } + gridster.movingItem = null; + item.setPosition(item.row, item.col); - this.enable = function() { - if (enabled === true) { - return; + scope.$apply(function() { + if (gridster.draggable && gridster.draggable.stop) { + gridster.draggable.stop(event, $el, itemOptions); } + }); - enabled = true; - - // timeout required for some template rendering - $el.ready(function() { - if (enabled !== true) { - return; - } + item.gridsterItemDragStop({item}) + } - // disable any existing draghandles - for (var u = 0, ul = unifiedInputs.length; u < ul; ++u) { - unifiedInputs[u].disable(); - } - unifiedInputs = []; + var enabled = null; + var $dragHandles = null; + var unifiedInputs = []; - if (gridster.draggable && gridster.draggable.handle) { - $dragHandles = angular.element($el[0].querySelectorAll(gridster.draggable.handle)); - if ($dragHandles.length === 0) { - // fall back to element if handle not found... - $dragHandles = $el; - } - } else { - $dragHandles = $el; - } + this.enable = function() { + if (enabled === true) { + return; + } - for (var h = 0, hl = $dragHandles.length; h < hl; ++h) { - unifiedInputs[h] = new GridsterTouch($dragHandles[h], mouseDown, mouseMove, mouseUp); - unifiedInputs[h].enable(); - } - }); - }; + enabled = true; - this.disable = function() { - if (enabled === false) { + // timeout required for some template rendering + $el.ready(function() { + if (enabled !== true) { return; } - enabled = false; - + // disable any existing draghandles for (var u = 0, ul = unifiedInputs.length; u < ul; ++u) { unifiedInputs[u].disable(); } - unifiedInputs = []; - }; - this.toggle = function(enabled) { - if (enabled) { - this.enable(); + if (gridster.draggable && gridster.draggable.handle) { + $dragHandles = angular.element($el[0].querySelectorAll(gridster.draggable.handle)); + if ($dragHandles.length === 0) { + // fall back to element if handle not found... + $dragHandles = $el; + } } else { - this.disable(); + $dragHandles = $el; } - }; - this.destroy = function() { + for (var h = 0, hl = $dragHandles.length; h < hl; ++h) { + unifiedInputs[h] = new GridsterTouch($dragHandles[h], mouseDown, mouseMove, mouseUp); + unifiedInputs[h].enable(); + } + }); + }; + + this.disable = function() { + if (enabled === false) { + return; + } + + enabled = false; + + for (var u = 0, ul = unifiedInputs.length; u < ul; ++u) { + unifiedInputs[u].disable(); + } + + unifiedInputs = []; + }; + + this.toggle = function(enabled) { + if (enabled) { + this.enable(); + } else { this.disable(); - }; - } + } + }; - return GridsterDraggable; + this.destroy = function() { + this.disable(); + }; } - ]) - .factory('GridsterResizable', ['GridsterTouch', function(GridsterTouch) { - function GridsterResizable($el, scope, gridster, item, itemOptions) { + return GridsterDraggable; + } +]) - function ResizeHandle(handleClass) { +.factory('GridsterResizable', ['GridsterTouch', function(GridsterTouch) { + function GridsterResizable($el, scope, gridster, item, itemOptions) { - var hClass = handleClass; + function ResizeHandle(handleClass) { - var elmX, elmY, elmW, elmH, + var hClass = handleClass; - mouseX = 0, - mouseY = 0, - lastMouseX = 0, - lastMouseY = 0, - mOffX = 0, - mOffY = 0, + var elmX, elmY, elmW, elmH, - minTop = 0, - maxTop = 9999, - minLeft = 0; + mouseX = 0, + mouseY = 0, + lastMouseX = 0, + lastMouseY = 0, + mOffX = 0, + mOffY = 0, - var getMinHeight = function() { - return (item.minSizeY ? item.minSizeY : 1) * gridster.curRowHeight - gridster.margins[0]; - }; - var getMinWidth = function() { - return (item.minSizeX ? item.minSizeX : 1) * gridster.curColWidth - gridster.margins[1]; - }; + minTop = 0, + maxTop = 9999, + minLeft = 0; - var originalWidth, originalHeight; - var savedDraggable; - - function mouseDown(e) { - switch (e.which) { - case 1: - // left mouse button - break; - case 2: - case 3: - // right or middle mouse button - return; - } + var getMinHeight = function() { + return (item.minSizeY ? item.minSizeY : 1) * gridster.curRowHeight - gridster.margins[0]; + }; + var getMinWidth = function() { + return (item.minSizeX ? item.minSizeX : 1) * gridster.curColWidth - gridster.margins[1]; + }; - // save the draggable setting to restore after resize - savedDraggable = gridster.draggable.enabled; - if (savedDraggable) { - gridster.draggable.enabled = false; - scope.$broadcast('gridster-draggable-changed', gridster); - } + var originalWidth, originalHeight; + var savedDraggable; + + function mouseDown(e) { + switch (e.which) { + case 1: + // left mouse button + break; + case 2: + case 3: + // right or middle mouse button + return; + } - // Get the current mouse position. - lastMouseX = e.pageX; - lastMouseY = e.pageY; + // save the draggable setting to restore after resize + savedDraggable = gridster.draggable.enabled; + if (savedDraggable) { + gridster.draggable.enabled = false; + scope.$broadcast('gridster-draggable-changed', gridster); + } - // Record current widget dimensions - elmX = parseInt($el.css('left'), 10); - elmY = parseInt($el.css('top'), 10); - elmW = $el[0].offsetWidth; - elmH = $el[0].offsetHeight; + // Get the current mouse position. + lastMouseX = e.pageX; + lastMouseY = e.pageY; - originalWidth = item.sizeX; - originalHeight = item.sizeY; + // Record current widget dimensions + elmX = parseInt($el.css('left'), 10); + elmY = parseInt($el.css('top'), 10); + elmW = $el[0].offsetWidth; + elmH = $el[0].offsetHeight; - resizeStart(e); + originalWidth = item.sizeX; + originalHeight = item.sizeY; - return true; - } + resizeStart(e); - function resizeStart(e) { - $el.addClass('gridster-item-moving'); - $el.addClass('gridster-item-resizing'); + return true; + } - gridster.movingItem = item; + function resizeStart(e) { + $el.addClass('gridster-item-moving'); + $el.addClass('gridster-item-resizing'); - item.setElementSizeX(); - item.setElementSizeY(); - item.setElementPosition(); - gridster.updateHeight(1); + gridster.movingItem = item; - scope.$apply(function() { - // callback - if (gridster.resizable && gridster.resizable.start) { - gridster.resizable.start(e, $el, itemOptions); // options is the item model - } - }); - } + item.setElementSizeX(); + item.setElementSizeY(); + item.setElementPosition(); + gridster.updateHeight(1); + + scope.$apply(function() { + // callback + if (gridster.resizable && gridster.resizable.start) { + gridster.resizable.start(e, $el, itemOptions); // options is the item model + } + }); + } - function mouseMove(e) { - var maxLeft = gridster.curWidth - 1; + function mouseMove(e) { + var maxLeft = gridster.curWidth - 1; - // Get the current mouse position. - mouseX = e.pageX; - mouseY = e.pageY; + // Get the current mouse position. + mouseX = e.pageX; + mouseY = e.pageY; - // Get the deltas - var diffX = mouseX - lastMouseX + mOffX; - var diffY = mouseY - lastMouseY + mOffY; - mOffX = mOffY = 0; + // Get the deltas + var diffX = mouseX - lastMouseX + mOffX; + var diffY = mouseY - lastMouseY + mOffY; + mOffX = mOffY = 0; - // Update last processed mouse positions. - lastMouseX = mouseX; - lastMouseY = mouseY; + // Update last processed mouse positions. + lastMouseX = mouseX; + lastMouseY = mouseY; - var dY = diffY, - dX = diffX; + var dY = diffY, + dX = diffX; - if (hClass.indexOf('n') >= 0) { - if (elmH - dY < getMinHeight()) { - diffY = elmH - getMinHeight(); - mOffY = dY - diffY; - } else if (elmY + dY < minTop) { - diffY = minTop - elmY; - mOffY = dY - diffY; - } - elmY += diffY; - elmH -= diffY; + if (hClass.indexOf('n') >= 0) { + if (elmH - dY < getMinHeight()) { + diffY = elmH - getMinHeight(); + mOffY = dY - diffY; + } else if (elmY + dY < minTop) { + diffY = minTop - elmY; + mOffY = dY - diffY; } - if (hClass.indexOf('s') >= 0) { - if (elmH + dY < getMinHeight()) { - diffY = getMinHeight() - elmH; - mOffY = dY - diffY; - } else if (elmY + elmH + dY > maxTop) { - diffY = maxTop - elmY - elmH; - mOffY = dY - diffY; - } - elmH += diffY; + elmY += diffY; + elmH -= diffY; + } + if (hClass.indexOf('s') >= 0) { + if (elmH + dY < getMinHeight()) { + diffY = getMinHeight() - elmH; + mOffY = dY - diffY; + } else if (elmY + elmH + dY > maxTop) { + diffY = maxTop - elmY - elmH; + mOffY = dY - diffY; } - if (hClass.indexOf('w') >= 0) { - if (elmW - dX < getMinWidth()) { - diffX = elmW - getMinWidth(); - mOffX = dX - diffX; - } else if (elmX + dX < minLeft) { - diffX = minLeft - elmX; - mOffX = dX - diffX; - } - elmX += diffX; - elmW -= diffX; + elmH += diffY; + } + if (hClass.indexOf('w') >= 0) { + if (elmW - dX < getMinWidth()) { + diffX = elmW - getMinWidth(); + mOffX = dX - diffX; + } else if (elmX + dX < minLeft) { + diffX = minLeft - elmX; + mOffX = dX - diffX; } - if (hClass.indexOf('e') >= 0) { - if (elmW + dX < getMinWidth()) { - diffX = getMinWidth() - elmW; - mOffX = dX - diffX; - } else if (elmX + elmW + dX > maxLeft) { - diffX = maxLeft - elmX - elmW; - mOffX = dX - diffX; - } - elmW += diffX; + elmX += diffX; + elmW -= diffX; + } + if (hClass.indexOf('e') >= 0) { + if (elmW + dX < getMinWidth()) { + diffX = getMinWidth() - elmW; + mOffX = dX - diffX; + } else if (elmX + elmW + dX > maxLeft) { + diffX = maxLeft - elmX - elmW; + mOffX = dX - diffX; } - - // set new position - $el.css({ - 'top': elmY + 'px', - 'left': elmX + 'px', - 'width': elmW + 'px', - 'height': elmH + 'px' - }); - - resize(e); - - return true; + elmW += diffX; } - function mouseUp(e) { - // restore draggable setting to its original state - if (gridster.draggable.enabled !== savedDraggable) { - gridster.draggable.enabled = savedDraggable; - scope.$broadcast('gridster-draggable-changed', gridster); - } + // set new position + $el.css({ + 'top': elmY + 'px', + 'left': elmX + 'px', + 'width': elmW + 'px', + 'height': elmH + 'px' + }); - mOffX = mOffY = 0; + resize(e); - resizeStop(e); + return true; + } - return true; + function mouseUp(e) { + // restore draggable setting to its original state + if (gridster.draggable.enabled !== savedDraggable) { + gridster.draggable.enabled = savedDraggable; + scope.$broadcast('gridster-draggable-changed', gridster); } - function resize(e) { - var oldRow = item.row, - oldCol = item.col, - oldSizeX = item.sizeX, - oldSizeY = item.sizeY, - hasCallback = gridster.resizable && gridster.resizable.resize; - - var col = item.col; - // only change column if grabbing left edge - if (['w', 'nw', 'sw'].indexOf(handleClass) !== -1) { - col = gridster.pixelsToColumns(elmX, false); - } + mOffX = mOffY = 0; - var row = item.row; - // only change row if grabbing top edge - if (['n', 'ne', 'nw'].indexOf(handleClass) !== -1) { - row = gridster.pixelsToRows(elmY, false); - } + resizeStop(e); - var sizeX = item.sizeX; - // only change row if grabbing left or right edge - if (['n', 's'].indexOf(handleClass) === -1) { - sizeX = gridster.pixelsToColumns(elmW, true); - } + return true; + } - var sizeY = item.sizeY; - // only change row if grabbing top or bottom edge - if (['e', 'w'].indexOf(handleClass) === -1) { - sizeY = gridster.pixelsToRows(elmH, true); - } + function resize(e) { + var oldRow = item.row, + oldCol = item.col, + oldSizeX = item.sizeX, + oldSizeY = item.sizeY, + hasCallback = gridster.resizable && gridster.resizable.resize; + var col = item.col; + // only change column if grabbing left edge + if (['w', 'nw', 'sw'].indexOf(handleClass) !== -1) { + col = gridster.pixelsToColumns(elmX, false); + } - var canOccupy = row > -1 && col > -1 && sizeX + col <= gridster.columns && sizeY + row <= gridster.maxRows; - if (canOccupy && (gridster.pushing !== false || gridster.getItems(row, col, sizeX, sizeY, item).length === 0)) { - item.row = row; - item.col = col; - item.sizeX = sizeX; - item.sizeY = sizeY; - } - var isChanged = item.row !== oldRow || item.col !== oldCol || item.sizeX !== oldSizeX || item.sizeY !== oldSizeY; + var row = item.row; + // only change row if grabbing top edge + if (['n', 'ne', 'nw'].indexOf(handleClass) !== -1) { + row = gridster.pixelsToRows(elmY, false); + } - if (isChanged){ - item.gridsterItemResizedStop({item}); - } + var sizeX = item.sizeX; + // only change row if grabbing left or right edge + if (['n', 's'].indexOf(handleClass) === -1) { + sizeX = gridster.pixelsToColumns(elmW, true); + } - if (hasCallback || isChanged) { - scope.$apply(function() { - if (hasCallback) { - gridster.resizable.resize(e, $el, itemOptions); // options is the item model - } - }); - } + var sizeY = item.sizeY; + // only change row if grabbing top or bottom edge + if (['e', 'w'].indexOf(handleClass) === -1) { + sizeY = gridster.pixelsToRows(elmH, true); } - function resizeStop(e) { - $el.removeClass('gridster-item-moving'); - $el.removeClass('gridster-item-resizing'); - gridster.movingItem = null; + var canOccupy = row > -1 && col > -1 && sizeX + col <= gridster.columns && sizeY + row <= gridster.maxRows; + if (canOccupy && (gridster.pushing !== false || gridster.getItems(row, col, sizeX, sizeY, item).length === 0)) { + item.row = row; + item.col = col; + item.sizeX = sizeX; + item.sizeY = sizeY; + } + var isChanged = item.row !== oldRow || item.col !== oldCol || item.sizeX !== oldSizeX || item.sizeY !== oldSizeY; - item.setPosition(item.row, item.col); - item.setSizeY(item.sizeY); - item.setSizeX(item.sizeX); + if (isChanged){ + item.gridsterItemResizing({item}); + } + if (hasCallback || isChanged) { scope.$apply(function() { - if (gridster.resizable && gridster.resizable.stop) { - gridster.resizable.stop(e, $el, itemOptions); // options is the item model + if (hasCallback) { + gridster.resizable.resize(e, $el, itemOptions); // options is the item model } }); } + } - var $dragHandle = null; - var unifiedInput; - - this.enable = function() { - if (!$dragHandle) { - $dragHandle = angular.element('
'); - $el.append($dragHandle); - } - - unifiedInput = new GridsterTouch($dragHandle[0], mouseDown, mouseMove, mouseUp); - unifiedInput.enable(); - }; + function resizeStop(e) { + $el.removeClass('gridster-item-moving'); + $el.removeClass('gridster-item-resizing'); - this.disable = function() { - if ($dragHandle) { - $dragHandle.remove(); - $dragHandle = null; - } + gridster.movingItem = null; - unifiedInput.disable(); - unifiedInput = undefined; - }; + item.setPosition(item.row, item.col); + item.setSizeY(item.sizeY); + item.setSizeX(item.sizeX); - this.destroy = function() { - this.disable(); - }; - } + if (gridster.resizable) { + item.gridsterItemResizedStop({item}); + } - var handles = []; - var handlesOpts = gridster.resizable.handles; - if (typeof handlesOpts === 'string') { - handlesOpts = gridster.resizable.handles.split(','); + scope.$apply(function() { + if (gridster.resizable && gridster.resizable.stop) { + gridster.resizable.stop(e, $el, itemOptions); // options is the item model + } + }); } - var enabled = false; - for (var c = 0, l = handlesOpts.length; c < l; c++) { - handles.push(new ResizeHandle(handlesOpts[c])); - } + var $dragHandle = null; + var unifiedInput; this.enable = function() { - if (enabled) { - return; + if (!$dragHandle) { + $dragHandle = angular.element('
'); + $el.append($dragHandle); } - for (var c = 0, l = handles.length; c < l; c++) { - handles[c].enable(); - } - enabled = true; + + unifiedInput = new GridsterTouch($dragHandle[0], mouseDown, mouseMove, mouseUp); + unifiedInput.enable(); }; this.disable = function() { - if (!enabled) { - return; + if ($dragHandle) { + $dragHandle.remove(); + $dragHandle = null; } - for (var c = 0, l = handles.length; c < l; c++) { - handles[c].disable(); - } - enabled = false; - }; - this.toggle = function(enabled) { - if (enabled) { - this.enable(); - } else { - this.disable(); - } + unifiedInput.disable(); + unifiedInput = undefined; }; this.destroy = function() { - for (var c = 0, l = handles.length; c < l; c++) { - handles[c].destroy(); - } + this.disable(); }; } - return GridsterResizable; - }]) - .factory('gridsterDebounce', function() { - return function gridsterDebounce(func, wait, immediate) { - var timeout; - return function() { - var context = this, - args = arguments; - var later = function() { - timeout = null; - if (!immediate) { - func.apply(context, args); - } - }; - var callNow = immediate && !timeout; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - if (callNow) { + var handles = []; + var handlesOpts = gridster.resizable.handles; + if (typeof handlesOpts === 'string') { + handlesOpts = gridster.resizable.handles.split(','); + } + var enabled = false; + + for (var c = 0, l = handlesOpts.length; c < l; c++) { + handles.push(new ResizeHandle(handlesOpts[c])); + } + + this.enable = function() { + if (enabled) { + return; + } + for (var c = 0, l = handles.length; c < l; c++) { + handles[c].enable(); + } + enabled = true; + }; + + this.disable = function() { + if (!enabled) { + return; + } + for (var c = 0, l = handles.length; c < l; c++) { + handles[c].disable(); + } + enabled = false; + }; + + this.toggle = function(enabled) { + if (enabled) { + this.enable(); + } else { + this.disable(); + } + }; + + this.destroy = function() { + for (var c = 0, l = handles.length; c < l; c++) { + handles[c].destroy(); + } + }; + } + return GridsterResizable; +}]) + +.factory('gridsterDebounce', function() { + return function gridsterDebounce(func, wait, immediate) { + var timeout; + return function() { + var context = this, + args = arguments; + var later = function() { + timeout = null; + if (!immediate) { func.apply(context, args); } }; + var callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) { + func.apply(context, args); + } }; - }) - - /** - * GridsterItem directive - * @param $parse - * @param GridsterDraggable - * @param GridsterResizable - * @param gridsterDebounce - */ - .directive('gridsterItem', ['$parse', 'GridsterDraggable', 'GridsterResizable', 'gridsterDebounce', - function($parse, GridsterDraggable, GridsterResizable, gridsterDebounce) { - return { - scope: true, - restrict: 'EA', - controller: 'GridsterItemCtrl', - controllerAs: 'gridsterItem', - require: ['^gridster', 'gridsterItem'], - bindToController:{ - gridsterItemInitialized:'&', - gridsterItemResized:'&', - gridsterItemDragStop : '&', - gridsterItemResizedStop: '&', - gridsterItemMovedDown: '&', - gridsterItemFloatUp: '&' - }, - - link: function(scope, $el, attrs, controllers) { - var optionsKey = attrs.gridsterItem, - options; - - var gridster = controllers[0], - item = controllers[1]; - - scope.gridster = gridster; - - // bind the item's position properties - // options can be an object specified by gridster-item="object" - // or the options can be the element html attributes object - if (optionsKey) { - var $optionsGetter = $parse(optionsKey); - options = $optionsGetter(scope) || {}; - if (!options && $optionsGetter.assign) { - options = { - row: item.row, - col: item.col, - sizeX: item.sizeX, - sizeY: item.sizeY, - minSizeX: 0, - minSizeY: 0, - maxSizeX: null, - maxSizeY: null - }; - $optionsGetter.assign(scope, options); - } - } else { - options = attrs; - } - - item.init($el, gridster); - - $el.addClass('gridster-item'); - - var aspects = ['minSizeX', 'maxSizeX', 'minSizeY', 'maxSizeY', 'sizeX', 'sizeY', 'row', 'col'], - $getters = {}; - - var expressions = []; - var aspectFn = function(aspect) { - var expression; - if (typeof options[aspect] === 'string') { - // watch the expression in the scope - expression = options[aspect]; - } else if (typeof options[aspect.toLowerCase()] === 'string') { - // watch the expression in the scope - expression = options[aspect.toLowerCase()]; - } else if (optionsKey) { - // watch the expression on the options object in the scope - expression = optionsKey + '.' + aspect; - } else { - return; - } - expressions.push('"' + aspect + '":' + expression); - $getters[aspect] = $parse(expression); + }; +}) + +/** +* GridsterItem directive +* @param $parse +* @param GridsterDraggable +* @param GridsterResizable +* @param gridsterDebounce +*/ +.directive('gridsterItem', ['$parse', 'GridsterDraggable', 'GridsterResizable', 'gridsterDebounce', +function($parse, GridsterDraggable, GridsterResizable, gridsterDebounce) { + return { + scope: true, + restrict: 'EA', + controller: 'GridsterItemCtrl', + controllerAs: 'gridsterItem', + require: ['^gridster', 'gridsterItem'], + bindToController:{ + gridsterItemInitialized:'&', + gridsterItemResized:'&', + gridsterItemDragStop : '&', + gridsterItemResizing: '&', + gridsterItemResizedStop: '&', + gridsterItemMovedDown: '&', + gridsterItemFloatUp: '&' + }, - // initial set - var val = $getters[aspect](scope); - if (typeof val === 'number') { - item[aspect] = val; - } + link: function(scope, $el, attrs, controllers) { + var optionsKey = attrs.gridsterItem, + options; + + var gridster = controllers[0], + item = controllers[1]; + + scope.gridster = gridster; + + // bind the item's position properties + // options can be an object specified by gridster-item="object" + // or the options can be the element html attributes object + if (optionsKey) { + var $optionsGetter = $parse(optionsKey); + options = $optionsGetter(scope) || {}; + if (!options && $optionsGetter.assign) { + options = { + row: item.row, + col: item.col, + sizeX: item.sizeX, + sizeY: item.sizeY, + minSizeX: 0, + minSizeY: 0, + maxSizeX: null, + maxSizeY: null }; + $optionsGetter.assign(scope, options); + } + } else { + options = attrs; + } - for (var i = 0, l = aspects.length; i < l; ++i) { - aspectFn(aspects[i]); - } + item.init($el, gridster); + + $el.addClass('gridster-item'); + + var aspects = ['minSizeX', 'maxSizeX', 'minSizeY', 'maxSizeY', 'sizeX', 'sizeY', 'row', 'col'], + $getters = {}; + + var expressions = []; + var aspectFn = function(aspect) { + var expression; + if (typeof options[aspect] === 'string') { + // watch the expression in the scope + expression = options[aspect]; + } else if (typeof options[aspect.toLowerCase()] === 'string') { + // watch the expression in the scope + expression = options[aspect.toLowerCase()]; + } else if (optionsKey) { + // watch the expression on the options object in the scope + expression = optionsKey + '.' + aspect; + } else { + return; + } + expressions.push('"' + aspect + '":' + expression); + $getters[aspect] = $parse(expression); - var watchExpressions = '{' + expressions.join(',') + '}'; - // when the value changes externally, update the internal item object - scope.$watchCollection(watchExpressions, function(newVals, oldVals) { - for (var aspect in newVals) { - var newVal = newVals[aspect]; - var oldVal = oldVals[aspect]; - if (oldVal === newVal) { - continue; - } - newVal = parseInt(newVal, 10); - if (!isNaN(newVal)) { - item[aspect] = newVal; - } - } - }); + // initial set + var val = $getters[aspect](scope); + if (typeof val === 'number') { + item[aspect] = val; + } + }; - function positionChanged() { - // call setPosition so the element and gridster controller are updated - item.setPosition(item.row, item.col); + for (var i = 0, l = aspects.length; i < l; ++i) { + aspectFn(aspects[i]); + } - // when internal item position changes, update externally bound values - if ($getters.row && $getters.row.assign) { - $getters.row.assign(scope, item.row); - } - if ($getters.col && $getters.col.assign) { - $getters.col.assign(scope, item.col); - } + var watchExpressions = '{' + expressions.join(',') + '}'; + // when the value changes externally, update the internal item object + scope.$watchCollection(watchExpressions, function(newVals, oldVals) { + for (var aspect in newVals) { + var newVal = newVals[aspect]; + var oldVal = oldVals[aspect]; + if (oldVal === newVal) { + continue; } - scope.$watch(function() { - return item.row + ',' + item.col; - }, positionChanged); + newVal = parseInt(newVal, 10); + if (!isNaN(newVal)) { + item[aspect] = newVal; + } + } + }); - function sizeChanged() { - var changedX = item.setSizeX(item.sizeX, true); - if (changedX && $getters.sizeX && $getters.sizeX.assign) { - $getters.sizeX.assign(scope, item.sizeX); - } - var changedY = item.setSizeY(item.sizeY, true); - if (changedY && $getters.sizeY && $getters.sizeY.assign) { - $getters.sizeY.assign(scope, item.sizeY); - } + function positionChanged() { + // call setPosition so the element and gridster controller are updated + item.setPosition(item.row, item.col); - if (changedX || changedY) { - item.gridster.moveOverlappingItems(item); - gridster.layoutChanged(); - scope.gridsterItem.gridsterItemResized({item}); - scope.$broadcast('gridster-item-resized', item); - } - } + // when internal item position changes, update externally bound values + if ($getters.row && $getters.row.assign) { + $getters.row.assign(scope, item.row); + } + if ($getters.col && $getters.col.assign) { + $getters.col.assign(scope, item.col); + } + } + scope.$watch(function() { + return item.row + ',' + item.col; + }, positionChanged); - scope.$watch(function() { - return item.sizeY + ',' + item.sizeX + ',' + item.minSizeX + ',' + item.maxSizeX + ',' + item.minSizeY + ',' + item.maxSizeY; - }, sizeChanged); + function sizeChanged() { + var changedX = item.setSizeX(item.sizeX, true); + if (changedX && $getters.sizeX && $getters.sizeX.assign) { + $getters.sizeX.assign(scope, item.sizeX); + } + var changedY = item.setSizeY(item.sizeY, true); + if (changedY && $getters.sizeY && $getters.sizeY.assign) { + $getters.sizeY.assign(scope, item.sizeY); + } - var draggable = new GridsterDraggable($el, scope, gridster, item, options); - var resizable = new GridsterResizable($el, scope, gridster, item, options); + if (changedX || changedY) { + item.gridster.moveOverlappingItems(item); + gridster.layoutChanged(); + scope.gridsterItem.gridsterItemResized({item}); + scope.$broadcast('gridster-item-resized', item); + } + } - var updateResizable = function() { - resizable.toggle(!gridster.isMobile && gridster.resizable && gridster.resizable.enabled); - }; - updateResizable(); + scope.$watch(function() { + return item.sizeY + ',' + item.sizeX + ',' + item.minSizeX + ',' + item.maxSizeX + ',' + item.minSizeY + ',' + item.maxSizeY; + }, sizeChanged); - var updateDraggable = function() { - draggable.toggle(!gridster.isMobile && gridster.draggable && gridster.draggable.enabled); - }; - updateDraggable(); - - scope.$on('gridster-draggable-changed', updateDraggable); - scope.$on('gridster-resizable-changed', updateResizable); - scope.$on('gridster-resized', updateResizable); - scope.$on('gridster-mobile-changed', function() { - updateResizable(); - updateDraggable(); - }); + var draggable = new GridsterDraggable($el, scope, gridster, item, options); + var resizable = new GridsterResizable($el, scope, gridster, item, options); - function whichTransitionEvent() { - var el = document.createElement('div'); - var transitions = { - 'transition': 'transitionend', - 'OTransition': 'oTransitionEnd', - 'MozTransition': 'transitionend', - 'WebkitTransition': 'webkitTransitionEnd' - }; - for (var t in transitions) { - if (el.style[t] !== undefined) { - return transitions[t]; - } - } + var updateResizable = function() { + resizable.toggle(!gridster.isMobile && gridster.resizable && gridster.resizable.enabled); + }; + updateResizable(); + + var updateDraggable = function() { + draggable.toggle(!gridster.isMobile && gridster.draggable && gridster.draggable.enabled); + }; + updateDraggable(); + + scope.$on('gridster-draggable-changed', updateDraggable); + scope.$on('gridster-resizable-changed', updateResizable); + scope.$on('gridster-resized', updateResizable); + scope.$on('gridster-mobile-changed', function() { + updateResizable(); + updateDraggable(); + }); + + function whichTransitionEvent() { + var el = document.createElement('div'); + var transitions = { + 'transition': 'transitionend', + 'OTransition': 'oTransitionEnd', + 'MozTransition': 'transitionend', + 'WebkitTransition': 'webkitTransitionEnd' + }; + for (var t in transitions) { + if (el.style[t] !== undefined) { + return transitions[t]; } + } + } - var debouncedTransitionEndPublisher = gridsterDebounce(function() { - scope.$apply(function() { - scope.$broadcast('gridster-item-transition-end', item); - }); - }, 50); + var debouncedTransitionEndPublisher = gridsterDebounce(function() { + scope.$apply(function() { + scope.$broadcast('gridster-item-transition-end', item); + }); + }, 50); - $el.on(whichTransitionEvent(), debouncedTransitionEndPublisher); + $el.on(whichTransitionEvent(), debouncedTransitionEndPublisher); - scope.gridsterItem.gridsterItemInitialized({item}); - scope.$broadcast('gridster-item-initialized', item); + scope.gridsterItem.gridsterItemInitialized({item}); + scope.$broadcast('gridster-item-initialized', item); - return scope.$on('$destroy', function() { - try { - resizable.destroy(); - draggable.destroy(); - } catch (e) {} + return scope.$on('$destroy', function() { + try { + resizable.destroy(); + draggable.destroy(); + } catch (e) {} - try { - gridster.removeItem(item); - } catch (e) {} + try { + gridster.removeItem(item); + } catch (e) {} - try { - item.destroy(); - } catch (e) {} - }); - } - }; + try { + item.destroy(); + } catch (e) {} + }); } - ]) - - .directive('gridsterNoDrag', function() { - return { - restrict: 'A', - link: function(scope, $element) { - $element.addClass('gridster-no-drag'); - } - }; - }) + }; +} +]) + +.directive('gridsterNoDrag', function() { + return { + restrict: 'A', + link: function(scope, $element) { + $element.addClass('gridster-no-drag'); + } + }; +}) - ; +; })); From fd3e3dff9e7cc0c5dd6ea22cad9e7436ba51e28b Mon Sep 17 00:00:00 2001 From: jocluz Date: Wed, 7 Oct 2015 15:49:33 -0300 Subject: [PATCH 06/16] change gridster item callback sintax. --- src/angular-gridster.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/angular-gridster.js b/src/angular-gridster.js index 109791e7..fa022e56 100755 --- a/src/angular-gridster.js +++ b/src/angular-gridster.js @@ -455,7 +455,7 @@ this.moveOverlappingItems(item, ignoreItems); } this.putItem(item, item.row, item.col, ignoreItems); - item.gridsterItemMovedDown({item}); + item.gridsterItemMovedDown({item: item}); }; /** @@ -508,7 +508,7 @@ this.putItem(item, bestRow, bestColumn); } - item.gridsterItemFloatUp({item}); + item.gridsterItemFloatUp({item: item}); }; /** @@ -1565,7 +1565,7 @@ function($timeout, $window, $rootScope, gridsterDebounce) { } }); - item.gridsterItemDragStop({item}) + item.gridsterItemDragStop({item: item}) } var enabled = null; @@ -1855,7 +1855,7 @@ function($timeout, $window, $rootScope, gridsterDebounce) { var isChanged = item.row !== oldRow || item.col !== oldCol || item.sizeX !== oldSizeX || item.sizeY !== oldSizeY; if (isChanged){ - item.gridsterItemResizing({item}); + item.gridsterItemResizing({item: item}); } if (hasCallback || isChanged) { @@ -1878,7 +1878,7 @@ function($timeout, $window, $rootScope, gridsterDebounce) { item.setSizeX(item.sizeX); if (gridster.resizable) { - item.gridsterItemResizedStop({item}); + item.gridsterItemResizedStop({item: item}); } scope.$apply(function() { @@ -2124,7 +2124,7 @@ function($parse, GridsterDraggable, GridsterResizable, gridsterDebounce) { if (changedX || changedY) { item.gridster.moveOverlappingItems(item); gridster.layoutChanged(); - scope.gridsterItem.gridsterItemResized({item}); + scope.gridsterItem.gridsterItemResized({item: item}); scope.$broadcast('gridster-item-resized', item); } } @@ -2177,7 +2177,7 @@ function($parse, GridsterDraggable, GridsterResizable, gridsterDebounce) { $el.on(whichTransitionEvent(), debouncedTransitionEndPublisher); - scope.gridsterItem.gridsterItemInitialized({item}); + scope.gridsterItem.gridsterItemInitialized({item: item}); scope.$broadcast('gridster-item-initialized', item); return scope.$on('$destroy', function() { From 4e9e12303b6891d8c89f20a42074049a745d92a7 Mon Sep 17 00:00:00 2001 From: jocluz Date: Thu, 15 Oct 2015 16:53:12 -0300 Subject: [PATCH 07/16] Changed module registration --- src/angular-gridster.js | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/src/angular-gridster.js b/src/angular-gridster.js index fa022e56..e7728890 100755 --- a/src/angular-gridster.js +++ b/src/angular-gridster.js @@ -1,20 +1,4 @@ -/*global define:true*/ -(function(root, factory) { - - 'use strict'; - - if (typeof define === 'function' && define.amd) { - // AMD - define(['angular'], factory); - } else if (typeof exports === 'object') { - // CommonJS - module.exports = factory(require('angular')); - } else { - // Browser, nothing "exported". Only registered as a module with angular. - factory(root.angular); - } -}(this, function(angular) { - +(function() { 'use strict'; // This returned angular module 'gridster' is what is exported. @@ -2210,4 +2194,4 @@ function($parse, GridsterDraggable, GridsterResizable, gridsterDebounce) { ; -})); +})(); From 793d77ba2c1f8121670e055619dc967c434f0830 Mon Sep 17 00:00:00 2001 From: jocluz Date: Mon, 19 Oct 2015 09:41:32 -0300 Subject: [PATCH 08/16] tests updated Added events for the tests --- test/spec/gridster.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/test/spec/gridster.js b/test/spec/gridster.js index fa6412d5..13e94bb4 100644 --- a/test/spec/gridster.js +++ b/test/spec/gridster.js @@ -13,25 +13,33 @@ describe('GridsterCtrl', function() { // Initialize the controller beforeEach(inject(function($controller) { - item1x1 = { + item1x1 = { sizeX: 1, sizeY: 1, - id: '1x1' + id: '1x1', + gridsterItemFloatUp: function(i){}, + gridsterItemMovedDown: function(i){} }; item2x1 = { sizeX: 2, sizeY: 1, - id: '2x1' + id: '2x1', + gridsterItemFloatUp: function(i){}, + gridsterItemMovedDown: function(i){} }; item2x2 = { sizeX: 2, sizeY: 2, - id: '2x2' + id: '2x2', + gridsterItemFloatUp: function(i){}, + gridsterItemMovedDown: function(i){} }; item1x2 = { sizeX: 1, sizeY: 2, - id: '1x2' + id: '1x2', + gridsterItemFloatUp: function(i){}, + gridsterItemMovedDown: function(i){} }; var config = [item1x1, item2x1, item2x2, item1x2]; From 04fe012b1f38706f4d544cced485f608ed994939 Mon Sep 17 00:00:00 2001 From: jocluz Date: Fri, 11 Dec 2015 08:25:28 -0300 Subject: [PATCH 09/16] Update package.json Remove package.json wrong changes. --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 39675b01..0fa324eb 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,8 @@ "version": "0.13.6", "description": "This directive gives you gridster behavior", "license": "MIT", - "homepage": "http://jocluz.github.io/proteus-angular-gridster", - "authors": "https://github.com/jocluz/proteus-angular-gridster/graphs/contributors", + "homepage": "http://manifestwebdesign.github.io/angular-gridster", + "authors": "https://github.com/ManifestWebDesign/angular-gridster/graphs/contributors", "devDependencies": { "karma-chrome-launcher": "^0.1.3", "karma-script-launcher": "^0.1.0", @@ -38,7 +38,7 @@ }, "repository": { "type": "git", - "url": "git@github.com:jocluz/proteus-angular-gridster.git" + "url": "git@github.com:ManifestWebDesign/angular-gridster.git" }, "main": "dist/angular-gridster.min.js" } From 78b07be3084ab3c2937bbdac01eef0f34739f99f Mon Sep 17 00:00:00 2001 From: jocluz Date: Fri, 11 Dec 2015 08:29:44 -0300 Subject: [PATCH 10/16] Update package.json Updated package name, changes. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0fa324eb..d451c8a0 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "proteus-angular-gridster", + "name": "angular-gridster", "version": "0.13.6", "description": "This directive gives you gridster behavior", "license": "MIT", From 425c510fa747e5f4e9eaf224f03c261f6c153215 Mon Sep 17 00:00:00 2001 From: jocluz Date: Fri, 11 Dec 2015 08:34:59 -0300 Subject: [PATCH 11/16] Update package.json Remove updated version. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d451c8a0..9663fa50 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "angular-gridster", - "version": "0.13.6", + "version": "0.13.5", "description": "This directive gives you gridster behavior", "license": "MIT", "homepage": "http://manifestwebdesign.github.io/angular-gridster", From 39c8008d10937bb0902ffa48d1145470d8f7443d Mon Sep 17 00:00:00 2001 From: jocluz Date: Wed, 20 Jan 2016 12:02:14 -0300 Subject: [PATCH 12/16] Add coryasilva sparse logic angular-gridster has problems when the number of colums/rows are big. I merged coryasilva login from his pull-request and the performance now is great. I will be waiting for this and coryasilva pull request to be merged into the main repo. --- src/angular-gridster.js | 85 ++++++++++++++++++++++++++++++++--------- 1 file changed, 68 insertions(+), 17 deletions(-) diff --git a/src/angular-gridster.js b/src/angular-gridster.js index e7728890..7920d58e 100755 --- a/src/angular-gridster.js +++ b/src/angular-gridster.js @@ -14,6 +14,7 @@ rowHeight: 'match', // height of grid rows. 'match' will make it the same as the column width, a numeric value will be interpreted as pixels, '/2' is half the column width, '*5' is five times the column width, etc. margins: [10, 10], // margins in between grid items outerMargin: true, + sparse: false, isMobile: false, // toggle mobile view mobileBreakPoint: 600, // width threshold to toggle mobile mode mobileModeEnabled: true, // whether or not to toggle mobile mode when screen width is less than mobileBreakPoint @@ -70,6 +71,7 @@ * A positional array of the items in the grid */ this.grid = []; + this.allItems = []; /** * Clean up after yourself @@ -80,7 +82,13 @@ if (this.grid) { this.grid = []; } + this.$element = null; + + if (this.allItems) { + this.allItems.length = 0; + this.allItems = null; + } }; /** @@ -169,14 +177,29 @@ if (excludeItems && !(excludeItems instanceof Array)) { excludeItems = [excludeItems]; } - for (var h = 0; h < sizeY; ++h) { - for (var w = 0; w < sizeX; ++w) { - var item = this.getItem(row + h, column + w, excludeItems); - if (item && (!excludeItems || excludeItems.indexOf(item) === -1) && items.indexOf(item) === -1) { - items.push(item); - } - } - } + + var item; + if (this.sparse === false){ // check all cells + for (var h = 0; h < sizeY; ++h) { + for (var w = 0; w < sizeX; ++w) { + var item = this.getItem(row + h, column + w, excludeItems); + if (item && (!excludeItems || excludeItems.indexOf(item) === -1) && items.indexOf(item) === -1) { + items.push(item); + } + } + } + } else { // check intersection with all items + var bottom = row + sizeY - 1; + var right = column + sizeX - 1; + for (var i = 0; i < this.allItems.length; ++i) { + item = this.allItems[i]; + if (item && (!excludeItems || excludeItems.indexOf(item) === -1) && items.indexOf(item) === -1 && this.intersect(item, column, right, row, bottom)) { + items.push(item); + } + } + } + + return items; }; @@ -218,7 +241,23 @@ sizeX: maxCol - minCol }; }; - + + /** + * Checks if item intersects specified box + * + * @param {object} item + * @param {number} left + * @param {number} right + * @param {number} top + * @param {number} bottom + */ + + this.intersect = function(item, left, right, top, bottom) { + return (left <= item.col + item.sizeX - 1 && + right >= item.col && + top <= item.row + item.sizeY - 1 && + bottom >= item.row); + }; /** * Removes an item from the grid @@ -226,17 +265,27 @@ * @param {Object} item */ this.removeItem = function(item) { + var index; for (var rowIndex = 0, l = this.grid.length; rowIndex < l; ++rowIndex) { var columns = this.grid[rowIndex]; if (!columns) { continue; } - var index = columns.indexOf(item); + + index = columns.indexOf(item); if (index !== -1) { columns[index] = null; break; } } + + if (this.sparse) { + index = this.allItems.indexOf(item); + if (index !== -1) { + this.allItems.splice(index, 1); + } + } + this.layoutChanged(); }; @@ -335,7 +384,9 @@ this.grid[row] = []; } this.grid[row][column] = item; - + if (this.sparse && this.allItems.indexOf(item) === -1) { + this.allItems.push(item); + } if (this.movingItem === item) { this.floatItemUp(item); } @@ -1450,7 +1501,7 @@ function($timeout, $window, $rootScope, gridsterDebounce) { gridster.updateHeight(item.sizeY); scope.$apply(function() { if (gridster.draggable && gridster.draggable.start) { - gridster.draggable.start(event, $el, itemOptions); + gridster.draggable.start(event, $el, itemOptions, item); } }); } @@ -1526,7 +1577,7 @@ function($timeout, $window, $rootScope, gridsterDebounce) { if (hasCallback || oldRow !== item.row || oldCol !== item.col) { scope.$apply(function() { if (hasCallback) { - gridster.draggable.drag(event, $el, itemOptions); + gridster.draggable.drag(event, $el, itemOptions, item); } }); } @@ -1545,7 +1596,7 @@ function($timeout, $window, $rootScope, gridsterDebounce) { scope.$apply(function() { if (gridster.draggable && gridster.draggable.stop) { - gridster.draggable.stop(event, $el, itemOptions); + gridster.draggable.stop(event, $el, itemOptions, item); } }); @@ -1703,7 +1754,7 @@ function($timeout, $window, $rootScope, gridsterDebounce) { scope.$apply(function() { // callback if (gridster.resizable && gridster.resizable.start) { - gridster.resizable.start(e, $el, itemOptions); // options is the item model + gridster.resizable.start(e, $el, itemOptions, item); // options is the item model } }); } @@ -1845,7 +1896,7 @@ function($timeout, $window, $rootScope, gridsterDebounce) { if (hasCallback || isChanged) { scope.$apply(function() { if (hasCallback) { - gridster.resizable.resize(e, $el, itemOptions); // options is the item model + gridster.resizable.resize(e, $el, itemOptions, item); // options is the item model } }); } @@ -1867,7 +1918,7 @@ function($timeout, $window, $rootScope, gridsterDebounce) { scope.$apply(function() { if (gridster.resizable && gridster.resizable.stop) { - gridster.resizable.stop(e, $el, itemOptions); // options is the item model + gridster.resizable.stop(e, $el, itemOptions, item); // options is the item model } }); } From 02c6f3af953f142f55230454a686652a02d2c076 Mon Sep 17 00:00:00 2001 From: jocluz Date: Thu, 31 Mar 2016 09:41:18 -0300 Subject: [PATCH 13/16] layout change event Event thrown when the layout changes. This is added to save the boxes layout. --- src/angular-gridster.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/angular-gridster.js b/src/angular-gridster.js index 7920d58e..1a2743ff 100755 --- a/src/angular-gridster.js +++ b/src/angular-gridster.js @@ -39,8 +39,8 @@ } }) - .controller('GridsterCtrl', ['gridsterConfig', '$timeout', - function(gridsterConfig, $timeout) { + .controller('GridsterCtrl', ['gridsterConfig', '$timeout', '$rootScope' + function(gridsterConfig, $timeout, $rootScope) { var gridster = this; @@ -64,6 +64,8 @@ gridster.floatItemsUp(); } gridster.updateHeight(gridster.movingItem ? gridster.movingItem.sizeY : 0); + // layout changed event to save the boxes layout + $rootScope.$broadcast('gridster-layout-changed'); }, 30); }; From 6d3bce89edbc81860653d100e26eb24f318015a9 Mon Sep 17 00:00:00 2001 From: jocluz Date: Thu, 31 Mar 2016 10:07:22 -0300 Subject: [PATCH 14/16] Missing comma --- src/angular-gridster.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/angular-gridster.js b/src/angular-gridster.js index 1a2743ff..621d6423 100755 --- a/src/angular-gridster.js +++ b/src/angular-gridster.js @@ -39,7 +39,7 @@ } }) - .controller('GridsterCtrl', ['gridsterConfig', '$timeout', '$rootScope' + .controller('GridsterCtrl', ['gridsterConfig', '$timeout', '$rootScope', function(gridsterConfig, $timeout, $rootScope) { var gridster = this; From a67aa635b971631f800ac25fcbf207efe5e0036d Mon Sep 17 00:00:00 2001 From: jocluz Date: Wed, 27 Apr 2016 17:37:11 -0300 Subject: [PATCH 15/16] Add event Added event on size changed to adapt the content of the gridster items --- src/angular-gridster.js | 3925 ++++++++++++++++++++------------------- 1 file changed, 1966 insertions(+), 1959 deletions(-) diff --git a/src/angular-gridster.js b/src/angular-gridster.js index 621d6423..c0b858e4 100755 --- a/src/angular-gridster.js +++ b/src/angular-gridster.js @@ -1,1198 +1,1215 @@ -(function() { +(function () { 'use strict'; // This returned angular module 'gridster' is what is exported. return angular.module('gridster', []) - .constant('gridsterConfig', { - columns: 6, // number of columns in the grid - pushing: true, // whether to push other items out of the way - floating: true, // whether to automatically float items up so they stack - swapping: false, // whether or not to have items switch places instead of push down if they are the same size - width: 'auto', // width of the grid. "auto" will expand the grid to its parent container - colWidth: 'auto', // width of grid columns. "auto" will divide the width of the grid evenly among the columns - rowHeight: 'match', // height of grid rows. 'match' will make it the same as the column width, a numeric value will be interpreted as pixels, '/2' is half the column width, '*5' is five times the column width, etc. - margins: [10, 10], // margins in between grid items - outerMargin: true, - sparse: false, - isMobile: false, // toggle mobile view - mobileBreakPoint: 600, // width threshold to toggle mobile mode - mobileModeEnabled: true, // whether or not to toggle mobile mode when screen width is less than mobileBreakPoint - minColumns: 1, // minimum amount of columns the grid can scale down to - minRows: 1, // minimum amount of rows to show if the grid is empty - maxRows: 100, // maximum amount of rows in the grid - defaultSizeX: 2, // default width of an item in columns - defaultSizeY: 1, // default height of an item in rows - minSizeX: 1, // minimum column width of an item - maxSizeX: null, // maximum column width of an item - minSizeY: 1, // minumum row height of an item - maxSizeY: null, // maximum row height of an item - saveGridItemCalculatedHeightInMobile: false, // grid item height in mobile display. true- to use the calculated height by sizeY given - resizable: { // options to pass to resizable handler - enabled: true, - handles: ['s', 'e', 'n', 'w', 'se', 'ne', 'sw', 'nw'] - }, - draggable: { // options to pass to draggable handler - enabled: true, - scrollSensitivity: 20, // Distance in pixels from the edge of the viewport after which the viewport should scroll, relative to pointer - scrollSpeed: 15 // Speed at which the window should scroll once the mouse pointer gets within scrollSensitivity distance - } - }) - - .controller('GridsterCtrl', ['gridsterConfig', '$timeout', '$rootScope', - function(gridsterConfig, $timeout, $rootScope) { - - var gridster = this; - - /** - * Create options from gridsterConfig constant - */ - angular.extend(this, gridsterConfig); - - this.resizable = angular.extend({}, gridsterConfig.resizable || {}); - this.draggable = angular.extend({}, gridsterConfig.draggable || {}); - - var flag = false; - this.layoutChanged = function() { - if (flag) { - return; + .constant('gridsterConfig', { + columns: 6, // number of columns in the grid + pushing: true, // whether to push other items out of the way + floating: true, // whether to automatically float items up so they stack + swapping: false, // whether or not to have items switch places instead of push down if they are the same size + width: 'auto', // width of the grid. "auto" will expand the grid to its parent container + colWidth: 'auto', // width of grid columns. "auto" will divide the width of the grid evenly among the columns + rowHeight: 'match', // height of grid rows. 'match' will make it the same as the column width, a numeric value will be interpreted as pixels, '/2' is half the column width, '*5' is five times the column width, etc. + margins: [10, 10], // margins in between grid items + outerMargin: true, + sparse: false, + isMobile: false, // toggle mobile view + mobileBreakPoint: 600, // width threshold to toggle mobile mode + mobileModeEnabled: true, // whether or not to toggle mobile mode when screen width is less than mobileBreakPoint + minColumns: 1, // minimum amount of columns the grid can scale down to + minRows: 1, // minimum amount of rows to show if the grid is empty + maxRows: 100, // maximum amount of rows in the grid + defaultSizeX: 2, // default width of an item in columns + defaultSizeY: 1, // default height of an item in rows + minSizeX: 1, // minimum column width of an item + maxSizeX: null, // maximum column width of an item + minSizeY: 1, // minumum row height of an item + maxSizeY: null, // maximum row height of an item + saveGridItemCalculatedHeightInMobile: false, // grid item height in mobile display. true- to use the calculated height by sizeY given + resizable: { // options to pass to resizable handler + enabled: true, + handles: ['s', 'e', 'n', 'w', 'se', 'ne', 'sw', 'nw'] + }, + draggable: { // options to pass to draggable handler + enabled: true, + scrollSensitivity: 20, // Distance in pixels from the edge of the viewport after which the viewport should scroll, relative to pointer + scrollSpeed: 15 // Speed at which the window should scroll once the mouse pointer gets within scrollSensitivity distance } - flag = true; - $timeout(function() { - flag = false; - if (gridster.loaded) { - gridster.floatItemsUp(); - } - gridster.updateHeight(gridster.movingItem ? gridster.movingItem.sizeY : 0); - // layout changed event to save the boxes layout - $rootScope.$broadcast('gridster-layout-changed'); - }, 30); - }; + }) - /** - * A positional array of the items in the grid - */ - this.grid = []; - this.allItems = []; + .controller('GridsterCtrl', ['gridsterConfig', '$timeout', '$rootScope', + function (gridsterConfig, $timeout, $rootScope) { - /** - * Clean up after yourself - */ - this.destroy = function() { - // empty the grid to cut back on the possibility - // of circular references - if (this.grid) { - this.grid = []; - } - - this.$element = null; - - if (this.allItems) { - this.allItems.length = 0; - this.allItems = null; - } - }; + var gridster = this; - /** - * Overrides default options - * - * @param {Object} options The options to override - */ - this.setOptions = function(options) { - if (!options) { - return; - } - - options = angular.extend({}, options); - - // all this to avoid using jQuery... - if (options.draggable) { - angular.extend(this.draggable, options.draggable); - delete(options.draggable); - } - if (options.resizable) { - angular.extend(this.resizable, options.resizable); - delete(options.resizable); - } - - angular.extend(this, options); + /** + * Create options from gridsterConfig constant + */ + angular.extend(this, gridsterConfig); - if (!this.margins || this.margins.length !== 2) { - this.margins = [0, 0]; - } else { - for (var x = 0, l = this.margins.length; x < l; ++x) { - this.margins[x] = parseInt(this.margins[x], 10); - if (isNaN(this.margins[x])) { - this.margins[x] = 0; - } - } - } - }; + this.resizable = angular.extend({}, gridsterConfig.resizable || {}); + this.draggable = angular.extend({}, gridsterConfig.draggable || {}); - /** - * Check if item can occupy a specified position in the grid - * - * @param {Object} item The item in question - * @param {Number} row The row index - * @param {Number} column The column index - * @returns {Boolean} True if if item fits - */ - this.canItemOccupy = function(item, row, column) { - return row > -1 && column > -1 && item.sizeX + column <= this.columns && item.sizeY + row <= this.maxRows; - }; - - /** - * Set the item in the first suitable position - * - * @param {Object} item The item to insert - */ - this.autoSetItemPosition = function(item) { - // walk through each row and column looking for a place it will fit - for (var rowIndex = 0; rowIndex < this.maxRows; ++rowIndex) { - for (var colIndex = 0; colIndex < this.columns; ++colIndex) { - // only insert if position is not already taken and it can fit - var items = this.getItems(rowIndex, colIndex, item.sizeX, item.sizeY, item); - if (items.length === 0 && this.canItemOccupy(item, rowIndex, colIndex)) { - this.putItem(item, rowIndex, colIndex); + var flag = false; + this.layoutChanged = function () { + if (flag) { return; } - } - } - throw new Error('Unable to place item!'); - }; + flag = true; + $timeout(function () { + flag = false; + if (gridster.loaded) { + gridster.floatItemsUp(); + } + gridster.updateHeight(gridster.movingItem ? gridster.movingItem.sizeY : 0); + // layout changed event to save the boxes layout + $rootScope.$broadcast('gridster-layout-changed'); + }, 30); + }; - /** - * Gets items at a specific coordinate - * - * @param {Number} row - * @param {Number} column - * @param {Number} sizeX - * @param {Number} sizeY - * @param {Array} excludeItems An array of items to exclude from selection - * @returns {Array} Items that match the criteria - */ - this.getItems = function(row, column, sizeX, sizeY, excludeItems) { - var items = []; - if (!sizeX || !sizeY) { - sizeX = sizeY = 1; - } - if (excludeItems && !(excludeItems instanceof Array)) { - excludeItems = [excludeItems]; - } - - var item; - if (this.sparse === false){ // check all cells - for (var h = 0; h < sizeY; ++h) { - for (var w = 0; w < sizeX; ++w) { - var item = this.getItem(row + h, column + w, excludeItems); - if (item && (!excludeItems || excludeItems.indexOf(item) === -1) && items.indexOf(item) === -1) { - items.push(item); - } - } - } - } else { // check intersection with all items - var bottom = row + sizeY - 1; - var right = column + sizeX - 1; - for (var i = 0; i < this.allItems.length; ++i) { - item = this.allItems[i]; - if (item && (!excludeItems || excludeItems.indexOf(item) === -1) && items.indexOf(item) === -1 && this.intersect(item, column, right, row, bottom)) { - items.push(item); - } - } - } - - - return items; - }; + /** + * A positional array of the items in the grid + */ + this.grid = []; + this.allItems = []; + + /** + * Clean up after yourself + */ + this.destroy = function () { + // empty the grid to cut back on the possibility + // of circular references + if (this.grid) { + this.grid = []; + } - /** - * @param {Array} items - * @returns {Object} An item that represents the bounding box of the items - */ - this.getBoundingBox = function(items) { + this.$element = null; - if (items.length === 0) { - return null; - } - if (items.length === 1) { - return { - row: items[0].row, - col: items[0].col, - sizeY: items[0].sizeY, - sizeX: items[0].sizeX + if (this.allItems) { + this.allItems.length = 0; + this.allItems = null; + } }; - } - var maxRow = 0; - var maxCol = 0; - var minRow = 9999; - var minCol = 9999; - - for (var i = 0, l = items.length; i < l; ++i) { - var item = items[i]; - minRow = Math.min(item.row, minRow); - minCol = Math.min(item.col, minCol); - maxRow = Math.max(item.row + item.sizeY, maxRow); - maxCol = Math.max(item.col + item.sizeX, maxCol); - } + /** + * Overrides default options + * + * @param {Object} options The options to override + */ + this.setOptions = function (options) { + if (!options) { + return; + } - return { - row: minRow, - col: minCol, - sizeY: maxRow - minRow, - sizeX: maxCol - minCol - }; - }; - - /** - * Checks if item intersects specified box - * - * @param {object} item - * @param {number} left - * @param {number} right - * @param {number} top - * @param {number} bottom - */ - - this.intersect = function(item, left, right, top, bottom) { - return (left <= item.col + item.sizeX - 1 && - right >= item.col && - top <= item.row + item.sizeY - 1 && - bottom >= item.row); - }; + options = angular.extend({}, options); - /** - * Removes an item from the grid - * - * @param {Object} item - */ - this.removeItem = function(item) { - var index; - for (var rowIndex = 0, l = this.grid.length; rowIndex < l; ++rowIndex) { - var columns = this.grid[rowIndex]; - if (!columns) { - continue; - } - - index = columns.indexOf(item); - if (index !== -1) { - columns[index] = null; - break; - } - } - - if (this.sparse) { - index = this.allItems.indexOf(item); - if (index !== -1) { - this.allItems.splice(index, 1); - } - } - - this.layoutChanged(); - }; + // all this to avoid using jQuery... + if (options.draggable) { + angular.extend(this.draggable, options.draggable); + delete (options.draggable); + } + if (options.resizable) { + angular.extend(this.resizable, options.resizable); + delete (options.resizable); + } - /** - * Returns the item at a specified coordinate - * - * @param {Number} row - * @param {Number} column - * @param {Array} excludeItems Items to exclude from selection - * @returns {Object} The matched item or null - */ - this.getItem = function(row, column, excludeItems) { - if (excludeItems && !(excludeItems instanceof Array)) { - excludeItems = [excludeItems]; - } - var sizeY = 1; - while (row > -1) { - var sizeX = 1, - col = column; - while (col > -1) { - var items = this.grid[row]; - if (items) { - var item = items[col]; - if (item && (!excludeItems || excludeItems.indexOf(item) === -1) && item.sizeX >= sizeX && item.sizeY >= sizeY) { - return item; - } - } - ++sizeX; - --col; - } - --row; - ++sizeY; - } - return null; - }; + angular.extend(this, options); - /** - * Insert an array of items into the grid - * - * @param {Array} items An array of items to insert - */ - this.putItems = function(items) { - for (var i = 0, l = items.length; i < l; ++i) { - this.putItem(items[i]); - } - }; + if (!this.margins || this.margins.length !== 2) { + this.margins = [0, 0]; + } else { + for (var x = 0, l = this.margins.length; x < l; ++x) { + this.margins[x] = parseInt(this.margins[x], 10); + if (isNaN(this.margins[x])) { + this.margins[x] = 0; + } + } + } + }; - /** - * Insert a single item into the grid - * - * @param {Object} item The item to insert - * @param {Number} row (Optional) Specifies the items row index - * @param {Number} column (Optional) Specifies the items column index - * @param {Array} ignoreItems - */ - this.putItem = function(item, row, column, ignoreItems) { - // auto place item if no row specified - if (typeof row === 'undefined' || row === null) { - row = item.row; - column = item.col; - if (typeof row === 'undefined' || row === null) { - this.autoSetItemPosition(item); - return; - } - } + /** + * Check if item can occupy a specified position in the grid + * + * @param {Object} item The item in question + * @param {Number} row The row index + * @param {Number} column The column index + * @returns {Boolean} True if if item fits + */ + this.canItemOccupy = function (item, row, column) { + return row > -1 && column > -1 && item.sizeX + column <= this.columns && item.sizeY + row <= this.maxRows; + }; - // keep item within allowed bounds - if (!this.canItemOccupy(item, row, column)) { - column = Math.min(this.columns - item.sizeX, Math.max(0, column)); - row = Math.min(this.maxRows - item.sizeY, Math.max(0, row)); - } + /** + * Set the item in the first suitable position + * + * @param {Object} item The item to insert + */ + this.autoSetItemPosition = function (item) { + // walk through each row and column looking for a place it will fit + for (var rowIndex = 0; rowIndex < this.maxRows; ++rowIndex) { + for (var colIndex = 0; colIndex < this.columns; ++colIndex) { + // only insert if position is not already taken and it can fit + var items = this.getItems(rowIndex, colIndex, item.sizeX, item.sizeY, item); + if (items.length === 0 && this.canItemOccupy(item, rowIndex, colIndex)) { + this.putItem(item, rowIndex, colIndex); + return; + } + } + } + throw new Error('Unable to place item!'); + }; - // check if item is already in grid - if (item.oldRow !== null && typeof item.oldRow !== 'undefined') { - var samePosition = item.oldRow === row && item.oldColumn === column; - var inGrid = this.grid[row] && this.grid[row][column] === item; - if (samePosition && inGrid) { - item.row = row; - item.col = column; - return; - } else { - // remove from old position - var oldRow = this.grid[item.oldRow]; - if (oldRow && oldRow[item.oldColumn] === item) { - delete oldRow[item.oldColumn]; + /** + * Gets items at a specific coordinate + * + * @param {Number} row + * @param {Number} column + * @param {Number} sizeX + * @param {Number} sizeY + * @param {Array} excludeItems An array of items to exclude from selection + * @returns {Array} Items that match the criteria + */ + this.getItems = function (row, column, sizeX, sizeY, excludeItems) { + var items = []; + if (!sizeX || !sizeY) { + sizeX = sizeY = 1; + } + if (excludeItems && !(excludeItems instanceof Array)) { + excludeItems = [excludeItems]; } - } - } - item.oldRow = item.row = row; - item.oldColumn = item.col = column; + var item; + if (this.sparse === false) { // check all cells + for (var h = 0; h < sizeY; ++h) { + for (var w = 0; w < sizeX; ++w) { + var item = this.getItem(row + h, column + w, excludeItems); + if (item && (!excludeItems || excludeItems.indexOf(item) === -1) && items.indexOf(item) === -1) { + items.push(item); + } + } + } + } else { // check intersection with all items + var bottom = row + sizeY - 1; + var right = column + sizeX - 1; + for (var i = 0; i < this.allItems.length; ++i) { + item = this.allItems[i]; + if (item && (!excludeItems || excludeItems.indexOf(item) === -1) && items.indexOf(item) === -1 && this.intersect(item, column, right, row, bottom)) { + items.push(item); + } + } + } - this.moveOverlappingItems(item, ignoreItems); - if (!this.grid[row]) { - this.grid[row] = []; - } - this.grid[row][column] = item; - if (this.sparse && this.allItems.indexOf(item) === -1) { - this.allItems.push(item); - } - if (this.movingItem === item) { - this.floatItemUp(item); - } - this.layoutChanged(); - }; + return items; + }; - /** - * Trade row and column if item1 with item2 - * - * @param {Object} item1 - * @param {Object} item2 - */ - this.swapItems = function(item1, item2) { - this.grid[item1.row][item1.col] = item2; - this.grid[item2.row][item2.col] = item1; - - var item1Row = item1.row; - var item1Col = item1.col; - item1.row = item2.row; - item1.col = item2.col; - item2.row = item1Row; - item2.col = item1Col; - }; + /** + * @param {Array} items + * @returns {Object} An item that represents the bounding box of the items + */ + this.getBoundingBox = function (items) { - /** - * Prevents items from being overlapped - * - * @param {Object} item The item that should remain - * @param {Array} ignoreItems - */ - this.moveOverlappingItems = function(item, ignoreItems) { - // don't move item, so ignore it - if (!ignoreItems) { - ignoreItems = [item]; - } else if (ignoreItems.indexOf(item) === -1) { - ignoreItems = ignoreItems.slice(0); - ignoreItems.push(item); - } + if (items.length === 0) { + return null; + } + if (items.length === 1) { + return { + row: items[0].row, + col: items[0].col, + sizeY: items[0].sizeY, + sizeX: items[0].sizeX + }; + } - // get the items in the space occupied by the item's coordinates - var overlappingItems = this.getItems( - item.row, - item.col, - item.sizeX, - item.sizeY, - ignoreItems - ); - this.moveItemsDown(overlappingItems, item.row + item.sizeY, ignoreItems); - }; + var maxRow = 0; + var maxCol = 0; + var minRow = 9999; + var minCol = 9999; + + for (var i = 0, l = items.length; i < l; ++i) { + var item = items[i]; + minRow = Math.min(item.row, minRow); + minCol = Math.min(item.col, minCol); + maxRow = Math.max(item.row + item.sizeY, maxRow); + maxCol = Math.max(item.col + item.sizeX, maxCol); + } - /** - * Moves an array of items to a specified row - * - * @param {Array} items The items to move - * @param {Number} newRow The target row - * @param {Array} ignoreItems - */ - this.moveItemsDown = function(items, newRow, ignoreItems) { - if (!items || items.length === 0) { - return; - } - items.sort(function(a, b) { - return a.row - b.row; - }); - - ignoreItems = ignoreItems ? ignoreItems.slice(0) : []; - var topRows = {}, - item, i, l; - - // calculate the top rows in each column - for (i = 0, l = items.length; i < l; ++i) { - item = items[i]; - var topRow = topRows[item.col]; - if (typeof topRow === 'undefined' || item.row < topRow) { - topRows[item.col] = item.row; - } - } + return { + row: minRow, + col: minCol, + sizeY: maxRow - minRow, + sizeX: maxCol - minCol + }; + }; - // move each item down from the top row in its column to the row - for (i = 0, l = items.length; i < l; ++i) { - item = items[i]; - var rowsToMove = newRow - topRows[item.col]; - this.moveItemDown(item, item.row + rowsToMove, ignoreItems); - ignoreItems.push(item); - } - }; + /** + * Checks if item intersects specified box + * + * @param {object} item + * @param {number} left + * @param {number} right + * @param {number} top + * @param {number} bottom + */ + + this.intersect = function (item, left, right, top, bottom) { + return (left <= item.col + item.sizeX - 1 && + right >= item.col && + top <= item.row + item.sizeY - 1 && + bottom >= item.row); + }; - /** - * Moves an item down to a specified row - * - * @param {Object} item The item to move - * @param {Number} newRow The target row - * @param {Array} ignoreItems - */ - this.moveItemDown = function(item, newRow, ignoreItems) { - if (item.row >= newRow) { - return; - } - while (item.row < newRow) { - ++item.row; - this.moveOverlappingItems(item, ignoreItems); - } - this.putItem(item, item.row, item.col, ignoreItems); - item.gridsterItemMovedDown({item: item}); - }; + /** + * Removes an item from the grid + * + * @param {Object} item + */ + this.removeItem = function (item) { + var index; + for (var rowIndex = 0, l = this.grid.length; rowIndex < l; ++rowIndex) { + var columns = this.grid[rowIndex]; + if (!columns) { + continue; + } - /** - * Moves all items up as much as possible - */ - this.floatItemsUp = function() { - if (this.floating === false) { - return; - } - for (var rowIndex = 0, l = this.grid.length; rowIndex < l; ++rowIndex) { - var columns = this.grid[rowIndex]; - if (!columns) { - continue; - } - for (var colIndex = 0, len = columns.length; colIndex < len; ++colIndex) { - var item = columns[colIndex]; - if (item) { - this.floatItemUp(item); + index = columns.indexOf(item); + if (index !== -1) { + columns[index] = null; + break; + } } - } - } - }; - /** - * Float an item up to the most suitable row - * - * @param {Object} item The item to move - */ - this.floatItemUp = function(item) { - if (this.floating === false) { - return; - } - var colIndex = item.col, - sizeY = item.sizeY, - sizeX = item.sizeX, - bestRow = null, - bestColumn = null, - rowIndex = item.row - 1; - - while (rowIndex > -1) { - var items = this.getItems(rowIndex, colIndex, sizeX, sizeY, item); - if (items.length !== 0) { - break; - } - bestRow = rowIndex; - bestColumn = colIndex; - --rowIndex; - } - if (bestRow !== null) { - this.putItem(item, bestRow, bestColumn); - } - - item.gridsterItemFloatUp({item: item}); - }; - - /** - * Update gridsters height - * - * @param {Number} plus (Optional) Additional height to add - */ - this.updateHeight = function(plus) { - var maxHeight = this.minRows; - plus = plus || 0; - for (var rowIndex = this.grid.length; rowIndex >= 0; --rowIndex) { - var columns = this.grid[rowIndex]; - if (!columns) { - continue; - } - for (var colIndex = 0, len = columns.length; colIndex < len; ++colIndex) { - if (columns[colIndex]) { - maxHeight = Math.max(maxHeight, rowIndex + plus + columns[colIndex].sizeY); + if (this.sparse) { + index = this.allItems.indexOf(item); + if (index !== -1) { + this.allItems.splice(index, 1); + } } - } - } - this.gridHeight = this.maxRows - maxHeight > 0 ? Math.min(this.maxRows, maxHeight) : Math.max(this.maxRows, maxHeight); - }; - /** - * Returns the number of rows that will fit in given amount of pixels - * - * @param {Number} pixels - * @param {Boolean} ceilOrFloor (Optional) Determines rounding method - */ - this.pixelsToRows = function(pixels, ceilOrFloor) { - if (!this.outerMargin) { - pixels += this.margins[0] / 2; - } + this.layoutChanged(); + }; - if (ceilOrFloor === true) { - return Math.ceil(pixels / this.curRowHeight); - } else if (ceilOrFloor === false) { - return Math.floor(pixels / this.curRowHeight); - } + /** + * Returns the item at a specified coordinate + * + * @param {Number} row + * @param {Number} column + * @param {Array} excludeItems Items to exclude from selection + * @returns {Object} The matched item or null + */ + this.getItem = function (row, column, excludeItems) { + if (excludeItems && !(excludeItems instanceof Array)) { + excludeItems = [excludeItems]; + } + var sizeY = 1; + while (row > -1) { + var sizeX = 1, + col = column; + while (col > -1) { + var items = this.grid[row]; + if (items) { + var item = items[col]; + if (item && (!excludeItems || excludeItems.indexOf(item) === -1) && item.sizeX >= sizeX && item.sizeY >= sizeY) { + return item; + } + } + ++sizeX; + --col; + } + --row; + ++sizeY; + } + return null; + }; - return Math.round(pixels / this.curRowHeight); - }; + /** + * Insert an array of items into the grid + * + * @param {Array} items An array of items to insert + */ + this.putItems = function (items) { + for (var i = 0, l = items.length; i < l; ++i) { + this.putItem(items[i]); + } + }; - /** - * Returns the number of columns that will fit in a given amount of pixels - * - * @param {Number} pixels - * @param {Boolean} ceilOrFloor (Optional) Determines rounding method - * @returns {Number} The number of columns - */ - this.pixelsToColumns = function(pixels, ceilOrFloor) { - if (!this.outerMargin) { - pixels += this.margins[1] / 2; - } + /** + * Insert a single item into the grid + * + * @param {Object} item The item to insert + * @param {Number} row (Optional) Specifies the items row index + * @param {Number} column (Optional) Specifies the items column index + * @param {Array} ignoreItems + */ + this.putItem = function (item, row, column, ignoreItems) { + // auto place item if no row specified + if (typeof row === 'undefined' || row === null) { + row = item.row; + column = item.col; + if (typeof row === 'undefined' || row === null) { + this.autoSetItemPosition(item); + return; + } + } - if (ceilOrFloor === true) { - return Math.ceil(pixels / this.curColWidth); - } else if (ceilOrFloor === false) { - return Math.floor(pixels / this.curColWidth); - } + // keep item within allowed bounds + if (!this.canItemOccupy(item, row, column)) { + column = Math.min(this.columns - item.sizeX, Math.max(0, column)); + row = Math.min(this.maxRows - item.sizeY, Math.max(0, row)); + } - return Math.round(pixels / this.curColWidth); - }; - } -]) + // check if item is already in grid + if (item.oldRow !== null && typeof item.oldRow !== 'undefined') { + var samePosition = item.oldRow === row && item.oldColumn === column; + var inGrid = this.grid[row] && this.grid[row][column] === item; + if (samePosition && inGrid) { + item.row = row; + item.col = column; + return; + } else { + // remove from old position + var oldRow = this.grid[item.oldRow]; + if (oldRow && oldRow[item.oldColumn] === item) { + delete oldRow[item.oldColumn]; + } + } + } -.directive('gridsterPreview', function() { - return { - replace: true, - scope: true, - require: '^gridster', - template: '
', - link: function(scope, $el, attrs, gridster) { + item.oldRow = item.row = row; + item.oldColumn = item.col = column; - /** - * @returns {Object} style object for preview element - */ - scope.previewStyle = function() { - if (!gridster.movingItem) { - return { - display: 'none' - }; - } + this.moveOverlappingItems(item, ignoreItems); - return { - display: 'block', - height: (gridster.movingItem.sizeY * gridster.curRowHeight - gridster.margins[0]) + 'px', - width: (gridster.movingItem.sizeX * gridster.curColWidth - gridster.margins[1]) + 'px', - top: (gridster.movingItem.row * gridster.curRowHeight + (gridster.outerMargin ? gridster.margins[0] : 0)) + 'px', - left: (gridster.movingItem.col * gridster.curColWidth + (gridster.outerMargin ? gridster.margins[1] : 0)) + 'px' + if (!this.grid[row]) { + this.grid[row] = []; + } + this.grid[row][column] = item; + if (this.sparse && this.allItems.indexOf(item) === -1) { + this.allItems.push(item); + } + if (this.movingItem === item) { + this.floatItemUp(item); + } + this.layoutChanged(); }; - }; - } - }; -}) - -/** -* The gridster directive -* -* @param {Function} $timeout -* @param {Object} $window -* @param {Object} $rootScope -* @param {Function} gridsterDebounce -*/ -.directive('gridster', ['$timeout', '$window', '$rootScope', 'gridsterDebounce', -function($timeout, $window, $rootScope, gridsterDebounce) { - return { - scope: true, - restrict: 'EAC', - controller: 'GridsterCtrl', - controllerAs: 'gridster', - compile: function($tplElem) { + /** + * Trade row and column if item1 with item2 + * + * @param {Object} item1 + * @param {Object} item2 + */ + this.swapItems = function (item1, item2) { + this.grid[item1.row][item1.col] = item2; + this.grid[item2.row][item2.col] = item1; + + var item1Row = item1.row; + var item1Col = item1.col; + item1.row = item2.row; + item1.col = item2.col; + item2.row = item1Row; + item2.col = item1Col; + }; - $tplElem.prepend('
'); + /** + * Prevents items from being overlapped + * + * @param {Object} item The item that should remain + * @param {Array} ignoreItems + */ + this.moveOverlappingItems = function (item, ignoreItems) { + // don't move item, so ignore it + if (!ignoreItems) { + ignoreItems = [item]; + } else if (ignoreItems.indexOf(item) === -1) { + ignoreItems = ignoreItems.slice(0); + ignoreItems.push(item); + } - return function(scope, $elem, attrs, gridster) { - gridster.loaded = false; + // get the items in the space occupied by the item's coordinates + var overlappingItems = this.getItems( + item.row, + item.col, + item.sizeX, + item.sizeY, + ignoreItems + ); + this.moveItemsDown(overlappingItems, item.row + item.sizeY, ignoreItems); + }; - gridster.$element = $elem; + /** + * Moves an array of items to a specified row + * + * @param {Array} items The items to move + * @param {Number} newRow The target row + * @param {Array} ignoreItems + */ + this.moveItemsDown = function (items, newRow, ignoreItems) { + if (!items || items.length === 0) { + return; + } + items.sort(function (a, b) { + return a.row - b.row; + }); - scope.gridster = gridster; + ignoreItems = ignoreItems ? ignoreItems.slice(0) : []; + var topRows = {}, + item, i, l; - $elem.addClass('gridster'); + // calculate the top rows in each column + for (i = 0, l = items.length; i < l; ++i) { + item = items[i]; + var topRow = topRows[item.col]; + if (typeof topRow === 'undefined' || item.row < topRow) { + topRows[item.col] = item.row; + } + } - var isVisible = function(ele) { - return ele.style.visibility !== 'hidden' && ele.style.display !== 'none'; + // move each item down from the top row in its column to the row + for (i = 0, l = items.length; i < l; ++i) { + item = items[i]; + var rowsToMove = newRow - topRows[item.col]; + this.moveItemDown(item, item.row + rowsToMove, ignoreItems); + ignoreItems.push(item); + } }; - function refresh(config) { - gridster.setOptions(config); - - if (!isVisible($elem[0])) { + /** + * Moves an item down to a specified row + * + * @param {Object} item The item to move + * @param {Number} newRow The target row + * @param {Array} ignoreItems + */ + this.moveItemDown = function (item, newRow, ignoreItems) { + if (item.row >= newRow) { return; } - - // resolve "auto" & "match" values - if (gridster.width === 'auto') { - gridster.curWidth = $elem[0].offsetWidth || parseInt($elem.css('width'), 10); - } else { - gridster.curWidth = gridster.width; + while (item.row < newRow) { + ++item.row; + this.moveOverlappingItems(item, ignoreItems); } + this.putItem(item, item.row, item.col, ignoreItems); + item.gridsterItemMovedDown({ item: item }); + }; - if (gridster.colWidth === 'auto') { - gridster.curColWidth = (gridster.curWidth + (gridster.outerMargin ? -gridster.margins[1] : gridster.margins[1])) / gridster.columns; - } else { - gridster.curColWidth = gridster.colWidth; + /** + * Moves all items up as much as possible + */ + this.floatItemsUp = function () { + if (this.floating === false) { + return; + } + for (var rowIndex = 0, l = this.grid.length; rowIndex < l; ++rowIndex) { + var columns = this.grid[rowIndex]; + if (!columns) { + continue; + } + for (var colIndex = 0, len = columns.length; colIndex < len; ++colIndex) { + var item = columns[colIndex]; + if (item) { + this.floatItemUp(item); + } + } } + }; - gridster.curRowHeight = gridster.rowHeight; - if (typeof gridster.rowHeight === 'string') { - if (gridster.rowHeight === 'match') { - gridster.curRowHeight = Math.round(gridster.curColWidth); - } else if (gridster.rowHeight.indexOf('*') !== -1) { - gridster.curRowHeight = Math.round(gridster.curColWidth * gridster.rowHeight.replace('*', '').replace(' ', '')); - } else if (gridster.rowHeight.indexOf('/') !== -1) { - gridster.curRowHeight = Math.round(gridster.curColWidth / gridster.rowHeight.replace('/', '').replace(' ', '')); + /** + * Float an item up to the most suitable row + * + * @param {Object} item The item to move + */ + this.floatItemUp = function (item) { + if (this.floating === false) { + return; + } + var colIndex = item.col, + sizeY = item.sizeY, + sizeX = item.sizeX, + bestRow = null, + bestColumn = null, + rowIndex = item.row - 1; + + while (rowIndex > -1) { + var items = this.getItems(rowIndex, colIndex, sizeX, sizeY, item); + if (items.length !== 0) { + break; } + bestRow = rowIndex; + bestColumn = colIndex; + --rowIndex; + } + if (bestRow !== null) { + this.putItem(item, bestRow, bestColumn); } - gridster.isMobile = gridster.mobileModeEnabled && gridster.curWidth <= gridster.mobileBreakPoint; + item.gridsterItemFloatUp({ item: item }); + }; - // loop through all items and reset their CSS - for (var rowIndex = 0, l = gridster.grid.length; rowIndex < l; ++rowIndex) { - var columns = gridster.grid[rowIndex]; + /** + * Update gridsters height + * + * @param {Number} plus (Optional) Additional height to add + */ + this.updateHeight = function (plus) { + var maxHeight = this.minRows; + plus = plus || 0; + for (var rowIndex = this.grid.length; rowIndex >= 0; --rowIndex) { + var columns = this.grid[rowIndex]; if (!columns) { continue; } - for (var colIndex = 0, len = columns.length; colIndex < len; ++colIndex) { if (columns[colIndex]) { - var item = columns[colIndex]; - item.setElementPosition(); - item.setElementSizeY(); - item.setElementSizeX(); + maxHeight = Math.max(maxHeight, rowIndex + plus + columns[colIndex].sizeY); } } } + this.gridHeight = this.maxRows - maxHeight > 0 ? Math.min(this.maxRows, maxHeight) : Math.max(this.maxRows, maxHeight); + }; - updateHeight(); - } + /** + * Returns the number of rows that will fit in given amount of pixels + * + * @param {Number} pixels + * @param {Boolean} ceilOrFloor (Optional) Determines rounding method + */ + this.pixelsToRows = function (pixels, ceilOrFloor) { + if (!this.outerMargin) { + pixels += this.margins[0] / 2; + } - var optionsKey = attrs.gridster; - if (optionsKey) { - scope.$parent.$watch(optionsKey, function(newConfig) { - refresh(newConfig); - }, true); - } else { - refresh({}); - } + if (ceilOrFloor === true) { + return Math.ceil(pixels / this.curRowHeight); + } else if (ceilOrFloor === false) { + return Math.floor(pixels / this.curRowHeight); + } - scope.$watch(function() { - return gridster.loaded; - }, function() { - if (gridster.loaded) { - $elem.addClass('gridster-loaded'); - } else { - $elem.removeClass('gridster-loaded'); + return Math.round(pixels / this.curRowHeight); + }; + + /** + * Returns the number of columns that will fit in a given amount of pixels + * + * @param {Number} pixels + * @param {Boolean} ceilOrFloor (Optional) Determines rounding method + * @returns {Number} The number of columns + */ + this.pixelsToColumns = function (pixels, ceilOrFloor) { + if (!this.outerMargin) { + pixels += this.margins[1] / 2; } - }); - scope.$watch(function() { - return gridster.isMobile; - }, function() { - if (gridster.isMobile) { - $elem.addClass('gridster-mobile').removeClass('gridster-desktop'); - } else { - $elem.removeClass('gridster-mobile').addClass('gridster-desktop'); + if (ceilOrFloor === true) { + return Math.ceil(pixels / this.curColWidth); + } else if (ceilOrFloor === false) { + return Math.floor(pixels / this.curColWidth); } - $rootScope.$broadcast('gridster-mobile-changed', gridster); - }); - scope.$watch(function() { - return gridster.draggable; - }, function() { - $rootScope.$broadcast('gridster-draggable-changed', gridster); - }, true); + return Math.round(pixels / this.curColWidth); + }; + } + ]) - scope.$watch(function() { - return gridster.resizable; - }, function() { - $rootScope.$broadcast('gridster-resizable-changed', gridster); - }, true); + .directive('gridsterPreview', function () { + return { + replace: true, + scope: true, + require: '^gridster', + template: '
', + link: function (scope, $el, attrs, gridster) { + + /** + * @returns {Object} style object for preview element + */ + scope.previewStyle = function () { + if (!gridster.movingItem) { + return { + display: 'none' + }; + } - function updateHeight() { - $elem.css('height', (gridster.gridHeight * gridster.curRowHeight) + (gridster.outerMargin ? gridster.margins[0] : -gridster.margins[0]) + 'px'); + return { + display: 'block', + height: (gridster.movingItem.sizeY * gridster.curRowHeight - gridster.margins[0]) + 'px', + width: (gridster.movingItem.sizeX * gridster.curColWidth - gridster.margins[1]) + 'px', + top: (gridster.movingItem.row * gridster.curRowHeight + (gridster.outerMargin ? gridster.margins[0] : 0)) + 'px', + left: (gridster.movingItem.col * gridster.curColWidth + (gridster.outerMargin ? gridster.margins[1] : 0)) + 'px' + }; + }; } + }; + }) - scope.$watch(function() { - return gridster.gridHeight; - }, updateHeight); + /** + * The gridster directive + * + * @param {Function} $timeout + * @param {Object} $window + * @param {Object} $rootScope + * @param {Function} gridsterDebounce + */ + .directive('gridster', ['$timeout', '$window', '$rootScope', 'gridsterDebounce', + function ($timeout, $window, $rootScope, gridsterDebounce) { + return { + scope: true, + restrict: 'EAC', + controller: 'GridsterCtrl', + controllerAs: 'gridster', + + compile: function ($tplElem) { + + $tplElem.prepend('
'); + + return function (scope, $elem, attrs, gridster) { + gridster.loaded = false; + + gridster.$element = $elem; + + scope.gridster = gridster; + + $elem.addClass('gridster'); + + var isVisible = function (ele) { + return ele.style.visibility !== 'hidden' && ele.style.display !== 'none'; + }; + + function refresh(config) { + gridster.setOptions(config); + + if (!isVisible($elem[0])) { + return; + } + + // resolve "auto" & "match" values + if (gridster.width === 'auto') { + gridster.curWidth = $elem[0].offsetWidth || parseInt($elem.css('width'), 10); + } else { + gridster.curWidth = gridster.width; + } + + if (gridster.colWidth === 'auto') { + gridster.curColWidth = (gridster.curWidth + (gridster.outerMargin ? -gridster.margins[1] : gridster.margins[1])) / gridster.columns; + } else { + gridster.curColWidth = gridster.colWidth; + } + + gridster.curRowHeight = gridster.rowHeight; + if (typeof gridster.rowHeight === 'string') { + if (gridster.rowHeight === 'match') { + gridster.curRowHeight = Math.round(gridster.curColWidth); + } else if (gridster.rowHeight.indexOf('*') !== -1) { + gridster.curRowHeight = Math.round(gridster.curColWidth * gridster.rowHeight.replace('*', '').replace(' ', '')); + } else if (gridster.rowHeight.indexOf('/') !== -1) { + gridster.curRowHeight = Math.round(gridster.curColWidth / gridster.rowHeight.replace('/', '').replace(' ', '')); + } + } + + gridster.isMobile = gridster.mobileModeEnabled && gridster.curWidth <= gridster.mobileBreakPoint; + + // loop through all items and reset their CSS + for (var rowIndex = 0, l = gridster.grid.length; rowIndex < l; ++rowIndex) { + var columns = gridster.grid[rowIndex]; + if (!columns) { + continue; + } + + for (var colIndex = 0, len = columns.length; colIndex < len; ++colIndex) { + if (columns[colIndex]) { + var item = columns[colIndex]; + item.setElementPosition(); + item.setElementSizeY(); + item.setElementSizeX(); + } + } + } + + updateHeight(); + } - scope.$watch(function() { - return gridster.movingItem; - }, function() { - gridster.updateHeight(gridster.movingItem ? gridster.movingItem.sizeY : 0); - }); + var optionsKey = attrs.gridster; + if (optionsKey) { + scope.$parent.$watch(optionsKey, function (newConfig) { + refresh(newConfig); + }, true); + } else { + refresh({}); + } - var prevWidth = $elem[0].offsetWidth || parseInt($elem.css('width'), 10); + scope.$watch(function () { + return gridster.loaded; + }, function () { + if (gridster.loaded) { + $elem.addClass('gridster-loaded'); + } else { + $elem.removeClass('gridster-loaded'); + } + }); - var resize = function() { - var width = $elem[0].offsetWidth || parseInt($elem.css('width'), 10); + scope.$watch(function () { + return gridster.isMobile; + }, function () { + if (gridster.isMobile) { + $elem.addClass('gridster-mobile').removeClass('gridster-desktop'); + } else { + $elem.removeClass('gridster-mobile').addClass('gridster-desktop'); + } + $rootScope.$broadcast('gridster-mobile-changed', gridster); + }); - if (!width || width === prevWidth || gridster.movingItem) { - return; - } - prevWidth = width; + scope.$watch(function () { + return gridster.draggable; + }, function () { + $rootScope.$broadcast('gridster-draggable-changed', gridster); + }, true); - if (gridster.loaded) { - $elem.removeClass('gridster-loaded'); - } + scope.$watch(function () { + return gridster.resizable; + }, function () { + $rootScope.$broadcast('gridster-resizable-changed', gridster); + }, true); + + function updateHeight() { + $elem.css('height', (gridster.gridHeight * gridster.curRowHeight) + (gridster.outerMargin ? gridster.margins[0] : -gridster.margins[0]) + 'px'); + } + + scope.$watch(function () { + return gridster.gridHeight; + }, updateHeight); + + scope.$watch(function () { + return gridster.movingItem; + }, function () { + gridster.updateHeight(gridster.movingItem ? gridster.movingItem.sizeY : 0); + }); + + var prevWidth = $elem[0].offsetWidth || parseInt($elem.css('width'), 10); + + var resize = function () { + var width = $elem[0].offsetWidth || parseInt($elem.css('width'), 10); + + if (!width || width === prevWidth || gridster.movingItem) { + return; + } + prevWidth = width; + + if (gridster.loaded) { + $elem.removeClass('gridster-loaded'); + } + + refresh(); - refresh(); + if (gridster.loaded) { + $elem.addClass('gridster-loaded'); + } + + $rootScope.$broadcast('gridster-resized', [width, $elem[0].offsetHeight], gridster); + }; + + // track element width changes any way we can + var onResize = gridsterDebounce(function onResize() { + resize(); + $timeout(function () { + scope.$apply(); + }); + }, 100); + + scope.$watch(function () { + return isVisible($elem[0]); + }, onResize); + + // see https://github.com/sdecima/javascript-detect-element-resize + if (typeof window.addResizeListener === 'function') { + window.addResizeListener($elem[0], onResize); + } else { + scope.$watch(function () { + return $elem[0].offsetWidth || parseInt($elem.css('width'), 10); + }, resize); + } + var $win = angular.element($window); + $win.on('resize', onResize); + + // be sure to cleanup + scope.$on('$destroy', function () { + gridster.destroy(); + $win.off('resize', onResize); + if (typeof window.removeResizeListener === 'function') { + window.removeResizeListener($elem[0], onResize); + } + }); - if (gridster.loaded) { - $elem.addClass('gridster-loaded'); + // allow a little time to place items before floating up + $timeout(function () { + scope.$watch('gridster.floating', function () { + gridster.floatItemsUp(); + }); + gridster.loaded = true; + }, 100); + }; } + }; + } + ]) - $rootScope.$broadcast('gridster-resized', [width, $elem[0].offsetHeight], gridster); + .controller('GridsterItemCtrl', function () { + this.$element = null; + this.gridster = null; + this.row = null; + this.col = null; + this.sizeX = null; + this.sizeY = null; + this.minSizeX = 0; + this.minSizeY = 0; + this.maxSizeX = null; + this.maxSizeY = null; + + this.init = function ($element, gridster) { + this.$element = $element; + this.gridster = gridster; + this.sizeX = gridster.defaultSizeX; + this.sizeY = gridster.defaultSizeY; + }; + + this.destroy = function () { + // set these to null to avoid the possibility of circular references + this.gridster = null; + this.$element = null; + }; + + /** + * Returns the items most important attributes + */ + this.toJSON = function () { + return { + row: this.row, + col: this.col, + sizeY: this.sizeY, + sizeX: this.sizeX }; + }; - // track element width changes any way we can - var onResize = gridsterDebounce(function onResize() { - resize(); - $timeout(function() { - scope.$apply(); - }); - }, 100); + this.isMoving = function () { + return this.gridster.movingItem === this; + }; - scope.$watch(function() { - return isVisible($elem[0]); - }, onResize); + /** + * Set the items position + * + * @param {Number} row + * @param {Number} column + */ + this.setPosition = function (row, column) { + this.gridster.putItem(this, row, column); - // see https://github.com/sdecima/javascript-detect-element-resize - if (typeof window.addResizeListener === 'function') { - window.addResizeListener($elem[0], onResize); - } else { - scope.$watch(function() { - return $elem[0].offsetWidth || parseInt($elem.css('width'), 10); - }, resize); + if (!this.isMoving()) { + this.setElementPosition(); } - var $win = angular.element($window); - $win.on('resize', onResize); - - // be sure to cleanup - scope.$on('$destroy', function() { - gridster.destroy(); - $win.off('resize', onResize); - if (typeof window.removeResizeListener === 'function') { - window.removeResizeListener($elem[0], onResize); - } - }); - - // allow a little time to place items before floating up - $timeout(function() { - scope.$watch('gridster.floating', function() { - gridster.floatItemsUp(); - }); - gridster.loaded = true; - }, 100); }; - } - }; -} -]) - -.controller('GridsterItemCtrl', function() { - this.$element = null; - this.gridster = null; - this.row = null; - this.col = null; - this.sizeX = null; - this.sizeY = null; - this.minSizeX = 0; - this.minSizeY = 0; - this.maxSizeX = null; - this.maxSizeY = null; - - this.init = function($element, gridster) { - this.$element = $element; - this.gridster = gridster; - this.sizeX = gridster.defaultSizeX; - this.sizeY = gridster.defaultSizeY; - }; - - this.destroy = function() { - // set these to null to avoid the possibility of circular references - this.gridster = null; - this.$element = null; - }; - - /** - * Returns the items most important attributes - */ - this.toJSON = function() { - return { - row: this.row, - col: this.col, - sizeY: this.sizeY, - sizeX: this.sizeX - }; - }; - - this.isMoving = function() { - return this.gridster.movingItem === this; - }; - - /** - * Set the items position - * - * @param {Number} row - * @param {Number} column - */ - this.setPosition = function(row, column) { - this.gridster.putItem(this, row, column); - - if (!this.isMoving()) { - this.setElementPosition(); - } - }; - - /** - * Sets a specified size property - * - * @param {String} key Can be either "x" or "y" - * @param {Number} value The size amount - * @param {Boolean} preventMove - */ - this.setSize = function(key, value, preventMove) { - key = key.toUpperCase(); - var camelCase = 'size' + key, - titleCase = 'Size' + key; - if (value === '') { - return; - } - value = parseInt(value, 10); - if (isNaN(value) || value === 0) { - value = this.gridster['default' + titleCase]; - } - var max = key === 'X' ? this.gridster.columns : this.gridster.maxRows; - if (this['max' + titleCase]) { - max = Math.min(this['max' + titleCase], max); - } - if (this.gridster['max' + titleCase]) { - max = Math.min(this.gridster['max' + titleCase], max); - } - if (key === 'X' && this.cols) { - max -= this.cols; - } else if (key === 'Y' && this.rows) { - max -= this.rows; - } - - var min = 0; - if (this['min' + titleCase]) { - min = Math.max(this['min' + titleCase], min); - } - if (this.gridster['min' + titleCase]) { - min = Math.max(this.gridster['min' + titleCase], min); - } - - value = Math.max(Math.min(value, max), min); - - var changed = (this[camelCase] !== value || (this['old' + titleCase] && this['old' + titleCase] !== value)); - this['old' + titleCase] = this[camelCase] = value; - - if (!this.isMoving()) { - this['setElement' + titleCase](); - } - if (!preventMove && changed) { - this.gridster.moveOverlappingItems(this); - this.gridster.layoutChanged(); - } - - return changed; - }; - - /** - * Sets the items sizeY property - * - * @param {Number} rows - * @param {Boolean} preventMove - */ - this.setSizeY = function(rows, preventMove) { - return this.setSize('Y', rows, preventMove); - }; - - /** - * Sets the items sizeX property - * - * @param {Number} columns - * @param {Boolean} preventMove - */ - this.setSizeX = function(columns, preventMove) { - return this.setSize('X', columns, preventMove); - }; - - /** - * Sets an elements position on the page - */ - this.setElementPosition = function() { - if (this.gridster.isMobile) { - this.$element.css({ - marginLeft: this.gridster.margins[0] + 'px', - marginRight: this.gridster.margins[0] + 'px', - marginTop: this.gridster.margins[1] + 'px', - marginBottom: this.gridster.margins[1] + 'px', - top: '', - left: '' - }); - } else { - this.$element.css({ - margin: 0, - top: (this.row * this.gridster.curRowHeight + (this.gridster.outerMargin ? this.gridster.margins[0] : 0)) + 'px', - left: (this.col * this.gridster.curColWidth + (this.gridster.outerMargin ? this.gridster.margins[1] : 0)) + 'px' - }); - } - }; - - /** - * Sets an elements height - */ - this.setElementSizeY = function() { - if (this.gridster.isMobile && !this.gridster.saveGridItemCalculatedHeightInMobile) { - this.$element.css('height', ''); - } else { - this.$element.css('height', (this.sizeY * this.gridster.curRowHeight - this.gridster.margins[0]) + 'px'); - } - }; - - /** - * Sets an elements width - */ - this.setElementSizeX = function() { - if (this.gridster.isMobile) { - this.$element.css('width', ''); - } else { - this.$element.css('width', (this.sizeX * this.gridster.curColWidth - this.gridster.margins[1]) + 'px'); - } - }; - - /** - * Gets an element's width - */ - this.getElementSizeX = function() { - return (this.sizeX * this.gridster.curColWidth - this.gridster.margins[1]); - }; - - /** - * Gets an element's height - */ - this.getElementSizeY = function() { - return (this.sizeY * this.gridster.curRowHeight - this.gridster.margins[0]); - }; - -}) - -.factory('GridsterTouch', [function() { - return function GridsterTouch(target, startEvent, moveEvent, endEvent) { - var lastXYById = {}; - - // Opera doesn't have Object.keys so we use this wrapper - var numberOfKeys = function(theObject) { - if (Object.keys) { - return Object.keys(theObject).length; - } - var n = 0, - key; - for (key in theObject) { - ++n; - } + /** + * Sets a specified size property + * + * @param {String} key Can be either "x" or "y" + * @param {Number} value The size amount + * @param {Boolean} preventMove + */ + this.setSize = function (key, value, preventMove) { + key = key.toUpperCase(); + var camelCase = 'size' + key, + titleCase = 'Size' + key; + if (value === '') { + return; + } + value = parseInt(value, 10); + if (isNaN(value) || value === 0) { + value = this.gridster['default' + titleCase]; + } + var max = key === 'X' ? this.gridster.columns : this.gridster.maxRows; + if (this['max' + titleCase]) { + max = Math.min(this['max' + titleCase], max); + } + if (this.gridster['max' + titleCase]) { + max = Math.min(this.gridster['max' + titleCase], max); + } + if (key === 'X' && this.cols) { + max -= this.cols; + } else if (key === 'Y' && this.rows) { + max -= this.rows; + } - return n; - }; - - // this calculates the delta needed to convert pageX/Y to offsetX/Y because offsetX/Y don't exist in the TouchEvent object or in Firefox's MouseEvent object - var computeDocumentToElementDelta = function(theElement) { - var elementLeft = 0; - var elementTop = 0; - var oldIEUserAgent = navigator.userAgent.match(/\bMSIE\b/); - - for (var offsetElement = theElement; offsetElement != null; offsetElement = offsetElement.offsetParent) { - // the following is a major hack for versions of IE less than 8 to avoid an apparent problem on the IEBlog with double-counting the offsets - // this may not be a general solution to IE7's problem with offsetLeft/offsetParent - if (oldIEUserAgent && - (!document.documentMode || document.documentMode < 8) && - offsetElement.currentStyle.position === 'relative' && offsetElement.offsetParent && offsetElement.offsetParent.currentStyle.position === 'relative' && offsetElement.offsetLeft === offsetElement.offsetParent.offsetLeft) { - // add only the top - elementTop += offsetElement.offsetTop; - } else { - elementLeft += offsetElement.offsetLeft; - elementTop += offsetElement.offsetTop; - } + var min = 0; + if (this['min' + titleCase]) { + min = Math.max(this['min' + titleCase], min); + } + if (this.gridster['min' + titleCase]) { + min = Math.max(this.gridster['min' + titleCase], min); } - return { - x: elementLeft, - y: elementTop - }; + value = Math.max(Math.min(value, max), min); + + var changed = (this[camelCase] !== value || (this['old' + titleCase] && this['old' + titleCase] !== value)); + this['old' + titleCase] = this[camelCase] = value; + + if (!this.isMoving()) { + this['setElement' + titleCase](); + } + if (!preventMove && changed) { + this.gridster.moveOverlappingItems(this); + this.gridster.layoutChanged(); + } + + return changed; }; - // cache the delta from the document to our event target (reinitialized each mousedown/MSPointerDown/touchstart) - var documentToTargetDelta = computeDocumentToElementDelta(target); + /** + * Sets the items sizeY property + * + * @param {Number} rows + * @param {Boolean} preventMove + */ + this.setSizeY = function (rows, preventMove) { + return this.setSize('Y', rows, preventMove); + }; - // common event handler for the mouse/pointer/touch models and their down/start, move, up/end, and cancel events - var doEvent = function(theEvtObj) { + /** + * Sets the items sizeX property + * + * @param {Number} columns + * @param {Boolean} preventMove + */ + this.setSizeX = function (columns, preventMove) { + return this.setSize('X', columns, preventMove); + }; - if (theEvtObj.type === 'mousemove' && numberOfKeys(lastXYById) === 0) { - return; + /** + * Sets an elements position on the page + */ + this.setElementPosition = function () { + if (this.gridster.isMobile) { + this.$element.css({ + marginLeft: this.gridster.margins[0] + 'px', + marginRight: this.gridster.margins[0] + 'px', + marginTop: this.gridster.margins[1] + 'px', + marginBottom: this.gridster.margins[1] + 'px', + top: '', + left: '' + }); + } else { + this.$element.css({ + margin: 0, + top: (this.row * this.gridster.curRowHeight + (this.gridster.outerMargin ? this.gridster.margins[0] : 0)) + 'px', + left: (this.col * this.gridster.curColWidth + (this.gridster.outerMargin ? this.gridster.margins[1] : 0)) + 'px' + }); } + }; - var prevent = true; - - var pointerList = theEvtObj.changedTouches ? theEvtObj.changedTouches : [theEvtObj]; - for (var i = 0; i < pointerList.length; ++i) { - var pointerObj = pointerList[i]; - var pointerId = (typeof pointerObj.identifier !== 'undefined') ? pointerObj.identifier : (typeof pointerObj.pointerId !== 'undefined') ? pointerObj.pointerId : 1; - - // use the pageX/Y coordinates to compute target-relative coordinates when we have them (in ie < 9, we need to do a little work to put them there) - if (typeof pointerObj.pageX === 'undefined') { - // initialize assuming our source element is our target - pointerObj.pageX = pointerObj.offsetX + documentToTargetDelta.x; - pointerObj.pageY = pointerObj.offsetY + documentToTargetDelta.y; - - if (pointerObj.srcElement.offsetParent === target && document.documentMode && document.documentMode === 8 && pointerObj.type === 'mousedown') { - // source element is a child piece of VML, we're in IE8, and we've not called setCapture yet - add the origin of the source element - pointerObj.pageX += pointerObj.srcElement.offsetLeft; - pointerObj.pageY += pointerObj.srcElement.offsetTop; - } else if (pointerObj.srcElement !== target && !document.documentMode || document.documentMode < 8) { - // source element isn't the target (most likely it's a child piece of VML) and we're in a version of IE before IE8 - - // the offsetX/Y values are unpredictable so use the clientX/Y values and adjust by the scroll offsets of its parents - // to get the document-relative coordinates (the same as pageX/Y) - var sx = -2, - sy = -2; // adjust for old IE's 2-pixel border - for (var scrollElement = pointerObj.srcElement; scrollElement !== null; scrollElement = scrollElement.parentNode) { - sx += scrollElement.scrollLeft ? scrollElement.scrollLeft : 0; - sy += scrollElement.scrollTop ? scrollElement.scrollTop : 0; - } + /** + * Sets an elements height + */ + this.setElementSizeY = function () { + if (this.gridster.isMobile && !this.gridster.saveGridItemCalculatedHeightInMobile) { + this.$element.css('height', ''); + } else { + this.$element.css('height', (this.sizeY * this.gridster.curRowHeight - this.gridster.margins[0]) + 'px'); + } + }; - pointerObj.pageX = pointerObj.clientX + sx; - pointerObj.pageY = pointerObj.clientY + sy; - } - } + /** + * Sets an elements width + */ + this.setElementSizeX = function () { + if (this.gridster.isMobile) { + this.$element.css('width', ''); + } else { + this.$element.css('width', (this.sizeX * this.gridster.curColWidth - this.gridster.margins[1]) + 'px'); + } + }; + + /** + * Gets an element's width + */ + this.getElementSizeX = function () { + return (this.sizeX * this.gridster.curColWidth - this.gridster.margins[1]); + }; + + /** + * Gets an element's height + */ + this.getElementSizeY = function () { + return (this.sizeY * this.gridster.curRowHeight - this.gridster.margins[0]); + }; + }) - var pageX = pointerObj.pageX; - var pageY = pointerObj.pageY; + .factory('GridsterTouch', [function () { + return function GridsterTouch(target, startEvent, moveEvent, endEvent) { + var lastXYById = {}; - if (theEvtObj.type.match(/(start|down)$/i)) { - // clause for processing MSPointerDown, touchstart, and mousedown + // Opera doesn't have Object.keys so we use this wrapper + var numberOfKeys = function (theObject) { + if (Object.keys) { + return Object.keys(theObject).length; + } - // refresh the document-to-target delta on start in case the target has moved relative to document - documentToTargetDelta = computeDocumentToElementDelta(target); + var n = 0, + key; + for (key in theObject) { + ++n; + } - // protect against failing to get an up or end on this pointerId - if (lastXYById[pointerId]) { - if (endEvent) { - endEvent({ - target: theEvtObj.target, - which: theEvtObj.which, - pointerId: pointerId, - pageX: pageX, - pageY: pageY - }); - } + return n; + }; - delete lastXYById[pointerId]; + // this calculates the delta needed to convert pageX/Y to offsetX/Y because offsetX/Y don't exist in the TouchEvent object or in Firefox's MouseEvent object + var computeDocumentToElementDelta = function (theElement) { + var elementLeft = 0; + var elementTop = 0; + var oldIEUserAgent = navigator.userAgent.match(/\bMSIE\b/); + + for (var offsetElement = theElement; offsetElement != null; offsetElement = offsetElement.offsetParent) { + // the following is a major hack for versions of IE less than 8 to avoid an apparent problem on the IEBlog with double-counting the offsets + // this may not be a general solution to IE7's problem with offsetLeft/offsetParent + if (oldIEUserAgent && + (!document.documentMode || document.documentMode < 8) && + offsetElement.currentStyle.position === 'relative' && offsetElement.offsetParent && offsetElement.offsetParent.currentStyle.position === 'relative' && offsetElement.offsetLeft === offsetElement.offsetParent.offsetLeft) { + // add only the top + elementTop += offsetElement.offsetTop; + } else { + elementLeft += offsetElement.offsetLeft; + elementTop += offsetElement.offsetTop; } + } - if (startEvent) { - if (prevent) { - prevent = startEvent({ - target: theEvtObj.target, - which: theEvtObj.which, - pointerId: pointerId, - pageX: pageX, - pageY: pageY - }); + return { + x: elementLeft, + y: elementTop + }; + }; + + // cache the delta from the document to our event target (reinitialized each mousedown/MSPointerDown/touchstart) + var documentToTargetDelta = computeDocumentToElementDelta(target); + + // common event handler for the mouse/pointer/touch models and their down/start, move, up/end, and cancel events + var doEvent = function (theEvtObj) { + + if (theEvtObj.type === 'mousemove' && numberOfKeys(lastXYById) === 0) { + return; + } + + var prevent = true; + + var pointerList = theEvtObj.changedTouches ? theEvtObj.changedTouches : [theEvtObj]; + for (var i = 0; i < pointerList.length; ++i) { + var pointerObj = pointerList[i]; + var pointerId = (typeof pointerObj.identifier !== 'undefined') ? pointerObj.identifier : (typeof pointerObj.pointerId !== 'undefined') ? pointerObj.pointerId : 1; + + // use the pageX/Y coordinates to compute target-relative coordinates when we have them (in ie < 9, we need to do a little work to put them there) + if (typeof pointerObj.pageX === 'undefined') { + // initialize assuming our source element is our target + pointerObj.pageX = pointerObj.offsetX + documentToTargetDelta.x; + pointerObj.pageY = pointerObj.offsetY + documentToTargetDelta.y; + + if (pointerObj.srcElement.offsetParent === target && document.documentMode && document.documentMode === 8 && pointerObj.type === 'mousedown') { + // source element is a child piece of VML, we're in IE8, and we've not called setCapture yet - add the origin of the source element + pointerObj.pageX += pointerObj.srcElement.offsetLeft; + pointerObj.pageY += pointerObj.srcElement.offsetTop; + } else if (pointerObj.srcElement !== target && !document.documentMode || document.documentMode < 8) { + // source element isn't the target (most likely it's a child piece of VML) and we're in a version of IE before IE8 - + // the offsetX/Y values are unpredictable so use the clientX/Y values and adjust by the scroll offsets of its parents + // to get the document-relative coordinates (the same as pageX/Y) + var sx = -2, + sy = -2; // adjust for old IE's 2-pixel border + for (var scrollElement = pointerObj.srcElement; scrollElement !== null; scrollElement = scrollElement.parentNode) { + sx += scrollElement.scrollLeft ? scrollElement.scrollLeft : 0; + sy += scrollElement.scrollTop ? scrollElement.scrollTop : 0; + } + + pointerObj.pageX = pointerObj.clientX + sx; + pointerObj.pageY = pointerObj.clientY + sy; } } - // init last page positions for this pointer - lastXYById[pointerId] = { - x: pageX, - y: pageY - }; - // IE pointer model - if (target.msSetPointerCapture) { - target.msSetPointerCapture(pointerId); - } else if (theEvtObj.type === 'mousedown' && numberOfKeys(lastXYById) === 1) { - if (useSetReleaseCapture) { - target.setCapture(true); - } else { - document.addEventListener('mousemove', doEvent, false); - document.addEventListener('mouseup', doEvent, false); + var pageX = pointerObj.pageX; + var pageY = pointerObj.pageY; + + if (theEvtObj.type.match(/(start|down)$/i)) { + // clause for processing MSPointerDown, touchstart, and mousedown + + // refresh the document-to-target delta on start in case the target has moved relative to document + documentToTargetDelta = computeDocumentToElementDelta(target); + + // protect against failing to get an up or end on this pointerId + if (lastXYById[pointerId]) { + if (endEvent) { + endEvent({ + target: theEvtObj.target, + which: theEvtObj.which, + pointerId: pointerId, + pageX: pageX, + pageY: pageY + }); + } + + delete lastXYById[pointerId]; + } + + if (startEvent) { + if (prevent) { + prevent = startEvent({ + target: theEvtObj.target, + which: theEvtObj.which, + pointerId: pointerId, + pageX: pageX, + pageY: pageY + }); + } } - } - } else if (theEvtObj.type.match(/move$/i)) { - // clause handles mousemove, MSPointerMove, and touchmove - if (lastXYById[pointerId] && !(lastXYById[pointerId].x === pageX && lastXYById[pointerId].y === pageY)) { - // only extend if the pointer is down and it's not the same as the last point + // init last page positions for this pointer + lastXYById[pointerId] = { + x: pageX, + y: pageY + }; + + // IE pointer model + if (target.msSetPointerCapture) { + target.msSetPointerCapture(pointerId); + } else if (theEvtObj.type === 'mousedown' && numberOfKeys(lastXYById) === 1) { + if (useSetReleaseCapture) { + target.setCapture(true); + } else { + document.addEventListener('mousemove', doEvent, false); + document.addEventListener('mouseup', doEvent, false); + } + } + } else if (theEvtObj.type.match(/move$/i)) { + // clause handles mousemove, MSPointerMove, and touchmove + + if (lastXYById[pointerId] && !(lastXYById[pointerId].x === pageX && lastXYById[pointerId].y === pageY)) { + // only extend if the pointer is down and it's not the same as the last point + + if (moveEvent && prevent) { + prevent = moveEvent({ + target: theEvtObj.target, + which: theEvtObj.which, + pointerId: pointerId, + pageX: pageX, + pageY: pageY + }); + } + + // update last page positions for this pointer + lastXYById[pointerId].x = pageX; + lastXYById[pointerId].y = pageY; + } + } else if (lastXYById[pointerId] && theEvtObj.type.match(/(up|end|cancel)$/i)) { + // clause handles up/end/cancel - if (moveEvent && prevent) { - prevent = moveEvent({ + if (endEvent && prevent) { + prevent = endEvent({ target: theEvtObj.target, which: theEvtObj.which, pointerId: pointerId, @@ -1201,1050 +1218,1040 @@ function($timeout, $window, $rootScope, gridsterDebounce) { }); } - // update last page positions for this pointer - lastXYById[pointerId].x = pageX; - lastXYById[pointerId].y = pageY; - } - } else if (lastXYById[pointerId] && theEvtObj.type.match(/(up|end|cancel)$/i)) { - // clause handles up/end/cancel - - if (endEvent && prevent) { - prevent = endEvent({ - target: theEvtObj.target, - which: theEvtObj.which, - pointerId: pointerId, - pageX: pageX, - pageY: pageY - }); - } - - // delete last page positions for this pointer - delete lastXYById[pointerId]; + // delete last page positions for this pointer + delete lastXYById[pointerId]; - // in the Microsoft pointer model, release the capture for this pointer - // in the mouse model, release the capture or remove document-level event handlers if there are no down points - // nothing is required for the iOS touch model because capture is implied on touchstart - if (target.msReleasePointerCapture) { - target.msReleasePointerCapture(pointerId); - } else if (theEvtObj.type === 'mouseup' && numberOfKeys(lastXYById) === 0) { - if (useSetReleaseCapture) { - target.releaseCapture(); - } else { - document.removeEventListener('mousemove', doEvent, false); - document.removeEventListener('mouseup', doEvent, false); + // in the Microsoft pointer model, release the capture for this pointer + // in the mouse model, release the capture or remove document-level event handlers if there are no down points + // nothing is required for the iOS touch model because capture is implied on touchstart + if (target.msReleasePointerCapture) { + target.msReleasePointerCapture(pointerId); + } else if (theEvtObj.type === 'mouseup' && numberOfKeys(lastXYById) === 0) { + if (useSetReleaseCapture) { + target.releaseCapture(); + } else { + document.removeEventListener('mousemove', doEvent, false); + document.removeEventListener('mouseup', doEvent, false); + } } } } - } - if (prevent) { - if (theEvtObj.preventDefault) { - theEvtObj.preventDefault(); - } + if (prevent) { + if (theEvtObj.preventDefault) { + theEvtObj.preventDefault(); + } - if (theEvtObj.preventManipulation) { - theEvtObj.preventManipulation(); - } + if (theEvtObj.preventManipulation) { + theEvtObj.preventManipulation(); + } - if (theEvtObj.preventMouseEvent) { - theEvtObj.preventMouseEvent(); + if (theEvtObj.preventMouseEvent) { + theEvtObj.preventMouseEvent(); + } } - } - }; + }; - var useSetReleaseCapture = false; - // saving the settings for contentZooming and touchaction before activation - var contentZooming, msTouchAction; + var useSetReleaseCapture = false; + // saving the settings for contentZooming and touchaction before activation + var contentZooming, msTouchAction; - this.enable = function() { + this.enable = function () { - if (window.navigator.msPointerEnabled) { - // Microsoft pointer model - target.addEventListener('MSPointerDown', doEvent, false); - target.addEventListener('MSPointerMove', doEvent, false); - target.addEventListener('MSPointerUp', doEvent, false); - target.addEventListener('MSPointerCancel', doEvent, false); + if (window.navigator.msPointerEnabled) { + // Microsoft pointer model + target.addEventListener('MSPointerDown', doEvent, false); + target.addEventListener('MSPointerMove', doEvent, false); + target.addEventListener('MSPointerUp', doEvent, false); + target.addEventListener('MSPointerCancel', doEvent, false); - // css way to prevent panning in our target area - if (typeof target.style.msContentZooming !== 'undefined') { - contentZooming = target.style.msContentZooming; - target.style.msContentZooming = 'none'; - } + // css way to prevent panning in our target area + if (typeof target.style.msContentZooming !== 'undefined') { + contentZooming = target.style.msContentZooming; + target.style.msContentZooming = 'none'; + } - // new in Windows Consumer Preview: css way to prevent all built-in touch actions on our target - // without this, you cannot touch draw on the element because IE will intercept the touch events - if (typeof target.style.msTouchAction !== 'undefined') { - msTouchAction = target.style.msTouchAction; - target.style.msTouchAction = 'none'; + // new in Windows Consumer Preview: css way to prevent all built-in touch actions on our target + // without this, you cannot touch draw on the element because IE will intercept the touch events + if (typeof target.style.msTouchAction !== 'undefined') { + msTouchAction = target.style.msTouchAction; + target.style.msTouchAction = 'none'; + } + } else if (target.addEventListener) { + // iOS touch model + target.addEventListener('touchstart', doEvent, false); + target.addEventListener('touchmove', doEvent, false); + target.addEventListener('touchend', doEvent, false); + target.addEventListener('touchcancel', doEvent, false); + + // mouse model + target.addEventListener('mousedown', doEvent, false); + + // mouse model with capture + // rejecting gecko because, unlike ie, firefox does not send events to target when the mouse is outside target + if (target.setCapture && !window.navigator.userAgent.match(/\bGecko\b/)) { + useSetReleaseCapture = true; + + target.addEventListener('mousemove', doEvent, false); + target.addEventListener('mouseup', doEvent, false); + } + } else if (target.attachEvent && target.setCapture) { + // legacy IE mode - mouse with capture + useSetReleaseCapture = true; + target.attachEvent('onmousedown', function () { + doEvent(window.event); + window.event.returnValue = false; + return false; + }); + target.attachEvent('onmousemove', function () { + doEvent(window.event); + window.event.returnValue = false; + return false; + }); + target.attachEvent('onmouseup', function () { + doEvent(window.event); + window.event.returnValue = false; + return false; + }); } - } else if (target.addEventListener) { - // iOS touch model - target.addEventListener('touchstart', doEvent, false); - target.addEventListener('touchmove', doEvent, false); - target.addEventListener('touchend', doEvent, false); - target.addEventListener('touchcancel', doEvent, false); + }; - // mouse model - target.addEventListener('mousedown', doEvent, false); + this.disable = function () { + if (window.navigator.msPointerEnabled) { + // Microsoft pointer model + target.removeEventListener('MSPointerDown', doEvent, false); + target.removeEventListener('MSPointerMove', doEvent, false); + target.removeEventListener('MSPointerUp', doEvent, false); + target.removeEventListener('MSPointerCancel', doEvent, false); + + // reset zooming to saved value + if (contentZooming) { + target.style.msContentZooming = contentZooming; + } - // mouse model with capture - // rejecting gecko because, unlike ie, firefox does not send events to target when the mouse is outside target - if (target.setCapture && !window.navigator.userAgent.match(/\bGecko\b/)) { + // reset touch action setting + if (msTouchAction) { + target.style.msTouchAction = msTouchAction; + } + } else if (target.removeEventListener) { + // iOS touch model + target.removeEventListener('touchstart', doEvent, false); + target.removeEventListener('touchmove', doEvent, false); + target.removeEventListener('touchend', doEvent, false); + target.removeEventListener('touchcancel', doEvent, false); + + // mouse model + target.removeEventListener('mousedown', doEvent, false); + + // mouse model with capture + // rejecting gecko because, unlike ie, firefox does not send events to target when the mouse is outside target + if (target.setCapture && !window.navigator.userAgent.match(/\bGecko\b/)) { + useSetReleaseCapture = true; + + target.removeEventListener('mousemove', doEvent, false); + target.removeEventListener('mouseup', doEvent, false); + } + } else if (target.detachEvent && target.setCapture) { + // legacy IE mode - mouse with capture useSetReleaseCapture = true; - - target.addEventListener('mousemove', doEvent, false); - target.addEventListener('mouseup', doEvent, false); + target.detachEvent('onmousedown'); + target.detachEvent('onmousemove'); + target.detachEvent('onmouseup'); } - } else if (target.attachEvent && target.setCapture) { - // legacy IE mode - mouse with capture - useSetReleaseCapture = true; - target.attachEvent('onmousedown', function() { - doEvent(window.event); - window.event.returnValue = false; - return false; - }); - target.attachEvent('onmousemove', function() { - doEvent(window.event); - window.event.returnValue = false; - return false; - }); - target.attachEvent('onmouseup', function() { - doEvent(window.event); - window.event.returnValue = false; - return false; - }); - } + }; + + return this; }; + }]) - this.disable = function() { - if (window.navigator.msPointerEnabled) { - // Microsoft pointer model - target.removeEventListener('MSPointerDown', doEvent, false); - target.removeEventListener('MSPointerMove', doEvent, false); - target.removeEventListener('MSPointerUp', doEvent, false); - target.removeEventListener('MSPointerCancel', doEvent, false); + .factory('GridsterDraggable', ['$document', '$window', 'GridsterTouch', + function ($document, $window, GridsterTouch) { + function GridsterDraggable($el, scope, gridster, item, itemOptions) { - // reset zooming to saved value - if (contentZooming) { - target.style.msContentZooming = contentZooming; - } + var elmX, elmY, elmW, elmH, - // reset touch action setting - if (msTouchAction) { - target.style.msTouchAction = msTouchAction; - } - } else if (target.removeEventListener) { - // iOS touch model - target.removeEventListener('touchstart', doEvent, false); - target.removeEventListener('touchmove', doEvent, false); - target.removeEventListener('touchend', doEvent, false); - target.removeEventListener('touchcancel', doEvent, false); + mouseX = 0, + mouseY = 0, + lastMouseX = 0, + lastMouseY = 0, + mOffX = 0, + mOffY = 0, - // mouse model - target.removeEventListener('mousedown', doEvent, false); + minTop = 0, + maxTop = 9999, + minLeft = 0, + realdocument = $document[0]; - // mouse model with capture - // rejecting gecko because, unlike ie, firefox does not send events to target when the mouse is outside target - if (target.setCapture && !window.navigator.userAgent.match(/\bGecko\b/)) { - useSetReleaseCapture = true; + var originalCol, originalRow; + var inputTags = ['select', 'input', 'textarea', 'button']; - target.removeEventListener('mousemove', doEvent, false); - target.removeEventListener('mouseup', doEvent, false); - } - } else if (target.detachEvent && target.setCapture) { - // legacy IE mode - mouse with capture - useSetReleaseCapture = true; - target.detachEvent('onmousedown'); - target.detachEvent('onmousemove'); - target.detachEvent('onmouseup'); - } - }; + function mouseDown(e) { + if (inputTags.indexOf(e.target.nodeName.toLowerCase()) !== -1) { + return false; + } - return this; - }; - }]) + var $target = angular.element(e.target); - .factory('GridsterDraggable', ['$document', '$window', 'GridsterTouch', - function($document, $window, GridsterTouch) { - function GridsterDraggable($el, scope, gridster, item, itemOptions) { + // exit, if a resize handle was hit + if ($target.hasClass('gridster-item-resizable-handler')) { + return false; + } - var elmX, elmY, elmW, elmH, + // exit, if the target has it's own click event + if ($target.attr('onclick') || $target.attr('ng-click')) { + return false; + } - mouseX = 0, - mouseY = 0, - lastMouseX = 0, - lastMouseY = 0, - mOffX = 0, - mOffY = 0, + // only works if you have jQuery + if ($target.closest && $target.closest('.gridster-no-drag').length) { + return false; + } - minTop = 0, - maxTop = 9999, - minLeft = 0, - realdocument = $document[0]; + switch (e.which) { + case 1: + // left mouse button + break; + case 2: + case 3: + // right or middle mouse button + return; + } - var originalCol, originalRow; - var inputTags = ['select', 'input', 'textarea', 'button']; + lastMouseX = e.pageX; + lastMouseY = e.pageY; - function mouseDown(e) { - if (inputTags.indexOf(e.target.nodeName.toLowerCase()) !== -1) { - return false; - } + elmX = parseInt($el.css('left'), 10); + elmY = parseInt($el.css('top'), 10); + elmW = $el[0].offsetWidth; + elmH = $el[0].offsetHeight; - var $target = angular.element(e.target); + originalCol = item.col; + originalRow = item.row; - // exit, if a resize handle was hit - if ($target.hasClass('gridster-item-resizable-handler')) { - return false; - } + dragStart(e); - // exit, if the target has it's own click event - if ($target.attr('onclick') || $target.attr('ng-click')) { - return false; - } + return true; + } - // only works if you have jQuery - if ($target.closest && $target.closest('.gridster-no-drag').length) { - return false; - } + function mouseMove(e) { + if (!$el.hasClass('gridster-item-moving') || $el.hasClass('gridster-item-resizing')) { + return false; + } - switch (e.which) { - case 1: - // left mouse button - break; - case 2: - case 3: - // right or middle mouse button - return; - } + var maxLeft = gridster.curWidth - 1; + + // Get the current mouse position. + mouseX = e.pageX; + mouseY = e.pageY; + + // Get the deltas + var diffX = mouseX - lastMouseX + mOffX; + var diffY = mouseY - lastMouseY + mOffY; + mOffX = mOffY = 0; + + // Update last processed mouse positions. + lastMouseX = mouseX; + lastMouseY = mouseY; + + var dX = diffX, + dY = diffY; + if (elmX + dX < minLeft) { + diffX = minLeft - elmX; + mOffX = dX - diffX; + } else if (elmX + elmW + dX > maxLeft) { + diffX = maxLeft - elmX - elmW; + mOffX = dX - diffX; + } - lastMouseX = e.pageX; - lastMouseY = e.pageY; + if (elmY + dY < minTop) { + diffY = minTop - elmY; + mOffY = dY - diffY; + } else if (elmY + elmH + dY > maxTop) { + diffY = maxTop - elmY - elmH; + mOffY = dY - diffY; + } + elmX += diffX; + elmY += diffY; - elmX = parseInt($el.css('left'), 10); - elmY = parseInt($el.css('top'), 10); - elmW = $el[0].offsetWidth; - elmH = $el[0].offsetHeight; + // set new position + $el.css({ + 'top': elmY + 'px', + 'left': elmX + 'px' + }); - originalCol = item.col; - originalRow = item.row; + drag(e); - dragStart(e); + return true; + } - return true; - } + function mouseUp(e) { + if (!$el.hasClass('gridster-item-moving') || $el.hasClass('gridster-item-resizing')) { + return false; + } - function mouseMove(e) { - if (!$el.hasClass('gridster-item-moving') || $el.hasClass('gridster-item-resizing')) { - return false; - } + mOffX = mOffY = 0; - var maxLeft = gridster.curWidth - 1; - - // Get the current mouse position. - mouseX = e.pageX; - mouseY = e.pageY; - - // Get the deltas - var diffX = mouseX - lastMouseX + mOffX; - var diffY = mouseY - lastMouseY + mOffY; - mOffX = mOffY = 0; - - // Update last processed mouse positions. - lastMouseX = mouseX; - lastMouseY = mouseY; - - var dX = diffX, - dY = diffY; - if (elmX + dX < minLeft) { - diffX = minLeft - elmX; - mOffX = dX - diffX; - } else if (elmX + elmW + dX > maxLeft) { - diffX = maxLeft - elmX - elmW; - mOffX = dX - diffX; - } + dragStop(e); - if (elmY + dY < minTop) { - diffY = minTop - elmY; - mOffY = dY - diffY; - } else if (elmY + elmH + dY > maxTop) { - diffY = maxTop - elmY - elmH; - mOffY = dY - diffY; - } - elmX += diffX; - elmY += diffY; + return true; + } - // set new position - $el.css({ - 'top': elmY + 'px', - 'left': elmX + 'px' - }); + function dragStart(event) { + $el.addClass('gridster-item-moving'); + gridster.movingItem = item; - drag(e); + gridster.updateHeight(item.sizeY); + scope.$apply(function () { + if (gridster.draggable && gridster.draggable.start) { + gridster.draggable.start(event, $el, itemOptions, item); + } + }); + } - return true; - } + function drag(event) { + var oldRow = item.row, + oldCol = item.col, + hasCallback = gridster.draggable && gridster.draggable.drag, + scrollSensitivity = gridster.draggable.scrollSensitivity, + scrollSpeed = gridster.draggable.scrollSpeed; + + var row = gridster.pixelsToRows(elmY); + var col = gridster.pixelsToColumns(elmX); + + var itemsInTheWay = gridster.getItems(row, col, item.sizeX, item.sizeY, item); + var hasItemsInTheWay = itemsInTheWay.length !== 0; + + if (gridster.swapping === true && hasItemsInTheWay) { + var boundingBoxItem = gridster.getBoundingBox(itemsInTheWay), + sameSize = boundingBoxItem.sizeX === item.sizeX && boundingBoxItem.sizeY === item.sizeY, + sameRow = boundingBoxItem.row === oldRow, + sameCol = boundingBoxItem.col === oldCol, + samePosition = boundingBoxItem.row === row && boundingBoxItem.col === col, + inline = sameRow || sameCol; + + if (sameSize && itemsInTheWay.length === 1) { + if (samePosition) { + gridster.swapItems(item, itemsInTheWay[0]); + } else if (inline) { + return; + } + } else if (boundingBoxItem.sizeX <= item.sizeX && boundingBoxItem.sizeY <= item.sizeY && inline) { + var emptyRow = item.row <= row ? item.row : row + item.sizeY, + emptyCol = item.col <= col ? item.col : col + item.sizeX, + rowOffset = emptyRow - boundingBoxItem.row, + colOffset = emptyCol - boundingBoxItem.col; + + for (var i = 0, l = itemsInTheWay.length; i < l; ++i) { + var itemInTheWay = itemsInTheWay[i]; + + var itemsInFreeSpace = gridster.getItems( + itemInTheWay.row + rowOffset, + itemInTheWay.col + colOffset, + itemInTheWay.sizeX, + itemInTheWay.sizeY, + item + ); + + if (itemsInFreeSpace.length === 0) { + gridster.putItem(itemInTheWay, itemInTheWay.row + rowOffset, itemInTheWay.col + colOffset); + } + } + } + } - function mouseUp(e) { - if (!$el.hasClass('gridster-item-moving') || $el.hasClass('gridster-item-resizing')) { - return false; - } + if (gridster.pushing !== false || !hasItemsInTheWay) { + item.row = row; + item.col = col; + } - mOffX = mOffY = 0; + if (event.pageY - realdocument.body.scrollTop < scrollSensitivity) { + realdocument.body.scrollTop = realdocument.body.scrollTop - scrollSpeed; + } else if ($window.innerHeight - (event.pageY - realdocument.body.scrollTop) < scrollSensitivity) { + realdocument.body.scrollTop = realdocument.body.scrollTop + scrollSpeed; + } - dragStop(e); + if (event.pageX - realdocument.body.scrollLeft < scrollSensitivity) { + realdocument.body.scrollLeft = realdocument.body.scrollLeft - scrollSpeed; + } else if ($window.innerWidth - (event.pageX - realdocument.body.scrollLeft) < scrollSensitivity) { + realdocument.body.scrollLeft = realdocument.body.scrollLeft + scrollSpeed; + } - return true; - } + if (hasCallback || oldRow !== item.row || oldCol !== item.col) { + scope.$apply(function () { + if (hasCallback) { + gridster.draggable.drag(event, $el, itemOptions, item); + } + }); + } + } + + function dragStop(event) { + $el.removeClass('gridster-item-moving'); + var row = gridster.pixelsToRows(elmY); + var col = gridster.pixelsToColumns(elmX); + if (gridster.pushing !== false || gridster.getItems(row, col, item.sizeX, item.sizeY, item).length === 0) { + item.row = row; + item.col = col; + } + gridster.movingItem = null; + item.setPosition(item.row, item.col); - function dragStart(event) { - $el.addClass('gridster-item-moving'); - gridster.movingItem = item; + scope.$apply(function () { + if (gridster.draggable && gridster.draggable.stop) { + gridster.draggable.stop(event, $el, itemOptions, item); + } + }); - gridster.updateHeight(item.sizeY); - scope.$apply(function() { - if (gridster.draggable && gridster.draggable.start) { - gridster.draggable.start(event, $el, itemOptions, item); + item.gridsterItemDragStop({ item: item }) } - }); - } - function drag(event) { - var oldRow = item.row, - oldCol = item.col, - hasCallback = gridster.draggable && gridster.draggable.drag, - scrollSensitivity = gridster.draggable.scrollSensitivity, - scrollSpeed = gridster.draggable.scrollSpeed; - - var row = gridster.pixelsToRows(elmY); - var col = gridster.pixelsToColumns(elmX); - - var itemsInTheWay = gridster.getItems(row, col, item.sizeX, item.sizeY, item); - var hasItemsInTheWay = itemsInTheWay.length !== 0; - - if (gridster.swapping === true && hasItemsInTheWay) { - var boundingBoxItem = gridster.getBoundingBox(itemsInTheWay), - sameSize = boundingBoxItem.sizeX === item.sizeX && boundingBoxItem.sizeY === item.sizeY, - sameRow = boundingBoxItem.row === oldRow, - sameCol = boundingBoxItem.col === oldCol, - samePosition = boundingBoxItem.row === row && boundingBoxItem.col === col, - inline = sameRow || sameCol; - - if (sameSize && itemsInTheWay.length === 1) { - if (samePosition) { - gridster.swapItems(item, itemsInTheWay[0]); - } else if (inline) { + var enabled = null; + var $dragHandles = null; + var unifiedInputs = []; + + this.enable = function () { + if (enabled === true) { return; } - } else if (boundingBoxItem.sizeX <= item.sizeX && boundingBoxItem.sizeY <= item.sizeY && inline) { - var emptyRow = item.row <= row ? item.row : row + item.sizeY, - emptyCol = item.col <= col ? item.col : col + item.sizeX, - rowOffset = emptyRow - boundingBoxItem.row, - colOffset = emptyCol - boundingBoxItem.col; - - for (var i = 0, l = itemsInTheWay.length; i < l; ++i) { - var itemInTheWay = itemsInTheWay[i]; - - var itemsInFreeSpace = gridster.getItems( - itemInTheWay.row + rowOffset, - itemInTheWay.col + colOffset, - itemInTheWay.sizeX, - itemInTheWay.sizeY, - item - ); - - if (itemsInFreeSpace.length === 0) { - gridster.putItem(itemInTheWay, itemInTheWay.row + rowOffset, itemInTheWay.col + colOffset); + + enabled = true; + + // timeout required for some template rendering + $el.ready(function () { + if (enabled !== true) { + return; + } + + // disable any existing draghandles + for (var u = 0, ul = unifiedInputs.length; u < ul; ++u) { + unifiedInputs[u].disable(); + } + unifiedInputs = []; + + if (gridster.draggable && gridster.draggable.handle) { + $dragHandles = angular.element($el[0].querySelectorAll(gridster.draggable.handle)); + if ($dragHandles.length === 0) { + // fall back to element if handle not found... + $dragHandles = $el; + } + } else { + $dragHandles = $el; + } + + for (var h = 0, hl = $dragHandles.length; h < hl; ++h) { + unifiedInputs[h] = new GridsterTouch($dragHandles[h], mouseDown, mouseMove, mouseUp); + unifiedInputs[h].enable(); } + }); + }; + + this.disable = function () { + if (enabled === false) { + return; } - } - } - if (gridster.pushing !== false || !hasItemsInTheWay) { - item.row = row; - item.col = col; - } + enabled = false; - if (event.pageY - realdocument.body.scrollTop < scrollSensitivity) { - realdocument.body.scrollTop = realdocument.body.scrollTop - scrollSpeed; - } else if ($window.innerHeight - (event.pageY - realdocument.body.scrollTop) < scrollSensitivity) { - realdocument.body.scrollTop = realdocument.body.scrollTop + scrollSpeed; - } + for (var u = 0, ul = unifiedInputs.length; u < ul; ++u) { + unifiedInputs[u].disable(); + } - if (event.pageX - realdocument.body.scrollLeft < scrollSensitivity) { - realdocument.body.scrollLeft = realdocument.body.scrollLeft - scrollSpeed; - } else if ($window.innerWidth - (event.pageX - realdocument.body.scrollLeft) < scrollSensitivity) { - realdocument.body.scrollLeft = realdocument.body.scrollLeft + scrollSpeed; - } + unifiedInputs = []; + }; - if (hasCallback || oldRow !== item.row || oldCol !== item.col) { - scope.$apply(function() { - if (hasCallback) { - gridster.draggable.drag(event, $el, itemOptions, item); + this.toggle = function (enabled) { + if (enabled) { + this.enable(); + } else { + this.disable(); } - }); - } - } + }; - function dragStop(event) { - $el.removeClass('gridster-item-moving'); - var row = gridster.pixelsToRows(elmY); - var col = gridster.pixelsToColumns(elmX); - if (gridster.pushing !== false || gridster.getItems(row, col, item.sizeX, item.sizeY, item).length === 0) { - item.row = row; - item.col = col; + this.destroy = function () { + this.disable(); + }; } - gridster.movingItem = null; - item.setPosition(item.row, item.col); - - scope.$apply(function() { - if (gridster.draggable && gridster.draggable.stop) { - gridster.draggable.stop(event, $el, itemOptions, item); - } - }); - item.gridsterItemDragStop({item: item}) + return GridsterDraggable; } + ]) - var enabled = null; - var $dragHandles = null; - var unifiedInputs = []; + .factory('GridsterResizable', ['GridsterTouch', function (GridsterTouch) { + function GridsterResizable($el, scope, gridster, item, itemOptions) { - this.enable = function() { - if (enabled === true) { - return; - } + function ResizeHandle(handleClass) { - enabled = true; + var hClass = handleClass; - // timeout required for some template rendering - $el.ready(function() { - if (enabled !== true) { - return; - } + var elmX, elmY, elmW, elmH, - // disable any existing draghandles - for (var u = 0, ul = unifiedInputs.length; u < ul; ++u) { - unifiedInputs[u].disable(); - } - unifiedInputs = []; + mouseX = 0, + mouseY = 0, + lastMouseX = 0, + lastMouseY = 0, + mOffX = 0, + mOffY = 0, + + minTop = 0, + maxTop = 9999, + minLeft = 0; + + var getMinHeight = function () { + return (item.minSizeY ? item.minSizeY : 1) * gridster.curRowHeight - gridster.margins[0]; + }; + var getMinWidth = function () { + return (item.minSizeX ? item.minSizeX : 1) * gridster.curColWidth - gridster.margins[1]; + }; - if (gridster.draggable && gridster.draggable.handle) { - $dragHandles = angular.element($el[0].querySelectorAll(gridster.draggable.handle)); - if ($dragHandles.length === 0) { - // fall back to element if handle not found... - $dragHandles = $el; + var originalWidth, originalHeight; + var savedDraggable; + + function mouseDown(e) { + switch (e.which) { + case 1: + // left mouse button + break; + case 2: + case 3: + // right or middle mouse button + return; } - } else { - $dragHandles = $el; - } - for (var h = 0, hl = $dragHandles.length; h < hl; ++h) { - unifiedInputs[h] = new GridsterTouch($dragHandles[h], mouseDown, mouseMove, mouseUp); - unifiedInputs[h].enable(); - } - }); - }; + // save the draggable setting to restore after resize + savedDraggable = gridster.draggable.enabled; + if (savedDraggable) { + gridster.draggable.enabled = false; + scope.$broadcast('gridster-draggable-changed', gridster); + } - this.disable = function() { - if (enabled === false) { - return; - } + // Get the current mouse position. + lastMouseX = e.pageX; + lastMouseY = e.pageY; - enabled = false; + // Record current widget dimensions + elmX = parseInt($el.css('left'), 10); + elmY = parseInt($el.css('top'), 10); + elmW = $el[0].offsetWidth; + elmH = $el[0].offsetHeight; - for (var u = 0, ul = unifiedInputs.length; u < ul; ++u) { - unifiedInputs[u].disable(); - } + originalWidth = item.sizeX; + originalHeight = item.sizeY; - unifiedInputs = []; - }; + resizeStart(e); - this.toggle = function(enabled) { - if (enabled) { - this.enable(); - } else { - this.disable(); - } - }; + return true; + } - this.destroy = function() { - this.disable(); - }; - } + function resizeStart(e) { + $el.addClass('gridster-item-moving'); + $el.addClass('gridster-item-resizing'); - return GridsterDraggable; - } -]) + gridster.movingItem = item; -.factory('GridsterResizable', ['GridsterTouch', function(GridsterTouch) { - function GridsterResizable($el, scope, gridster, item, itemOptions) { + item.setElementSizeX(); + item.setElementSizeY(); + item.setElementPosition(); + gridster.updateHeight(1); - function ResizeHandle(handleClass) { + scope.$apply(function () { + // callback + if (gridster.resizable && gridster.resizable.start) { + gridster.resizable.start(e, $el, itemOptions, item); // options is the item model + } + }); + } - var hClass = handleClass; + function mouseMove(e) { + var maxLeft = gridster.curWidth - 1; - var elmX, elmY, elmW, elmH, + // Get the current mouse position. + mouseX = e.pageX; + mouseY = e.pageY; - mouseX = 0, - mouseY = 0, - lastMouseX = 0, - lastMouseY = 0, - mOffX = 0, - mOffY = 0, + // Get the deltas + var diffX = mouseX - lastMouseX + mOffX; + var diffY = mouseY - lastMouseY + mOffY; + mOffX = mOffY = 0; - minTop = 0, - maxTop = 9999, - minLeft = 0; + // Update last processed mouse positions. + lastMouseX = mouseX; + lastMouseY = mouseY; - var getMinHeight = function() { - return (item.minSizeY ? item.minSizeY : 1) * gridster.curRowHeight - gridster.margins[0]; - }; - var getMinWidth = function() { - return (item.minSizeX ? item.minSizeX : 1) * gridster.curColWidth - gridster.margins[1]; - }; + var dY = diffY, + dX = diffX; - var originalWidth, originalHeight; - var savedDraggable; - - function mouseDown(e) { - switch (e.which) { - case 1: - // left mouse button - break; - case 2: - case 3: - // right or middle mouse button - return; - } + if (hClass.indexOf('n') >= 0) { + if (elmH - dY < getMinHeight()) { + diffY = elmH - getMinHeight(); + mOffY = dY - diffY; + } else if (elmY + dY < minTop) { + diffY = minTop - elmY; + mOffY = dY - diffY; + } + elmY += diffY; + elmH -= diffY; + } + if (hClass.indexOf('s') >= 0) { + if (elmH + dY < getMinHeight()) { + diffY = getMinHeight() - elmH; + mOffY = dY - diffY; + } else if (elmY + elmH + dY > maxTop) { + diffY = maxTop - elmY - elmH; + mOffY = dY - diffY; + } + elmH += diffY; + } + if (hClass.indexOf('w') >= 0) { + if (elmW - dX < getMinWidth()) { + diffX = elmW - getMinWidth(); + mOffX = dX - diffX; + } else if (elmX + dX < minLeft) { + diffX = minLeft - elmX; + mOffX = dX - diffX; + } + elmX += diffX; + elmW -= diffX; + } + if (hClass.indexOf('e') >= 0) { + if (elmW + dX < getMinWidth()) { + diffX = getMinWidth() - elmW; + mOffX = dX - diffX; + } else if (elmX + elmW + dX > maxLeft) { + diffX = maxLeft - elmX - elmW; + mOffX = dX - diffX; + } + elmW += diffX; + } - // save the draggable setting to restore after resize - savedDraggable = gridster.draggable.enabled; - if (savedDraggable) { - gridster.draggable.enabled = false; - scope.$broadcast('gridster-draggable-changed', gridster); - } + // set new position + $el.css({ + 'top': elmY + 'px', + 'left': elmX + 'px', + 'width': elmW + 'px', + 'height': elmH + 'px' + }); - // Get the current mouse position. - lastMouseX = e.pageX; - lastMouseY = e.pageY; + resize(e); - // Record current widget dimensions - elmX = parseInt($el.css('left'), 10); - elmY = parseInt($el.css('top'), 10); - elmW = $el[0].offsetWidth; - elmH = $el[0].offsetHeight; + return true; + } - originalWidth = item.sizeX; - originalHeight = item.sizeY; + function mouseUp(e) { + // restore draggable setting to its original state + if (gridster.draggable.enabled !== savedDraggable) { + gridster.draggable.enabled = savedDraggable; + scope.$broadcast('gridster-draggable-changed', gridster); + } - resizeStart(e); + mOffX = mOffY = 0; - return true; - } + resizeStop(e); - function resizeStart(e) { - $el.addClass('gridster-item-moving'); - $el.addClass('gridster-item-resizing'); + return true; + } - gridster.movingItem = item; + function resize(e) { + var oldRow = item.row, + oldCol = item.col, + oldSizeX = item.sizeX, + oldSizeY = item.sizeY, + hasCallback = gridster.resizable && gridster.resizable.resize; + + var col = item.col; + // only change column if grabbing left edge + if (['w', 'nw', 'sw'].indexOf(handleClass) !== -1) { + col = gridster.pixelsToColumns(elmX, false); + } - item.setElementSizeX(); - item.setElementSizeY(); - item.setElementPosition(); - gridster.updateHeight(1); + var row = item.row; + // only change row if grabbing top edge + if (['n', 'ne', 'nw'].indexOf(handleClass) !== -1) { + row = gridster.pixelsToRows(elmY, false); + } - scope.$apply(function() { - // callback - if (gridster.resizable && gridster.resizable.start) { - gridster.resizable.start(e, $el, itemOptions, item); // options is the item model - } - }); - } + var sizeX = item.sizeX; + // only change row if grabbing left or right edge + if (['n', 's'].indexOf(handleClass) === -1) { + sizeX = gridster.pixelsToColumns(elmW, true); + } - function mouseMove(e) { - var maxLeft = gridster.curWidth - 1; + var sizeY = item.sizeY; + // only change row if grabbing top or bottom edge + if (['e', 'w'].indexOf(handleClass) === -1) { + sizeY = gridster.pixelsToRows(elmH, true); + } - // Get the current mouse position. - mouseX = e.pageX; - mouseY = e.pageY; - // Get the deltas - var diffX = mouseX - lastMouseX + mOffX; - var diffY = mouseY - lastMouseY + mOffY; - mOffX = mOffY = 0; + var canOccupy = row > -1 && col > -1 && sizeX + col <= gridster.columns && sizeY + row <= gridster.maxRows; + if (canOccupy && (gridster.pushing !== false || gridster.getItems(row, col, sizeX, sizeY, item).length === 0)) { + item.row = row; + item.col = col; + item.sizeX = sizeX; + item.sizeY = sizeY; + } + var isChanged = item.row !== oldRow || item.col !== oldCol || item.sizeX !== oldSizeX || item.sizeY !== oldSizeY; - // Update last processed mouse positions. - lastMouseX = mouseX; - lastMouseY = mouseY; + if (isChanged) { + item.gridsterItemResizing({ item: item }); + } - var dY = diffY, - dX = diffX; + if (hasCallback || isChanged) { + scope.$apply(function () { + if (hasCallback) { + gridster.resizable.resize(e, $el, itemOptions, item); // options is the item model + } - if (hClass.indexOf('n') >= 0) { - if (elmH - dY < getMinHeight()) { - diffY = elmH - getMinHeight(); - mOffY = dY - diffY; - } else if (elmY + dY < minTop) { - diffY = minTop - elmY; - mOffY = dY - diffY; + scope.gridsterItem.gridsterItemSizeChanged({ item: item }); + }); + } } - elmY += diffY; - elmH -= diffY; - } - if (hClass.indexOf('s') >= 0) { - if (elmH + dY < getMinHeight()) { - diffY = getMinHeight() - elmH; - mOffY = dY - diffY; - } else if (elmY + elmH + dY > maxTop) { - diffY = maxTop - elmY - elmH; - mOffY = dY - diffY; - } - elmH += diffY; - } - if (hClass.indexOf('w') >= 0) { - if (elmW - dX < getMinWidth()) { - diffX = elmW - getMinWidth(); - mOffX = dX - diffX; - } else if (elmX + dX < minLeft) { - diffX = minLeft - elmX; - mOffX = dX - diffX; - } - elmX += diffX; - elmW -= diffX; - } - if (hClass.indexOf('e') >= 0) { - if (elmW + dX < getMinWidth()) { - diffX = getMinWidth() - elmW; - mOffX = dX - diffX; - } else if (elmX + elmW + dX > maxLeft) { - diffX = maxLeft - elmX - elmW; - mOffX = dX - diffX; - } - elmW += diffX; - } - // set new position - $el.css({ - 'top': elmY + 'px', - 'left': elmX + 'px', - 'width': elmW + 'px', - 'height': elmH + 'px' - }); + function resizeStop(e) { + $el.removeClass('gridster-item-moving'); + $el.removeClass('gridster-item-resizing'); - resize(e); + gridster.movingItem = null; - return true; - } - - function mouseUp(e) { - // restore draggable setting to its original state - if (gridster.draggable.enabled !== savedDraggable) { - gridster.draggable.enabled = savedDraggable; - scope.$broadcast('gridster-draggable-changed', gridster); - } + item.setPosition(item.row, item.col); + item.setSizeY(item.sizeY); + item.setSizeX(item.sizeX); - mOffX = mOffY = 0; + if (gridster.resizable) { + item.gridsterItemResizedStop({ item: item }); + } - resizeStop(e); + scope.$apply(function () { + if (gridster.resizable && gridster.resizable.stop) { + gridster.resizable.stop(e, $el, itemOptions, item); // options is the item model + } - return true; - } + scope.gridsterItem.gridsterItemSizeChanged({ item: item }); + }); + } - function resize(e) { - var oldRow = item.row, - oldCol = item.col, - oldSizeX = item.sizeX, - oldSizeY = item.sizeY, - hasCallback = gridster.resizable && gridster.resizable.resize; - - var col = item.col; - // only change column if grabbing left edge - if (['w', 'nw', 'sw'].indexOf(handleClass) !== -1) { - col = gridster.pixelsToColumns(elmX, false); - } + var $dragHandle = null; + var unifiedInput; - var row = item.row; - // only change row if grabbing top edge - if (['n', 'ne', 'nw'].indexOf(handleClass) !== -1) { - row = gridster.pixelsToRows(elmY, false); - } + this.enable = function () { + if (!$dragHandle) { + $dragHandle = angular.element('
'); + $el.append($dragHandle); + } - var sizeX = item.sizeX; - // only change row if grabbing left or right edge - if (['n', 's'].indexOf(handleClass) === -1) { - sizeX = gridster.pixelsToColumns(elmW, true); - } + unifiedInput = new GridsterTouch($dragHandle[0], mouseDown, mouseMove, mouseUp); + unifiedInput.enable(); + }; - var sizeY = item.sizeY; - // only change row if grabbing top or bottom edge - if (['e', 'w'].indexOf(handleClass) === -1) { - sizeY = gridster.pixelsToRows(elmH, true); - } + this.disable = function () { + if ($dragHandle) { + $dragHandle.remove(); + $dragHandle = null; + } + unifiedInput.disable(); + unifiedInput = undefined; + }; - var canOccupy = row > -1 && col > -1 && sizeX + col <= gridster.columns && sizeY + row <= gridster.maxRows; - if (canOccupy && (gridster.pushing !== false || gridster.getItems(row, col, sizeX, sizeY, item).length === 0)) { - item.row = row; - item.col = col; - item.sizeX = sizeX; - item.sizeY = sizeY; + this.destroy = function () { + this.disable(); + }; } - var isChanged = item.row !== oldRow || item.col !== oldCol || item.sizeX !== oldSizeX || item.sizeY !== oldSizeY; - if (isChanged){ - item.gridsterItemResizing({item: item}); + var handles = []; + var handlesOpts = gridster.resizable.handles; + if (typeof handlesOpts === 'string') { + handlesOpts = gridster.resizable.handles.split(','); } + var enabled = false; - if (hasCallback || isChanged) { - scope.$apply(function() { - if (hasCallback) { - gridster.resizable.resize(e, $el, itemOptions, item); // options is the item model - } - }); + for (var c = 0, l = handlesOpts.length; c < l; c++) { + handles.push(new ResizeHandle(handlesOpts[c])); } - } - - function resizeStop(e) { - $el.removeClass('gridster-item-moving'); - $el.removeClass('gridster-item-resizing'); - gridster.movingItem = null; + this.enable = function () { + if (enabled) { + return; + } + for (var c = 0, l = handles.length; c < l; c++) { + handles[c].enable(); + } + enabled = true; + }; - item.setPosition(item.row, item.col); - item.setSizeY(item.sizeY); - item.setSizeX(item.sizeX); + this.disable = function () { + if (!enabled) { + return; + } + for (var c = 0, l = handles.length; c < l; c++) { + handles[c].disable(); + } + enabled = false; + }; - if (gridster.resizable) { - item.gridsterItemResizedStop({item: item}); - } + this.toggle = function (enabled) { + if (enabled) { + this.enable(); + } else { + this.disable(); + } + }; - scope.$apply(function() { - if (gridster.resizable && gridster.resizable.stop) { - gridster.resizable.stop(e, $el, itemOptions, item); // options is the item model + this.destroy = function () { + for (var c = 0, l = handles.length; c < l; c++) { + handles[c].destroy(); } - }); + }; } + return GridsterResizable; + }]) + + .factory('gridsterDebounce', function () { + return function gridsterDebounce(func, wait, immediate) { + var timeout; + return function () { + var context = this, + args = arguments; + var later = function () { + timeout = null; + if (!immediate) { + func.apply(context, args); + } + }; + var callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) { + func.apply(context, args); + } + }; + }; + }) - var $dragHandle = null; - var unifiedInput; - - this.enable = function() { - if (!$dragHandle) { - $dragHandle = angular.element('
'); - $el.append($dragHandle); - } + /** + * GridsterItem directive + * @param $parse + * @param GridsterDraggable + * @param GridsterResizable + * @param gridsterDebounce + */ + .directive('gridsterItem', ['$parse', 'GridsterDraggable', 'GridsterResizable', 'gridsterDebounce', + function ($parse, GridsterDraggable, GridsterResizable, gridsterDebounce) { + return { + scope: true, + restrict: 'EA', + controller: 'GridsterItemCtrl', + controllerAs: 'gridsterItem', + require: ['^gridster', 'gridsterItem'], + bindToController: { + gridsterItemInitialized: '&', + gridsterItemResized: '&', + gridsterItemDragStop: '&', + gridsterItemResizing: '&', + gridsterItemResizedStop: '&', + gridsterItemMovedDown: '&', + gridsterItemFloatUp: '&', + gridsterItemSizeChanged: '&' + }, + + link: function (scope, $el, attrs, controllers) { + var optionsKey = attrs.gridsterItem, + options; + + var gridster = controllers[0], + item = controllers[1]; + + scope.gridster = gridster; + + // bind the item's position properties + // options can be an object specified by gridster-item="object" + // or the options can be the element html attributes object + if (optionsKey) { + var $optionsGetter = $parse(optionsKey); + options = $optionsGetter(scope) || {}; + if (!options && $optionsGetter.assign) { + options = { + row: item.row, + col: item.col, + sizeX: item.sizeX, + sizeY: item.sizeY, + minSizeX: 0, + minSizeY: 0, + maxSizeX: null, + maxSizeY: null + }; + $optionsGetter.assign(scope, options); + } + } else { + options = attrs; + } - unifiedInput = new GridsterTouch($dragHandle[0], mouseDown, mouseMove, mouseUp); - unifiedInput.enable(); - }; + item.init($el, gridster); + + $el.addClass('gridster-item'); + + var aspects = ['minSizeX', 'maxSizeX', 'minSizeY', 'maxSizeY', 'sizeX', 'sizeY', 'row', 'col'], + $getters = {}; + + var expressions = []; + var aspectFn = function (aspect) { + var expression; + if (typeof options[aspect] === 'string') { + // watch the expression in the scope + expression = options[aspect]; + } else if (typeof options[aspect.toLowerCase()] === 'string') { + // watch the expression in the scope + expression = options[aspect.toLowerCase()]; + } else if (optionsKey) { + // watch the expression on the options object in the scope + expression = optionsKey + '.' + aspect; + } else { + return; + } + expressions.push('"' + aspect + '":' + expression); + $getters[aspect] = $parse(expression); - this.disable = function() { - if ($dragHandle) { - $dragHandle.remove(); - $dragHandle = null; - } + // initial set + var val = $getters[aspect](scope); + if (typeof val === 'number') { + item[aspect] = val; + } + }; - unifiedInput.disable(); - unifiedInput = undefined; - }; + for (var i = 0, l = aspects.length; i < l; ++i) { + aspectFn(aspects[i]); + } - this.destroy = function() { - this.disable(); - }; - } - - var handles = []; - var handlesOpts = gridster.resizable.handles; - if (typeof handlesOpts === 'string') { - handlesOpts = gridster.resizable.handles.split(','); - } - var enabled = false; - - for (var c = 0, l = handlesOpts.length; c < l; c++) { - handles.push(new ResizeHandle(handlesOpts[c])); - } - - this.enable = function() { - if (enabled) { - return; - } - for (var c = 0, l = handles.length; c < l; c++) { - handles[c].enable(); - } - enabled = true; - }; + var watchExpressions = '{' + expressions.join(',') + '}'; + // when the value changes externally, update the internal item object + scope.$watchCollection(watchExpressions, function (newVals, oldVals) { + for (var aspect in newVals) { + var newVal = newVals[aspect]; + var oldVal = oldVals[aspect]; + if (oldVal === newVal) { + continue; + } + newVal = parseInt(newVal, 10); + if (!isNaN(newVal)) { + item[aspect] = newVal; + } + } + }); - this.disable = function() { - if (!enabled) { - return; - } - for (var c = 0, l = handles.length; c < l; c++) { - handles[c].disable(); - } - enabled = false; - }; - - this.toggle = function(enabled) { - if (enabled) { - this.enable(); - } else { - this.disable(); - } - }; + function positionChanged() { + // call setPosition so the element and gridster controller are updated + item.setPosition(item.row, item.col); - this.destroy = function() { - for (var c = 0, l = handles.length; c < l; c++) { - handles[c].destroy(); - } - }; - } - return GridsterResizable; -}]) - -.factory('gridsterDebounce', function() { - return function gridsterDebounce(func, wait, immediate) { - var timeout; - return function() { - var context = this, - args = arguments; - var later = function() { - timeout = null; - if (!immediate) { - func.apply(context, args); - } - }; - var callNow = immediate && !timeout; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - if (callNow) { - func.apply(context, args); - } - }; - }; -}) - -/** -* GridsterItem directive -* @param $parse -* @param GridsterDraggable -* @param GridsterResizable -* @param gridsterDebounce -*/ -.directive('gridsterItem', ['$parse', 'GridsterDraggable', 'GridsterResizable', 'gridsterDebounce', -function($parse, GridsterDraggable, GridsterResizable, gridsterDebounce) { - return { - scope: true, - restrict: 'EA', - controller: 'GridsterItemCtrl', - controllerAs: 'gridsterItem', - require: ['^gridster', 'gridsterItem'], - bindToController:{ - gridsterItemInitialized:'&', - gridsterItemResized:'&', - gridsterItemDragStop : '&', - gridsterItemResizing: '&', - gridsterItemResizedStop: '&', - gridsterItemMovedDown: '&', - gridsterItemFloatUp: '&' - }, - - link: function(scope, $el, attrs, controllers) { - var optionsKey = attrs.gridsterItem, - options; - - var gridster = controllers[0], - item = controllers[1]; - - scope.gridster = gridster; - - // bind the item's position properties - // options can be an object specified by gridster-item="object" - // or the options can be the element html attributes object - if (optionsKey) { - var $optionsGetter = $parse(optionsKey); - options = $optionsGetter(scope) || {}; - if (!options && $optionsGetter.assign) { - options = { - row: item.row, - col: item.col, - sizeX: item.sizeX, - sizeY: item.sizeY, - minSizeX: 0, - minSizeY: 0, - maxSizeX: null, - maxSizeY: null - }; - $optionsGetter.assign(scope, options); - } - } else { - options = attrs; - } + // when internal item position changes, update externally bound values + if ($getters.row && $getters.row.assign) { + $getters.row.assign(scope, item.row); + } + if ($getters.col && $getters.col.assign) { + $getters.col.assign(scope, item.col); + } + } + scope.$watch(function () { + return item.row + ',' + item.col; + }, positionChanged); + + function sizeChanged() { + var changedX = item.setSizeX(item.sizeX, true); + if (changedX && $getters.sizeX && $getters.sizeX.assign) { + $getters.sizeX.assign(scope, item.sizeX); + } + var changedY = item.setSizeY(item.sizeY, true); + if (changedY && $getters.sizeY && $getters.sizeY.assign) { + $getters.sizeY.assign(scope, item.sizeY); + } - item.init($el, gridster); - - $el.addClass('gridster-item'); - - var aspects = ['minSizeX', 'maxSizeX', 'minSizeY', 'maxSizeY', 'sizeX', 'sizeY', 'row', 'col'], - $getters = {}; - - var expressions = []; - var aspectFn = function(aspect) { - var expression; - if (typeof options[aspect] === 'string') { - // watch the expression in the scope - expression = options[aspect]; - } else if (typeof options[aspect.toLowerCase()] === 'string') { - // watch the expression in the scope - expression = options[aspect.toLowerCase()]; - } else if (optionsKey) { - // watch the expression on the options object in the scope - expression = optionsKey + '.' + aspect; - } else { - return; - } - expressions.push('"' + aspect + '":' + expression); - $getters[aspect] = $parse(expression); + if (changedX || changedY) { + item.gridster.moveOverlappingItems(item); + gridster.layoutChanged(); + scope.gridsterItem.gridsterItemResized({ item: item }); + scope.gridsterItem.gridsterItemSizeChanged({ item: item }); + scope.$broadcast('gridster-item-resized', item); + } + } - // initial set - var val = $getters[aspect](scope); - if (typeof val === 'number') { - item[aspect] = val; - } - }; + scope.$watch(function () { + return item.sizeY + ',' + item.sizeX + ',' + item.minSizeX + ',' + item.maxSizeX + ',' + item.minSizeY + ',' + item.maxSizeY; + }, sizeChanged); - for (var i = 0, l = aspects.length; i < l; ++i) { - aspectFn(aspects[i]); - } + var draggable = new GridsterDraggable($el, scope, gridster, item, options); + var resizable = new GridsterResizable($el, scope, gridster, item, options); - var watchExpressions = '{' + expressions.join(',') + '}'; - // when the value changes externally, update the internal item object - scope.$watchCollection(watchExpressions, function(newVals, oldVals) { - for (var aspect in newVals) { - var newVal = newVals[aspect]; - var oldVal = oldVals[aspect]; - if (oldVal === newVal) { - continue; - } - newVal = parseInt(newVal, 10); - if (!isNaN(newVal)) { - item[aspect] = newVal; - } - } - }); + var updateResizable = function () { + resizable.toggle(!gridster.isMobile && gridster.resizable && gridster.resizable.enabled); + }; + updateResizable(); - function positionChanged() { - // call setPosition so the element and gridster controller are updated - item.setPosition(item.row, item.col); + var updateDraggable = function () { + draggable.toggle(!gridster.isMobile && gridster.draggable && gridster.draggable.enabled); + }; + updateDraggable(); + + scope.$on('gridster-draggable-changed', updateDraggable); + scope.$on('gridster-resizable-changed', updateResizable); + scope.$on('gridster-resized', updateResizable); + scope.$on('gridster-mobile-changed', function () { + updateResizable(); + updateDraggable(); + }); + + function whichTransitionEvent() { + var el = document.createElement('div'); + var transitions = { + 'transition': 'transitionend', + 'OTransition': 'oTransitionEnd', + 'MozTransition': 'transitionend', + 'WebkitTransition': 'webkitTransitionEnd' + }; + for (var t in transitions) { + if (el.style[t] !== undefined) { + return transitions[t]; + } + } + } - // when internal item position changes, update externally bound values - if ($getters.row && $getters.row.assign) { - $getters.row.assign(scope, item.row); - } - if ($getters.col && $getters.col.assign) { - $getters.col.assign(scope, item.col); - } - } - scope.$watch(function() { - return item.row + ',' + item.col; - }, positionChanged); - - function sizeChanged() { - var changedX = item.setSizeX(item.sizeX, true); - if (changedX && $getters.sizeX && $getters.sizeX.assign) { - $getters.sizeX.assign(scope, item.sizeX); - } - var changedY = item.setSizeY(item.sizeY, true); - if (changedY && $getters.sizeY && $getters.sizeY.assign) { - $getters.sizeY.assign(scope, item.sizeY); - } + var debouncedTransitionEndPublisher = gridsterDebounce(function () { + scope.$apply(function () { + scope.gridsterItem.gridsterItemSizeChanged({ item: item }); + scope.$broadcast('gridster-item-transition-end', item); + }); + }, 50); - if (changedX || changedY) { - item.gridster.moveOverlappingItems(item); - gridster.layoutChanged(); - scope.gridsterItem.gridsterItemResized({item: item}); - scope.$broadcast('gridster-item-resized', item); - } - } + $el.on(whichTransitionEvent(), debouncedTransitionEndPublisher); - scope.$watch(function() { - return item.sizeY + ',' + item.sizeX + ',' + item.minSizeX + ',' + item.maxSizeX + ',' + item.minSizeY + ',' + item.maxSizeY; - }, sizeChanged); + scope.gridsterItem.gridsterItemInitialized({ item: item }); + scope.$broadcast('gridster-item-initialized', item); - var draggable = new GridsterDraggable($el, scope, gridster, item, options); - var resizable = new GridsterResizable($el, scope, gridster, item, options); + return scope.$on('$destroy', function () { + try { + resizable.destroy(); + draggable.destroy(); + } catch (e) { } - var updateResizable = function() { - resizable.toggle(!gridster.isMobile && gridster.resizable && gridster.resizable.enabled); - }; - updateResizable(); + try { + gridster.removeItem(item); + } catch (e) { } - var updateDraggable = function() { - draggable.toggle(!gridster.isMobile && gridster.draggable && gridster.draggable.enabled); - }; - updateDraggable(); - - scope.$on('gridster-draggable-changed', updateDraggable); - scope.$on('gridster-resizable-changed', updateResizable); - scope.$on('gridster-resized', updateResizable); - scope.$on('gridster-mobile-changed', function() { - updateResizable(); - updateDraggable(); - }); - - function whichTransitionEvent() { - var el = document.createElement('div'); - var transitions = { - 'transition': 'transitionend', - 'OTransition': 'oTransitionEnd', - 'MozTransition': 'transitionend', - 'WebkitTransition': 'webkitTransitionEnd' - }; - for (var t in transitions) { - if (el.style[t] !== undefined) { - return transitions[t]; + try { + item.destroy(); + } catch (e) { } + }); } - } + }; } + ]) + + .directive('gridsterNoDrag', function () { + return { + restrict: 'A', + link: function (scope, $element) { + $element.addClass('gridster-no-drag'); + } + }; + }) - var debouncedTransitionEndPublisher = gridsterDebounce(function() { - scope.$apply(function() { - scope.$broadcast('gridster-item-transition-end', item); - }); - }, 50); - - $el.on(whichTransitionEvent(), debouncedTransitionEndPublisher); - - scope.gridsterItem.gridsterItemInitialized({item: item}); - scope.$broadcast('gridster-item-initialized', item); - - return scope.$on('$destroy', function() { - try { - resizable.destroy(); - draggable.destroy(); - } catch (e) {} - - try { - gridster.removeItem(item); - } catch (e) {} - - try { - item.destroy(); - } catch (e) {} - }); - } - }; -} -]) - -.directive('gridsterNoDrag', function() { - return { - restrict: 'A', - link: function(scope, $element) { - $element.addClass('gridster-no-drag'); - } - }; -}) - -; + ; })(); From 75d6df4f2ca6cac72ee1e9067123d0e18606629a Mon Sep 17 00:00:00 2001 From: jocluz Date: Mon, 9 May 2016 09:10:54 -0300 Subject: [PATCH 16/16] Added event Added gridsterItemSizeChanged when gridster item position changed --- src/angular-gridster.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/angular-gridster.js b/src/angular-gridster.js index c0b858e4..8f269cf0 100755 --- a/src/angular-gridster.js +++ b/src/angular-gridster.js @@ -2148,6 +2148,7 @@ if ($getters.col && $getters.col.assign) { $getters.col.assign(scope, item.col); } + scope.gridsterItem.gridsterItemSizeChanged({ item: item }); } scope.$watch(function () { return item.row + ',' + item.col;