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