CLIENT GUI Framework
[vnfsdk/refrepo.git] / portal-common / src / main / webapp / common / thirdparty / freewall / freewall.js
1 // created by Minh Nguyen;\r
2 // version 1.05;\r
3 \r
4 (function($) {\r
5     \r
6     // for zeptojs;\r
7     $.isNumeric == null && ($.isNumeric = function(src) {\r
8         return src != null && src.constructor === Number;\r
9     });\r
10 \r
11     $.isFunction == null && ($.isFunction = function(src) {\r
12         return src != null && src instanceof Function;\r
13     });\r
14 \r
15     var $W = $(window);\r
16     var $D = $(document);\r
17     \r
18     var layoutManager = {\r
19         // default setting;\r
20         defaultConfig: {\r
21             animate: false,\r
22             cellW: 100, // function(container) {return 100;}\r
23             cellH: 100, // function(container) {return 100;}\r
24             delay: 0, // slowdown active block;\r
25             engine: 'giot', // 'giot' is a person name;\r
26             fixSize: null, // resize + adjust = fill gap;\r
27             //fixSize: 0, resize but keep ratio = no fill gap;\r
28             //fixSize: 1, no resize + no adjust = no fill gap;\r
29             gutterX: 15, // width spacing between blocks;\r
30             gutterY: 15, // height spacing between blocks;\r
31             keepOrder: false,\r
32             selector: '> div',\r
33             draggable: false,\r
34             cacheSize: true, // caches the original size of block;\r
35             rightToLeft: false,\r
36             bottomToTop: false,\r
37             onGapFound: function() {},\r
38             onComplete: function() {},\r
39             onResize: function() {},\r
40             onBlockDrag: function() {},\r
41             onBlockMove: function() {},\r
42             onBlockDrop: function() {},\r
43             onBlockReady: function() {},\r
44             onBlockFinish: function() {},\r
45             onBlockActive: function() {},\r
46             onBlockResize: function() {}\r
47         },\r
48         plugin: {},\r
49         totalGrid: 1,\r
50         transition: false,\r
51         loadBlock: function(item, setting) {\r
52             var runtime = setting.runtime;\r
53             var gutterX = runtime.gutterX;\r
54             var gutterY = runtime.gutterY;\r
55             var cellH = runtime.cellH;\r
56             var cellW = runtime.cellW;\r
57             var block = null;\r
58             var $item = $(item);\r
59             var active = $item.data("active");\r
60             var fixPos = $item.attr('data-position');\r
61             var fixSize = parseInt($item.attr('data-fixSize'));\r
62             var blockId = runtime.lastId++ + '-' + runtime.totalGrid;\r
63             \r
64             //ignore dragging block;\r
65             if ($item.hasClass('fw-float')) return;\r
66             $item.attr({id: blockId, 'data-delay': item.index});\r
67 \r
68             //remove animation for speed render;\r
69             if (setting.animate && this.transition) {\r
70                 this.setTransition(item, "");\r
71             }\r
72 \r
73             isNaN(fixSize) && (fixSize = null);\r
74             (fixSize == null) && (fixSize = setting.fixSize);\r
75             var makeRound = (fixSize >= 1) ? "ceil" : "round";\r
76             // store original size;\r
77            \r
78             $item.attr('data-height') == null && $item.attr('data-height', $item.height());\r
79             $item.attr('data-width') == null && $item.attr('data-width', $item.width());\r
80             var height = 1 * $item.attr('data-height');\r
81             var width = 1 * $item.attr('data-width');\r
82             \r
83             if (!setting.cacheSize) {\r
84                 item.style.width = "";\r
85                 width = $item.width();\r
86 \r
87                 item.style.height = "";\r
88                 height = $item.height();\r
89             }\r
90 \r
91             var col = !width ? 0 : Math[makeRound]((width + gutterX) / cellW);\r
92             var row = !height ? 0 : Math[makeRound]((height + gutterY) / cellH);\r
93 \r
94             // estimate size;\r
95             if (!fixSize && setting.cellH == 'auto') {\r
96                 $item.width(cellW * col - gutterX);\r
97                 item.style.height = "";\r
98                 height = $item.height();\r
99                 row = !height ? 0 : Math.round((height + gutterY) / cellH);\r
100             }\r
101 \r
102             if (!fixSize && setting.cellW == 'auto') {\r
103                 $item.height(cellH * row - gutterY);\r
104                 item.style.width = "";\r
105                 width = $item.width();\r
106                 col = !width ? 0 : Math.round((width + gutterX) / cellW);\r
107             }\r
108             \r
109             // for none resize block;\r
110             if ((fixSize != null) && (col > runtime.limitCol || row > runtime.limitRow)) {\r
111                 block = null;\r
112             } else {\r
113                 // get smallest width and smallest height of block;\r
114                 // using for image runtime;\r
115                 row && row < runtime.minHoB && (runtime.minHoB = row);\r
116                 col && col < runtime.minWoB && (runtime.minWoB = col);\r
117 \r
118                 // get biggest width and biggest height of block;\r
119                 row > runtime.maxHoB && (runtime.maxHoB = row);\r
120                 col > runtime.maxWoB && (runtime.maxWoB = col);\r
121 \r
122                 width == 0 && (col = 0);\r
123                 height == 0 && (row = 0);\r
124 \r
125                 block = {\r
126                     resize: false,\r
127                     id: blockId,\r
128                     width: col,\r
129                     height: row,\r
130                     fixSize: fixSize\r
131                 };\r
132 \r
133                 // for fix position;\r
134                 if (fixPos) {\r
135                     fixPos = fixPos.split("-");\r
136                     block.y = 1 * fixPos[0];\r
137                     block.x = 1 * fixPos[1];\r
138                     block.width = fixSize != null ? col : Math.min(col, runtime.limitCol - block.x);\r
139                     block.height = fixSize != null ? row : Math.min(row, runtime.limitRow - block.y);\r
140                     var holeId = block.y + "-" + block.x + "-" + block.width + "-" + block.height;\r
141                     if (active) {\r
142                         runtime.holes[holeId] = {\r
143                             id: block.id,\r
144                             top: block.y,\r
145                             left: block.x,\r
146                             width: block.width,\r
147                             height: block.height\r
148                         };\r
149                         this.setBlock(block, setting);\r
150                     } else {\r
151                         delete runtime.holes[holeId];\r
152                     }\r
153                     \r
154                 }\r
155             }\r
156 \r
157             // for css animation;\r
158             if ($item.attr("data-state") == null) {\r
159                 $item.attr("data-state", "init");\r
160             } else {\r
161                 $item.attr("data-state", "move");\r
162             }\r
163 \r
164             setting.onBlockReady.call(item, block, setting);\r
165 \r
166             return (fixPos && active) ? null : block;\r
167         },\r
168         setBlock: function(block, setting) {\r
169             var runtime = setting.runtime;\r
170             var gutterX = runtime.gutterX;\r
171             var gutterY = runtime.gutterY;\r
172             var height = block.height;\r
173             var width = block.width;\r
174             var cellH = runtime.cellH;\r
175             var cellW = runtime.cellW;\r
176             var x = block.x;\r
177             var y = block.y;\r
178 \r
179             if (setting.rightToLeft) {\r
180                 x = runtime.limitCol - x - width;\r
181             }\r
182             if (setting.bottomToTop) {\r
183                 y = runtime.limitRow - y - height;\r
184             }\r
185 \r
186             var realBlock = {\r
187                 fixSize: block.fixSize,\r
188                 resize: block.resize,\r
189                 top: y * cellH,\r
190                 left: x  * cellW,\r
191                 width: cellW * width - gutterX,\r
192                 height: cellH * height - gutterY\r
193             };\r
194             \r
195             realBlock.top = 1 * realBlock.top.toFixed(2);\r
196             realBlock.left = 1 * realBlock.left.toFixed(2);\r
197             realBlock.width = 1 * realBlock.width.toFixed(2);\r
198             realBlock.height = 1 * realBlock.height.toFixed(2);\r
199 \r
200             //runtime.length += 1;\r
201             block.id && (runtime.blocks[block.id] = realBlock);\r
202 \r
203             // for append feature;\r
204             return realBlock;\r
205         },\r
206         showBlock: function(item, setting) {\r
207             var runtime = setting.runtime;\r
208             var method = setting.animate && !this.transition ? 'animate' : 'css';\r
209             var block = runtime.blocks[item.id];\r
210             var $item = $(item);\r
211             var self = this;\r
212             var start = $item.attr("data-state") != "move";\r
213             var trans = start ? "width 0.5s, height 0.5s" : "top 0.5s, left 0.5s, width 0.5s, height 0.5s, opacity 0.5s";\r
214             \r
215             item.delay && clearTimeout(item.delay);\r
216             //ignore dragging block;\r
217             if ($item.hasClass('fw-float')) return;\r
218             \r
219             // kill the old transition;\r
220             self.setTransition(item, "");\r
221             item.style.position = "absolute";\r
222             setting.onBlockActive.call(item, block, setting);\r
223             \r
224             function action() {\r
225                 // start to arrange;\r
226                 start && $item.attr("data-state", "start");\r
227                 // add animation by using css3 transition;\r
228                 if (setting.animate && self.transition) {\r
229                     self.setTransition(item, trans);\r
230                 }\r
231 \r
232                 // for hidden block;\r
233                 if (!block) {\r
234                     //var position = $item.position(); <= make speed so slow;\r
235                     var height = parseInt(item.style.height) || 0;\r
236                     var width = parseInt(item.style.width) || 0;\r
237                     var left = parseInt(item.style.left) || 0;\r
238                     var top = parseInt(item.style.top) || 0;\r
239                     $item[method]({\r
240                         left: left + width / 2,\r
241                         top: top + height / 2,\r
242                         width: 0,\r
243                         height: 0,\r
244                         opacity: 0\r
245                     });\r
246                 } else {\r
247                     if (block.fixSize) {\r
248                         block.height = 1 * $item.attr("data-height");\r
249                         block.width = 1 * $item.attr("data-width");\r
250                     }\r
251 \r
252                     $item["css"]({\r
253                         opacity: 1,\r
254                         width: block.width,\r
255                         height: block.height\r
256                     });\r
257 \r
258                     // for animating by javascript;\r
259                     $item[method]({\r
260                         top: block.top,\r
261                         left: block.left\r
262                     });\r
263 \r
264                     if ($item.attr('data-nested') != null) {\r
265                         self.nestedGrid(item, setting);\r
266                     }\r
267                 }\r
268 \r
269                 runtime.length -= 1;\r
270 \r
271                 setting.onBlockFinish.call(item, block, setting);\r
272 \r
273                 runtime.length == 0 && setting.onComplete.call(item, block, setting);\r
274             }\r
275 \r
276             block && block.resize && setting.onBlockResize.call(item, block, setting);\r
277             \r
278             setting.delay > 0 ? (item.delay = setTimeout(action, setting.delay * $item.attr("data-delay"))) : action(); \r
279         },\r
280         nestedGrid: function(item, setting) {\r
281             var innerWall, $item = $(item), runtime = setting.runtime;\r
282             var gutterX = $item.attr("data-gutterX") || setting.gutterX;\r
283             var gutterY = $item.attr("data-gutterY") || setting.gutterY;\r
284             var method = $item.attr("data-method") || "fitZone";\r
285             var nested = $item.attr('data-nested') || "> div";\r
286             var cellH = $item.attr("data-cellH") || setting.cellH;\r
287             var cellW = $item.attr("data-cellW") || setting.cellW;\r
288             var block = runtime.blocks[item.id];\r
289             \r
290             if (block) {\r
291                 innerWall = new freewall($item);\r
292                 innerWall.reset({\r
293                     cellH: cellH,\r
294                     cellW: cellW,\r
295                     gutterX: 1 * gutterX,\r
296                     gutterY: 1 * gutterY,\r
297                     selector: nested,\r
298                     cacheSize: false\r
299                 });\r
300 \r
301                 switch (method) {\r
302                     case "fitHeight":\r
303                         innerWall[method](block.height);\r
304                         break;\r
305                     case "fitWidth":\r
306                         innerWall[method](block.width);\r
307                         break;\r
308                     case "fitZone":\r
309                         innerWall[method](block.width, block.height);\r
310                         break;\r
311                 }\r
312             }\r
313         },\r
314         adjustBlock: function(block, setting) {\r
315             var runtime = setting.runtime;\r
316             var gutterX = runtime.gutterX;\r
317             var gutterY = runtime.gutterY;\r
318             var $item = $("#" + block.id);\r
319             var cellH = runtime.cellH;\r
320             var cellW = runtime.cellW;\r
321 \r
322             if (setting.cellH == 'auto') {\r
323                 $item.width(block.width * cellW - gutterX);\r
324                 $item[0].style.height = "";\r
325                 block.height = Math.round(($item.height() + gutterY) / cellH);\r
326             }\r
327         },\r
328         adjustUnit: function(width, height, setting) {\r
329             var gutterX = setting.gutterX;\r
330             var gutterY = setting.gutterY;\r
331             var runtime = setting.runtime;\r
332             var cellW = setting.cellW;\r
333             var cellH = setting.cellH;\r
334 \r
335             $.isFunction(cellW) && (cellW = cellW(width));\r
336             cellW = 1 * cellW;\r
337             !$.isNumeric(cellW) && (cellW = 1);\r
338             \r
339             $.isFunction(cellH) && (cellH = cellH(height));\r
340             cellH = 1 * cellH;\r
341             !$.isNumeric(cellH) && (cellH = 1);\r
342 \r
343             if ($.isNumeric(width)) {\r
344                 // adjust cell width via container;\r
345                 cellW < 1 && (cellW = cellW * width);\r
346 \r
347                 // estimate total columns;\r
348                 var limitCol = Math.max(1, Math.floor(width / cellW));\r
349 \r
350                 // adjust unit size for fit width;\r
351                 if (!$.isNumeric(gutterX)) {\r
352                     gutterX = (width - limitCol * cellW) / Math.max(1, (limitCol - 1));\r
353                     gutterX = Math.max(0, gutterX);\r
354                 }\r
355 \r
356                 limitCol = Math.floor((width + gutterX) / cellW);\r
357                 runtime.cellW = (width + gutterX) / Math.max(limitCol, 1);\r
358                 runtime.cellS = runtime.cellW / cellW;\r
359                 runtime.gutterX = gutterX;\r
360                 runtime.limitCol = limitCol;\r
361             } \r
362 \r
363             if ($.isNumeric(height)) {\r
364                 // adjust cell height via container;\r
365                 cellH < 1 && (cellH = cellH * height);\r
366 \r
367                 // estimate total rows;\r
368                 var limitRow = Math.max(1, Math.floor(height / cellH));\r
369 \r
370                 // adjust size unit for fit height;\r
371                 if (!$.isNumeric(gutterY)) {\r
372                     gutterY = (height - limitRow * cellH) / Math.max(1, (limitRow - 1));\r
373                     gutterY = Math.max(0, gutterY);\r
374                 }\r
375 \r
376                 limitRow = Math.floor((height + gutterY) / cellH);\r
377                 runtime.cellH = (height + gutterY) / Math.max(limitRow, 1);\r
378                 runtime.cellS = runtime.cellH / cellH;\r
379                 runtime.gutterY = gutterY;\r
380                 runtime.limitRow = limitRow;\r
381             } \r
382 \r
383             if (!$.isNumeric(width)) {\r
384                 // adjust cell width via cell height;\r
385                 cellW < 1 && (cellW = runtime.cellH);\r
386                 runtime.cellW = cellW != 1 ? cellW * runtime.cellS : 1;\r
387                 runtime.gutterX = gutterX;\r
388                 runtime.limitCol = 666666;\r
389             }\r
390 \r
391             if (!$.isNumeric(height)) {\r
392                 // adjust cell height via cell width;\r
393                 cellH < 1 && (cellH = runtime.cellW);\r
394                 runtime.cellH = cellH != 1 ? cellH * runtime.cellS : 1;\r
395                 runtime.gutterY = gutterY;\r
396                 runtime.limitRow = 666666;\r
397             }\r
398         },\r
399         resetGrid: function(runtime) {\r
400             runtime.blocks = {};\r
401             runtime.length = 0;\r
402             runtime.cellH = 0;\r
403             runtime.cellW = 0;\r
404             runtime.lastId = 1;\r
405             runtime.matrix = {};\r
406             runtime.totalCol = 0;\r
407             runtime.totalRow = 0;\r
408         },\r
409         setDraggable: function(item, option) {\r
410             var isTouch = false;\r
411             var config = {\r
412                 startX: 0, //start clientX;\r
413                 startY: 0, \r
414                 top: 0,\r
415                 left: 0,\r
416                 handle: null,\r
417                 onDrop: function() {},\r
418                 onDrag: function() {},\r
419                 onStart: function() {}\r
420             };\r
421 \r
422             $(item).each(function() {\r
423                 var setting = $.extend({}, config, option);\r
424                 var handle = setting.handle || this;\r
425                 var ele = this;\r
426                 var $E = $(ele);\r
427                 var $H = $(handle);\r
428 \r
429                 var posStyle = $E.css("position");\r
430                 posStyle != "absolute" && $E.css("position", "relative");\r
431                 \r
432 \r
433                 function mouseDown(evt) {\r
434                     evt.stopPropagation();\r
435                     evt = evt.originalEvent;\r
436 \r
437                     if (evt.touches) {\r
438                         isTouch = true;\r
439                         evt = evt.changedTouches[0];\r
440                     }\r
441 \r
442                     if (evt.button != 2 && evt.which != 3) {\r
443                         setting.onStart.call(ele, evt);\r
444                         \r
445                         setting.startX = evt.clientX;\r
446                         setting.startY = evt.clientY;\r
447                         setting.top = parseInt($E.css("top")) || 0;\r
448                         setting.left = parseInt($E.css("left")) || 0;\r
449                         \r
450                         $D.bind("mouseup touchend", mouseUp);\r
451                         $D.bind("mousemove touchmove", mouseMove); \r
452                     }\r
453 \r
454                     return false;\r
455                 };\r
456                 \r
457                         \r
458                 function mouseMove(evt) {\r
459                     evt = evt.originalEvent;\r
460                     isTouch && (evt = evt.changedTouches[0]);\r
461                     \r
462                     $E.css({\r
463                         top: setting.top - (setting.startY - evt.clientY),\r
464                         left: setting.left - (setting.startX - evt.clientX)\r
465                     });\r
466                     \r
467                     setting.onDrag.call(ele, evt);\r
468                 };\r
469                 \r
470                 function mouseUp(evt) {\r
471                     evt = evt.originalEvent;\r
472                     isTouch && (evt = evt.changedTouches[0]);\r
473         \r
474                     setting.onDrop.call(ele, evt);\r
475 \r
476                     $D.unbind("mouseup touchend", mouseUp);\r
477                     $D.unbind("mousemove touchmove", mouseMove);\r
478                 };\r
479 \r
480                 // ignore drag drop on text field;\r
481                 $E.find("iframe, form, input, textarea, .ignore-drag")\r
482                 .each(function() {\r
483                     $(this).on("touchstart mousedown", function(evt) {\r
484                         evt.stopPropagation();\r
485                     });\r
486                 });\r
487                 \r
488                 $D.unbind("mouseup touchend", mouseUp);\r
489                 $D.unbind("mousemove touchmove", mouseMove);\r
490                 $H.unbind("mousedown touchstart").bind("mousedown touchstart", mouseDown);\r
491 \r
492             });\r
493         },\r
494         setTransition: function(item, trans) {\r
495             var style = item.style;\r
496             var $item = $(item);\r
497                 \r
498             // remove animation;\r
499             if (!this.transition && $item.stop) {\r
500                 $item.stop();\r
501             } else if (style.webkitTransition != null) {\r
502                 style.webkitTransition = trans;\r
503             } else if (style.MozTransition != null) {\r
504                 style.MozTransition = trans;\r
505             } else if (style.msTransition != null) {\r
506                 style.msTransition = trans;\r
507             } else if (style.OTransition != null) {\r
508                 style.OTransition = trans;\r
509             } else {\r
510                 style.transition = trans;\r
511             }\r
512         },\r
513         getFreeArea: function(t, l, runtime) {\r
514             var maxY = Math.min(t + runtime.maxHoB, runtime.limitRow);\r
515             var maxX = Math.min(l + runtime.maxWoB, runtime.limitCol);\r
516             var minX = maxX;\r
517             var minY = maxY;\r
518             var matrix = runtime.matrix;\r
519             \r
520             // find limit zone by horizon;\r
521             for (var y = t; y < minY; ++y) {\r
522                 for (var x = l; x < maxX; ++x) {\r
523                     if (matrix[y + '-' + x]) {\r
524                         (l < x && x < minX) && (minX = x);\r
525                     }\r
526                 }\r
527             }\r
528             \r
529             // find limit zone by vertical;\r
530             for (var y = t; y < maxY; ++y) {\r
531                 for (var x = l; x < minX; ++x) {\r
532                     if (matrix[y + '-' + x]) {\r
533                         (t < y && y < minY) && (minY = y);\r
534                     }\r
535                 }\r
536             }\r
537 \r
538             return {\r
539                 top: t,\r
540                 left: l,\r
541                 width: minX - l,\r
542                 height: minY - t\r
543             };\r
544 \r
545         },\r
546         setWallSize: function(runtime, container) {\r
547             var totalRow = runtime.totalRow;\r
548             var totalCol = runtime.totalCol;\r
549             var gutterY = runtime.gutterY;\r
550             var gutterX = runtime.gutterX;\r
551             var cellH = runtime.cellH;\r
552             var cellW = runtime.cellW;\r
553             var totalWidth = Math.max(0, cellW * totalCol - gutterX);\r
554             var totalHeight = Math.max(0, cellH * totalRow - gutterY);\r
555             \r
556             container.attr({\r
557                 'data-total-col': totalCol,\r
558                 'data-total-row': totalRow,\r
559                 'data-wall-width': Math.ceil(totalWidth),\r
560                 'data-wall-height': Math.ceil(totalHeight)\r
561             });\r
562 \r
563             if (runtime.limitCol < runtime.limitRow) {\r
564                 // do not set height with nesting grid;\r
565                 !container.attr("data-height") && container.height(Math.ceil(totalHeight));\r
566             }\r
567         }\r
568     };\r
569 \r
570     \r
571 \r
572     var engine = {\r
573         // Giot just a person name;\r
574         giot: function(items, setting) {\r
575             var runtime = setting.runtime,\r
576                 row = runtime.limitRow,\r
577                 col = runtime.limitCol,\r
578                 x = 0,\r
579                 y = 0,\r
580                 maxX = runtime.totalCol,\r
581                 maxY = runtime.totalRow,\r
582                 wall = {},\r
583                 holes = runtime.holes,\r
584                 block = null,\r
585                 matrix = runtime.matrix,\r
586                 bigLoop = Math.max(col, row),\r
587                 freeArea = null,\r
588                 misBlock = null,\r
589                 fitWidth = col < row ? 1 : 0,\r
590                 lastBlock = null,\r
591                 smallLoop = Math.min(col, row);\r
592 \r
593             // fill area with top, left, width, height;\r
594             function fillMatrix(id, t, l, w, h) {\r
595                 for (var y = t; y < t + h;) {\r
596                     for (var x = l; x < l + w;) {\r
597                         matrix[y + '-' + x] = id;\r
598                         ++x > maxX && (maxX = x);\r
599                     }\r
600                     ++y > maxY && (maxY = y);\r
601                 }\r
602             }\r
603             \r
604             // set holes on the wall;\r
605             for (var i in holes) {\r
606                 if (holes.hasOwnProperty(i)) {\r
607                     fillMatrix(holes[i]["id"] || true, holes[i]['top'], holes[i]['left'], holes[i]['width'], holes[i]['height']);\r
608                 }\r
609             }\r
610             \r
611 \r
612             for (var b = 0; b < bigLoop; ++b) {\r
613                 if (!items.length) break;\r
614                 fitWidth ? (y = b) : (x = b);\r
615                 lastBlock = null;\r
616 \r
617                 for (var s = 0; s < smallLoop; ++s) {\r
618                     if (!items.length) break;\r
619                     block = null;\r
620                     fitWidth ? (x = s) : (y = s);\r
621                     if (runtime.matrix[y + '-' + x]) continue;\r
622                     freeArea = layoutManager.getFreeArea(y, x, runtime);\r
623 \r
624                     // trying resize last block to fit free area;\r
625                     if (setting.fixSize == null) {\r
626                         // resize near block to fill gap;\r
627                         if (lastBlock && !fitWidth && runtime.minHoB > freeArea.height) {\r
628                             lastBlock.height += freeArea.height;\r
629                             lastBlock.resize = true;\r
630                             fillMatrix(lastBlock.id, lastBlock.y, lastBlock.x, lastBlock.width, lastBlock.height);\r
631                             layoutManager.setBlock(lastBlock, setting);\r
632                             continue;\r
633                         } else if (lastBlock && fitWidth && runtime.minWoB > freeArea.width) {\r
634                             lastBlock.width += freeArea.width;\r
635                             lastBlock.resize = true;\r
636                             fillMatrix(lastBlock.id, lastBlock.y, lastBlock.x, lastBlock.width, lastBlock.height);\r
637                             layoutManager.setBlock(lastBlock, setting);\r
638                             continue;\r
639                         }\r
640                     }\r
641                     \r
642                     // get the next block to keep order;\r
643                     if (setting.keepOrder) {\r
644                         block = items.shift();\r
645                         block.resize = true;\r
646                     } else {\r
647                         // find a suitable block to fit gap;\r
648                         for (var i = 0; i < items.length; ++i) {\r
649                             if (items[i].height > freeArea.height) continue;\r
650                             if (items[i].width > freeArea.width) continue;\r
651                             block = items.splice(i, 1)[0];\r
652                             break;\r
653                         }\r
654 \r
655                         // trying resize the other block to fit gap;\r
656                         if (block == null && setting.fixSize == null) {\r
657                             // get other block fill to gap;\r
658                             for (var i = 0; i < items.length; ++i) {\r
659                                 if (items[i]['fixSize'] != null) continue;\r
660                                 block = items.splice(i, 1)[0];\r
661                                 block.resize = true;\r
662                                 break;\r
663                             }\r
664                             \r
665                         }\r
666                     }\r
667 \r
668                     \r
669                     if (block != null) {\r
670                         // resize block with free area;\r
671                         if (block.resize) {\r
672                             if (fitWidth) {\r
673                                 block.width = freeArea.width;\r
674                                 if (setting.cellH == 'auto') {\r
675                                     layoutManager.adjustBlock(block, setting);\r
676                                 }\r
677                                 // for fitZone;\r
678                                 block.height = Math.min(block.height, freeArea.height);\r
679                             } else {\r
680                                 block.height = freeArea.height;\r
681                                 // for fitZone;\r
682                                 block.width = Math.min(block.width, freeArea.width);\r
683                             }\r
684                         }\r
685 \r
686                         wall[block.id] = {\r
687                             id: block.id,\r
688                             x: x,\r
689                             y: y,\r
690                             width: block.width,\r
691                             height: block.height,\r
692                             resize: block.resize,\r
693                             fixSize: block.fixSize\r
694                         };\r
695                         \r
696                         // keep success block for next round;\r
697                         lastBlock = wall[block.id];\r
698 \r
699                         fillMatrix(lastBlock.id, lastBlock.y, lastBlock.x, lastBlock.width, lastBlock.height);\r
700                         layoutManager.setBlock(lastBlock, setting);\r
701                     } else {\r
702                         // get expect area;\r
703                         var misBlock = {\r
704                             x: x,\r
705                             y: y,\r
706                             fixSize: 0\r
707                         };\r
708                         if (fitWidth) {\r
709                             misBlock.width = freeArea.width;\r
710                             misBlock.height = 0;\r
711                             var lastX = x - 1;\r
712                             var lastY = y;\r
713                             \r
714                             while (matrix[lastY + '-' + lastX]) {\r
715                                 matrix[lastY + '-' + x] = true;\r
716                                 misBlock.height += 1;\r
717                                 lastY += 1;\r
718                             }\r
719                         } else {\r
720                             misBlock.height = freeArea.height;\r
721                             misBlock.width = 0;\r
722                             var lastY = y - 1;\r
723                             var lastX = x;\r
724                             \r
725                             while (matrix[lastY + '-' + lastX]) {\r
726                                 matrix[y + '-' + lastX] = true;\r
727                                 misBlock.width += 1;\r
728                                 lastX += 1;\r
729                             }\r
730                         }\r
731                         setting.onGapFound(layoutManager.setBlock(misBlock, setting), setting);\r
732                     }\r
733                 }\r
734 \r
735             }\r
736 \r
737             runtime.matrix = matrix;\r
738             runtime.totalRow = maxY;\r
739             runtime.totalCol = maxX;\r
740         }\r
741     };\r
742 \r
743 \r
744 \r
745     window.freewall = function(selector) {\r
746         \r
747         var container = $(selector);\r
748         if (container.css('position') == 'static') {\r
749             container.css('position', 'relative');\r
750         }\r
751         var MAX = Number.MAX_VALUE;\r
752         var klass = this;\r
753         // increase the instance index;\r
754         layoutManager.totalGrid += 1;\r
755 \r
756         var setting = $.extend({}, layoutManager.defaultConfig);\r
757         var runtime = {\r
758             blocks: {}, // store all items;\r
759             events: {}, // store custome events;\r
760             matrix: {},\r
761             holes: {}, // forbidden zone;\r
762             \r
763             cellW: 0,\r
764             cellH: 0, // unit adjust;\r
765             cellS: 1, // unit scale;\r
766             \r
767             filter: '', // filter selector;\r
768             \r
769             lastId: 0,\r
770             length: 0,\r
771 \r
772             maxWoB: 0, // max width of block;\r
773             maxHoB: 0,\r
774             minWoB: MAX, \r
775             minHoB: MAX, // min height of block;\r
776 \r
777             running: 0, // flag to check layout arranging;\r
778 \r
779             gutterX: 15, \r
780             gutterY: 15,\r
781 \r
782             totalCol: 0,\r
783             totalRow: 0,\r
784 \r
785             limitCol: 666666, // maximum column; \r
786             limitRow: 666666,\r
787             \r
788             currentMethod: null,\r
789             currentArguments: []\r
790         };\r
791         setting.runtime = runtime;\r
792         runtime.totalGrid = layoutManager.totalGrid;\r
793         \r
794         // check browser support transition;\r
795         var bodyStyle = document.body.style;\r
796         if (!layoutManager.transition) {\r
797             (bodyStyle.webkitTransition != null ||\r
798             bodyStyle.MozTransition != null ||\r
799             bodyStyle.msTransition != null ||\r
800             bodyStyle.OTransition != null ||\r
801             bodyStyle.transition != null) &&\r
802             (layoutManager.transition = true);\r
803         }\r
804         \r
805 \r
806         function setDraggable(item) {\r
807             \r
808             var gutterX = runtime.gutterX;\r
809             var gutterY = runtime.gutterY;\r
810             var cellH = runtime.cellH;\r
811             var cellW = runtime.cellW;\r
812             var $item = $(item);\r
813             var handle = $item.find($item.attr("data-handle"));\r
814             layoutManager.setDraggable(item, {\r
815                 handle: handle[0],\r
816                 onStart: function(event) {\r
817                     if (setting.animate && layoutManager.transition) {\r
818                         layoutManager.setTransition(this, "");\r
819                     }\r
820                     $item.css('z-index', 9999).addClass('fw-float');\r
821                     \r
822                     setting.onBlockDrag.call(item, event);\r
823                 },\r
824                 onDrag: function(event, tracker) {\r
825                     var position = $item.position();\r
826                     var top = Math.round(position.top / cellH);\r
827                     var left = Math.round(position.left / cellW);\r
828                     var width = Math.round($item.width() / cellW);\r
829                     var height = Math.round($item.height() / cellH);\r
830                     top = Math.min(Math.max(0, top), runtime.limitRow - height);\r
831                     left = Math.min(Math.max(0, left), runtime.limitCol - width);\r
832                     klass.setHoles({top: top, left: left, width: width, height: height});\r
833                     klass.refresh();\r
834 \r
835                     setting.onBlockMove.call(item, event);\r
836                 },\r
837                 onDrop: function(event) {\r
838                     var position = $item.position();\r
839                     var top = Math.round(position.top / cellH);\r
840                     var left = Math.round(position.left / cellW);\r
841                     var width = Math.round($item.width() / cellW);\r
842                     var height = Math.round($item.height() / cellH);\r
843                     top = Math.min(Math.max(0, top), runtime.limitRow - height);\r
844                     left = Math.min(Math.max(0, left), runtime.limitCol - width);\r
845 \r
846                     $item.removeClass('fw-float');\r
847                     $item.css({\r
848                         zIndex: "auto",\r
849                         top: top * cellH,\r
850                         left: left * cellW\r
851                     });\r
852                     \r
853                     //check old drag element;\r
854                     var x, y, key, oldDropId;\r
855                     for (y = 0; y < height; ++y) {\r
856                         for (x = 0; x < width; ++x) {\r
857                             key = (y + top) + "-" + (x + left);\r
858                             oldDropId = runtime.matrix[key];\r
859                             if (oldDropId && oldDropId != true) {\r
860                                 $("#" + oldDropId).removeAttr("data-position");\r
861                             }\r
862                         }\r
863                     }\r
864                     \r
865                     runtime.holes = {};\r
866                     \r
867                     $item.attr({\r
868                         "data-width": $item.width(),\r
869                         "data-height": $item.height(),\r
870                         "data-position": top + "-" + left\r
871                     });\r
872 \r
873                     klass.refresh();\r
874 \r
875                     setting.onBlockDrop.call(item, event);\r
876                 }\r
877             });\r
878         }\r
879         \r
880 \r
881         $.extend(klass, {\r
882             \r
883             addCustomEvent: function(name, func) {\r
884                 var events = runtime.events;\r
885                 name = name.toLowerCase();\r
886                 !events[name] && (events[name] = []);\r
887                 func.eid = events[name].length;\r
888                 events[name].push(func);\r
889                 return this;\r
890             },\r
891 \r
892             appendBlock: function(items) {\r
893                 var allBlock = $(items).appendTo(container);\r
894                 var block = null;\r
895                 var activeBlock = [];\r
896                 \r
897                 if (runtime.currentMethod) {\r
898                     allBlock.each(function(index, item) {\r
899                         item.index = ++index;\r
900                         block = layoutManager.loadBlock(item, setting);\r
901                         block && activeBlock.push(block);\r
902                     });\r
903                 \r
904                     engine[setting.engine](activeBlock, setting);\r
905                     \r
906                     layoutManager.setWallSize(runtime, container);\r
907                     \r
908                     runtime.length = allBlock.length;\r
909 \r
910                     allBlock.each(function(index, item) {\r
911                         layoutManager.showBlock(item, setting);\r
912                         if (setting.draggable || item.getAttribute('data-draggable')) {\r
913                             setDraggable(item);\r
914                         }\r
915                     });\r
916                 }\r
917             },\r
918             /*\r
919             add one or more blank area (hole) on layout;\r
920             example:\r
921                 \r
922                 wall.appendHoles({\r
923                     top: 10,\r
924                     left: 36,\r
925                     width: 2,\r
926                     height: 6\r
927                 });\r
928 \r
929                 wall.appendHoles([\r
930                     {\r
931                         top: 16,\r
932                         left: 16,\r
933                         width: 8,\r
934                         height: 2\r
935                     },\r
936                     {\r
937                         top: 10,\r
938                         left: 36,\r
939                         width: 2,\r
940                         height: 6\r
941                     }\r
942                 ]);\r
943 \r
944             */\r
945             appendHoles: function(holes) {\r
946                 var newHoles = [].concat(holes), h = {}, i;\r
947                 for (i = 0; i < newHoles.length; ++i) {\r
948                     h = newHoles[i];\r
949                     runtime.holes[h.top + "-" + h.left + "-" + h.width + "-" + h.height] = h;\r
950                 }\r
951                 return this;\r
952             },\r
953 \r
954             container: container,\r
955 \r
956             destroy: function() {\r
957                 var allBlock = container.find(setting.selector).removeAttr('id'),\r
958                     block = null,\r
959                     activeBlock = [];\r
960 \r
961                 allBlock.each(function(index, item) {\r
962                     $item = $(item);\r
963                     var width = 1 * $item.attr('data-width') || "";\r
964                     var height = 1 * $item.attr('data-height') || "";\r
965                     $item.width(width).height(height).css({\r
966                         position: 'static'\r
967                     });\r
968                 });\r
969             },\r
970 \r
971             fillHoles: function(holes) {\r
972                 if (arguments.length == 0) {\r
973                     runtime.holes = {};\r
974                 } else {\r
975                     var newHoles = [].concat(holes), h = {}, i;\r
976                     for (i = 0; i < newHoles.length; ++i) {\r
977                         h = newHoles[i];\r
978                         delete runtime.holes[h.top + "-" + h.left + "-" + h.width + "-" + h.height];\r
979                     }\r
980                 }\r
981                 return this;\r
982             },\r
983 \r
984             filter: function(filter) {\r
985                 runtime.filter = filter;\r
986                 runtime.currentMethod && this.refresh();\r
987                 return this;\r
988             },\r
989 \r
990             fireEvent: function(name, object, setting) {\r
991                 var events = runtime.events;\r
992                 name = name.toLowerCase();\r
993                 if (events[name] && events[name].length) {\r
994                     for (var i = 0; i < events[name].length; ++i) {\r
995                         events[name][i].call(this, object, setting);\r
996                     }\r
997                 }\r
998                 return this;\r
999             },\r
1000 \r
1001             fitHeight: function(height) {\r
1002                 var allBlock = container.find(setting.selector).removeAttr('id'),\r
1003                     block = null,\r
1004                     activeBlock = [];\r
1005 \r
1006                 height = height ? height : container.height() || $W.height();\r
1007                 \r
1008                 runtime.currentMethod = arguments.callee;\r
1009                 runtime.currentArguments = arguments;\r
1010                 \r
1011                 layoutManager.resetGrid(runtime);\r
1012                 layoutManager.adjustUnit('auto', height, setting);\r
1013                 \r
1014                 if (runtime.filter) {\r
1015                     allBlock.data('active', 0);\r
1016                     allBlock.filter(runtime.filter).data('active', 1);\r
1017                 } else {\r
1018                     allBlock.data('active', 1);\r
1019                 }\r
1020 \r
1021                 allBlock.each(function(index, item) {\r
1022                     var $item = $(item);\r
1023                     item.index = ++index;\r
1024                     block = layoutManager.loadBlock(item, setting);\r
1025                     block && $item.data("active") && activeBlock.push(block);\r
1026                 });\r
1027                 \r
1028                 klass.fireEvent('onGridReady', container, setting);\r
1029 \r
1030                 engine[setting.engine](activeBlock, setting);\r
1031                 \r
1032                 layoutManager.setWallSize(runtime, container);\r
1033 \r
1034                 klass.fireEvent('onGridArrange', container, setting);\r
1035 \r
1036                 runtime.length = allBlock.length;\r
1037 \r
1038                 allBlock.each(function(index, item) {\r
1039                     layoutManager.showBlock(item, setting);\r
1040                     if (setting.draggable || item.getAttribute('data-draggable')) {\r
1041                         setDraggable(item);\r
1042                     }\r
1043                 });\r
1044             },\r
1045 \r
1046             fitWidth: function(width) {\r
1047                 var allBlock = container.find(setting.selector).removeAttr('id'),\r
1048                     block = null,\r
1049                     activeBlock = [];\r
1050 \r
1051                 width = width ? width : container.width() || $W.width();\r
1052 \r
1053                 runtime.currentMethod = arguments.callee;\r
1054                 runtime.currentArguments = arguments;\r
1055                 \r
1056                 layoutManager.resetGrid(runtime);\r
1057                 layoutManager.adjustUnit(width, 'auto', setting);\r
1058                 \r
1059                 if (runtime.filter) {\r
1060                     allBlock.data('active', 0);\r
1061                     allBlock.filter(runtime.filter).data('active', 1);\r
1062                 } else {\r
1063                     allBlock.data('active', 1);\r
1064                 }\r
1065                 \r
1066                 allBlock.each(function(index, item) {\r
1067                     var $item = $(item);\r
1068                     item.index = ++index;\r
1069                     block = layoutManager.loadBlock(item, setting);\r
1070                     block && $item.data("active") && activeBlock.push(block);\r
1071                 });\r
1072                 \r
1073                 klass.fireEvent('onGridReady', container, setting);\r
1074                 \r
1075                 engine[setting.engine](activeBlock, setting);\r
1076 \r
1077                 layoutManager.setWallSize(runtime, container);\r
1078                 \r
1079                 klass.fireEvent('onGridArrange', container, setting);\r
1080 \r
1081                 runtime.length = allBlock.length;\r
1082 \r
1083                 allBlock.each(function(index, item) {\r
1084                     layoutManager.showBlock(item, setting);\r
1085                     if (setting.draggable || item.getAttribute('data-draggable')) {\r
1086                         setDraggable(item);\r
1087                     }\r
1088                 });\r
1089             },\r
1090 \r
1091             fitZone: function(width, height) {\r
1092                 var allBlock = container.find(setting.selector).removeAttr('id'),\r
1093                     block = null,\r
1094                     activeBlock = [];\r
1095 \r
1096                 height = height ? height : container.height() || $W.height();\r
1097                 width = width ? width : container.width() || $W.width();\r
1098                 \r
1099                 runtime.currentMethod = arguments.callee;\r
1100                 runtime.currentArguments = arguments;\r
1101                 \r
1102                 layoutManager.resetGrid(runtime);\r
1103                 layoutManager.adjustUnit(width, height, setting);\r
1104 \r
1105                 if (runtime.filter) {\r
1106                     allBlock.data('active', 0);\r
1107                     allBlock.filter(runtime.filter).data('active', 1);\r
1108                 } else {\r
1109                     allBlock.data('active', 1);\r
1110                 }\r
1111                 \r
1112                 allBlock.each(function(index, item) {\r
1113                     var $item = $(item);\r
1114                     item.index = ++index;\r
1115                     block = layoutManager.loadBlock(item, setting);\r
1116                     block && $item.data("active") && activeBlock.push(block);\r
1117                 });\r
1118 \r
1119                 klass.fireEvent('onGridReady', container, setting);\r
1120 \r
1121                 engine[setting.engine](activeBlock, setting);\r
1122                 \r
1123                 layoutManager.setWallSize(runtime, container);\r
1124                 \r
1125                 klass.fireEvent('onGridArrange', container, setting);\r
1126 \r
1127                 runtime.length = allBlock.length;\r
1128                \r
1129                 allBlock.each(function(index, item) {\r
1130                     layoutManager.showBlock(item, setting);\r
1131                     if (setting.draggable || item.getAttribute('data-draggable')) {\r
1132                         setDraggable(item);\r
1133                     }\r
1134                 });\r
1135             },\r
1136 \r
1137             /*\r
1138             set block with special position, the top and left are multiple of unit width/height;\r
1139             example:\r
1140 \r
1141                 wall.fixPos({\r
1142                     top: 0,\r
1143                     left: 0,\r
1144                     block: $('.free')\r
1145                 });\r
1146             */\r
1147             fixPos: function(option) {\r
1148                 $(option.block).attr({'data-position': option.top + "-" + option.left});\r
1149                 return this;\r
1150             },\r
1151 \r
1152             /*\r
1153             set block with special size, the width and height are multiple of unit width/height;\r
1154             example:\r
1155 \r
1156                 wall.fixSize({\r
1157                     height: 5,\r
1158                     width: 2,\r
1159                     block: $('.free')\r
1160                 });\r
1161             */\r
1162             fixSize: function(option) {\r
1163                 option.height != null && $(option.block).attr({'data-height': option.height});\r
1164                 option.width != null && $(option.block).attr({'data-width': option.width});\r
1165                 return this;\r
1166             },\r
1167 \r
1168             prepend: function(items) {\r
1169                 container.prepend(items);\r
1170                 runtime.currentMethod && this.refresh();\r
1171                 return this;\r
1172             },\r
1173 \r
1174             refresh: function() {\r
1175                 var params = arguments.length ? arguments : runtime.currentArguments;\r
1176                 runtime.currentMethod == null && (runtime.currentMethod = this.fitWidth);\r
1177                 runtime.currentMethod.apply(this, Array.prototype.slice.call(params, 0));\r
1178                 return this;\r
1179             },\r
1180 \r
1181             /*\r
1182             custom layout setting;\r
1183             example:\r
1184 \r
1185                 wall.reset({\r
1186                     selector: '.brick',\r
1187                     animate: true,\r
1188                     cellW: 160,\r
1189                     cellH: 160,\r
1190                     delay: 50,\r
1191                     onResize: function() {\r
1192                         wall.fitWidth();\r
1193                     }\r
1194                 });\r
1195             */\r
1196             reset: function(option) {\r
1197                 $.extend(setting, option);\r
1198                 return this;\r
1199             },\r
1200             \r
1201             /*\r
1202             create one or more blank area (hole) on layout;\r
1203             example:\r
1204                 \r
1205                 wall.setHoles({\r
1206                     top: 2,\r
1207                     left: 2,\r
1208                     width: 2,\r
1209                     height: 2\r
1210                 });\r
1211             */\r
1212             \r
1213             setHoles: function(holes) {\r
1214                 var newHoles = [].concat(holes), h = {}, i;\r
1215                 runtime.holes = {};\r
1216                 for (i = 0; i < newHoles.length; ++i) {\r
1217                     h = newHoles[i];\r
1218                     runtime.holes[h.top + "-" + h.left + "-" + h.width + "-" + h.height] = h;\r
1219                 }\r
1220                 return this;\r
1221             },\r
1222 \r
1223             unFilter: function() {\r
1224                 delete runtime.filter;\r
1225                 this.refresh();\r
1226                 return this;\r
1227             }\r
1228         });\r
1229         \r
1230         container.attr('data-min-width', Math.floor($W.width() / 80) * 80);\r
1231         // execute plugins;\r
1232         for (var i in layoutManager.plugin) {\r
1233             if (layoutManager.plugin.hasOwnProperty(i)) {\r
1234                 layoutManager.plugin[i].call(klass, setting, container);\r
1235             }\r
1236         }\r
1237 \r
1238         // setup resize event;\r
1239         $W.resize(function() {\r
1240             if (runtime.running) return;\r
1241             runtime.running = 1;\r
1242             setTimeout(function() {\r
1243                 runtime.running = 0;\r
1244                 setting.onResize.call(klass, container);\r
1245             }, 122);\r
1246             container.attr('data-min-width', Math.floor($W.width() / 80) * 80);\r
1247         });\r
1248     };\r
1249 \r
1250     /*\r
1251     add default setting;\r
1252     example:\r
1253 \r
1254         freewall.addConfig({\r
1255             offsetLeft: 0\r
1256         });\r
1257     */\r
1258     freewall.addConfig = function(newConfig) {\r
1259         // add default setting;\r
1260         $.extend(layoutManager.defaultConfig, newConfig);    \r
1261     };\r
1262     \r
1263 \r
1264     /*\r
1265     support create new arrange algorithm;\r
1266     example:\r
1267 \r
1268         freewall.createEngine({\r
1269             slice: function(items, setting) {\r
1270                 // slice engine;\r
1271             }\r
1272         });\r
1273     */\r
1274     freewall.createEngine = function(engineData) {\r
1275         // create new engine;\r
1276         $.extend(engine, engineData);\r
1277     };\r
1278     \r
1279     /*\r
1280     support create new plugin;\r
1281     example:\r
1282         \r
1283         freewall.createPlugin({\r
1284             centering: function(setting, container) {\r
1285                 console.log(this);\r
1286                 console.log(setting);\r
1287             }\r
1288         })l\r
1289     */\r
1290     freewall.createPlugin = function(pluginData) {\r
1291         // register new plugin;\r
1292         $.extend(layoutManager.plugin, pluginData);\r
1293     };\r
1294 \r
1295     /*\r
1296     support access helper function;\r
1297     example:\r
1298 \r
1299         freewall.getMethod('setBlock')(block, setting);\r
1300     */\r
1301     freewall.getMethod = function(method) {\r
1302         // get helper method;\r
1303         return layoutManager[method];\r
1304     };\r
1305  \r
1306 })(window.Zepto || window.jQuery);\r