nexus site path corrected
[portal.git] / ecomp-portal-FE / client / bower_components_external / angular-att-gridster / angular-gridster.js
1 /*global define:true*/
2 (function(root, factory) {
3
4         'use strict';
5
6         if (typeof define === 'function' && define.amd) {
7                 // AMD
8                 define(['../angular/angular'], factory);
9         } else if (typeof exports === 'object') {
10                 // CommonJS
11                 module.exports = factory(require('angular'));
12         } else {
13                 // Browser, nothing "exported". Only registered as a module with angular.
14                 factory(root.angular);
15         }
16 }(this, function(angular) {
17
18         'use strict';
19         
20         var ie8 = false;
21         
22         var getInternetExplorerVersion = function()
23         // Returns the version of Internet Explorer or a -1
24         // (indicating the use of another browser).
25         {
26             var rv = -1; // Return value assumes failure.
27             if (navigator.appName == 'Microsoft Internet Explorer')
28             {
29                 var ua = navigator.userAgent;
30                 var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
31                 if (re.exec(ua) != null)
32                     rv = parseFloat(RegExp.$1);
33             }
34             return rv;
35         };
36         
37         var ver = getInternetExplorerVersion();
38
39         if ( ver > -1 && ver===8.0){
40             ie8 = true;
41         }
42         
43         // This returned angular module 'gridster' is what is exported.
44         return angular.module('attGridsterLib', [])
45
46         .constant('gridsterConfig', {
47                 columns: 6, // number of columns in the grid
48                 pushing: true, // whether to push other items out of the way
49                 floating: true, // whether to automatically float items up so they stack
50                 swapping: true, // whether or not to have items switch places instead of push down if they are the same size
51                 width: 'auto', // width of the grid. "auto" will expand the grid to its parent container
52                 colWidth: 'auto', // width of grid columns. "auto" will divide the width of the grid evenly among the columns
53                 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.
54                 margins: [10, 10], // margins in between grid items
55                 outerMargin: false,
56                 isMobile: false, // toggle mobile view
57                 mobileBreakPoint: 100, // width threshold to toggle mobile mode
58                 mobileModeEnabled: true, // whether or not to toggle mobile mode when screen width is less than mobileBreakPoint
59                 minColumns: 1, // minimum amount of columns the grid can scale down to
60                 minRows: 1, // minimum amount of rows to show if the grid is empty
61                 maxRows: 100, // maximum amount of rows in the grid
62                 defaultSizeX: 1, // default width of an item in columns
63                 defaultSizeY: 1, // default height of an item in rows
64                 minSizeX: 1, // minimum column width of an item
65                 maxSizeX: null, // maximum column width of an item
66                 minSizeY: 1, // minumum row height of an item
67                 maxSizeY: null, // maximum row height of an item
68                 saveGridItemCalculatedHeightInMobile: false, // grid item height in mobile display. true- to use the calculated height by sizeY given
69                 resizable: { // options to pass to resizable handler
70                         enabled: false,
71                         handles: ['s', 'e', 'n', 'w', 'se', 'ne', 'sw', 'nw']
72                 },
73                 draggable: { // options to pass to draggable handler
74                         enabled: true,
75                         scrollSensitivity: 20, // Distance in pixels from the edge of the viewport after which the viewport should scroll, relative to pointer
76                         scrollSpeed: 15 // Speed at which the window should scroll once the mouse pointer gets within scrollSensitivity distance
77                 }
78         })
79
80         .controller('GridsterCtrl', ['gridsterConfig', '$timeout',
81                 function(gridsterConfig, $timeout) {
82
83                         var gridster = this;
84
85                         /**
86                          * Create options from gridsterConfig constant
87                          */
88                         angular.extend(this, gridsterConfig);
89
90                         this.resizable = angular.extend({}, gridsterConfig.resizable || {});
91                         this.draggable = angular.extend({}, gridsterConfig.draggable || {});
92
93                         var flag = false;
94                         this.layoutChanged = function() {
95                                 if (flag) {
96                                         return;
97                                 }
98                                 flag = true;
99                                 $timeout(function() {
100                                         flag = false;
101                                         if (gridster.loaded) {
102                                                 gridster.floatItemsUp();
103                                         }
104                                         gridster.updateHeight(gridster.movingItem ? gridster.movingItem.sizeY : 0);
105                                 }, 30);
106                         };
107
108                         /**
109                          * A positional array of the items in the grid
110                          */
111                         this.grid = [];
112
113                         /**
114                          * Clean up after yourself
115                          */
116                         this.destroy = function() {
117                                 // empty the grid to cut back on the possibility
118                                 // of circular references
119                                 if (this.grid) {
120                                         this.grid = [];
121                                 }
122                                 this.$element = null;
123                         };
124
125                         /**
126                          * Overrides default options
127                          *
128                          * @param {Object} options The options to override
129                          */
130                         this.setOptions = function(options) {
131                                 if (!options) {
132                                         return;
133                                 }
134
135                                 options = angular.extend({}, options);
136
137                                 // all this to avoid using jQuery...
138                                 if (options.draggable) {
139                                         angular.extend(this.draggable, options.draggable);
140                                         delete(options.draggable);
141                                 }
142                                 if (options.resizable) {
143                                         angular.extend(this.resizable, options.resizable);
144                                         delete(options.resizable);
145                                 }
146
147                                 angular.extend(this, options);
148
149                                 if (!this.margins || this.margins.length !== 2) {
150                                         this.margins = [0, 0];
151                                 } else {
152                                         for (var x = 0, l = this.margins.length; x < l; ++x) {
153                                                 this.margins[x] = parseInt(this.margins[x], 10);
154                                                 if (isNaN(this.margins[x])) {
155                                                         this.margins[x] = 0;
156                                                 }
157                                         }
158                                 }
159                         };
160
161                         /**
162                          * Check if item can occupy a specified position in the grid
163                          *
164                          * @param {Object} item The item in question
165                          * @param {Number} row The row index
166                          * @param {Number} column The column index
167                          * @returns {Boolean} True if if item fits
168                          */
169                         this.canItemOccupy = function(item, row, column) {
170                                 return row > -1 && column > -1 && item.sizeX + column <= this.columns && item.sizeY + row <= this.maxRows;
171                         };
172
173                         /**
174                          * Set the item in the first suitable position
175                          *
176                          * @param {Object} item The item to insert
177                          */
178                         this.autoSetItemPosition = function(item) {
179                                 // walk through each row and column looking for a place it will fit
180                                 for (var rowIndex = 0; rowIndex < this.maxRows; ++rowIndex) {
181                                         for (var colIndex = 0; colIndex < this.columns; ++colIndex) {
182                                                 // only insert if position is not already taken and it can fit
183                                                 var items = this.getItems(rowIndex, colIndex, item.sizeX, item.sizeY, item);
184                                                 if (items.length === 0 && this.canItemOccupy(item, rowIndex, colIndex)) {
185                                                         this.putItem(item, rowIndex, colIndex);
186                                                         return;
187                                                 }
188                                         }
189                                 }
190                                 throw new Error('Unable to place item!');
191                         };
192
193                         /**
194                          * Gets items at a specific coordinate
195                          *
196                          * @param {Number} row
197                          * @param {Number} column
198                          * @param {Number} sizeX
199                          * @param {Number} sizeY
200                          * @param {Array} excludeItems An array of items to exclude from selection
201                          * @returns {Array} Items that match the criteria
202                          */
203                         this.getItems = function(row, column, sizeX, sizeY, excludeItems) {
204                                 var items = [];
205                                 if (!sizeX || !sizeY) {
206                                         sizeX = sizeY = 1;
207                                 }
208                                 if (excludeItems && !(excludeItems instanceof Array)) {
209                                         excludeItems = [excludeItems];
210                                 }
211                                 for (var h = 0; h < sizeY; ++h) {
212                                         for (var w = 0; w < sizeX; ++w) {
213                                                 var item = this.getItem(row + h, column + w, excludeItems);
214                                                 if (item && (!excludeItems || excludeItems.indexOf(item) === -1) && items.indexOf(item) === -1) {
215                                                         items.push(item);
216                                                 }
217                                         }
218                                 }
219                                 return items;
220                         };
221
222                         /**
223                          * @param {Array} items
224                          * @returns {Object} An item that represents the bounding box of the items
225                          */
226                         this.getBoundingBox = function(items) {
227
228                                 if (items.length === 0) {
229                                         return null;
230                                 }
231                                 if (items.length === 1) {
232                                         return {
233                                                 row: items[0].row,
234                                                 col: items[0].col,
235                                                 sizeY: items[0].sizeY,
236                                                 sizeX: items[0].sizeX
237                                         };
238                                 }
239
240                                 var maxRow = 0;
241                                 var maxCol = 0;
242                                 var minRow = 9999;
243                                 var minCol = 9999;
244
245                                 for (var i = 0, l = items.length; i < l; ++i) {
246                                         var item = items[i];
247                                         minRow = Math.min(item.row, minRow);
248                                         minCol = Math.min(item.col, minCol);
249                                         maxRow = Math.max(item.row + item.sizeY, maxRow);
250                                         maxCol = Math.max(item.col + item.sizeX, maxCol);
251                                 }
252
253                                 return {
254                                         row: minRow,
255                                         col: minCol,
256                                         sizeY: maxRow - minRow,
257                                         sizeX: maxCol - minCol
258                                 };
259                         };
260
261
262                         /**
263                          * Removes an item from the grid
264                          *
265                          * @param {Object} item
266                          */
267                         this.removeItem = function(item) {
268                                 for (var rowIndex = 0, l = this.grid.length; rowIndex < l; ++rowIndex) {
269                                         var columns = this.grid[rowIndex];
270                                         if (!columns) {
271                                                 continue;
272                                         }
273                                         var index = columns.indexOf(item);
274                                         if (index !== -1) {
275                                                 columns[index] = null;
276                                                 break;
277                                         }
278                                 }
279                                 this.layoutChanged();
280                         };
281
282                         /**
283                          * Returns the item at a specified coordinate
284                          *
285                          * @param {Number} row
286                          * @param {Number} column
287                          * @param {Array} excludeItems Items to exclude from selection
288                          * @returns {Object} The matched item or null
289                          */
290                         this.getItem = function(row, column, excludeItems) {
291                                 if (excludeItems && !(excludeItems instanceof Array)) {
292                                         excludeItems = [excludeItems];
293                                 }
294                                 var sizeY = 1;
295                                 while (row > -1) {
296                                         var sizeX = 1,
297                                                 col = column;
298                                         while (col > -1) {
299                                                 var items = this.grid[row];
300                                                 if (items) {
301                                                         var item = items[col];
302                                                         if (item && (!excludeItems || excludeItems.indexOf(item) === -1) && item.sizeX >= sizeX && item.sizeY >= sizeY) {
303                                                                 return item;
304                                                         }
305                                                 }
306                                                 ++sizeX;
307                                                 --col;
308                                         }
309                                         --row;
310                                         ++sizeY;
311                                 }
312                                 return null;
313                         };
314
315                         /**
316                          * Insert an array of items into the grid
317                          *
318                          * @param {Array} items An array of items to insert
319                          */
320                         this.putItems = function(items) {
321                                 for (var i = 0, l = items.length; i < l; ++i) {
322                                         this.putItem(items[i]);
323                                 }
324                         };
325
326                         /**
327                          * Insert a single item into the grid
328                          *
329                          * @param {Object} item The item to insert
330                          * @param {Number} row (Optional) Specifies the items row index
331                          * @param {Number} column (Optional) Specifies the items column index
332                          * @param {Array} ignoreItems
333                          */
334                         this.putItem = function(item, row, column, ignoreItems) {
335                                 // auto place item if no row specified
336                                 if (typeof row === 'undefined' || row === null) {
337                                         row = item.row;
338                                         column = item.col;
339                                         if (typeof row === 'undefined' || row === null) {
340                                                 this.autoSetItemPosition(item);
341                                                 return;
342                                         }
343                                 }
344
345                                 // keep item within allowed bounds
346                                 if (!this.canItemOccupy(item, row, column)) {
347                                         column = Math.min(this.columns - item.sizeX, Math.max(0, column));
348                                         row = Math.min(this.maxRows - item.sizeY, Math.max(0, row));
349                                 }
350
351                                 // check if item is already in grid
352                                 if (item.oldRow !== null && typeof item.oldRow !== 'undefined') {
353                                         var samePosition = item.oldRow === row && item.oldColumn === column;
354                                         var inGrid = this.grid[row] && this.grid[row][column] === item;
355                                         if (samePosition && inGrid) {
356                                                 item.row = row;
357                                                 item.col = column;
358                                                 return;
359                                         } else {
360                                                 // remove from old position
361                                                 var oldRow = this.grid[item.oldRow];
362                                                 if (oldRow && oldRow[item.oldColumn] === item) {
363                                                         delete oldRow[item.oldColumn];
364                                                 }
365                                         }
366                                 }
367
368                                 item.oldRow = item.row = row;
369                                 item.oldColumn = item.col = column;
370
371                                 this.moveOverlappingItems(item, ignoreItems);
372
373                                 if (!this.grid[row]) {
374                                         this.grid[row] = [];
375                                 }
376                                 this.grid[row][column] = item;
377
378                                 if (this.movingItem === item) {
379                                         this.floatItemUp(item);
380                                 }
381                                 this.layoutChanged();
382                         };
383
384                         /**
385                          * Trade row and column if item1 with item2
386                          *
387                          * @param {Object} item1
388                          * @param {Object} item2
389                          */
390                         this.swapItems = function(item1, item2) {
391                                 this.grid[item1.row][item1.col] = item2;
392                                 this.grid[item2.row][item2.col] = item1;
393
394                                 var item1Row = item1.row;
395                                 var item1Col = item1.col;
396                                 item1.row = item2.row;
397                                 item1.col = item2.col;
398                                 item2.row = item1Row;
399                                 item2.col = item1Col;
400                         };
401
402                         /**
403                          * Prevents items from being overlapped
404                          *
405                          * @param {Object} item The item that should remain
406                          * @param {Array} ignoreItems
407                          */
408                         this.moveOverlappingItems = function(item, ignoreItems) {
409                                 // don't move item, so ignore it
410                                 if (!ignoreItems) {
411                                         ignoreItems = [item];
412                                 } else if (ignoreItems.indexOf(item) === -1) {
413                                         ignoreItems = ignoreItems.slice(0);
414                                         ignoreItems.push(item);
415                                 }
416
417                                 // get the items in the space occupied by the item's coordinates
418                                 var overlappingItems = this.getItems(
419                                         item.row,
420                                         item.col,
421                                         item.sizeX,
422                                         item.sizeY,
423                                         ignoreItems
424                                 );
425                                 this.moveItemsDown(overlappingItems, item.row + item.sizeY, ignoreItems);
426                         };
427
428                         /**
429                          * Moves an array of items to a specified row
430                          *
431                          * @param {Array} items The items to move
432                          * @param {Number} newRow The target row
433                          * @param {Array} ignoreItems
434                          */
435                         this.moveItemsDown = function(items, newRow, ignoreItems) {
436                                 if (!items || items.length === 0) {
437                                         return;
438                                 }
439                                 items.sort(function(a, b) {
440                                         return a.row - b.row;
441                                 });
442
443                                 ignoreItems = ignoreItems ? ignoreItems.slice(0) : [];
444                                 var topRows = {},
445                                         item, i, l;
446
447                                 // calculate the top rows in each column
448                                 for (i = 0, l = items.length; i < l; ++i) {
449                                         item = items[i];
450                                         var topRow = topRows[item.col];
451                                         if (typeof topRow === 'undefined' || item.row < topRow) {
452                                                 topRows[item.col] = item.row;
453                                         }
454                                 }
455
456                                 // move each item down from the top row in its column to the row
457                                 for (i = 0, l = items.length; i < l; ++i) {
458                                         item = items[i];
459                                         var rowsToMove = newRow - topRows[item.col];
460                                         this.moveItemDown(item, item.row + rowsToMove, ignoreItems);
461                                         ignoreItems.push(item);
462                                 }
463                         };
464
465                         /**
466                          * Moves an item down to a specified row
467                          *
468                          * @param {Object} item The item to move
469                          * @param {Number} newRow The target row
470                          * @param {Array} ignoreItems
471                          */
472                         this.moveItemDown = function(item, newRow, ignoreItems) {
473                                 if (item.row >= newRow) {
474                                         return;
475                                 }
476                                 while (item.row < newRow) {
477                                         ++item.row;
478                                         this.moveOverlappingItems(item, ignoreItems);
479                                 }
480                                 this.putItem(item, item.row, item.col, ignoreItems);
481                         };
482
483                         /**
484                          * Moves all items up as much as possible
485                          */
486                         this.floatItemsUp = function() {
487                                 if (this.floating === false) {
488                                         return;
489                                 }
490                                 for (var rowIndex = 0, l = this.grid.length; rowIndex < l; ++rowIndex) {
491                                         var columns = this.grid[rowIndex];
492                                         if (!columns) {
493                                                 continue;
494                                         }
495                                         for (var colIndex = 0, len = columns.length; colIndex < len; ++colIndex) {
496                                                 var item = columns[colIndex];
497                                                 if (item) {
498                                                         this.floatItemUp(item);
499                                                 }
500                                         }
501                                 }
502                         };
503
504                         /**
505                          * Float an item up to the most suitable row
506                          *
507                          * @param {Object} item The item to move
508                          */
509                         this.floatItemUp = function(item) {
510                                 if (this.floating === false) {
511                                         return;
512                                 }
513                                 var colIndex = item.col,
514                                         sizeY = item.sizeY,
515                                         sizeX = item.sizeX,
516                                         bestRow = null,
517                                         bestColumn = null,
518                                         rowIndex = item.row - 1;
519
520                                 while (rowIndex > -1) {
521                                         var items = this.getItems(rowIndex, colIndex, sizeX, sizeY, item);
522                                         if (items.length !== 0) {
523                                                 break;
524                                         }
525                                         bestRow = rowIndex;
526                                         bestColumn = colIndex;
527                                         --rowIndex;
528                                 }
529                                 if (bestRow !== null) {
530                                         this.putItem(item, bestRow, bestColumn);
531                                 }
532                         };
533
534                         /**
535                          * Update gridsters height
536                          *
537                          * @param {Number} plus (Optional) Additional height to add
538                          */
539                         this.updateHeight = function(plus) {
540                                 var maxHeight = this.minRows;
541                                 plus = plus || 0;
542                                 for (var rowIndex = this.grid.length; rowIndex >= 0; --rowIndex) {
543                                         var columns = this.grid[rowIndex];
544                                         if (!columns) {
545                                                 continue;
546                                         }
547                                         for (var colIndex = 0, len = columns.length; colIndex < len; ++colIndex) {
548                                                 if (columns[colIndex]) {
549                                                         maxHeight = Math.max(maxHeight, rowIndex + plus + columns[colIndex].sizeY);
550                                                 }
551                                         }
552                                 }
553                                 this.gridHeight = this.maxRows - maxHeight > 0 ? Math.min(this.maxRows, maxHeight) : Math.max(this.maxRows, maxHeight);
554                         };
555
556                         /**
557                          * Returns the number of rows that will fit in given amount of pixels
558                          *
559                          * @param {Number} pixels
560                          * @param {Boolean} ceilOrFloor (Optional) Determines rounding method
561                          */
562                         this.pixelsToRows = function(pixels, ceilOrFloor) {
563                                 if (ceilOrFloor === true) {
564                                         return Math.ceil(pixels / this.curRowHeight);
565                                 } else if (ceilOrFloor === false) {
566                                         return Math.floor(pixels / this.curRowHeight);
567                                 }
568
569                                 return Math.round(pixels / this.curRowHeight);
570                         };
571
572                         /**
573                          * Returns the number of columns that will fit in a given amount of pixels
574                          *
575                          * @param {Number} pixels
576                          * @param {Boolean} ceilOrFloor (Optional) Determines rounding method
577                          * @returns {Number} The number of columns
578                          */
579                         this.pixelsToColumns = function(pixels, ceilOrFloor) {
580                                 if (ceilOrFloor === true) {
581                                         return Math.ceil(pixels / this.curColWidth);
582                                 } else if (ceilOrFloor === false) {
583                                         return Math.floor(pixels / this.curColWidth);
584                                 }
585
586                                 return Math.round(pixels / this.curColWidth);
587                         };
588                 }
589         ])
590
591         .directive('gridsterPreview', function() {
592                 return {
593                         replace: true,
594                         scope: true,
595                         require: '^gridster',
596                         template: '<div ng-style="previewStyle()" class="gridster-item gridster-preview-holder"></div>',
597                         link: function(scope, $el, attrs, gridster) {
598
599                                 /**
600                                  * @returns {Object} style object for preview element
601                                  */
602                                 scope.previewStyle = function() {
603                                     
604                                         if (!gridster.movingItem) {
605                                                 return {
606                                                         display: 'none'
607                                                 };
608                                         }
609
610                                         return {
611                                                 display: 'block',
612                                                 height: (gridster.movingItem.sizeY * gridster.curRowHeight - gridster.margins[0]) + 'px',
613                                                 width: (gridster.movingItem.sizeX * gridster.curColWidth - gridster.margins[1]) + 'px',
614                                                 top: (gridster.movingItem.row * gridster.curRowHeight + (gridster.outerMargin ? gridster.margins[0] : 0)) + 'px',
615                                                 left: (gridster.movingItem.col * gridster.curColWidth + (gridster.outerMargin ? gridster.margins[1] : 0)) + 'px'
616                                         };
617                                 };
618                         }
619                 };
620         })
621
622         /**
623          * The gridster directive
624          *
625          * @param {Function} $timeout
626          * @param {Object} $window
627          * @param {Object} $rootScope
628          * @param {Function} gridsterDebounce
629          */
630         .directive('gridster', ['$timeout', '$window', '$rootScope', 'gridsterDebounce',
631                 function($timeout, $window, $rootScope, gridsterDebounce) {
632                         return {
633                                 scope: true,
634                                 restrict: 'EAC',
635                                 controller: 'GridsterCtrl',
636                                 controllerAs: 'gridster',
637                                 compile: function($tplElem) {
638                                         
639                                         $tplElem.prepend('<div ng-if="gridster.movingItem" gridster-preview></div>');
640                                         
641                                         return function(scope, $elem, attrs, gridster) {
642                                                 gridster.loaded = false;
643
644                                                 gridster.$element = $elem;
645
646                                                 scope.gridster = gridster;
647
648                                                 $elem.addClass('gridster');
649
650                                                 var isVisible = function(ele) {
651                                                         return ele.style.visibility !== 'hidden' && ele.style.display !== 'none';
652                                                 };
653
654                                                 function refresh(config) {
655                                                         gridster.setOptions(config);
656
657                                                         if (!isVisible($elem[0])) {
658                                                                 return;
659                                                         }
660
661                                                         // resolve "auto" & "match" values
662                                                         if (gridster.width === 'auto') {
663                                                                 gridster.curWidth = $elem[0].offsetWidth || parseInt($elem.css('width'), 10);
664                                                         } else {
665                                                                 gridster.curWidth = gridster.width;
666                                                         }
667
668                                                         if (gridster.colWidth === 'auto') {
669                                                                 gridster.curColWidth = (gridster.curWidth + (gridster.outerMargin ? -gridster.margins[1] : gridster.margins[1])) / gridster.columns;
670                                                         } else {
671                                                                 gridster.curColWidth = gridster.colWidth;
672                                                         }
673
674                                                         gridster.curRowHeight = gridster.rowHeight;
675                                                         if (typeof gridster.rowHeight === 'string') {
676                                                                 if (gridster.rowHeight === 'match') {
677                                                                         gridster.curRowHeight = Math.round(gridster.curColWidth);
678                                                                 } else if (gridster.rowHeight.indexOf('*') !== -1) {
679                                                                         gridster.curRowHeight = Math.round(gridster.curColWidth * gridster.rowHeight.replace('*', '').replace(' ', ''));
680                                                                 } else if (gridster.rowHeight.indexOf('/') !== -1) {
681                                                                         gridster.curRowHeight = Math.round(gridster.curColWidth / gridster.rowHeight.replace('/', '').replace(' ', ''));
682                                                                 }
683                                                         }
684
685                                                         gridster.isMobile = gridster.mobileModeEnabled && gridster.curWidth <= gridster.mobileBreakPoint;
686
687                                                         // loop through all items and reset their CSS
688                                                         for (var rowIndex = 0, l = gridster.grid.length; rowIndex < l; ++rowIndex) {
689                                                                 var columns = gridster.grid[rowIndex];
690                                                                 if (!columns) {
691                                                                         continue;
692                                                                 }
693
694                                                                 for (var colIndex = 0, len = columns.length; colIndex < len; ++colIndex) {
695                                                                         if (columns[colIndex]) {
696                                                                                 var item = columns[colIndex];
697                                                                                 item.setElementPosition();
698                                                                                 item.setElementSizeY();
699                                                                                 item.setElementSizeX();
700                                                                         }
701                                                                 }
702                                                         }
703
704                                                         updateHeight();
705                                                 }
706
707                                                 var optionsKey = attrs.gridster;
708                                                 if (optionsKey) {
709                                                         scope.$parent.$watch(optionsKey, function(newConfig) {
710                                                                 refresh(newConfig);
711                                                         }, true);
712                                                 } else {
713                                                         refresh({});
714                                                 }
715
716                                                 scope.$watch(function() {
717                                                         return gridster.loaded;
718                                                 }, function() {
719                                                         if (gridster.loaded) {
720                                                                 $elem.addClass('gridster-loaded');
721                                                         } else {
722                                                                 $elem.removeClass('gridster-loaded');
723                                                         }
724                                                 });
725
726                                                 scope.$watch(function() {
727                                                         return gridster.isMobile;
728                                                 }, function() {
729                                                         if (gridster.isMobile) {
730                                                                 $elem.addClass('gridster-mobile').removeClass('gridster-desktop');
731                                                         } else {
732                                                                 $elem.removeClass('gridster-mobile').addClass('gridster-desktop');
733                                                         }
734                                                         $rootScope.$broadcast('gridster-mobile-changed', gridster);
735                                                 });
736
737                                                 scope.$watch(function() {
738                                                         return gridster.draggable;
739                                                 }, function() {
740                                                         $rootScope.$broadcast('gridster-draggable-changed', gridster);
741                                                 }, true);
742
743                                                 scope.$watch(function() {
744                                                         return gridster.resizable;
745                                                 }, function() {
746                                                         $rootScope.$broadcast('gridster-resizable-changed', gridster);
747                                                 }, true);
748
749                                                 function updateHeight() {
750                                                     if(gridster.gridHeight){ //need to put this check, otherwise fail in IE8
751                                                         $elem.css('height', (gridster.gridHeight * gridster.curRowHeight) + (gridster.outerMargin ? gridster.margins[0] : -gridster.margins[0]) + 'px');
752                                                     }
753                                                 }
754
755                                                 scope.$watch(function() {
756                                                         return gridster.gridHeight;
757                                                 }, updateHeight);
758
759                                                 scope.$watch(function() {
760                                                         return gridster.movingItem;
761                                                 }, function() {
762                                                         gridster.updateHeight(gridster.movingItem ? gridster.movingItem.sizeY : 0);
763                                                 });
764
765                                                 var prevWidth = $elem[0].offsetWidth || parseInt($elem.css('width'), 10);
766
767                                                 var resize = function() {
768                                                         var width = $elem[0].offsetWidth || parseInt($elem.css('width'), 10);
769
770                                                         if (!width || width === prevWidth || gridster.movingItem) {
771                                                                 return;
772                                                         }
773                                                         prevWidth = width;
774
775                                                         if (gridster.loaded) {
776                                                                 $elem.removeClass('gridster-loaded');
777                                                         }
778
779                                                         refresh();
780
781                                                         if (gridster.loaded) {
782                                                                 $elem.addClass('gridster-loaded');
783                                                         }
784
785                                                         $rootScope.$broadcast('gridster-resized', [width, $elem[0].offsetHeight], gridster);
786                                                 };
787
788                                                 // track element width changes any way we can
789                                                 var onResize = gridsterDebounce(function onResize() {
790                                                         resize();
791                                                         $timeout(function() {
792                                                                 scope.$apply();
793                                                         });
794                                                 }, 100);
795
796                                                 scope.$watch(function() {
797                                                         return isVisible($elem[0]);
798                                                 }, onResize);
799
800                                                 // see https://github.com/sdecima/javascript-detect-element-resize
801                                                 if (typeof window.addResizeListener === 'function') {
802                                                         window.addResizeListener($elem[0], onResize);
803                                                 } else {
804                                                         scope.$watch(function() {
805                                                                 return $elem[0].offsetWidth || parseInt($elem.css('width'), 10);
806                                                         }, resize);
807                                                 }
808                                                 var $win = angular.element($window);
809                                                 $win.on('resize', onResize);
810
811                                                 // be sure to cleanup
812                                                 scope.$on('$destroy', function() {
813                                                         gridster.destroy();
814                                                         $win.off('resize', onResize);
815                                                         if (typeof window.removeResizeListener === 'function') {
816                                                                 window.removeResizeListener($elem[0], onResize);
817                                                         }
818                                                 });
819
820                                                 // allow a little time to place items before floating up
821                                                 $timeout(function() {
822                                                         scope.$watch('gridster.floating', function() {
823                                                                 gridster.floatItemsUp();
824                                                         });
825                                                         gridster.loaded = true;
826                                                 }, 100);
827                                         };
828                                 }
829                         };
830                 }
831         ])
832
833         .controller('GridsterItemCtrl', function() {
834                 this.$element = null;
835                 this.gridster = null;
836                 this.row = null;
837                 this.col = null;
838                 this.sizeX = null;
839                 this.sizeY = null;
840                 this.minSizeX = 0;
841                 this.minSizeY = 0;
842                 this.maxSizeX = null;
843                 this.maxSizeY = null;
844
845                 this.init = function($element, gridster) {
846                         this.$element = $element;
847                         this.gridster = gridster;
848                         this.sizeX = gridster.defaultSizeX;
849                         this.sizeY = gridster.defaultSizeY;
850                 };
851
852                 this.destroy = function() {
853                         // set these to null to avoid the possibility of circular references
854                         this.gridster = null;
855                         this.$element = null;
856                 };
857
858                 /**
859                  * Returns the items most important attributes
860                  */
861                 this.toJSON = function() {
862                         return {
863                                 row: this.row,
864                                 col: this.col,
865                                 sizeY: this.sizeY,
866                                 sizeX: this.sizeX
867                         };
868                 };
869
870                 this.isMoving = function() {
871                         return this.gridster.movingItem === this;
872                 };
873
874                 /**
875                  * Set the items position
876                  *
877                  * @param {Number} row
878                  * @param {Number} column
879                  */
880                 this.setPosition = function(row, column) {
881                         this.gridster.putItem(this, row, column);
882
883                         if (!this.isMoving()) {
884                                 this.setElementPosition();
885                         }
886                 };
887
888                 /**
889                  * Sets a specified size property
890                  *
891                  * @param {String} key Can be either "x" or "y"
892                  * @param {Number} value The size amount
893                  * @param {Boolean} preventMove
894                  */
895                 this.setSize = function(key, value, preventMove) {
896                         key = key.toUpperCase();
897                         var camelCase = 'size' + key,
898                                 titleCase = 'Size' + key;
899                         if (value === '') {
900                                 return;
901                         }
902                         value = parseInt(value, 10);
903                         if (isNaN(value) || value === 0) {
904                                 value = this.gridster['default' + titleCase];
905                         }
906                         var max = key === 'X' ? this.gridster.columns : this.gridster.maxRows;
907                         if (this['max' + titleCase]) {
908                                 max = Math.min(this['max' + titleCase], max);
909                         }
910                         if (this.gridster['max' + titleCase]) {
911                                 max = Math.min(this.gridster['max' + titleCase], max);
912                         }
913                         if (key === 'X' && this.cols) {
914                                 max -= this.cols;
915                         } else if (key === 'Y' && this.rows) {
916                                 max -= this.rows;
917                         }
918
919                         var min = 0;
920                         if (this['min' + titleCase]) {
921                                 min = Math.max(this['min' + titleCase], min);
922                         }
923                         if (this.gridster['min' + titleCase]) {
924                                 min = Math.max(this.gridster['min' + titleCase], min);
925                         }
926
927                         value = Math.max(Math.min(value, max), min);
928
929                         var changed = (this[camelCase] !== value || (this['old' + titleCase] && this['old' + titleCase] !== value));
930                         this['old' + titleCase] = this[camelCase] = value;
931
932                         if (!this.isMoving()) {
933                                 this['setElement' + titleCase]();
934                         }
935                         if (!preventMove && changed) {
936                                 this.gridster.moveOverlappingItems(this);
937                                 this.gridster.layoutChanged();
938                         }
939
940                         return changed;
941                 };
942
943                 /**
944                  * Sets the items sizeY property
945                  *
946                  * @param {Number} rows
947                  * @param {Boolean} preventMove
948                  */
949                 this.setSizeY = function(rows, preventMove) {
950                         return this.setSize('Y', rows, preventMove);
951                 };
952
953                 /**
954                  * Sets the items sizeX property
955                  *
956                  * @param {Number} columns
957                  * @param {Boolean} preventMove
958                  */
959                 this.setSizeX = function(columns, preventMove) {
960                         return this.setSize('X', columns, preventMove);
961                 };
962
963                 /**
964                  * Sets an elements position on the page
965                  */
966                 this.setElementPosition = function() {
967                         if (this.gridster.isMobile) {
968                                 this.$element.css({
969                                         marginLeft: this.gridster.margins[0] + 'px',
970                                         marginRight: this.gridster.margins[0] + 'px',
971                                         marginTop: this.gridster.margins[1] + 'px',
972                                         marginBottom: this.gridster.margins[1] + 'px',
973                                         top: '',
974                                         left: ''
975                                 });
976                         } else {
977                                 this.$element.css({
978                                         margin: 0,
979                                         top: (this.row * this.gridster.curRowHeight + (this.gridster.outerMargin ? this.gridster.margins[0] : 0)) + 'px',
980                                         left: (this.col * this.gridster.curColWidth + (this.gridster.outerMargin ? this.gridster.margins[1] : 0)) + 'px'
981                                 });
982                         }
983                 };
984
985                 /**
986                  * Sets an elements height
987                  */
988                 this.setElementSizeY = function() {
989                         if (this.gridster.isMobile && !this.gridster.saveGridItemCalculatedHeightInMobile) {
990                                 this.$element.css('height', '');
991                         } else {
992                                 this.$element.css('height', (this.sizeY * this.gridster.curRowHeight - this.gridster.margins[0]) + 'px');
993                         }
994                 };
995
996                 /**
997                  * Sets an elements width
998                  */
999                 this.setElementSizeX = function() {
1000                         if (this.gridster.isMobile) {
1001                                 this.$element.css('width', '');
1002                         } else {
1003                                 this.$element.css('width', (this.sizeX * this.gridster.curColWidth - this.gridster.margins[1]) + 'px');
1004                         }
1005                 };
1006
1007                 /**
1008                  * Gets an element's width
1009                  */
1010                 this.getElementSizeX = function() {
1011                         return (this.sizeX * this.gridster.curColWidth - this.gridster.margins[1]);
1012                 };
1013
1014                 /**
1015                  * Gets an element's height
1016                  */
1017                 this.getElementSizeY = function() {
1018                         return (this.sizeY * this.gridster.curRowHeight - this.gridster.margins[0]);
1019                 };
1020
1021         })
1022
1023         .factory('GridsterTouch', [function() {
1024                 return function GridsterTouch(target, startEvent, moveEvent, endEvent) {
1025                         var lastXYById = {};
1026
1027                         //  Opera doesn't have Object.keys so we use this wrapper
1028                         var numberOfKeys = function(theObject) {
1029                                 if (Object.keys) {
1030                                         return Object.keys(theObject).length;
1031                                 }
1032
1033                                 var n = 0,
1034                                         key;
1035                                 for (key in theObject) {
1036                                         ++n;
1037                                 }
1038
1039                                 return n;
1040                         };
1041
1042                         //  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
1043                         var computeDocumentToElementDelta = function(theElement) {
1044                                 var elementLeft = 0;
1045                                 var elementTop = 0;
1046                                 var oldIEUserAgent = navigator.userAgent.match(/\bMSIE\b/);
1047
1048                                 for (var offsetElement = theElement; offsetElement != null; offsetElement = offsetElement.offsetParent) {
1049                                         //  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
1050                                         //  this may not be a general solution to IE7's problem with offsetLeft/offsetParent
1051                                         if (oldIEUserAgent &&
1052                                                 (!document.documentMode || document.documentMode < 8) &&
1053                                                 offsetElement.currentStyle.position === 'relative' && offsetElement.offsetParent && offsetElement.offsetParent.currentStyle.position === 'relative' && offsetElement.offsetLeft === offsetElement.offsetParent.offsetLeft) {
1054                                                 // add only the top
1055                                                 elementTop += offsetElement.offsetTop;
1056                                         } else {
1057                                                 elementLeft += offsetElement.offsetLeft;
1058                                                 elementTop += offsetElement.offsetTop;
1059                                         }
1060                                 }
1061
1062                                 return {
1063                                         x: elementLeft,
1064                                         y: elementTop
1065                                 };
1066                         };
1067
1068                         //  cache the delta from the document to our event target (reinitialized each mousedown/MSPointerDown/touchstart)
1069                         var documentToTargetDelta = computeDocumentToElementDelta(target);
1070
1071                         //  common event handler for the mouse/pointer/touch models and their down/start, move, up/end, and cancel events
1072                         var doEvent = function(theEvtObj) {
1073
1074                                 if (theEvtObj.type === 'mousemove' && numberOfKeys(lastXYById) === 0) {
1075                                         return;
1076                                 }
1077
1078                                 var prevent = true;
1079
1080                                 var pointerList = theEvtObj.changedTouches ? theEvtObj.changedTouches : [theEvtObj];
1081                                 
1082                                 for (var i = 0; i < pointerList.length; ++i) {
1083                                         var pointerObj = pointerList[i];
1084                                         var pointerId = (typeof pointerObj.identifier !== 'undefined') ? pointerObj.identifier : (typeof pointerObj.pointerId !== 'undefined') ? pointerObj.pointerId : 1;
1085                                         
1086                                         //  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)
1087                                         if (typeof pointerObj.pageX === 'undefined') {
1088                                                 
1089                                                 //  initialize assuming our source element is our target
1090                                                 if(!ie8){
1091                                                     pointerObj.pageX = pointerObj.offsetX + documentToTargetDelta.x;
1092                                                     pointerObj.pageY = pointerObj.offsetY + documentToTargetDelta.y;
1093                                                 }
1094                                                 else{
1095                                                     pointerObj.pageX = pointerObj.clientX;
1096                                                     pointerObj.pageY = pointerObj.clientY;
1097                                                 }
1098
1099                                                 if (pointerObj.srcElement.offsetParent === target && document.documentMode && document.documentMode === 8 && pointerObj.type === 'mousedown') {
1100                                                         //  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
1101                                                         pointerObj.pageX += pointerObj.srcElement.offsetLeft;
1102                                                         pointerObj.pageY += pointerObj.srcElement.offsetTop;
1103                                                 } else if (pointerObj.srcElement !== target && !document.documentMode || document.documentMode < 8) {
1104                                                         //  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 -
1105                                                         //  the offsetX/Y values are unpredictable so use the clientX/Y values and adjust by the scroll offsets of its parents
1106                                                         //  to get the document-relative coordinates (the same as pageX/Y)
1107                                                         var sx = -2,
1108                                                                 sy = -2; // adjust for old IE's 2-pixel border
1109                                                         for (var scrollElement = pointerObj.srcElement; scrollElement !== null; scrollElement = scrollElement.parentNode) {
1110                                                                 sx += scrollElement.scrollLeft ? scrollElement.scrollLeft : 0;
1111                                                                 sy += scrollElement.scrollTop ? scrollElement.scrollTop : 0;
1112                                                         }
1113
1114                                                         pointerObj.pageX = pointerObj.clientX + sx;
1115                                                         pointerObj.pageY = pointerObj.clientY + sy;
1116                                                 }
1117                                         }
1118
1119
1120                                         var pageX = pointerObj.pageX;
1121                                         var pageY = pointerObj.pageY;
1122
1123                                         if (theEvtObj.type.match(/(start|down)$/i)) {
1124                                                 //  clause for processing MSPointerDown, touchstart, and mousedown
1125
1126                                                 //  refresh the document-to-target delta on start in case the target has moved relative to document
1127                                                 documentToTargetDelta = computeDocumentToElementDelta(target);
1128
1129                                                 //  protect against failing to get an up or end on this pointerId
1130                                                 if (lastXYById[pointerId]) {
1131                                                         if (endEvent) {
1132                                                                 endEvent({
1133                                                                         target: theEvtObj.target,
1134                                                                         which: theEvtObj.which,
1135                                                                         pointerId: pointerId,
1136                                                                         pageX: pageX,
1137                                                                         pageY: pageY
1138                                                                 });
1139                                                         }
1140
1141                                                         delete lastXYById[pointerId];
1142                                                 }
1143
1144                                                 if (startEvent) {
1145                                                         if (prevent) {
1146                                                                 prevent = startEvent({
1147                                                                         target: theEvtObj.target,
1148                                                                         which: theEvtObj.which,
1149                                                                         pointerId: pointerId,
1150                                                                         pageX: pageX,
1151                                                                         pageY: pageY
1152                                                                 });
1153                                                         }
1154                                                 }
1155
1156                                                 //  init last page positions for this pointer
1157                                                 lastXYById[pointerId] = {
1158                                                         x: pageX,
1159                                                         y: pageY
1160                                                 };
1161
1162                                                 // IE pointer model
1163                                                 if (target.msSetPointerCapture) {
1164                                                         target.msSetPointerCapture(pointerId);
1165                                                 } else if (theEvtObj.type === 'mousedown' && numberOfKeys(lastXYById) === 1) {
1166                                                         if (useSetReleaseCapture) {
1167                                                                 target.setCapture(true);
1168                                                         } else {
1169                                                                 document.addEventListener('mousemove', doEvent, false);
1170                                                                 document.addEventListener('mouseup', doEvent, false);
1171                                                         }
1172                                                 }
1173                                         } else if (theEvtObj.type.match(/move$/i)) {
1174                                                 //  clause handles mousemove, MSPointerMove, and touchmove
1175
1176                                                 if (lastXYById[pointerId] && !(lastXYById[pointerId].x === pageX && lastXYById[pointerId].y === pageY)) {
1177                                                         //  only extend if the pointer is down and it's not the same as the last point
1178
1179                                                         if (moveEvent && prevent) {
1180                                                                 prevent = moveEvent({
1181                                                                         target: theEvtObj.target,
1182                                                                         which: theEvtObj.which,
1183                                                                         pointerId: pointerId,
1184                                                                         pageX: pageX,
1185                                                                         pageY: pageY
1186                                                                 });
1187                                                         }
1188
1189                                                         //  update last page positions for this pointer
1190                                                         lastXYById[pointerId].x = pageX;
1191                                                         lastXYById[pointerId].y = pageY;
1192                                                 }
1193                                         } else if (lastXYById[pointerId] && theEvtObj.type.match(/(up|end|cancel)$/i)) {
1194                                                 //  clause handles up/end/cancel
1195
1196                                                 if (endEvent && prevent) {
1197                                                         prevent = endEvent({
1198                                                                 target: theEvtObj.target,
1199                                                                 which: theEvtObj.which,
1200                                                                 pointerId: pointerId,
1201                                                                 pageX: pageX,
1202                                                                 pageY: pageY
1203                                                         });
1204                                                 }
1205
1206                                                 //  delete last page positions for this pointer
1207                                                 delete lastXYById[pointerId];
1208
1209                                                 //  in the Microsoft pointer model, release the capture for this pointer
1210                                                 //  in the mouse model, release the capture or remove document-level event handlers if there are no down points
1211                                                 //  nothing is required for the iOS touch model because capture is implied on touchstart
1212                                                 if (target.msReleasePointerCapture) {
1213                                                         target.msReleasePointerCapture(pointerId);
1214                                                 } else if (theEvtObj.type === 'mouseup' && numberOfKeys(lastXYById) === 0) {
1215                                                         if (useSetReleaseCapture) {
1216                                                                 target.releaseCapture();
1217                                                         } else {
1218                                                                 document.removeEventListener('mousemove', doEvent, false);
1219                                                                 document.removeEventListener('mouseup', doEvent, false);
1220                                                         }
1221                                                 }
1222                                         }
1223                                 }
1224
1225                                 if (prevent) {
1226                                         if (theEvtObj.preventDefault) {
1227                                                 theEvtObj.preventDefault();
1228                                         }
1229
1230                                         if (theEvtObj.preventManipulation) {
1231                                                 theEvtObj.preventManipulation();
1232                                         }
1233
1234                                         if (theEvtObj.preventMouseEvent) {
1235                                                 theEvtObj.preventMouseEvent();
1236                                         }
1237                                 }
1238                         };
1239
1240                         var useSetReleaseCapture = false;
1241                         // saving the settings for contentZooming and touchaction before activation
1242                         var contentZooming, msTouchAction;
1243
1244                         this.enable = function() {
1245
1246                                 if (window.navigator.msPointerEnabled) {
1247                                         //  Microsoft pointer model
1248                                         target.addEventListener('MSPointerDown', doEvent, false);
1249                                         target.addEventListener('MSPointerMove', doEvent, false);
1250                                         target.addEventListener('MSPointerUp', doEvent, false);
1251                                         target.addEventListener('MSPointerCancel', doEvent, false);
1252
1253                                         //  css way to prevent panning in our target area
1254                                         if (typeof target.style.msContentZooming !== 'undefined') {
1255                                                 contentZooming = target.style.msContentZooming;
1256                                                 target.style.msContentZooming = 'none';
1257                                         }
1258
1259                                         //  new in Windows Consumer Preview: css way to prevent all built-in touch actions on our target
1260                                         //  without this, you cannot touch draw on the element because IE will intercept the touch events
1261                                         if (typeof target.style.msTouchAction !== 'undefined') {
1262                                                 msTouchAction = target.style.msTouchAction;
1263                                                 target.style.msTouchAction = 'none';
1264                                         }
1265                                 } else if (target.addEventListener) {
1266                                         //  iOS touch model
1267                                         target.addEventListener('touchstart', doEvent, false);
1268                                         target.addEventListener('touchmove', doEvent, false);
1269                                         target.addEventListener('touchend', doEvent, false);
1270                                         target.addEventListener('touchcancel', doEvent, false);
1271
1272                                         //  mouse model
1273                                         target.addEventListener('mousedown', doEvent, false);
1274
1275                                         //  mouse model with capture
1276                                         //  rejecting gecko because, unlike ie, firefox does not send events to target when the mouse is outside target
1277                                         if (target.setCapture && !window.navigator.userAgent.match(/\bGecko\b/)) {
1278                                                 useSetReleaseCapture = true;
1279
1280                                                 target.addEventListener('mousemove', doEvent, false);
1281                                                 target.addEventListener('mouseup', doEvent, false);
1282                                         }
1283                                 } else if (target.attachEvent && target.setCapture) {
1284                                         //  legacy IE mode - mouse with capture
1285                                         useSetReleaseCapture = true;
1286                                         target.attachEvent('onmousedown', function() {
1287                                                 doEvent(window.event);
1288                                                 window.event.returnValue = false;
1289                                                 return false;
1290                                         });
1291                                         target.attachEvent('onmousemove', function() {
1292                                                 doEvent(window.event);
1293                                                 window.event.returnValue = false;
1294                                                 return false;
1295                                         });
1296                                         target.attachEvent('onmouseup', function() {
1297                                                 doEvent(window.event);
1298                                                 window.event.returnValue = false;
1299                                                 return false;
1300                                         });
1301                                 }
1302                         };
1303
1304                         this.disable = function() {
1305                                 if (window.navigator.msPointerEnabled) {
1306                                         //  Microsoft pointer model
1307                                         target.removeEventListener('MSPointerDown', doEvent, false);
1308                                         target.removeEventListener('MSPointerMove', doEvent, false);
1309                                         target.removeEventListener('MSPointerUp', doEvent, false);
1310                                         target.removeEventListener('MSPointerCancel', doEvent, false);
1311
1312                                         //  reset zooming to saved value
1313                                         if (contentZooming) {
1314                                                 target.style.msContentZooming = contentZooming;
1315                                         }
1316
1317                                         // reset touch action setting
1318                                         if (msTouchAction) {
1319                                                 target.style.msTouchAction = msTouchAction;
1320                                         }
1321                                 } else if (target.removeEventListener) {
1322                                         //  iOS touch model
1323                                         target.removeEventListener('touchstart', doEvent, false);
1324                                         target.removeEventListener('touchmove', doEvent, false);
1325                                         target.removeEventListener('touchend', doEvent, false);
1326                                         target.removeEventListener('touchcancel', doEvent, false);
1327
1328                                         //  mouse model
1329                                         target.removeEventListener('mousedown', doEvent, false);
1330
1331                                         //  mouse model with capture
1332                                         //  rejecting gecko because, unlike ie, firefox does not send events to target when the mouse is outside target
1333                                         if (target.setCapture && !window.navigator.userAgent.match(/\bGecko\b/)) {
1334                                                 useSetReleaseCapture = true;
1335
1336                                                 target.removeEventListener('mousemove', doEvent, false);
1337                                                 target.removeEventListener('mouseup', doEvent, false);
1338                                         }
1339                                 } else if (target.detachEvent && target.setCapture) {
1340                                         //  legacy IE mode - mouse with capture
1341                                         useSetReleaseCapture = true;
1342                                         target.detachEvent('onmousedown');
1343                                         target.detachEvent('onmousemove');
1344                                         target.detachEvent('onmouseup');
1345                                 }
1346                         };
1347
1348                         return this;
1349                 };
1350         }])
1351
1352         .factory('GridsterDraggable', ['$document', '$timeout', '$window', 'GridsterTouch',
1353                 function($document, $timeout, $window, GridsterTouch) {
1354                         function GridsterDraggable($el, scope, gridster, item, itemOptions) {
1355
1356                                 var elmX, elmY, elmW, elmH,
1357
1358                                         mouseX = 0,
1359                                         mouseY = 0,
1360                                         lastMouseX = 0,
1361                                         lastMouseY = 0,
1362                                         mOffX = 0,
1363                                         mOffY = 0,
1364
1365                                         minTop = 0,
1366                                         maxTop = 9999,
1367                                         minLeft = 0,
1368                                         realdocument = $document[0];
1369
1370                                 var originalCol, originalRow;
1371                                 var inputTags = ['select', 'input', 'textarea', 'button'];
1372                                 
1373                                 var gridsterItemDragElement = $el[0].querySelector('[gridster-item-drag]');
1374                                 //console.log(gridsterItemDragElement);
1375                                 var isDraggableAreaDefined = gridsterItemDragElement?true:false;
1376                                //console.log(isDraggableAreaDefined);
1377                                 
1378                                 function mouseDown(e) {
1379                                     
1380                                     if(ie8){
1381                                         e.target = window.event.srcElement;
1382                                         e.which = window.event.button;
1383                                     }
1384                                     
1385                                     if(isDraggableAreaDefined && (!gridsterItemDragElement.contains(e.target))){
1386                                         return false;
1387                                     }
1388                                     
1389                                         if (inputTags.indexOf(e.target.nodeName.toLowerCase()) !== -1) {
1390                                             return false;
1391                                         }
1392
1393                                         var $target = angular.element(e.target);
1394
1395                                         // exit, if a resize handle was hit
1396                                         if ($target.hasClass('gridster-item-resizable-handler')) {
1397                                                 return false;
1398                                         }
1399
1400                                         // exit, if the target has it's own click event
1401                                         if ($target.attr('onclick') || $target.attr('ng-click')) {
1402                                                 return false;
1403                                         }
1404
1405                                         // only works if you have jQuery
1406                                         if ($target.closest && $target.closest('.gridster-no-drag').length) {
1407                                                 return false;
1408                                         }
1409                                         
1410                                         switch (e.which) {
1411                                                 case 1:
1412                                                         // left mouse button
1413                                                         break;
1414                                                 case 2:
1415                                                 case 3:
1416                                                         // right or middle mouse button
1417                                                         return;
1418                                         }
1419
1420                                         lastMouseX = e.pageX;
1421                                         lastMouseY = e.pageY;
1422
1423                                         elmX = parseInt($el.css('left'), 10);
1424                                         elmY = parseInt($el.css('top'), 10);
1425                                         elmW = $el[0].offsetWidth;
1426                                         elmH = $el[0].offsetHeight;
1427
1428                                         originalCol = item.col;
1429                                         originalRow = item.row;
1430
1431                                         dragStart(e);
1432
1433                                         return true;
1434                                 }
1435
1436                                 function mouseMove(e) {
1437                                         if (!$el.hasClass('gridster-item-moving') || $el.hasClass('gridster-item-resizing')) {
1438                                                 return false;
1439                                         }
1440
1441                                         var maxLeft = gridster.curWidth - 1;
1442
1443                                         // Get the current mouse position.
1444                                         mouseX = e.pageX;
1445                                         mouseY = e.pageY;
1446
1447                                         // Get the deltas
1448                                         var diffX = mouseX - lastMouseX + mOffX;
1449                                         var diffY = mouseY - lastMouseY + mOffY;
1450                                         mOffX = mOffY = 0;
1451
1452                                         // Update last processed mouse positions.
1453                                         lastMouseX = mouseX;
1454                                         lastMouseY = mouseY;
1455
1456                                         var dX = diffX,
1457                                                 dY = diffY;
1458                                         if (elmX + dX < minLeft) {
1459                                                 diffX = minLeft - elmX;
1460                                                 mOffX = dX - diffX;
1461                                         } else if (elmX + elmW + dX > maxLeft) {
1462                                                 diffX = maxLeft - elmX - elmW;
1463                                                 mOffX = dX - diffX;
1464                                         }
1465
1466                                         if (elmY + dY < minTop) {
1467                                                 diffY = minTop - elmY;
1468                                                 mOffY = dY - diffY;
1469                                         } else if (elmY + elmH + dY > maxTop) {
1470                                                 diffY = maxTop - elmY - elmH;
1471                                                 mOffY = dY - diffY;
1472                                         }
1473                                         elmX += diffX;
1474                                         elmY += diffY;
1475
1476                                         // set new position
1477                                         $el.css({
1478                                                 'top': elmY + 'px',
1479                                                 'left': elmX + 'px'
1480                                         });
1481
1482                                         drag(e);
1483
1484                                         return true;
1485                                 }
1486
1487                                 function mouseUp(e) {
1488                                         if (!$el.hasClass('gridster-item-moving') || $el.hasClass('gridster-item-resizing')) {
1489                                                 return false;
1490                                         }
1491
1492                                         mOffX = mOffY = 0;
1493
1494                                         dragStop(e);
1495
1496                                         return true;
1497                                 }
1498
1499                                 function dragStart(event) {
1500                                         $el.addClass('gridster-item-moving');
1501                                         gridster.movingItem = item;
1502
1503                                         gridster.updateHeight(item.sizeY);
1504                                         scope.$apply(function() {
1505                                                 if (gridster.draggable && gridster.draggable.start) {
1506                                                         gridster.draggable.start(event, $el, itemOptions);
1507                                                 }
1508                                         });
1509                                 }
1510
1511                                 function drag(event) {
1512                                         var oldRow = item.row,
1513                                                 oldCol = item.col,
1514                                                 hasCallback = gridster.draggable && gridster.draggable.drag,
1515                                                 scrollSensitivity = gridster.draggable.scrollSensitivity,
1516                                                 scrollSpeed = gridster.draggable.scrollSpeed;
1517                                         
1518                                         var row = gridster.pixelsToRows(elmY);
1519                                         var col = gridster.pixelsToColumns(elmX);
1520
1521                                         var itemsInTheWay = gridster.getItems(row, col, item.sizeX, item.sizeY, item);
1522                                         var hasItemsInTheWay = itemsInTheWay.length !== 0;
1523                                         
1524                                         if (gridster.swapping === true && hasItemsInTheWay) {
1525                                                 var boundingBoxItem = gridster.getBoundingBox(itemsInTheWay),
1526                                                         sameSize = boundingBoxItem.sizeX === item.sizeX && boundingBoxItem.sizeY === item.sizeY,
1527                                                         sameRow = boundingBoxItem.row === oldRow,
1528                                                         sameCol = boundingBoxItem.col === oldCol,
1529                                                         samePosition = boundingBoxItem.row === row && boundingBoxItem.col === col,
1530                                                         inline = sameRow || sameCol;
1531
1532                                                 if (sameSize && itemsInTheWay.length === 1) {
1533                                                         if (samePosition) {
1534                                                                 gridster.swapItems(item, itemsInTheWay[0]);
1535                                                         } else if (inline) {
1536                                                                 return;
1537                                                         }
1538                                                 } else if (boundingBoxItem.sizeX <= item.sizeX && boundingBoxItem.sizeY <= item.sizeY && inline) {
1539                                                         var emptyRow = item.row <= row ? item.row : row + item.sizeY,
1540                                                                 emptyCol = item.col <= col ? item.col : col + item.sizeX,
1541                                                                 rowOffset = emptyRow - boundingBoxItem.row,
1542                                                                 colOffset = emptyCol - boundingBoxItem.col;
1543
1544                                                         for (var i = 0, l = itemsInTheWay.length; i < l; ++i) {
1545                                                                 var itemInTheWay = itemsInTheWay[i];
1546
1547                                                                 var itemsInFreeSpace = gridster.getItems(
1548                                                                         itemInTheWay.row + rowOffset,
1549                                                                         itemInTheWay.col + colOffset,
1550                                                                         itemInTheWay.sizeX,
1551                                                                         itemInTheWay.sizeY,
1552                                                                         item
1553                                                                 );
1554
1555                                                                 if (itemsInFreeSpace.length === 0) {
1556                                                                         gridster.putItem(itemInTheWay, itemInTheWay.row + rowOffset, itemInTheWay.col + colOffset);
1557                                                                 }
1558                                                         }
1559                                                 }
1560                                         }
1561
1562                                         if (gridster.pushing !== false || !hasItemsInTheWay) {
1563                                                 item.row = row;
1564                                                 item.col = col;
1565                                         }
1566                                         
1567                                         if(($window.navigator.appName === 'Microsoft Internet Explorer' && !ie8) || $window.navigator.userAgent.indexOf("Firefox")!==-1){
1568                                             if (event.pageY - realdocument.documentElement.scrollTop < scrollSensitivity) {
1569                                                     realdocument.documentElement.scrollTop = realdocument.documentElement.scrollTop - scrollSpeed;
1570                                             } else if ($window.innerHeight - (event.pageY - realdocument.documentElement.scrollTop) < scrollSensitivity) {
1571                                                     realdocument.documentElement.scrollTop = realdocument.documentElement.scrollTop + scrollSpeed;
1572                                             }
1573                                         }
1574                                         else{
1575                                             if (event.pageY - realdocument.body.scrollTop < scrollSensitivity) {
1576                                                     realdocument.body.scrollTop = realdocument.body.scrollTop - scrollSpeed;
1577                                             } else if ($window.innerHeight - (event.pageY - realdocument.body.scrollTop) < scrollSensitivity) {
1578                                                     realdocument.body.scrollTop = realdocument.body.scrollTop + scrollSpeed;
1579                                             }
1580                                         }
1581                                         
1582                                         
1583
1584                                         if (event.pageX - realdocument.body.scrollLeft < scrollSensitivity) {
1585                                                 realdocument.body.scrollLeft = realdocument.body.scrollLeft - scrollSpeed;
1586                                         } else if ($window.innerWidth - (event.pageX - realdocument.body.scrollLeft) < scrollSensitivity) {
1587                                                 realdocument.body.scrollLeft = realdocument.body.scrollLeft + scrollSpeed;
1588                                         }
1589
1590                                         if (hasCallback || oldRow !== item.row || oldCol !== item.col) {
1591                                                 scope.$apply(function() {
1592                                                         if (hasCallback) {
1593                                                                 gridster.draggable.drag(event, $el, itemOptions);
1594                                                         }
1595                                                 });
1596                                         }
1597                                 }
1598
1599                                 function dragStop(event) {
1600                                         $el.removeClass('gridster-item-moving');
1601                                         var row = gridster.pixelsToRows(elmY);
1602                                         var col = gridster.pixelsToColumns(elmX);
1603                                         if (gridster.pushing !== false || gridster.getItems(row, col, item.sizeX, item.sizeY, item).length === 0) {
1604                                                 item.row = row;
1605                                                 item.col = col;
1606                                         }
1607                                         gridster.movingItem = null;
1608                                         item.setPosition(item.row, item.col);
1609
1610                                         scope.$apply(function() {
1611                                                 if (gridster.draggable && gridster.draggable.stop) {
1612                                                         gridster.draggable.stop(event, $el, itemOptions);
1613                                                 }
1614                                         });
1615                                 }
1616
1617                                 var enabled = null;
1618                                 var $dragHandles = null;
1619                                 var unifiedInputs = [];
1620
1621                                 this.enable = function() {
1622                                         if (enabled === true) {
1623                                                 return;
1624                                         }
1625
1626                                         // disable and timeout required for some template rendering
1627                                         $timeout(function() {
1628                                                 // disable any existing draghandles
1629                                                 for (var u = 0, ul = unifiedInputs.length; u < ul; ++u) {
1630                                                         unifiedInputs[u].disable();
1631                                                 }
1632                                                 unifiedInputs = [];
1633
1634                                                 if (gridster.draggable && gridster.draggable.handle) {
1635                                                         $dragHandles = angular.element($el[0].querySelectorAll(gridster.draggable.handle));
1636                                                         if ($dragHandles.length === 0) {
1637                                                                 // fall back to element if handle not found...
1638                                                                 $dragHandles = $el;
1639                                                         }
1640                                                 } else {
1641                                                         $dragHandles = $el;
1642                                                 }
1643
1644                                                 for (var h = 0, hl = $dragHandles.length; h < hl; ++h) {
1645                                                         unifiedInputs[h] = new GridsterTouch($dragHandles[h], mouseDown, mouseMove, mouseUp);
1646                                                         unifiedInputs[h].enable();
1647                                                 }
1648
1649                                                 enabled = true;
1650                                         });
1651                                 };
1652
1653                                 this.disable = function() {
1654                                         if (enabled === false) {
1655                                                 return;
1656                                         }
1657
1658                                         // timeout to avoid race contition with the enable timeout
1659                                         $timeout(function() {
1660
1661                                                 for (var u = 0, ul = unifiedInputs.length; u < ul; ++u) {
1662                                                         unifiedInputs[u].disable();
1663                                                 }
1664
1665                                                 unifiedInputs = [];
1666                                                 enabled = false;
1667                                         });
1668                                 };
1669
1670                                 this.toggle = function(enabled) {
1671                                         if (enabled) {
1672                                                 this.enable();
1673                                         } else {
1674                                                 this.disable();
1675                                         }
1676                                 };
1677
1678                                 this.destroy = function() {
1679                                         this.disable();
1680                                 };
1681                         }
1682
1683                         return GridsterDraggable;
1684                 }
1685         ])
1686
1687         .factory('GridsterResizable', ['GridsterTouch', function(GridsterTouch) {
1688                 function GridsterResizable($el, scope, gridster, item, itemOptions) {
1689
1690                         function ResizeHandle(handleClass) {
1691
1692                                 var hClass = handleClass;
1693
1694                                 var elmX, elmY, elmW, elmH,
1695
1696                                         mouseX = 0,
1697                                         mouseY = 0,
1698                                         lastMouseX = 0,
1699                                         lastMouseY = 0,
1700                                         mOffX = 0,
1701                                         mOffY = 0,
1702
1703                                         minTop = 0,
1704                                         maxTop = 9999,
1705                                         minLeft = 0;
1706
1707                                 var getMinHeight = function() {
1708                                         return (item.minSizeY ? item.minSizeY : 1) * gridster.curRowHeight - gridster.margins[0];
1709                                 };
1710                                 var getMinWidth = function() {
1711                                         return (item.minSizeX ? item.minSizeX : 1) * gridster.curColWidth - gridster.margins[1];
1712                                 };
1713
1714                                 var originalWidth, originalHeight;
1715                                 var savedDraggable;
1716
1717                                 function mouseDown(e) {
1718                                         switch (e.which) {
1719                                                 case 1:
1720                                                         // left mouse button
1721                                                         break;
1722                                                 case 2:
1723                                                 case 3:
1724                                                         // right or middle mouse button
1725                                                         return;
1726                                         }
1727
1728                                         // save the draggable setting to restore after resize
1729                                         savedDraggable = gridster.draggable.enabled;
1730                                         if (savedDraggable) {
1731                                                 gridster.draggable.enabled = false;
1732                                                 scope.$broadcast('gridster-draggable-changed', gridster);
1733                                         }
1734
1735                                         // Get the current mouse position.
1736                                         lastMouseX = e.pageX;
1737                                         lastMouseY = e.pageY;
1738
1739                                         // Record current widget dimensions
1740                                         elmX = parseInt($el.css('left'), 10);
1741                                         elmY = parseInt($el.css('top'), 10);
1742                                         elmW = $el[0].offsetWidth;
1743                                         elmH = $el[0].offsetHeight;
1744
1745                                         originalWidth = item.sizeX;
1746                                         originalHeight = item.sizeY;
1747
1748                                         resizeStart(e);
1749
1750                                         return true;
1751                                 }
1752
1753                                 function resizeStart(e) {
1754                                         $el.addClass('gridster-item-moving');
1755                                         $el.addClass('gridster-item-resizing');
1756
1757                                         gridster.movingItem = item;
1758
1759                                         item.setElementSizeX();
1760                                         item.setElementSizeY();
1761                                         item.setElementPosition();
1762                                         gridster.updateHeight(1);
1763
1764                                         scope.$apply(function() {
1765                                                 // callback
1766                                                 if (gridster.resizable && gridster.resizable.start) {
1767                                                         gridster.resizable.start(e, $el, itemOptions); // options is the item model
1768                                                 }
1769                                         });
1770                                 }
1771
1772                                 function mouseMove(e) {
1773                                         var maxLeft = gridster.curWidth - 1;
1774
1775                                         // Get the current mouse position.
1776                                         mouseX = e.pageX;
1777                                         mouseY = e.pageY;
1778
1779                                         // Get the deltas
1780                                         var diffX = mouseX - lastMouseX + mOffX;
1781                                         var diffY = mouseY - lastMouseY + mOffY;
1782                                         mOffX = mOffY = 0;
1783
1784                                         // Update last processed mouse positions.
1785                                         lastMouseX = mouseX;
1786                                         lastMouseY = mouseY;
1787
1788                                         var dY = diffY,
1789                                                 dX = diffX;
1790
1791                                         if (hClass.indexOf('n') >= 0) {
1792                                                 if (elmH - dY < getMinHeight()) {
1793                                                         diffY = elmH - getMinHeight();
1794                                                         mOffY = dY - diffY;
1795                                                 } else if (elmY + dY < minTop) {
1796                                                         diffY = minTop - elmY;
1797                                                         mOffY = dY - diffY;
1798                                                 }
1799                                                 elmY += diffY;
1800                                                 elmH -= diffY;
1801                                         }
1802                                         if (hClass.indexOf('s') >= 0) {
1803                                                 if (elmH + dY < getMinHeight()) {
1804                                                         diffY = getMinHeight() - elmH;
1805                                                         mOffY = dY - diffY;
1806                                                 } else if (elmY + elmH + dY > maxTop) {
1807                                                         diffY = maxTop - elmY - elmH;
1808                                                         mOffY = dY - diffY;
1809                                                 }
1810                                                 elmH += diffY;
1811                                         }
1812                                         if (hClass.indexOf('w') >= 0) {
1813                                                 if (elmW - dX < getMinWidth()) {
1814                                                         diffX = elmW - getMinWidth();
1815                                                         mOffX = dX - diffX;
1816                                                 } else if (elmX + dX < minLeft) {
1817                                                         diffX = minLeft - elmX;
1818                                                         mOffX = dX - diffX;
1819                                                 }
1820                                                 elmX += diffX;
1821                                                 elmW -= diffX;
1822                                         }
1823                                         if (hClass.indexOf('e') >= 0) {
1824                                                 if (elmW + dX < getMinWidth()) {
1825                                                         diffX = getMinWidth() - elmW;
1826                                                         mOffX = dX - diffX;
1827                                                 } else if (elmX + elmW + dX > maxLeft) {
1828                                                         diffX = maxLeft - elmX - elmW;
1829                                                         mOffX = dX - diffX;
1830                                                 }
1831                                                 elmW += diffX;
1832                                         }
1833
1834                                         // set new position
1835                                         $el.css({
1836                                                 'top': elmY + 'px',
1837                                                 'left': elmX + 'px',
1838                                                 'width': elmW + 'px',
1839                                                 'height': elmH + 'px'
1840                                         });
1841
1842                                         resize(e);
1843
1844                                         return true;
1845                                 }
1846
1847                                 function mouseUp(e) {
1848                                         // restore draggable setting to its original state
1849                                         if (gridster.draggable.enabled !== savedDraggable) {
1850                                                 gridster.draggable.enabled = savedDraggable;
1851                                                 scope.$broadcast('gridster-draggable-changed', gridster);
1852                                         }
1853
1854                                         mOffX = mOffY = 0;
1855
1856                                         resizeStop(e);
1857
1858                                         return true;
1859                                 }
1860
1861                                 function resize(e) {
1862                                         var oldRow = item.row,
1863                                                 oldCol = item.col,
1864                                                 oldSizeX = item.sizeX,
1865                                                 oldSizeY = item.sizeY,
1866                                                 hasCallback = gridster.resizable && gridster.resizable.resize;
1867
1868                                         var col = item.col;
1869                                         // only change column if grabbing left edge
1870                                         if (['w', 'nw', 'sw'].indexOf(handleClass) !== -1) {
1871                                                 col = gridster.pixelsToColumns(elmX, false);
1872                                         }
1873
1874                                         var row = item.row;
1875                                         // only change row if grabbing top edge
1876                                         if (['n', 'ne', 'nw'].indexOf(handleClass) !== -1) {
1877                                                 row = gridster.pixelsToRows(elmY, false);
1878                                         }
1879
1880                                         var sizeX = item.sizeX;
1881                                         // only change row if grabbing left or right edge
1882                                         if (['n', 's'].indexOf(handleClass) === -1) {
1883                                                 sizeX = gridster.pixelsToColumns(elmW, true);
1884                                         }
1885
1886                                         var sizeY = item.sizeY;
1887                                         // only change row if grabbing top or bottom edge
1888                                         if (['e', 'w'].indexOf(handleClass) === -1) {
1889                                                 sizeY = gridster.pixelsToRows(elmH, true);
1890                                         }
1891
1892                                         if (gridster.pushing !== false || gridster.getItems(row, col, sizeX, sizeY, item).length === 0) {
1893                                                 item.row = row;
1894                                                 item.col = col;
1895                                                 item.sizeX = sizeX;
1896                                                 item.sizeY = sizeY;
1897                                         }
1898                                         var isChanged = item.row !== oldRow || item.col !== oldCol || item.sizeX !== oldSizeX || item.sizeY !== oldSizeY;
1899
1900                                         if (hasCallback || isChanged) {
1901                                                 scope.$apply(function() {
1902                                                         if (hasCallback) {
1903                                                                 gridster.resizable.resize(e, $el, itemOptions); // options is the item model
1904                                                         }
1905                                                 });
1906                                         }
1907                                 }
1908
1909                                 function resizeStop(e) {
1910                                         $el.removeClass('gridster-item-moving');
1911                                         $el.removeClass('gridster-item-resizing');
1912
1913                                         gridster.movingItem = null;
1914
1915                                         item.setPosition(item.row, item.col);
1916                                         item.setSizeY(item.sizeY);
1917                                         item.setSizeX(item.sizeX);
1918
1919                                         scope.$apply(function() {
1920                                                 if (gridster.resizable && gridster.resizable.stop) {
1921                                                         gridster.resizable.stop(e, $el, itemOptions); // options is the item model
1922                                                 }
1923                                         });
1924                                 }
1925
1926                                 var $dragHandle = null;
1927                                 var unifiedInput;
1928
1929                                 this.enable = function() {
1930                                         if (!$dragHandle) {
1931                                                 $dragHandle = angular.element('<div class="gridster-item-resizable-handler handle-' + hClass + '"></div>');
1932                                                 $el.append($dragHandle);
1933                                         }
1934
1935                                         unifiedInput = new GridsterTouch($dragHandle[0], mouseDown, mouseMove, mouseUp);
1936                                         unifiedInput.enable();
1937                                 };
1938
1939                                 this.disable = function() {
1940                                         if ($dragHandle) {
1941                                                 $dragHandle.remove();
1942                                                 $dragHandle = null;
1943                                         }
1944
1945                                         unifiedInput.disable();
1946                                         unifiedInput = undefined;
1947                                 };
1948
1949                                 this.destroy = function() {
1950                                         this.disable();
1951                                 };
1952                         }
1953
1954                         var handles = [];
1955                         var handlesOpts = gridster.resizable.handles;
1956                         if (typeof handlesOpts === 'string') {
1957                                 handlesOpts = gridster.resizable.handles.split(',');
1958                         }
1959                         var enabled = false;
1960
1961                         for (var c = 0, l = handlesOpts.length; c < l; c++) {
1962                                 handles.push(new ResizeHandle(handlesOpts[c]));
1963                         }
1964
1965                         this.enable = function() {
1966                                 if (enabled) {
1967                                         return;
1968                                 }
1969                                 for (var c = 0, l = handles.length; c < l; c++) {
1970                                         handles[c].enable();
1971                                 }
1972                                 enabled = true;
1973                         };
1974
1975                         this.disable = function() {
1976                                 if (!enabled) {
1977                                         return;
1978                                 }
1979                                 for (var c = 0, l = handles.length; c < l; c++) {
1980                                         handles[c].disable();
1981                                 }
1982                                 enabled = false;
1983                         };
1984
1985                         this.toggle = function(enabled) {
1986                                 if (enabled) {
1987                                         this.enable();
1988                                 } else {
1989                                         this.disable();
1990                                 }
1991                         };
1992
1993                         this.destroy = function() {
1994                                 for (var c = 0, l = handles.length; c < l; c++) {
1995                                         handles[c].destroy();
1996                                 }
1997                         };
1998                 }
1999                 return GridsterResizable;
2000         }])
2001
2002         .factory('gridsterDebounce', function() {
2003                 return function gridsterDebounce(func, wait, immediate) {
2004                         var timeout;
2005                         return function() {
2006                                 var context = this,
2007                                         args = arguments;
2008                                 var later = function() {
2009                                         timeout = null;
2010                                         if (!immediate) {
2011                                                 func.apply(context, args);
2012                                         }
2013                                 };
2014                                 var callNow = immediate && !timeout;
2015                                 clearTimeout(timeout);
2016                                 timeout = setTimeout(later, wait);
2017                                 if (callNow) {
2018                                         func.apply(context, args);
2019                                 }
2020                         };
2021                 };
2022         })
2023
2024         /**
2025          * GridsterItem directive
2026          * @param $parse
2027          * @param GridsterDraggable
2028          * @param GridsterResizable
2029          * @param gridsterDebounce
2030          */
2031         .directive('gridsterItem', ['$parse', 'GridsterDraggable', 'GridsterResizable', 'gridsterDebounce',
2032                 function($parse, GridsterDraggable, GridsterResizable, gridsterDebounce) {
2033                         return {
2034                                 scope: true,
2035                                 restrict: 'EA',
2036                                 controller: 'GridsterItemCtrl',
2037                                 controllerAs: 'gridsterItem',
2038                                 require: ['^gridster', 'gridsterItem'],
2039                                 link: function(scope, $el, attrs, controllers) {
2040                                         var optionsKey = attrs.gridsterItem,
2041                                                 options;
2042                                         
2043                                         var gridster = controllers[0],
2044                                                 item = controllers[1];
2045
2046                                         scope.gridster = gridster;
2047                                         
2048
2049                                         // bind the item's position properties
2050                                         // options can be an object specified by gridster-item="object"
2051                                         // or the options can be the element html attributes object
2052                                         if (optionsKey) {
2053                                                 var $optionsGetter = $parse(optionsKey);
2054                                                 options = $optionsGetter(scope) || {};
2055                                                 if (!options && $optionsGetter.assign) {
2056                                                         options = {
2057                                                                 row: item.row,
2058                                                                 col: item.col,
2059                                                                 sizeX: item.sizeX,
2060                                                                 sizeY: item.sizeY,
2061                                                                 minSizeX: 0,
2062                                                                 minSizeY: 0,
2063                                                                 maxSizeX: null,
2064                                                                 maxSizeY: null
2065                                                         };
2066                                                         $optionsGetter.assign(scope, options);
2067                                                 }
2068                                         } else {
2069                                                 options = attrs;
2070                                         }
2071                                         
2072                                         item.init($el, gridster);
2073
2074                                         $el.addClass('gridster-item');
2075
2076                                         var aspects = ['minSizeX', 'maxSizeX', 'minSizeY', 'maxSizeY', 'sizeX', 'sizeY', 'row', 'col'],
2077                                                 $getters = {};
2078
2079                                         var expressions = [];
2080                                         var aspectFn = function(aspect) {
2081                                                 var expression;
2082                                                 if (typeof options[aspect] === 'string') {
2083                                                         // watch the expression in the scope
2084                                                         expression = options[aspect];
2085                                                 } else if (typeof options[aspect.toLowerCase()] === 'string') {
2086                                                         // watch the expression in the scope
2087                                                         expression = options[aspect.toLowerCase()];
2088                                                 } else if (optionsKey) {
2089                                                         // watch the expression on the options object in the scope
2090                                                         expression = optionsKey + '.' + aspect;
2091                                                 } else {
2092                                                         return;
2093                                                 }
2094                                                 expressions.push('"' + aspect + '":' + expression);
2095                                                 $getters[aspect] = $parse(expression);
2096
2097                                                 // initial set
2098                                                 var val = $getters[aspect](scope);
2099                                                 if (typeof val === 'number') {
2100                                                         item[aspect] = val;
2101                                                 }
2102                                         };
2103
2104                                         for (var i = 0, l = aspects.length; i < l; ++i) {
2105                                                 aspectFn(aspects[i]);
2106                                         }
2107
2108                                         var watchExpressions = '{' + expressions.join(',') + '}';
2109                                         
2110                                         // when the value changes externally, update the internal item object
2111                                         scope.$watchCollection(watchExpressions, function(newVals, oldVals) {
2112                                                 for (var aspect in newVals) {
2113                                                         var newVal = newVals[aspect];
2114                                                         var oldVal = oldVals[aspect];
2115                                                         if (oldVal === newVal) {
2116                                                                 continue;
2117                                                         }
2118                                                         newVal = parseInt(newVal, 10);
2119                                                         if (!isNaN(newVal)) {
2120                                                                 item[aspect] = newVal;
2121                                                         }
2122                                                 }
2123                                         });
2124
2125                                         function positionChanged() {
2126                                                 // call setPosition so the element and gridster controller are updated
2127                                                 item.setPosition(item.row, item.col);
2128
2129                                                 // when internal item position changes, update externally bound values
2130                                                 if ($getters.row && $getters.row.assign) {
2131                                                         $getters.row.assign(scope, item.row);
2132                                                 }
2133                                                 if ($getters.col && $getters.col.assign) {
2134                                                         $getters.col.assign(scope, item.col);
2135                                                 }
2136                                         }
2137                                         scope.$watch(function() {
2138                                                 return item.row + ',' + item.col;
2139                                         }, positionChanged);
2140
2141                                         function sizeChanged() {
2142                                                 var changedX = item.setSizeX(item.sizeX, true);
2143                                                 if (changedX && $getters.sizeX && $getters.sizeX.assign) {
2144                                                         $getters.sizeX.assign(scope, item.sizeX);
2145                                                 }
2146                                                 var changedY = item.setSizeY(item.sizeY, true);
2147                                                 if (changedY && $getters.sizeY && $getters.sizeY.assign) {
2148                                                         $getters.sizeY.assign(scope, item.sizeY);
2149                                                 }
2150
2151                                                 if (changedX || changedY) {
2152                                                         item.gridster.moveOverlappingItems(item);
2153                                                         gridster.layoutChanged();
2154                                                         scope.$broadcast('gridster-item-resized', item);
2155                                                 }
2156                                         }
2157
2158                                         scope.$watch(function() {
2159                                                 return item.sizeY + ',' + item.sizeX + ',' + item.minSizeX + ',' + item.maxSizeX + ',' + item.minSizeY + ',' + item.maxSizeY;
2160                                         }, sizeChanged);
2161
2162                                         var draggable = new GridsterDraggable($el, scope, gridster, item, options);
2163                                         var resizable = new GridsterResizable($el, scope, gridster, item, options);
2164
2165                                         var updateResizable = function() {
2166                                                 resizable.toggle(!gridster.isMobile && gridster.resizable && gridster.resizable.enabled);
2167                                         };
2168                                         updateResizable();
2169
2170                                         var updateDraggable = function() {
2171                                                 draggable.toggle(!gridster.isMobile && gridster.draggable && gridster.draggable.enabled);
2172                                         };
2173                                         updateDraggable();
2174
2175                                         scope.$on('gridster-draggable-changed', updateDraggable);
2176                                         scope.$on('gridster-resizable-changed', updateResizable);
2177                                         scope.$on('gridster-resized', updateResizable);
2178                                         scope.$on('gridster-mobile-changed', function() {
2179                                                 updateResizable();
2180                                                 updateDraggable();
2181                                         });
2182
2183                                         function whichTransitionEvent() {
2184                                                 var el = document.createElement('div');
2185                                                 var transitions = {
2186                                                         'transition': 'transitionend',
2187                                                         'OTransition': 'oTransitionEnd',
2188                                                         'MozTransition': 'transitionend',
2189                                                         'WebkitTransition': 'webkitTransitionEnd'
2190                                                 };
2191                                                 for (var t in transitions) {
2192                                                         if (el.style[t] !== undefined) {
2193                                                                 return transitions[t];
2194                                                         }
2195                                                 }
2196                                         }
2197
2198                                         var debouncedTransitionEndPublisher = gridsterDebounce(function() {
2199                                                 scope.$apply(function() {
2200                                                         scope.$broadcast('gridster-item-transition-end', item);
2201                                                 });
2202                                         }, 50);
2203
2204                                         if(whichTransitionEvent()){ //check for IE8, as it evaluates to null
2205                                             $el.on(whichTransitionEvent(), debouncedTransitionEndPublisher);
2206                                         }
2207
2208                                         scope.$broadcast('gridster-item-initialized', item);
2209
2210                                         return scope.$on('$destroy', function() {
2211                                                 try {
2212                                                         resizable.destroy();
2213                                                         draggable.destroy();
2214                                                 } catch (e) {}
2215
2216                                                 try {
2217                                                         gridster.removeItem(item);
2218                                                 } catch (e) {}
2219
2220                                                 try {
2221                                                         item.destroy();
2222                                                 } catch (e) {}
2223                                         });
2224                                 }
2225                         };
2226                 }
2227         ])
2228
2229         .directive('gridsterNoDrag', function() {
2230                 return {
2231                         restrict: 'A',
2232                         link: function(scope, $element) {
2233                                 $element.addClass('gridster-no-drag');
2234                         }
2235                 };
2236         })
2237
2238         ;
2239
2240 }));