|
1 /* =================================================== |
|
2 * jquery-sortable.js v0.9.13 |
|
3 * http://johnny.github.com/jquery-sortable/ |
|
4 * =================================================== |
|
5 * Copyright (c) 2012 Jonas von Andrian |
|
6 * All rights reserved. |
|
7 * |
|
8 * Redistribution and use in source and binary forms, with or without |
|
9 * modification, are permitted provided that the following conditions are met: |
|
10 * * Redistributions of source code must retain the above copyright |
|
11 * notice, this list of conditions and the following disclaimer. |
|
12 * * Redistributions in binary form must reproduce the above copyright |
|
13 * notice, this list of conditions and the following disclaimer in the |
|
14 * documentation and/or other materials provided with the distribution. |
|
15 * * The name of the author may not be used to endorse or promote products |
|
16 * derived from this software without specific prior written permission. |
|
17 * |
|
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
|
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
|
21 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY |
|
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
|
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
|
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
|
25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
|
27 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
28 * ========================================================== */ |
|
29 |
|
30 |
|
31 !function ( $, window, pluginName, undefined){ |
|
32 var containerDefaults = { |
|
33 // If true, items can be dragged from this container |
|
34 drag: true, |
|
35 // If true, items can be droped onto this container |
|
36 drop: true, |
|
37 // Exclude items from being draggable, if the |
|
38 // selector matches the item |
|
39 exclude: "", |
|
40 // If true, search for nested containers within an item.If you nest containers, |
|
41 // either the original selector with which you call the plugin must only match the top containers, |
|
42 // or you need to specify a group (see the bootstrap nav example) |
|
43 nested: true, |
|
44 // If true, the items are assumed to be arranged vertically |
|
45 vertical: true |
|
46 }, // end container defaults |
|
47 groupDefaults = { |
|
48 // This is executed after the placeholder has been moved. |
|
49 // $closestItemOrContainer contains the closest item, the placeholder |
|
50 // has been put at or the closest empty Container, the placeholder has |
|
51 // been appended to. |
|
52 afterMove: function ($placeholder, container, $closestItemOrContainer) { |
|
53 }, |
|
54 // The exact css path between the container and its items, e.g. "> tbody" |
|
55 containerPath: "", |
|
56 // The css selector of the containers |
|
57 containerSelector: "ol, ul", |
|
58 // Distance the mouse has to travel to start dragging |
|
59 distance: 0, |
|
60 // Time in milliseconds after mousedown until dragging should start. |
|
61 // This option can be used to prevent unwanted drags when clicking on an element. |
|
62 delay: 0, |
|
63 // The css selector of the drag handle |
|
64 handle: "", |
|
65 // The exact css path between the item and its subcontainers. |
|
66 // It should only match the immediate items of a container. |
|
67 // No item of a subcontainer should be matched. E.g. for ol>div>li the itemPath is "> div" |
|
68 itemPath: "", |
|
69 // The css selector of the items |
|
70 itemSelector: "li", |
|
71 // The class given to "body" while an item is being dragged |
|
72 bodyClass: "dragging", |
|
73 // The class giving to an item while being dragged |
|
74 draggedClass: "dragged", |
|
75 // Check if the dragged item may be inside the container. |
|
76 // Use with care, since the search for a valid container entails a depth first search |
|
77 // and may be quite expensive. |
|
78 isValidTarget: function ($item, container) { |
|
79 return true |
|
80 }, |
|
81 // Executed before onDrop if placeholder is detached. |
|
82 // This happens if pullPlaceholder is set to false and the drop occurs outside a container. |
|
83 onCancel: function ($item, container, _super, event) { |
|
84 }, |
|
85 // Executed at the beginning of a mouse move event. |
|
86 // The Placeholder has not been moved yet. |
|
87 onDrag: function ($item, position, _super, event) { |
|
88 $item.css(position) |
|
89 }, |
|
90 // Called after the drag has been started, |
|
91 // that is the mouse button is being held down and |
|
92 // the mouse is moving. |
|
93 // The container is the closest initialized container. |
|
94 // Therefore it might not be the container, that actually contains the item. |
|
95 onDragStart: function ($item, container, _super, event) { |
|
96 $item.css({ |
|
97 height: $item.outerHeight(), |
|
98 width: $item.outerWidth() |
|
99 }) |
|
100 $item.addClass(container.group.options.draggedClass) |
|
101 $("body").addClass(container.group.options.bodyClass) |
|
102 }, |
|
103 // Called when the mouse button is being released |
|
104 onDrop: function ($item, container, _super, event) { |
|
105 $item.removeClass(container.group.options.draggedClass).removeAttr("style") |
|
106 $("body").removeClass(container.group.options.bodyClass) |
|
107 }, |
|
108 // Called on mousedown. If falsy value is returned, the dragging will not start. |
|
109 // Ignore if element clicked is input, select or textarea |
|
110 onMousedown: function ($item, _super, event) { |
|
111 if (!event.target.nodeName.match(/^(input|select|textarea)$/i)) { |
|
112 event.preventDefault() |
|
113 return true |
|
114 } |
|
115 }, |
|
116 // The class of the placeholder (must match placeholder option markup) |
|
117 placeholderClass: "placeholder", |
|
118 // Template for the placeholder. Can be any valid jQuery input |
|
119 // e.g. a string, a DOM element. |
|
120 // The placeholder must have the class "placeholder" |
|
121 placeholder: '<li class="placeholder"></li>', |
|
122 // If true, the position of the placeholder is calculated on every mousemove. |
|
123 // If false, it is only calculated when the mouse is above a container. |
|
124 pullPlaceholder: true, |
|
125 // Specifies serialization of the container group. |
|
126 // The pair $parent/$children is either container/items or item/subcontainers. |
|
127 serialize: function ($parent, $children, parentIsContainer) { |
|
128 var result = $.extend({}, $parent.data()) |
|
129 |
|
130 if(parentIsContainer) |
|
131 return [$children] |
|
132 else if ($children[0]){ |
|
133 result.children = $children |
|
134 } |
|
135 |
|
136 delete result.subContainers |
|
137 delete result.sortable |
|
138 |
|
139 return result |
|
140 }, |
|
141 // Set tolerance while dragging. Positive values decrease sensitivity, |
|
142 // negative values increase it. |
|
143 tolerance: 0 |
|
144 }, // end group defaults |
|
145 containerGroups = {}, |
|
146 groupCounter = 0, |
|
147 emptyBox = { |
|
148 left: 0, |
|
149 top: 0, |
|
150 bottom: 0, |
|
151 right:0 |
|
152 }, |
|
153 eventNames = { |
|
154 start: "touchstart.sortable mousedown.sortable", |
|
155 drop: "touchend.sortable touchcancel.sortable mouseup.sortable", |
|
156 drag: "touchmove.sortable mousemove.sortable", |
|
157 scroll: "scroll.sortable" |
|
158 }, |
|
159 subContainerKey = "subContainers" |
|
160 |
|
161 /* |
|
162 * a is Array [left, right, top, bottom] |
|
163 * b is array [left, top] |
|
164 */ |
|
165 function d(a,b) { |
|
166 var x = Math.max(0, a[0] - b[0], b[0] - a[1]), |
|
167 y = Math.max(0, a[2] - b[1], b[1] - a[3]) |
|
168 return x+y; |
|
169 } |
|
170 |
|
171 function setDimensions(array, dimensions, tolerance, useOffset) { |
|
172 var i = array.length, |
|
173 offsetMethod = useOffset ? "offset" : "position" |
|
174 tolerance = tolerance || 0 |
|
175 |
|
176 while(i--){ |
|
177 var el = array[i].el ? array[i].el : $(array[i]), |
|
178 // use fitting method |
|
179 pos = el[offsetMethod]() |
|
180 pos.left += parseInt(el.css('margin-left'), 10) |
|
181 pos.top += parseInt(el.css('margin-top'),10) |
|
182 dimensions[i] = [ |
|
183 pos.left - tolerance, |
|
184 pos.left + el.outerWidth() + tolerance, |
|
185 pos.top - tolerance, |
|
186 pos.top + el.outerHeight() + tolerance |
|
187 ] |
|
188 } |
|
189 } |
|
190 |
|
191 function getRelativePosition(pointer, element) { |
|
192 var offset = element.offset() |
|
193 return { |
|
194 left: pointer.left - offset.left, |
|
195 top: pointer.top - offset.top |
|
196 } |
|
197 } |
|
198 |
|
199 function sortByDistanceDesc(dimensions, pointer, lastPointer) { |
|
200 pointer = [pointer.left, pointer.top] |
|
201 lastPointer = lastPointer && [lastPointer.left, lastPointer.top] |
|
202 |
|
203 var dim, |
|
204 i = dimensions.length, |
|
205 distances = [] |
|
206 |
|
207 while(i--){ |
|
208 dim = dimensions[i] |
|
209 distances[i] = [i,d(dim,pointer), lastPointer && d(dim, lastPointer)] |
|
210 } |
|
211 distances = distances.sort(function (a,b) { |
|
212 return b[1] - a[1] || b[2] - a[2] || b[0] - a[0] |
|
213 }) |
|
214 |
|
215 // last entry is the closest |
|
216 return distances |
|
217 } |
|
218 |
|
219 function ContainerGroup(options) { |
|
220 this.options = $.extend({}, groupDefaults, options) |
|
221 this.containers = [] |
|
222 |
|
223 if(!this.options.rootGroup){ |
|
224 this.scrollProxy = $.proxy(this.scroll, this) |
|
225 this.dragProxy = $.proxy(this.drag, this) |
|
226 this.dropProxy = $.proxy(this.drop, this) |
|
227 this.placeholder = $(this.options.placeholder) |
|
228 |
|
229 if(!options.isValidTarget) |
|
230 this.options.isValidTarget = undefined |
|
231 } |
|
232 } |
|
233 |
|
234 ContainerGroup.get = function (options) { |
|
235 if(!containerGroups[options.group]) { |
|
236 if(options.group === undefined) |
|
237 options.group = groupCounter ++ |
|
238 |
|
239 containerGroups[options.group] = new ContainerGroup(options) |
|
240 } |
|
241 |
|
242 return containerGroups[options.group] |
|
243 } |
|
244 |
|
245 ContainerGroup.prototype = { |
|
246 dragInit: function (e, itemContainer) { |
|
247 this.$document = $(itemContainer.el[0].ownerDocument) |
|
248 |
|
249 // get item to drag |
|
250 var closestItem = $(e.target).closest(this.options.itemSelector); |
|
251 // using the length of this item, prevents the plugin from being started if there is no handle being clicked on. |
|
252 // this may also be helpful in instantiating multidrag. |
|
253 if (closestItem.length) { |
|
254 this.item = closestItem; |
|
255 this.itemContainer = itemContainer; |
|
256 if (this.item.is(this.options.exclude) || !this.options.onMousedown(this.item, groupDefaults.onMousedown, e)) { |
|
257 return; |
|
258 } |
|
259 this.setPointer(e); |
|
260 this.toggleListeners('on'); |
|
261 this.setupDelayTimer(); |
|
262 this.dragInitDone = true; |
|
263 } |
|
264 }, |
|
265 drag: function (e) { |
|
266 if(!this.dragging){ |
|
267 if(!this.distanceMet(e) || !this.delayMet) |
|
268 return |
|
269 |
|
270 this.options.onDragStart(this.item, this.itemContainer, groupDefaults.onDragStart, e) |
|
271 this.item.before(this.placeholder) |
|
272 this.dragging = true |
|
273 } |
|
274 |
|
275 this.setPointer(e) |
|
276 // place item under the cursor |
|
277 this.options.onDrag(this.item, |
|
278 getRelativePosition(this.pointer, this.item.offsetParent()), |
|
279 groupDefaults.onDrag, |
|
280 e) |
|
281 |
|
282 var p = this.getPointer(e), |
|
283 box = this.sameResultBox, |
|
284 t = this.options.tolerance |
|
285 |
|
286 if(!box || box.top - t > p.top || box.bottom + t < p.top || box.left - t > p.left || box.right + t < p.left) |
|
287 if(!this.searchValidTarget()){ |
|
288 this.placeholder.detach() |
|
289 this.lastAppendedItem = undefined |
|
290 } |
|
291 }, |
|
292 drop: function (e) { |
|
293 this.toggleListeners('off') |
|
294 |
|
295 this.dragInitDone = false |
|
296 |
|
297 if(this.dragging){ |
|
298 // processing Drop, check if placeholder is detached |
|
299 if(this.placeholder.closest("html")[0]){ |
|
300 this.placeholder.before(this.item).detach() |
|
301 } else { |
|
302 this.options.onCancel(this.item, this.itemContainer, groupDefaults.onCancel, e) |
|
303 } |
|
304 this.options.onDrop(this.item, this.getContainer(this.item), groupDefaults.onDrop, e) |
|
305 |
|
306 // cleanup |
|
307 this.clearDimensions() |
|
308 this.clearOffsetParent() |
|
309 this.lastAppendedItem = this.sameResultBox = undefined |
|
310 this.dragging = false |
|
311 } |
|
312 }, |
|
313 searchValidTarget: function (pointer, lastPointer) { |
|
314 if(!pointer){ |
|
315 pointer = this.relativePointer || this.pointer |
|
316 lastPointer = this.lastRelativePointer || this.lastPointer |
|
317 } |
|
318 |
|
319 var distances = sortByDistanceDesc(this.getContainerDimensions(), |
|
320 pointer, |
|
321 lastPointer), |
|
322 i = distances.length |
|
323 |
|
324 while(i--){ |
|
325 var index = distances[i][0], |
|
326 distance = distances[i][1] |
|
327 |
|
328 if(!distance || this.options.pullPlaceholder){ |
|
329 var container = this.containers[index] |
|
330 if(!container.disabled){ |
|
331 if(!this.$getOffsetParent()){ |
|
332 var offsetParent = container.getItemOffsetParent() |
|
333 pointer = getRelativePosition(pointer, offsetParent) |
|
334 lastPointer = getRelativePosition(lastPointer, offsetParent) |
|
335 } |
|
336 if(container.searchValidTarget(pointer, lastPointer)) |
|
337 return true |
|
338 } |
|
339 } |
|
340 } |
|
341 if(this.sameResultBox) |
|
342 this.sameResultBox = undefined |
|
343 }, |
|
344 movePlaceholder: function (container, item, method, sameResultBox) { |
|
345 var lastAppendedItem = this.lastAppendedItem |
|
346 if(!sameResultBox && lastAppendedItem && lastAppendedItem[0] === item[0]) |
|
347 return; |
|
348 |
|
349 item[method](this.placeholder) |
|
350 this.lastAppendedItem = item |
|
351 this.sameResultBox = sameResultBox |
|
352 this.options.afterMove(this.placeholder, container, item) |
|
353 }, |
|
354 getContainerDimensions: function () { |
|
355 if(!this.containerDimensions) |
|
356 setDimensions(this.containers, this.containerDimensions = [], this.options.tolerance, !this.$getOffsetParent()) |
|
357 return this.containerDimensions |
|
358 }, |
|
359 getContainer: function (element) { |
|
360 return element.closest(this.options.containerSelector).data(pluginName) |
|
361 }, |
|
362 $getOffsetParent: function () { |
|
363 if(this.offsetParent === undefined){ |
|
364 var i = this.containers.length - 1, |
|
365 offsetParent = this.containers[i].getItemOffsetParent() |
|
366 |
|
367 if(!this.options.rootGroup){ |
|
368 while(i--){ |
|
369 if(offsetParent[0] != this.containers[i].getItemOffsetParent()[0]){ |
|
370 // If every container has the same offset parent, |
|
371 // use position() which is relative to this parent, |
|
372 // otherwise use offset() |
|
373 // compare #setDimensions |
|
374 offsetParent = false |
|
375 break; |
|
376 } |
|
377 } |
|
378 } |
|
379 |
|
380 this.offsetParent = offsetParent |
|
381 } |
|
382 return this.offsetParent |
|
383 }, |
|
384 setPointer: function (e) { |
|
385 var pointer = this.getPointer(e) |
|
386 |
|
387 if(this.$getOffsetParent()){ |
|
388 var relativePointer = getRelativePosition(pointer, this.$getOffsetParent()) |
|
389 this.lastRelativePointer = this.relativePointer |
|
390 this.relativePointer = relativePointer |
|
391 } |
|
392 |
|
393 this.lastPointer = this.pointer |
|
394 this.pointer = pointer |
|
395 }, |
|
396 distanceMet: function (e) { |
|
397 var currentPointer = this.getPointer(e) |
|
398 return (Math.max( |
|
399 Math.abs(this.pointer.left - currentPointer.left), |
|
400 Math.abs(this.pointer.top - currentPointer.top) |
|
401 ) >= this.options.distance) |
|
402 }, |
|
403 getPointer: function(e) { |
|
404 var o = e.originalEvent || e.originalEvent.touches && e.originalEvent.touches[0] |
|
405 return { |
|
406 left: e.pageX || o.pageX, |
|
407 top: e.pageY || o.pageY |
|
408 } |
|
409 }, |
|
410 setupDelayTimer: function () { |
|
411 var that = this |
|
412 this.delayMet = !this.options.delay |
|
413 |
|
414 // init delay timer if needed |
|
415 if (!this.delayMet) { |
|
416 clearTimeout(this._mouseDelayTimer); |
|
417 this._mouseDelayTimer = setTimeout(function() { |
|
418 that.delayMet = true |
|
419 }, this.options.delay) |
|
420 } |
|
421 }, |
|
422 scroll: function (e) { |
|
423 this.clearDimensions() |
|
424 this.clearOffsetParent() // TODO is this needed? |
|
425 }, |
|
426 toggleListeners: function (method) { |
|
427 var that = this, |
|
428 events = ['drag','drop','scroll'] |
|
429 |
|
430 $.each(events,function (i,event) { |
|
431 that.$document[method](eventNames[event], that[event + 'Proxy']) |
|
432 }) |
|
433 }, |
|
434 clearOffsetParent: function () { |
|
435 this.offsetParent = undefined |
|
436 }, |
|
437 // Recursively clear container and item dimensions |
|
438 clearDimensions: function () { |
|
439 this.traverse(function(object){ |
|
440 object._clearDimensions() |
|
441 }) |
|
442 }, |
|
443 traverse: function(callback) { |
|
444 callback(this) |
|
445 var i = this.containers.length |
|
446 while(i--){ |
|
447 this.containers[i].traverse(callback) |
|
448 } |
|
449 }, |
|
450 _clearDimensions: function(){ |
|
451 this.containerDimensions = undefined |
|
452 }, |
|
453 _destroy: function () { |
|
454 containerGroups[this.options.group] = undefined |
|
455 } |
|
456 } |
|
457 |
|
458 function Container(element, options) { |
|
459 this.el = element |
|
460 this.options = $.extend( {}, containerDefaults, options) |
|
461 |
|
462 this.group = ContainerGroup.get(this.options) |
|
463 this.rootGroup = this.options.rootGroup || this.group |
|
464 this.handle = this.rootGroup.options.handle || this.rootGroup.options.itemSelector |
|
465 |
|
466 var itemPath = this.rootGroup.options.itemPath |
|
467 this.target = itemPath ? this.el.find(itemPath) : this.el |
|
468 |
|
469 this.target.on(eventNames.start, this.handle, $.proxy(this.dragInit, this)) |
|
470 |
|
471 if(this.options.drop) |
|
472 this.group.containers.push(this) |
|
473 } |
|
474 |
|
475 Container.prototype = { |
|
476 dragInit: function (e) { |
|
477 var rootGroup = this.rootGroup |
|
478 |
|
479 if( !this.disabled && |
|
480 !rootGroup.dragInitDone && |
|
481 this.options.drag && |
|
482 this.isValidDrag(e)) { |
|
483 rootGroup.dragInit(e, this) |
|
484 } |
|
485 }, |
|
486 isValidDrag: function(e) { |
|
487 return e.which == 1 || |
|
488 e.type == "touchstart" && e.originalEvent.touches.length == 1 |
|
489 }, |
|
490 searchValidTarget: function (pointer, lastPointer) { |
|
491 var distances = sortByDistanceDesc(this.getItemDimensions(), |
|
492 pointer, |
|
493 lastPointer), |
|
494 i = distances.length, |
|
495 rootGroup = this.rootGroup, |
|
496 validTarget = !rootGroup.options.isValidTarget || |
|
497 rootGroup.options.isValidTarget(rootGroup.item, this) |
|
498 |
|
499 if(!i && validTarget){ |
|
500 rootGroup.movePlaceholder(this, this.target, "append") |
|
501 return true |
|
502 } else |
|
503 while(i--){ |
|
504 var index = distances[i][0], |
|
505 distance = distances[i][1] |
|
506 if(!distance && this.hasChildGroup(index)){ |
|
507 var found = this.getContainerGroup(index).searchValidTarget(pointer, lastPointer) |
|
508 if(found) |
|
509 return true |
|
510 } |
|
511 else if(validTarget){ |
|
512 this.movePlaceholder(index, pointer) |
|
513 return true |
|
514 } |
|
515 } |
|
516 }, |
|
517 movePlaceholder: function (index, pointer) { |
|
518 var item = $(this.items[index]), |
|
519 dim = this.itemDimensions[index], |
|
520 method = "after", |
|
521 width = item.outerWidth(), |
|
522 height = item.outerHeight(), |
|
523 offset = item.offset(), |
|
524 sameResultBox = { |
|
525 left: offset.left, |
|
526 right: offset.left + width, |
|
527 top: offset.top, |
|
528 bottom: offset.top + height |
|
529 } |
|
530 if(this.options.vertical){ |
|
531 var yCenter = (dim[2] + dim[3]) / 2, |
|
532 inUpperHalf = pointer.top <= yCenter |
|
533 if(inUpperHalf){ |
|
534 method = "before" |
|
535 sameResultBox.bottom -= height / 2 |
|
536 } else |
|
537 sameResultBox.top += height / 2 |
|
538 } else { |
|
539 var xCenter = (dim[0] + dim[1]) / 2, |
|
540 inLeftHalf = pointer.left <= xCenter |
|
541 if(inLeftHalf){ |
|
542 method = "before" |
|
543 sameResultBox.right -= width / 2 |
|
544 } else |
|
545 sameResultBox.left += width / 2 |
|
546 } |
|
547 if(this.hasChildGroup(index)) |
|
548 sameResultBox = emptyBox |
|
549 this.rootGroup.movePlaceholder(this, item, method, sameResultBox) |
|
550 }, |
|
551 getItemDimensions: function () { |
|
552 if(!this.itemDimensions){ |
|
553 this.items = this.$getChildren(this.el, "item").filter( |
|
554 ":not(." + this.group.options.placeholderClass + ", ." + this.group.options.draggedClass + ")" |
|
555 ).get() |
|
556 setDimensions(this.items, this.itemDimensions = [], this.options.tolerance) |
|
557 } |
|
558 return this.itemDimensions |
|
559 }, |
|
560 getItemOffsetParent: function () { |
|
561 var offsetParent, |
|
562 el = this.el |
|
563 // Since el might be empty we have to check el itself and |
|
564 // can not do something like el.children().first().offsetParent() |
|
565 if(el.css("position") === "relative" || el.css("position") === "absolute" || el.css("position") === "fixed") |
|
566 offsetParent = el |
|
567 else |
|
568 offsetParent = el.offsetParent() |
|
569 return offsetParent |
|
570 }, |
|
571 hasChildGroup: function (index) { |
|
572 return this.options.nested && this.getContainerGroup(index) |
|
573 }, |
|
574 getContainerGroup: function (index) { |
|
575 var childGroup = $.data(this.items[index], subContainerKey) |
|
576 if( childGroup === undefined){ |
|
577 var childContainers = this.$getChildren(this.items[index], "container") |
|
578 childGroup = false |
|
579 |
|
580 if(childContainers[0]){ |
|
581 var options = $.extend({}, this.options, { |
|
582 rootGroup: this.rootGroup, |
|
583 group: groupCounter ++ |
|
584 }) |
|
585 childGroup = childContainers[pluginName](options).data(pluginName).group |
|
586 } |
|
587 $.data(this.items[index], subContainerKey, childGroup) |
|
588 } |
|
589 return childGroup |
|
590 }, |
|
591 $getChildren: function (parent, type) { |
|
592 var options = this.rootGroup.options, |
|
593 path = options[type + "Path"], |
|
594 selector = options[type + "Selector"] |
|
595 |
|
596 parent = $(parent) |
|
597 if(path) |
|
598 parent = parent.find(path) |
|
599 |
|
600 return parent.children(selector) |
|
601 }, |
|
602 _serialize: function (parent, isContainer) { |
|
603 var that = this, |
|
604 childType = isContainer ? "item" : "container", |
|
605 |
|
606 children = this.$getChildren(parent, childType).not(this.options.exclude).map(function () { |
|
607 return that._serialize($(this), !isContainer) |
|
608 }).get() |
|
609 |
|
610 return this.rootGroup.options.serialize(parent, children, isContainer) |
|
611 }, |
|
612 traverse: function(callback) { |
|
613 $.each(this.items || [], function(item){ |
|
614 var group = $.data(this, subContainerKey) |
|
615 if(group) |
|
616 group.traverse(callback) |
|
617 }); |
|
618 |
|
619 callback(this) |
|
620 }, |
|
621 _clearDimensions: function () { |
|
622 this.itemDimensions = undefined |
|
623 }, |
|
624 _destroy: function() { |
|
625 var that = this; |
|
626 |
|
627 this.target.off(eventNames.start, this.handle); |
|
628 this.el.removeData(pluginName) |
|
629 |
|
630 if(this.options.drop) |
|
631 this.group.containers = $.grep(this.group.containers, function(val){ |
|
632 return val != that |
|
633 }) |
|
634 |
|
635 $.each(this.items || [], function(){ |
|
636 $.removeData(this, subContainerKey) |
|
637 }) |
|
638 } |
|
639 } |
|
640 |
|
641 var API = { |
|
642 enable: function() { |
|
643 this.traverse(function(object){ |
|
644 object.disabled = false |
|
645 }) |
|
646 }, |
|
647 disable: function (){ |
|
648 this.traverse(function(object){ |
|
649 object.disabled = true |
|
650 }) |
|
651 }, |
|
652 serialize: function () { |
|
653 return this._serialize(this.el, true) |
|
654 }, |
|
655 refresh: function() { |
|
656 this.traverse(function(object){ |
|
657 object._clearDimensions() |
|
658 }) |
|
659 }, |
|
660 destroy: function () { |
|
661 this.traverse(function(object){ |
|
662 object._destroy(); |
|
663 }) |
|
664 } |
|
665 } |
|
666 |
|
667 $.extend(Container.prototype, API) |
|
668 |
|
669 /** |
|
670 * jQuery API |
|
671 * |
|
672 * Parameters are |
|
673 * either options on init |
|
674 * or a method name followed by arguments to pass to the method |
|
675 */ |
|
676 $.fn[pluginName] = function(methodOrOptions) { |
|
677 var args = Array.prototype.slice.call(arguments, 1) |
|
678 |
|
679 return this.map(function(){ |
|
680 var $t = $(this), |
|
681 object = $t.data(pluginName) |
|
682 |
|
683 if(object && API[methodOrOptions]) |
|
684 return API[methodOrOptions].apply(object, args) || this |
|
685 else if(!object && (methodOrOptions === undefined || |
|
686 typeof methodOrOptions === "object")) |
|
687 $t.data(pluginName, new Container($t, methodOrOptions)) |
|
688 |
|
689 return this |
|
690 }); |
|
691 }; |
|
692 |
|
693 }(jQuery, window, 'sortable'); |