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