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