2 * jQuery UI Draggable 1.8.11
4 * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
5 * Dual licensed under the MIT or GPL Version 2 licenses.
6 * http://jquery.org/license
8 * http://docs.jquery.com/UI/Draggables
15 (function( $, undefined ) {
17 $.widget("ui.draggable", $.ui.mouse, {
18 widgetEventPrefix: "drag",
23 connectToSortable: false,
32 refreshPositions: false,
37 scrollSensitivity: 20,
47 if (this.options.helper == 'original' && !(/^(?:r|a|f)/).test(this.element.css("position")))
48 this.element[0].style.position = 'relative';
50 (this.options.addClasses && this.element.addClass("ui-draggable"));
51 (this.options.disabled && this.element.addClass("ui-draggable-disabled"));
58 if(!this.element.data('draggable')) return;
60 .removeData("draggable")
62 .removeClass("ui-draggable"
63 + " ui-draggable-dragging"
64 + " ui-draggable-disabled");
70 _mouseCapture: function(event) {
74 // among others, prevent a drag on a resizable-handle
75 if (this.helper || o.disabled || $(event.target).is('.ui-resizable-handle'))
78 //Quit if we're not on a valid handle
79 this.handle = this._getHandle(event);
87 _mouseStart: function(event) {
91 //Create and append the visible helper
92 this.helper = this._createHelper(event);
94 //Cache the helper size
95 this._cacheHelperProportions();
97 //If ddmanager is used for droppables, set the global draggable
99 $.ui.ddmanager.current = this;
102 * - Position generation -
103 * This block generates everything position related - it's the core of draggables.
106 //Cache the margins of the original element
107 this._cacheMargins();
109 //Store the helper's css position
110 this.cssPosition = this.helper.css("position");
111 this.scrollParent = this.helper.scrollParent();
113 //The element's absolute position on the page minus margins
114 this.offset = this.positionAbs = this.element.offset();
116 top: this.offset.top - this.margins.top,
117 left: this.offset.left - this.margins.left
120 $.extend(this.offset, {
121 click: { //Where the click happened, relative to the element
122 left: event.pageX - this.offset.left,
123 top: event.pageY - this.offset.top
125 parent: this._getParentOffset(),
126 relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
129 //Generate the original position
130 this.originalPosition = this.position = this._generatePosition(event);
131 this.originalPageX = event.pageX;
132 this.originalPageY = event.pageY;
134 //Adjust the mouse offset relative to the helper if 'cursorAt' is supplied
135 (o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));
137 //Set a containment if given in the options
139 this._setContainment();
141 //Trigger event + callbacks
142 if(this._trigger("start", event) === false) {
147 //Recache the helper size
148 this._cacheHelperProportions();
150 //Prepare the droppable offsets
151 if ($.ui.ddmanager && !o.dropBehaviour)
152 $.ui.ddmanager.prepareOffsets(this, event);
154 this.helper.addClass("ui-draggable-dragging");
155 this._mouseDrag(event, true); //Execute the drag once - this causes the helper not to be visible before getting its correct position
159 _mouseDrag: function(event, noPropagation) {
161 //Compute the helpers position
162 this.position = this._generatePosition(event);
163 this.positionAbs = this._convertPositionTo("absolute");
165 //Call plugins and callbacks and use the resulting position if something is returned
166 if (!noPropagation) {
167 var ui = this._uiHash();
168 if(this._trigger('drag', event, ui) === false) {
172 this.position = ui.position;
175 if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px';
176 if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px';
177 if($.ui.ddmanager) $.ui.ddmanager.drag(this, event);
182 _mouseStop: function(event) {
184 //If we are using droppables, inform the manager about the drop
186 if ($.ui.ddmanager && !this.options.dropBehaviour)
187 dropped = $.ui.ddmanager.drop(this, event);
189 //if a drop comes from outside (a sortable)
191 dropped = this.dropped;
192 this.dropped = false;
195 //if the original element is removed, don't bother to continue if helper is set to "original"
196 if((!this.element[0] || !this.element[0].parentNode) && this.options.helper == "original")
199 if((this.options.revert == "invalid" && !dropped) || (this.options.revert == "valid" && dropped) || this.options.revert === true || ($.isFunction(this.options.revert) && this.options.revert.call(this.element, dropped))) {
201 $(this.helper).animate(this.originalPosition, parseInt(this.options.revertDuration, 10), function() {
202 if(self._trigger("stop", event) !== false) {
207 if(this._trigger("stop", event) !== false) {
217 if(this.helper.is(".ui-draggable-dragging")) {
227 _getHandle: function(event) {
229 var handle = !this.options.handle || !$(this.options.handle, this.element).length ? true : false;
230 $(this.options.handle, this.element)
234 if(this == event.target) handle = true;
241 _createHelper: function(event) {
243 var o = this.options;
244 var helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event])) : (o.helper == 'clone' ? this.element.clone() : this.element);
246 if(!helper.parents('body').length)
247 helper.appendTo((o.appendTo == 'parent' ? this.element[0].parentNode : o.appendTo));
249 if(helper[0] != this.element[0] && !(/(fixed|absolute)/).test(helper.css("position")))
250 helper.css("position", "absolute");
256 _adjustOffsetFromHelper: function(obj) {
257 if (typeof obj == 'string') {
258 obj = obj.split(' ');
260 if ($.isArray(obj)) {
261 obj = {left: +obj[0], top: +obj[1] || 0};
264 this.offset.click.left = obj.left + this.margins.left;
266 if ('right' in obj) {
267 this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
270 this.offset.click.top = obj.top + this.margins.top;
272 if ('bottom' in obj) {
273 this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
277 _getParentOffset: function() {
279 //Get the offsetParent and cache its position
280 this.offsetParent = this.helper.offsetParent();
281 var po = this.offsetParent.offset();
283 // This is a special case where we need to modify a offset calculated on start, since the following happened:
284 // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
285 // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
286 // the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
287 if(this.cssPosition == 'absolute' && this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) {
288 po.left += this.scrollParent.scrollLeft();
289 po.top += this.scrollParent.scrollTop();
292 if((this.offsetParent[0] == document.body) //This needs to be actually done for all browsers, since pageX/pageY includes this information
293 || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() == 'html' && $.browser.msie)) //Ugly IE fix
294 po = { top: 0, left: 0 };
297 top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
298 left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
303 _getRelativeOffset: function() {
305 if(this.cssPosition == "relative") {
306 var p = this.element.position();
308 top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
309 left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
312 return { top: 0, left: 0 };
317 _cacheMargins: function() {
319 left: (parseInt(this.element.css("marginLeft"),10) || 0),
320 top: (parseInt(this.element.css("marginTop"),10) || 0),
321 right: (parseInt(this.element.css("marginRight"),10) || 0),
322 bottom: (parseInt(this.element.css("marginBottom"),10) || 0)
326 _cacheHelperProportions: function() {
327 this.helperProportions = {
328 width: this.helper.outerWidth(),
329 height: this.helper.outerHeight()
333 _setContainment: function() {
335 var o = this.options;
336 if(o.containment == 'parent') o.containment = this.helper[0].parentNode;
337 if(o.containment == 'document' || o.containment == 'window') this.containment = [
338 (o.containment == 'document' ? 0 : $(window).scrollLeft()) - this.offset.relative.left - this.offset.parent.left,
339 (o.containment == 'document' ? 0 : $(window).scrollTop()) - this.offset.relative.top - this.offset.parent.top,
340 (o.containment == 'document' ? 0 : $(window).scrollLeft()) + $(o.containment == 'document' ? document : window).width() - this.helperProportions.width - this.margins.left,
341 (o.containment == 'document' ? 0 : $(window).scrollTop()) + ($(o.containment == 'document' ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top
344 if(!(/^(document|window|parent)$/).test(o.containment) && o.containment.constructor != Array) {
345 var ce = $(o.containment)[0]; if(!ce) return;
346 var co = $(o.containment).offset();
347 var over = ($(ce).css("overflow") != 'hidden');
350 co.left + (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0),
351 co.top + (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0),
352 co.left+(over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left - this.margins.right,
353 co.top+(over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top - this.margins.bottom
355 } else if(o.containment.constructor == Array) {
356 this.containment = o.containment;
361 _convertPositionTo: function(d, pos) {
363 if(!pos) pos = this.position;
364 var mod = d == "absolute" ? 1 : -1;
365 var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
369 pos.top // The absolute mouse position
370 + this.offset.relative.top * mod // Only for relative positioned nodes: Relative offset from element to offset parent
371 + this.offset.parent.top * mod // The offsetParent's offset without borders (offset + border)
372 - ($.browser.safari && $.browser.version < 526 && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod)
375 pos.left // The absolute mouse position
376 + this.offset.relative.left * mod // Only for relative positioned nodes: Relative offset from element to offset parent
377 + this.offset.parent.left * mod // The offsetParent's offset without borders (offset + border)
378 - ($.browser.safari && $.browser.version < 526 && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod)
384 _generatePosition: function(event) {
386 var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
387 var pageX = event.pageX;
388 var pageY = event.pageY;
391 * - Position constraining -
392 * Constrain the position to a mix of grid, containment.
395 if(this.originalPosition) { //If we are not dragging yet, we won't check for options
397 if(this.containment) {
398 if(event.pageX - this.offset.click.left < this.containment[0]) pageX = this.containment[0] + this.offset.click.left;
399 if(event.pageY - this.offset.click.top < this.containment[1]) pageY = this.containment[1] + this.offset.click.top;
400 if(event.pageX - this.offset.click.left > this.containment[2]) pageX = this.containment[2] + this.offset.click.left;
401 if(event.pageY - this.offset.click.top > this.containment[3]) pageY = this.containment[3] + this.offset.click.top;
405 var top = this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1];
406 pageY = this.containment ? (!(top - this.offset.click.top < this.containment[1] || top - this.offset.click.top > this.containment[3]) ? top : (!(top - this.offset.click.top < this.containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;
408 var left = this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0];
409 pageX = this.containment ? (!(left - this.offset.click.left < this.containment[0] || left - this.offset.click.left > this.containment[2]) ? left : (!(left - this.offset.click.left < this.containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
416 pageY // The absolute mouse position
417 - this.offset.click.top // Click offset (relative to the element)
418 - this.offset.relative.top // Only for relative positioned nodes: Relative offset from element to offset parent
419 - this.offset.parent.top // The offsetParent's offset without borders (offset + border)
420 + ($.browser.safari && $.browser.version < 526 && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ))
423 pageX // The absolute mouse position
424 - this.offset.click.left // Click offset (relative to the element)
425 - this.offset.relative.left // Only for relative positioned nodes: Relative offset from element to offset parent
426 - this.offset.parent.left // The offsetParent's offset without borders (offset + border)
427 + ($.browser.safari && $.browser.version < 526 && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ))
434 this.helper.removeClass("ui-draggable-dragging");
435 if(this.helper[0] != this.element[0] && !this.cancelHelperRemoval) this.helper.remove();
436 //if($.ui.ddmanager) $.ui.ddmanager.current = null;
438 this.cancelHelperRemoval = false;
441 // From now on bulk stuff - mainly helpers
443 _trigger: function(type, event, ui) {
444 ui = ui || this._uiHash();
445 $.ui.plugin.call(this, type, [event, ui]);
446 if(type == "drag") this.positionAbs = this._convertPositionTo("absolute"); //The absolute position has to be recalculated after plugins
447 return $.Widget.prototype._trigger.call(this, type, event, ui);
452 _uiHash: function(event) {
455 position: this.position,
456 originalPosition: this.originalPosition,
457 offset: this.positionAbs
463 $.extend($.ui.draggable, {
467 $.ui.plugin.add("draggable", "connectToSortable", {
468 start: function(event, ui) {
470 var inst = $(this).data("draggable"), o = inst.options,
471 uiSortable = $.extend({}, ui, { item: inst.element });
473 $(o.connectToSortable).each(function() {
474 var sortable = $.data(this, 'sortable');
475 if (sortable && !sortable.options.disabled) {
476 inst.sortables.push({
478 shouldRevert: sortable.options.revert
480 sortable.refreshPositions(); // Call the sortable's refreshPositions at drag start to refresh the containerCache since the sortable container cache is used in drag and needs to be up to date (this will ensure it's initialised as well as being kept in step with any changes that might have happened on the page).
481 sortable._trigger("activate", event, uiSortable);
486 stop: function(event, ui) {
488 //If we are still over the sortable, we fake the stop event of the sortable, but also remove helper
489 var inst = $(this).data("draggable"),
490 uiSortable = $.extend({}, ui, { item: inst.element });
492 $.each(inst.sortables, function() {
493 if(this.instance.isOver) {
495 this.instance.isOver = 0;
497 inst.cancelHelperRemoval = true; //Don't remove the helper in the draggable instance
498 this.instance.cancelHelperRemoval = false; //Remove it in the sortable instance (so sortable plugins like revert still work)
500 //The sortable revert is supported, and we have to set a temporary dropped variable on the draggable to support revert: 'valid/invalid'
501 if(this.shouldRevert) this.instance.options.revert = true;
503 //Trigger the stop of the sortable
504 this.instance._mouseStop(event);
506 this.instance.options.helper = this.instance.options._helper;
508 //If the helper has been the original item, restore properties in the sortable
509 if(inst.options.helper == 'original')
510 this.instance.currentItem.css({ top: 'auto', left: 'auto' });
513 this.instance.cancelHelperRemoval = false; //Remove the helper in the sortable instance
514 this.instance._trigger("deactivate", event, uiSortable);
520 drag: function(event, ui) {
522 var inst = $(this).data("draggable"), self = this;
524 var checkPos = function(o) {
525 var dyClick = this.offset.click.top, dxClick = this.offset.click.left;
526 var helperTop = this.positionAbs.top, helperLeft = this.positionAbs.left;
527 var itemHeight = o.height, itemWidth = o.width;
528 var itemTop = o.top, itemLeft = o.left;
530 return $.ui.isOver(helperTop + dyClick, helperLeft + dxClick, itemTop, itemLeft, itemHeight, itemWidth);
533 $.each(inst.sortables, function(i) {
535 //Copy over some variables to allow calling the sortable's native _intersectsWith
536 this.instance.positionAbs = inst.positionAbs;
537 this.instance.helperProportions = inst.helperProportions;
538 this.instance.offset.click = inst.offset.click;
540 if(this.instance._intersectsWith(this.instance.containerCache)) {
542 //If it intersects, we use a little isOver variable and set it once, so our move-in stuff gets fired only once
543 if(!this.instance.isOver) {
545 this.instance.isOver = 1;
546 //Now we fake the start of dragging for the sortable instance,
547 //by cloning the list group item, appending it to the sortable and using it as inst.currentItem
548 //We can then fire the start event of the sortable with our passed browser event, and our own helper (so it doesn't create a new one)
549 this.instance.currentItem = $(self).clone().appendTo(this.instance.element).data("sortable-item", true);
550 this.instance.options._helper = this.instance.options.helper; //Store helper option to later restore it
551 this.instance.options.helper = function() { return ui.helper[0]; };
553 event.target = this.instance.currentItem[0];
554 this.instance._mouseCapture(event, true);
555 this.instance._mouseStart(event, true, true);
557 //Because the browser event is way off the new appended portlet, we modify a couple of variables to reflect the changes
558 this.instance.offset.click.top = inst.offset.click.top;
559 this.instance.offset.click.left = inst.offset.click.left;
560 this.instance.offset.parent.left -= inst.offset.parent.left - this.instance.offset.parent.left;
561 this.instance.offset.parent.top -= inst.offset.parent.top - this.instance.offset.parent.top;
563 inst._trigger("toSortable", event);
564 inst.dropped = this.instance.element; //draggable revert needs that
565 //hack so receive/update callbacks work (mostly)
566 inst.currentItem = inst.element;
567 this.instance.fromOutside = inst;
571 //Provided we did all the previous steps, we can fire the drag event of the sortable on every draggable drag, when it intersects with the sortable
572 if(this.instance.currentItem) this.instance._mouseDrag(event);
576 //If it doesn't intersect with the sortable, and it intersected before,
577 //we fake the drag stop of the sortable, but make sure it doesn't remove the helper by using cancelHelperRemoval
578 if(this.instance.isOver) {
580 this.instance.isOver = 0;
581 this.instance.cancelHelperRemoval = true;
583 //Prevent reverting on this forced stop
584 this.instance.options.revert = false;
586 // The out event needs to be triggered independently
587 this.instance._trigger('out', event, this.instance._uiHash(this.instance));
589 this.instance._mouseStop(event, true);
590 this.instance.options.helper = this.instance.options._helper;
592 //Now we remove our currentItem, the list group clone again, and the placeholder, and animate the helper back to it's original size
593 this.instance.currentItem.remove();
594 if(this.instance.placeholder) this.instance.placeholder.remove();
596 inst._trigger("fromSortable", event);
597 inst.dropped = false; //draggable revert needs that
607 $.ui.plugin.add("draggable", "cursor", {
608 start: function(event, ui) {
609 var t = $('body'), o = $(this).data('draggable').options;
610 if (t.css("cursor")) o._cursor = t.css("cursor");
611 t.css("cursor", o.cursor);
613 stop: function(event, ui) {
614 var o = $(this).data('draggable').options;
615 if (o._cursor) $('body').css("cursor", o._cursor);
619 $.ui.plugin.add("draggable", "iframeFix", {
620 start: function(event, ui) {
621 var o = $(this).data('draggable').options;
622 $(o.iframeFix === true ? "iframe" : o.iframeFix).each(function() {
623 $('<div class="ui-draggable-iframeFix" style="background: #fff;"></div>')
625 width: this.offsetWidth+"px", height: this.offsetHeight+"px",
626 position: "absolute", opacity: "0.001", zIndex: 1000
628 .css($(this).offset())
632 stop: function(event, ui) {
633 $("div.ui-draggable-iframeFix").each(function() { this.parentNode.removeChild(this); }); //Remove frame helpers
637 $.ui.plugin.add("draggable", "opacity", {
638 start: function(event, ui) {
639 var t = $(ui.helper), o = $(this).data('draggable').options;
640 if(t.css("opacity")) o._opacity = t.css("opacity");
641 t.css('opacity', o.opacity);
643 stop: function(event, ui) {
644 var o = $(this).data('draggable').options;
645 if(o._opacity) $(ui.helper).css('opacity', o._opacity);
649 $.ui.plugin.add("draggable", "scroll", {
650 start: function(event, ui) {
651 var i = $(this).data("draggable");
652 if(i.scrollParent[0] != document && i.scrollParent[0].tagName != 'HTML') i.overflowOffset = i.scrollParent.offset();
654 drag: function(event, ui) {
656 var i = $(this).data("draggable"), o = i.options, scrolled = false;
658 if(i.scrollParent[0] != document && i.scrollParent[0].tagName != 'HTML') {
660 if(!o.axis || o.axis != 'x') {
661 if((i.overflowOffset.top + i.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity)
662 i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop + o.scrollSpeed;
663 else if(event.pageY - i.overflowOffset.top < o.scrollSensitivity)
664 i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop - o.scrollSpeed;
667 if(!o.axis || o.axis != 'y') {
668 if((i.overflowOffset.left + i.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity)
669 i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft + o.scrollSpeed;
670 else if(event.pageX - i.overflowOffset.left < o.scrollSensitivity)
671 i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft - o.scrollSpeed;
676 if(!o.axis || o.axis != 'x') {
677 if(event.pageY - $(document).scrollTop() < o.scrollSensitivity)
678 scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
679 else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity)
680 scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
683 if(!o.axis || o.axis != 'y') {
684 if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity)
685 scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
686 else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity)
687 scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
692 if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour)
693 $.ui.ddmanager.prepareOffsets(i, event);
698 $.ui.plugin.add("draggable", "snap", {
699 start: function(event, ui) {
701 var i = $(this).data("draggable"), o = i.options;
704 $(o.snap.constructor != String ? ( o.snap.items || ':data(draggable)' ) : o.snap).each(function() {
705 var $t = $(this); var $o = $t.offset();
706 if(this != i.element[0]) i.snapElements.push({
708 width: $t.outerWidth(), height: $t.outerHeight(),
709 top: $o.top, left: $o.left
714 drag: function(event, ui) {
716 var inst = $(this).data("draggable"), o = inst.options;
717 var d = o.snapTolerance;
719 var x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width,
720 y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height;
722 for (var i = inst.snapElements.length - 1; i >= 0; i--){
724 var l = inst.snapElements[i].left, r = l + inst.snapElements[i].width,
725 t = inst.snapElements[i].top, b = t + inst.snapElements[i].height;
727 //Yes, I know, this is insane ;)
728 if(!((l-d < x1 && x1 < r+d && t-d < y1 && y1 < b+d) || (l-d < x1 && x1 < r+d && t-d < y2 && y2 < b+d) || (l-d < x2 && x2 < r+d && t-d < y1 && y1 < b+d) || (l-d < x2 && x2 < r+d && t-d < y2 && y2 < b+d))) {
729 if(inst.snapElements[i].snapping) (inst.options.snap.release && inst.options.snap.release.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
730 inst.snapElements[i].snapping = false;
734 if(o.snapMode != 'inner') {
735 var ts = Math.abs(t - y2) <= d;
736 var bs = Math.abs(b - y1) <= d;
737 var ls = Math.abs(l - x2) <= d;
738 var rs = Math.abs(r - x1) <= d;
739 if(ts) ui.position.top = inst._convertPositionTo("relative", { top: t - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
740 if(bs) ui.position.top = inst._convertPositionTo("relative", { top: b, left: 0 }).top - inst.margins.top;
741 if(ls) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l - inst.helperProportions.width }).left - inst.margins.left;
742 if(rs) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r }).left - inst.margins.left;
745 var first = (ts || bs || ls || rs);
747 if(o.snapMode != 'outer') {
748 var ts = Math.abs(t - y1) <= d;
749 var bs = Math.abs(b - y2) <= d;
750 var ls = Math.abs(l - x1) <= d;
751 var rs = Math.abs(r - x2) <= d;
752 if(ts) ui.position.top = inst._convertPositionTo("relative", { top: t, left: 0 }).top - inst.margins.top;
753 if(bs) ui.position.top = inst._convertPositionTo("relative", { top: b - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
754 if(ls) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l }).left - inst.margins.left;
755 if(rs) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r - inst.helperProportions.width }).left - inst.margins.left;
758 if(!inst.snapElements[i].snapping && (ts || bs || ls || rs || first))
759 (inst.options.snap.snap && inst.options.snap.snap.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
760 inst.snapElements[i].snapping = (ts || bs || ls || rs || first);
767 $.ui.plugin.add("draggable", "stack", {
768 start: function(event, ui) {
770 var o = $(this).data("draggable").options;
772 var group = $.makeArray($(o.stack)).sort(function(a,b) {
773 return (parseInt($(a).css("zIndex"),10) || 0) - (parseInt($(b).css("zIndex"),10) || 0);
775 if (!group.length) { return; }
777 var min = parseInt(group[0].style.zIndex) || 0;
778 $(group).each(function(i) {
779 this.style.zIndex = min + i;
782 this[0].style.zIndex = min + group.length;
787 $.ui.plugin.add("draggable", "zIndex", {
788 start: function(event, ui) {
789 var t = $(ui.helper), o = $(this).data("draggable").options;
790 if(t.css("zIndex")) o._zIndex = t.css("zIndex");
791 t.css('zIndex', o.zIndex);
793 stop: function(event, ui) {
794 var o = $(this).data("draggable").options;
795 if(o._zIndex) $(ui.helper).css('zIndex', o._zIndex);