draggable.js (35343B)
1 /*! 2 * jQuery UI Draggable 1.12.1 3 * http://jqueryui.com 4 * 5 * Copyright jQuery Foundation and other contributors 6 * Released under the MIT license. 7 * http://jquery.org/license 8 */ 9 10 //>>label: Draggable 11 //>>group: Interactions 12 //>>description: Enables dragging functionality for any element. 13 //>>docs: http://api.jqueryui.com/draggable/ 14 //>>demos: http://jqueryui.com/draggable/ 15 //>>css.structure: ../../themes/base/draggable.css 16 17 ( function( factory ) { 18 if ( typeof define === "function" && define.amd ) { 19 20 // AMD. Register as an anonymous module. 21 define( [ 22 "jquery", 23 "./mouse", 24 "./core" 25 ], factory ); 26 } else { 27 28 // Browser globals 29 factory( jQuery ); 30 } 31 }( function( $ ) { 32 33 $.widget( "ui.draggable", $.ui.mouse, { 34 version: "1.12.1", 35 widgetEventPrefix: "drag", 36 options: { 37 addClasses: true, 38 appendTo: "parent", 39 axis: false, 40 connectToSortable: false, 41 containment: false, 42 cursor: "auto", 43 cursorAt: false, 44 grid: false, 45 handle: false, 46 helper: "original", 47 iframeFix: false, 48 opacity: false, 49 refreshPositions: false, 50 revert: false, 51 revertDuration: 500, 52 scope: "default", 53 scroll: true, 54 scrollSensitivity: 20, 55 scrollSpeed: 20, 56 snap: false, 57 snapMode: "both", 58 snapTolerance: 20, 59 stack: false, 60 zIndex: false, 61 62 // Callbacks 63 drag: null, 64 start: null, 65 stop: null 66 }, 67 _create: function() { 68 69 if ( this.options.helper === "original" ) { 70 this._setPositionRelative(); 71 } 72 if ( this.options.addClasses ) { 73 this._addClass( "ui-draggable" ); 74 } 75 this._setHandleClassName(); 76 77 this._mouseInit(); 78 }, 79 80 _setOption: function( key, value ) { 81 this._super( key, value ); 82 if ( key === "handle" ) { 83 this._removeHandleClassName(); 84 this._setHandleClassName(); 85 } 86 }, 87 88 _destroy: function() { 89 if ( ( this.helper || this.element ).is( ".ui-draggable-dragging" ) ) { 90 this.destroyOnClear = true; 91 return; 92 } 93 this._removeHandleClassName(); 94 this._mouseDestroy(); 95 }, 96 97 _mouseCapture: function( event ) { 98 var o = this.options; 99 100 // Among others, prevent a drag on a resizable-handle 101 if ( this.helper || o.disabled || 102 $( event.target ).closest( ".ui-resizable-handle" ).length > 0 ) { 103 return false; 104 } 105 106 //Quit if we're not on a valid handle 107 this.handle = this._getHandle( event ); 108 if ( !this.handle ) { 109 return false; 110 } 111 112 this._blurActiveElement( event ); 113 114 this._blockFrames( o.iframeFix === true ? "iframe" : o.iframeFix ); 115 116 return true; 117 118 }, 119 120 _blockFrames: function( selector ) { 121 this.iframeBlocks = this.document.find( selector ).map( function() { 122 var iframe = $( this ); 123 124 return $( "<div>" ) 125 .css( "position", "absolute" ) 126 .appendTo( iframe.parent() ) 127 .outerWidth( iframe.outerWidth() ) 128 .outerHeight( iframe.outerHeight() ) 129 .offset( iframe.offset() )[ 0 ]; 130 } ); 131 }, 132 133 _unblockFrames: function() { 134 if ( this.iframeBlocks ) { 135 this.iframeBlocks.remove(); 136 delete this.iframeBlocks; 137 } 138 }, 139 140 _blurActiveElement: function( event ) { 141 var activeElement = $.ui.safeActiveElement( this.document[ 0 ] ), 142 target = $( event.target ); 143 144 // Don't blur if the event occurred on an element that is within 145 // the currently focused element 146 // See #10527, #12472 147 if ( target.closest( activeElement ).length ) { 148 return; 149 } 150 151 // Blur any element that currently has focus, see #4261 152 $.ui.safeBlur( activeElement ); 153 }, 154 155 _mouseStart: function( event ) { 156 157 var o = this.options; 158 159 //Create and append the visible helper 160 this.helper = this._createHelper( event ); 161 162 this._addClass( this.helper, "ui-draggable-dragging" ); 163 164 //Cache the helper size 165 this._cacheHelperProportions(); 166 167 //If ddmanager is used for droppables, set the global draggable 168 if ( $.ui.ddmanager ) { 169 $.ui.ddmanager.current = this; 170 } 171 172 /* 173 * - Position generation - 174 * This block generates everything position related - it's the core of draggables. 175 */ 176 177 //Cache the margins of the original element 178 this._cacheMargins(); 179 180 //Store the helper's css position 181 this.cssPosition = this.helper.css( "position" ); 182 this.scrollParent = this.helper.scrollParent( true ); 183 this.offsetParent = this.helper.offsetParent(); 184 this.hasFixedAncestor = this.helper.parents().filter( function() { 185 return $( this ).css( "position" ) === "fixed"; 186 } ).length > 0; 187 188 //The element's absolute position on the page minus margins 189 this.positionAbs = this.element.offset(); 190 this._refreshOffsets( event ); 191 192 //Generate the original position 193 this.originalPosition = this.position = this._generatePosition( event, false ); 194 this.originalPageX = event.pageX; 195 this.originalPageY = event.pageY; 196 197 //Adjust the mouse offset relative to the helper if "cursorAt" is supplied 198 ( o.cursorAt && this._adjustOffsetFromHelper( o.cursorAt ) ); 199 200 //Set a containment if given in the options 201 this._setContainment(); 202 203 //Trigger event + callbacks 204 if ( this._trigger( "start", event ) === false ) { 205 this._clear(); 206 return false; 207 } 208 209 //Recache the helper size 210 this._cacheHelperProportions(); 211 212 //Prepare the droppable offsets 213 if ( $.ui.ddmanager && !o.dropBehaviour ) { 214 $.ui.ddmanager.prepareOffsets( this, event ); 215 } 216 217 // Execute the drag once - this causes the helper not to be visible before getting its 218 // correct position 219 this._mouseDrag( event, true ); 220 221 // If the ddmanager is used for droppables, inform the manager that dragging has started 222 // (see #5003) 223 if ( $.ui.ddmanager ) { 224 $.ui.ddmanager.dragStart( this, event ); 225 } 226 227 return true; 228 }, 229 230 _refreshOffsets: function( event ) { 231 this.offset = { 232 top: this.positionAbs.top - this.margins.top, 233 left: this.positionAbs.left - this.margins.left, 234 scroll: false, 235 parent: this._getParentOffset(), 236 relative: this._getRelativeOffset() 237 }; 238 239 this.offset.click = { 240 left: event.pageX - this.offset.left, 241 top: event.pageY - this.offset.top 242 }; 243 }, 244 245 _mouseDrag: function( event, noPropagation ) { 246 247 // reset any necessary cached properties (see #5009) 248 if ( this.hasFixedAncestor ) { 249 this.offset.parent = this._getParentOffset(); 250 } 251 252 //Compute the helpers position 253 this.position = this._generatePosition( event, true ); 254 this.positionAbs = this._convertPositionTo( "absolute" ); 255 256 //Call plugins and callbacks and use the resulting position if something is returned 257 if ( !noPropagation ) { 258 var ui = this._uiHash(); 259 if ( this._trigger( "drag", event, ui ) === false ) { 260 this._mouseUp( new $.Event( "mouseup", event ) ); 261 return false; 262 } 263 this.position = ui.position; 264 } 265 266 this.helper[ 0 ].style.left = this.position.left + "px"; 267 this.helper[ 0 ].style.top = this.position.top + "px"; 268 269 if ( $.ui.ddmanager ) { 270 $.ui.ddmanager.drag( this, event ); 271 } 272 273 return false; 274 }, 275 276 _mouseStop: function( event ) { 277 278 //If we are using droppables, inform the manager about the drop 279 var that = this, 280 dropped = false; 281 if ( $.ui.ddmanager && !this.options.dropBehaviour ) { 282 dropped = $.ui.ddmanager.drop( this, event ); 283 } 284 285 //if a drop comes from outside (a sortable) 286 if ( this.dropped ) { 287 dropped = this.dropped; 288 this.dropped = false; 289 } 290 291 if ( ( this.options.revert === "invalid" && !dropped ) || 292 ( this.options.revert === "valid" && dropped ) || 293 this.options.revert === true || ( $.isFunction( this.options.revert ) && 294 this.options.revert.call( this.element, dropped ) ) 295 ) { 296 $( this.helper ).animate( 297 this.originalPosition, 298 parseInt( this.options.revertDuration, 10 ), 299 function() { 300 if ( that._trigger( "stop", event ) !== false ) { 301 that._clear(); 302 } 303 } 304 ); 305 } else { 306 if ( this._trigger( "stop", event ) !== false ) { 307 this._clear(); 308 } 309 } 310 311 return false; 312 }, 313 314 _mouseUp: function( event ) { 315 this._unblockFrames(); 316 317 // If the ddmanager is used for droppables, inform the manager that dragging has stopped 318 // (see #5003) 319 if ( $.ui.ddmanager ) { 320 $.ui.ddmanager.dragStop( this, event ); 321 } 322 323 // Only need to focus if the event occurred on the draggable itself, see #10527 324 if ( this.handleElement.is( event.target ) ) { 325 326 // The interaction is over; whether or not the click resulted in a drag, 327 // focus the element 328 this.element.trigger( "focus" ); 329 } 330 331 return $.ui.mouse.prototype._mouseUp.call( this, event ); 332 }, 333 334 cancel: function() { 335 336 if ( this.helper.is( ".ui-draggable-dragging" ) ) { 337 this._mouseUp( new $.Event( "mouseup", { target: this.element[ 0 ] } ) ); 338 } else { 339 this._clear(); 340 } 341 342 return this; 343 344 }, 345 346 _getHandle: function( event ) { 347 return this.options.handle ? 348 !!$( event.target ).closest( this.element.find( this.options.handle ) ).length : 349 true; 350 }, 351 352 _setHandleClassName: function() { 353 this.handleElement = this.options.handle ? 354 this.element.find( this.options.handle ) : this.element; 355 this._addClass( this.handleElement, "ui-draggable-handle" ); 356 }, 357 358 _removeHandleClassName: function() { 359 this._removeClass( this.handleElement, "ui-draggable-handle" ); 360 }, 361 362 _createHelper: function( event ) { 363 364 var o = this.options, 365 helperIsFunction = $.isFunction( o.helper ), 366 helper = helperIsFunction ? 367 $( o.helper.apply( this.element[ 0 ], [ event ] ) ) : 368 ( o.helper === "clone" ? 369 this.element.clone().removeAttr( "id" ) : 370 this.element ); 371 372 if ( !helper.parents( "body" ).length ) { 373 helper.appendTo( ( o.appendTo === "parent" ? 374 this.element[ 0 ].parentNode : 375 o.appendTo ) ); 376 } 377 378 // Http://bugs.jqueryui.com/ticket/9446 379 // a helper function can return the original element 380 // which wouldn't have been set to relative in _create 381 if ( helperIsFunction && helper[ 0 ] === this.element[ 0 ] ) { 382 this._setPositionRelative(); 383 } 384 385 if ( helper[ 0 ] !== this.element[ 0 ] && 386 !( /(fixed|absolute)/ ).test( helper.css( "position" ) ) ) { 387 helper.css( "position", "absolute" ); 388 } 389 390 return helper; 391 392 }, 393 394 _setPositionRelative: function() { 395 if ( !( /^(?:r|a|f)/ ).test( this.element.css( "position" ) ) ) { 396 this.element[ 0 ].style.position = "relative"; 397 } 398 }, 399 400 _adjustOffsetFromHelper: function( obj ) { 401 if ( typeof obj === "string" ) { 402 obj = obj.split( " " ); 403 } 404 if ( $.isArray( obj ) ) { 405 obj = { left: +obj[ 0 ], top: +obj[ 1 ] || 0 }; 406 } 407 if ( "left" in obj ) { 408 this.offset.click.left = obj.left + this.margins.left; 409 } 410 if ( "right" in obj ) { 411 this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left; 412 } 413 if ( "top" in obj ) { 414 this.offset.click.top = obj.top + this.margins.top; 415 } 416 if ( "bottom" in obj ) { 417 this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top; 418 } 419 }, 420 421 _isRootNode: function( element ) { 422 return ( /(html|body)/i ).test( element.tagName ) || element === this.document[ 0 ]; 423 }, 424 425 _getParentOffset: function() { 426 427 //Get the offsetParent and cache its position 428 var po = this.offsetParent.offset(), 429 document = this.document[ 0 ]; 430 431 // This is a special case where we need to modify a offset calculated on start, since the 432 // following happened: 433 // 1. The position of the helper is absolute, so it's position is calculated based on the 434 // next positioned parent 435 // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't 436 // the document, which means that the scroll is included in the initial calculation of the 437 // offset of the parent, and never recalculated upon drag 438 if ( this.cssPosition === "absolute" && this.scrollParent[ 0 ] !== document && 439 $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) { 440 po.left += this.scrollParent.scrollLeft(); 441 po.top += this.scrollParent.scrollTop(); 442 } 443 444 if ( this._isRootNode( this.offsetParent[ 0 ] ) ) { 445 po = { top: 0, left: 0 }; 446 } 447 448 return { 449 top: po.top + ( parseInt( this.offsetParent.css( "borderTopWidth" ), 10 ) || 0 ), 450 left: po.left + ( parseInt( this.offsetParent.css( "borderLeftWidth" ), 10 ) || 0 ) 451 }; 452 453 }, 454 455 _getRelativeOffset: function() { 456 if ( this.cssPosition !== "relative" ) { 457 return { top: 0, left: 0 }; 458 } 459 460 var p = this.element.position(), 461 scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] ); 462 463 return { 464 top: p.top - ( parseInt( this.helper.css( "top" ), 10 ) || 0 ) + 465 ( !scrollIsRootNode ? this.scrollParent.scrollTop() : 0 ), 466 left: p.left - ( parseInt( this.helper.css( "left" ), 10 ) || 0 ) + 467 ( !scrollIsRootNode ? this.scrollParent.scrollLeft() : 0 ) 468 }; 469 470 }, 471 472 _cacheMargins: function() { 473 this.margins = { 474 left: ( parseInt( this.element.css( "marginLeft" ), 10 ) || 0 ), 475 top: ( parseInt( this.element.css( "marginTop" ), 10 ) || 0 ), 476 right: ( parseInt( this.element.css( "marginRight" ), 10 ) || 0 ), 477 bottom: ( parseInt( this.element.css( "marginBottom" ), 10 ) || 0 ) 478 }; 479 }, 480 481 _cacheHelperProportions: function() { 482 this.helperProportions = { 483 width: this.helper.outerWidth(), 484 height: this.helper.outerHeight() 485 }; 486 }, 487 488 _setContainment: function() { 489 490 var isUserScrollable, c, ce, 491 o = this.options, 492 document = this.document[ 0 ]; 493 494 this.relativeContainer = null; 495 496 if ( !o.containment ) { 497 this.containment = null; 498 return; 499 } 500 501 if ( o.containment === "window" ) { 502 this.containment = [ 503 $( window ).scrollLeft() - this.offset.relative.left - this.offset.parent.left, 504 $( window ).scrollTop() - this.offset.relative.top - this.offset.parent.top, 505 $( window ).scrollLeft() + $( window ).width() - 506 this.helperProportions.width - this.margins.left, 507 $( window ).scrollTop() + 508 ( $( window ).height() || document.body.parentNode.scrollHeight ) - 509 this.helperProportions.height - this.margins.top 510 ]; 511 return; 512 } 513 514 if ( o.containment === "document" ) { 515 this.containment = [ 516 0, 517 0, 518 $( document ).width() - this.helperProportions.width - this.margins.left, 519 ( $( document ).height() || document.body.parentNode.scrollHeight ) - 520 this.helperProportions.height - this.margins.top 521 ]; 522 return; 523 } 524 525 if ( o.containment.constructor === Array ) { 526 this.containment = o.containment; 527 return; 528 } 529 530 if ( o.containment === "parent" ) { 531 o.containment = this.helper[ 0 ].parentNode; 532 } 533 534 c = $( o.containment ); 535 ce = c[ 0 ]; 536 537 if ( !ce ) { 538 return; 539 } 540 541 isUserScrollable = /(scroll|auto)/.test( c.css( "overflow" ) ); 542 543 this.containment = [ 544 ( parseInt( c.css( "borderLeftWidth" ), 10 ) || 0 ) + 545 ( parseInt( c.css( "paddingLeft" ), 10 ) || 0 ), 546 ( parseInt( c.css( "borderTopWidth" ), 10 ) || 0 ) + 547 ( parseInt( c.css( "paddingTop" ), 10 ) || 0 ), 548 ( isUserScrollable ? Math.max( ce.scrollWidth, ce.offsetWidth ) : ce.offsetWidth ) - 549 ( parseInt( c.css( "borderRightWidth" ), 10 ) || 0 ) - 550 ( parseInt( c.css( "paddingRight" ), 10 ) || 0 ) - 551 this.helperProportions.width - 552 this.margins.left - 553 this.margins.right, 554 ( isUserScrollable ? Math.max( ce.scrollHeight, ce.offsetHeight ) : ce.offsetHeight ) - 555 ( parseInt( c.css( "borderBottomWidth" ), 10 ) || 0 ) - 556 ( parseInt( c.css( "paddingBottom" ), 10 ) || 0 ) - 557 this.helperProportions.height - 558 this.margins.top - 559 this.margins.bottom 560 ]; 561 this.relativeContainer = c; 562 }, 563 564 _convertPositionTo: function( d, pos ) { 565 566 if ( !pos ) { 567 pos = this.position; 568 } 569 570 var mod = d === "absolute" ? 1 : -1, 571 scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] ); 572 573 return { 574 top: ( 575 576 // The absolute mouse position 577 pos.top + 578 579 // Only for relative positioned nodes: Relative offset from element to offset parent 580 this.offset.relative.top * mod + 581 582 // The offsetParent's offset without borders (offset + border) 583 this.offset.parent.top * mod - 584 ( ( this.cssPosition === "fixed" ? 585 -this.offset.scroll.top : 586 ( scrollIsRootNode ? 0 : this.offset.scroll.top ) ) * mod ) 587 ), 588 left: ( 589 590 // The absolute mouse position 591 pos.left + 592 593 // Only for relative positioned nodes: Relative offset from element to offset parent 594 this.offset.relative.left * mod + 595 596 // The offsetParent's offset without borders (offset + border) 597 this.offset.parent.left * mod - 598 ( ( this.cssPosition === "fixed" ? 599 -this.offset.scroll.left : 600 ( scrollIsRootNode ? 0 : this.offset.scroll.left ) ) * mod ) 601 ) 602 }; 603 604 }, 605 606 _generatePosition: function( event, constrainPosition ) { 607 608 var containment, co, top, left, 609 o = this.options, 610 scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] ), 611 pageX = event.pageX, 612 pageY = event.pageY; 613 614 // Cache the scroll 615 if ( !scrollIsRootNode || !this.offset.scroll ) { 616 this.offset.scroll = { 617 top: this.scrollParent.scrollTop(), 618 left: this.scrollParent.scrollLeft() 619 }; 620 } 621 622 /* 623 * - Position constraining - 624 * Constrain the position to a mix of grid, containment. 625 */ 626 627 // If we are not dragging yet, we won't check for options 628 if ( constrainPosition ) { 629 if ( this.containment ) { 630 if ( this.relativeContainer ) { 631 co = this.relativeContainer.offset(); 632 containment = [ 633 this.containment[ 0 ] + co.left, 634 this.containment[ 1 ] + co.top, 635 this.containment[ 2 ] + co.left, 636 this.containment[ 3 ] + co.top 637 ]; 638 } else { 639 containment = this.containment; 640 } 641 642 if ( event.pageX - this.offset.click.left < containment[ 0 ] ) { 643 pageX = containment[ 0 ] + this.offset.click.left; 644 } 645 if ( event.pageY - this.offset.click.top < containment[ 1 ] ) { 646 pageY = containment[ 1 ] + this.offset.click.top; 647 } 648 if ( event.pageX - this.offset.click.left > containment[ 2 ] ) { 649 pageX = containment[ 2 ] + this.offset.click.left; 650 } 651 if ( event.pageY - this.offset.click.top > containment[ 3 ] ) { 652 pageY = containment[ 3 ] + this.offset.click.top; 653 } 654 } 655 656 if ( o.grid ) { 657 658 //Check for grid elements set to 0 to prevent divide by 0 error causing invalid 659 // argument errors in IE (see ticket #6950) 660 top = o.grid[ 1 ] ? this.originalPageY + Math.round( ( pageY - 661 this.originalPageY ) / o.grid[ 1 ] ) * o.grid[ 1 ] : this.originalPageY; 662 pageY = containment ? ( ( top - this.offset.click.top >= containment[ 1 ] || 663 top - this.offset.click.top > containment[ 3 ] ) ? 664 top : 665 ( ( top - this.offset.click.top >= containment[ 1 ] ) ? 666 top - o.grid[ 1 ] : top + o.grid[ 1 ] ) ) : top; 667 668 left = o.grid[ 0 ] ? this.originalPageX + 669 Math.round( ( pageX - this.originalPageX ) / o.grid[ 0 ] ) * o.grid[ 0 ] : 670 this.originalPageX; 671 pageX = containment ? ( ( left - this.offset.click.left >= containment[ 0 ] || 672 left - this.offset.click.left > containment[ 2 ] ) ? 673 left : 674 ( ( left - this.offset.click.left >= containment[ 0 ] ) ? 675 left - o.grid[ 0 ] : left + o.grid[ 0 ] ) ) : left; 676 } 677 678 if ( o.axis === "y" ) { 679 pageX = this.originalPageX; 680 } 681 682 if ( o.axis === "x" ) { 683 pageY = this.originalPageY; 684 } 685 } 686 687 return { 688 top: ( 689 690 // The absolute mouse position 691 pageY - 692 693 // Click offset (relative to the element) 694 this.offset.click.top - 695 696 // Only for relative positioned nodes: Relative offset from element to offset parent 697 this.offset.relative.top - 698 699 // The offsetParent's offset without borders (offset + border) 700 this.offset.parent.top + 701 ( this.cssPosition === "fixed" ? 702 -this.offset.scroll.top : 703 ( scrollIsRootNode ? 0 : this.offset.scroll.top ) ) 704 ), 705 left: ( 706 707 // The absolute mouse position 708 pageX - 709 710 // Click offset (relative to the element) 711 this.offset.click.left - 712 713 // Only for relative positioned nodes: Relative offset from element to offset parent 714 this.offset.relative.left - 715 716 // The offsetParent's offset without borders (offset + border) 717 this.offset.parent.left + 718 ( this.cssPosition === "fixed" ? 719 -this.offset.scroll.left : 720 ( scrollIsRootNode ? 0 : this.offset.scroll.left ) ) 721 ) 722 }; 723 724 }, 725 726 _clear: function() { 727 this._removeClass( this.helper, "ui-draggable-dragging" ); 728 if ( this.helper[ 0 ] !== this.element[ 0 ] && !this.cancelHelperRemoval ) { 729 this.helper.remove(); 730 } 731 this.helper = null; 732 this.cancelHelperRemoval = false; 733 if ( this.destroyOnClear ) { 734 this.destroy(); 735 } 736 }, 737 738 // From now on bulk stuff - mainly helpers 739 740 _trigger: function( type, event, ui ) { 741 ui = ui || this._uiHash(); 742 $.ui.plugin.call( this, type, [ event, ui, this ], true ); 743 744 // Absolute position and offset (see #6884 ) have to be recalculated after plugins 745 if ( /^(drag|start|stop)/.test( type ) ) { 746 this.positionAbs = this._convertPositionTo( "absolute" ); 747 ui.offset = this.positionAbs; 748 } 749 return $.Widget.prototype._trigger.call( this, type, event, ui ); 750 }, 751 752 plugins: {}, 753 754 _uiHash: function() { 755 return { 756 helper: this.helper, 757 position: this.position, 758 originalPosition: this.originalPosition, 759 offset: this.positionAbs 760 }; 761 } 762 763 } ); 764 765 $.ui.plugin.add( "draggable", "connectToSortable", { 766 start: function( event, ui, draggable ) { 767 var uiSortable = $.extend( {}, ui, { 768 item: draggable.element 769 } ); 770 771 draggable.sortables = []; 772 $( draggable.options.connectToSortable ).each( function() { 773 var sortable = $( this ).sortable( "instance" ); 774 775 if ( sortable && !sortable.options.disabled ) { 776 draggable.sortables.push( sortable ); 777 778 // RefreshPositions is called at drag start to refresh the containerCache 779 // which is used in drag. This ensures it's initialized and synchronized 780 // with any changes that might have happened on the page since initialization. 781 sortable.refreshPositions(); 782 sortable._trigger( "activate", event, uiSortable ); 783 } 784 } ); 785 }, 786 stop: function( event, ui, draggable ) { 787 var uiSortable = $.extend( {}, ui, { 788 item: draggable.element 789 } ); 790 791 draggable.cancelHelperRemoval = false; 792 793 $.each( draggable.sortables, function() { 794 var sortable = this; 795 796 if ( sortable.isOver ) { 797 sortable.isOver = 0; 798 799 // Allow this sortable to handle removing the helper 800 draggable.cancelHelperRemoval = true; 801 sortable.cancelHelperRemoval = false; 802 803 // Use _storedCSS To restore properties in the sortable, 804 // as this also handles revert (#9675) since the draggable 805 // may have modified them in unexpected ways (#8809) 806 sortable._storedCSS = { 807 position: sortable.placeholder.css( "position" ), 808 top: sortable.placeholder.css( "top" ), 809 left: sortable.placeholder.css( "left" ) 810 }; 811 812 sortable._mouseStop( event ); 813 814 // Once drag has ended, the sortable should return to using 815 // its original helper, not the shared helper from draggable 816 sortable.options.helper = sortable.options._helper; 817 } else { 818 819 // Prevent this Sortable from removing the helper. 820 // However, don't set the draggable to remove the helper 821 // either as another connected Sortable may yet handle the removal. 822 sortable.cancelHelperRemoval = true; 823 824 sortable._trigger( "deactivate", event, uiSortable ); 825 } 826 } ); 827 }, 828 drag: function( event, ui, draggable ) { 829 $.each( draggable.sortables, function() { 830 var innermostIntersecting = false, 831 sortable = this; 832 833 // Copy over variables that sortable's _intersectsWith uses 834 sortable.positionAbs = draggable.positionAbs; 835 sortable.helperProportions = draggable.helperProportions; 836 sortable.offset.click = draggable.offset.click; 837 838 if ( sortable._intersectsWith( sortable.containerCache ) ) { 839 innermostIntersecting = true; 840 841 $.each( draggable.sortables, function() { 842 843 // Copy over variables that sortable's _intersectsWith uses 844 this.positionAbs = draggable.positionAbs; 845 this.helperProportions = draggable.helperProportions; 846 this.offset.click = draggable.offset.click; 847 848 if ( this !== sortable && 849 this._intersectsWith( this.containerCache ) && 850 $.contains( sortable.element[ 0 ], this.element[ 0 ] ) ) { 851 innermostIntersecting = false; 852 } 853 854 return innermostIntersecting; 855 } ); 856 } 857 858 if ( innermostIntersecting ) { 859 860 // If it intersects, we use a little isOver variable and set it once, 861 // so that the move-in stuff gets fired only once. 862 if ( !sortable.isOver ) { 863 sortable.isOver = 1; 864 865 // Store draggable's parent in case we need to reappend to it later. 866 draggable._parent = ui.helper.parent(); 867 868 sortable.currentItem = ui.helper 869 .appendTo( sortable.element ) 870 .data( "ui-sortable-item", true ); 871 872 // Store helper option to later restore it 873 sortable.options._helper = sortable.options.helper; 874 875 sortable.options.helper = function() { 876 return ui.helper[ 0 ]; 877 }; 878 879 // Fire the start events of the sortable with our passed browser event, 880 // and our own helper (so it doesn't create a new one) 881 event.target = sortable.currentItem[ 0 ]; 882 sortable._mouseCapture( event, true ); 883 sortable._mouseStart( event, true, true ); 884 885 // Because the browser event is way off the new appended portlet, 886 // modify necessary variables to reflect the changes 887 sortable.offset.click.top = draggable.offset.click.top; 888 sortable.offset.click.left = draggable.offset.click.left; 889 sortable.offset.parent.left -= draggable.offset.parent.left - 890 sortable.offset.parent.left; 891 sortable.offset.parent.top -= draggable.offset.parent.top - 892 sortable.offset.parent.top; 893 894 draggable._trigger( "toSortable", event ); 895 896 // Inform draggable that the helper is in a valid drop zone, 897 // used solely in the revert option to handle "valid/invalid". 898 draggable.dropped = sortable.element; 899 900 // Need to refreshPositions of all sortables in the case that 901 // adding to one sortable changes the location of the other sortables (#9675) 902 $.each( draggable.sortables, function() { 903 this.refreshPositions(); 904 } ); 905 906 // Hack so receive/update callbacks work (mostly) 907 draggable.currentItem = draggable.element; 908 sortable.fromOutside = draggable; 909 } 910 911 if ( sortable.currentItem ) { 912 sortable._mouseDrag( event ); 913 914 // Copy the sortable's position because the draggable's can potentially reflect 915 // a relative position, while sortable is always absolute, which the dragged 916 // element has now become. (#8809) 917 ui.position = sortable.position; 918 } 919 } else { 920 921 // If it doesn't intersect with the sortable, and it intersected before, 922 // we fake the drag stop of the sortable, but make sure it doesn't remove 923 // the helper by using cancelHelperRemoval. 924 if ( sortable.isOver ) { 925 926 sortable.isOver = 0; 927 sortable.cancelHelperRemoval = true; 928 929 // Calling sortable's mouseStop would trigger a revert, 930 // so revert must be temporarily false until after mouseStop is called. 931 sortable.options._revert = sortable.options.revert; 932 sortable.options.revert = false; 933 934 sortable._trigger( "out", event, sortable._uiHash( sortable ) ); 935 sortable._mouseStop( event, true ); 936 937 // Restore sortable behaviors that were modfied 938 // when the draggable entered the sortable area (#9481) 939 sortable.options.revert = sortable.options._revert; 940 sortable.options.helper = sortable.options._helper; 941 942 if ( sortable.placeholder ) { 943 sortable.placeholder.remove(); 944 } 945 946 // Restore and recalculate the draggable's offset considering the sortable 947 // may have modified them in unexpected ways. (#8809, #10669) 948 ui.helper.appendTo( draggable._parent ); 949 draggable._refreshOffsets( event ); 950 ui.position = draggable._generatePosition( event, true ); 951 952 draggable._trigger( "fromSortable", event ); 953 954 // Inform draggable that the helper is no longer in a valid drop zone 955 draggable.dropped = false; 956 957 // Need to refreshPositions of all sortables just in case removing 958 // from one sortable changes the location of other sortables (#9675) 959 $.each( draggable.sortables, function() { 960 this.refreshPositions(); 961 } ); 962 } 963 } 964 } ); 965 } 966 } ); 967 968 $.ui.plugin.add( "draggable", "cursor", { 969 start: function( event, ui, instance ) { 970 var t = $( "body" ), 971 o = instance.options; 972 973 if ( t.css( "cursor" ) ) { 974 o._cursor = t.css( "cursor" ); 975 } 976 t.css( "cursor", o.cursor ); 977 }, 978 stop: function( event, ui, instance ) { 979 var o = instance.options; 980 if ( o._cursor ) { 981 $( "body" ).css( "cursor", o._cursor ); 982 } 983 } 984 } ); 985 986 $.ui.plugin.add( "draggable", "opacity", { 987 start: function( event, ui, instance ) { 988 var t = $( ui.helper ), 989 o = instance.options; 990 if ( t.css( "opacity" ) ) { 991 o._opacity = t.css( "opacity" ); 992 } 993 t.css( "opacity", o.opacity ); 994 }, 995 stop: function( event, ui, instance ) { 996 var o = instance.options; 997 if ( o._opacity ) { 998 $( ui.helper ).css( "opacity", o._opacity ); 999 } 1000 } 1001 } ); 1002 1003 $.ui.plugin.add( "draggable", "scroll", { 1004 start: function( event, ui, i ) { 1005 if ( !i.scrollParentNotHidden ) { 1006 i.scrollParentNotHidden = i.helper.scrollParent( false ); 1007 } 1008 1009 if ( i.scrollParentNotHidden[ 0 ] !== i.document[ 0 ] && 1010 i.scrollParentNotHidden[ 0 ].tagName !== "HTML" ) { 1011 i.overflowOffset = i.scrollParentNotHidden.offset(); 1012 } 1013 }, 1014 drag: function( event, ui, i ) { 1015 1016 var o = i.options, 1017 scrolled = false, 1018 scrollParent = i.scrollParentNotHidden[ 0 ], 1019 document = i.document[ 0 ]; 1020 1021 if ( scrollParent !== document && scrollParent.tagName !== "HTML" ) { 1022 if ( !o.axis || o.axis !== "x" ) { 1023 if ( ( i.overflowOffset.top + scrollParent.offsetHeight ) - event.pageY < 1024 o.scrollSensitivity ) { 1025 scrollParent.scrollTop = scrolled = scrollParent.scrollTop + o.scrollSpeed; 1026 } else if ( event.pageY - i.overflowOffset.top < o.scrollSensitivity ) { 1027 scrollParent.scrollTop = scrolled = scrollParent.scrollTop - o.scrollSpeed; 1028 } 1029 } 1030 1031 if ( !o.axis || o.axis !== "y" ) { 1032 if ( ( i.overflowOffset.left + scrollParent.offsetWidth ) - event.pageX < 1033 o.scrollSensitivity ) { 1034 scrollParent.scrollLeft = scrolled = scrollParent.scrollLeft + o.scrollSpeed; 1035 } else if ( event.pageX - i.overflowOffset.left < o.scrollSensitivity ) { 1036 scrollParent.scrollLeft = scrolled = scrollParent.scrollLeft - o.scrollSpeed; 1037 } 1038 } 1039 1040 } else { 1041 1042 if ( !o.axis || o.axis !== "x" ) { 1043 if ( event.pageY - $( document ).scrollTop() < o.scrollSensitivity ) { 1044 scrolled = $( document ).scrollTop( $( document ).scrollTop() - o.scrollSpeed ); 1045 } else if ( $( window ).height() - ( event.pageY - $( document ).scrollTop() ) < 1046 o.scrollSensitivity ) { 1047 scrolled = $( document ).scrollTop( $( document ).scrollTop() + o.scrollSpeed ); 1048 } 1049 } 1050 1051 if ( !o.axis || o.axis !== "y" ) { 1052 if ( event.pageX - $( document ).scrollLeft() < o.scrollSensitivity ) { 1053 scrolled = $( document ).scrollLeft( 1054 $( document ).scrollLeft() - o.scrollSpeed 1055 ); 1056 } else if ( $( window ).width() - ( event.pageX - $( document ).scrollLeft() ) < 1057 o.scrollSensitivity ) { 1058 scrolled = $( document ).scrollLeft( 1059 $( document ).scrollLeft() + o.scrollSpeed 1060 ); 1061 } 1062 } 1063 1064 } 1065 1066 if ( scrolled !== false && $.ui.ddmanager && !o.dropBehaviour ) { 1067 $.ui.ddmanager.prepareOffsets( i, event ); 1068 } 1069 1070 } 1071 } ); 1072 1073 $.ui.plugin.add( "draggable", "snap", { 1074 start: function( event, ui, i ) { 1075 1076 var o = i.options; 1077 1078 i.snapElements = []; 1079 1080 $( o.snap.constructor !== String ? ( o.snap.items || ":data(ui-draggable)" ) : o.snap ) 1081 .each( function() { 1082 var $t = $( this ), 1083 $o = $t.offset(); 1084 if ( this !== i.element[ 0 ] ) { 1085 i.snapElements.push( { 1086 item: this, 1087 width: $t.outerWidth(), height: $t.outerHeight(), 1088 top: $o.top, left: $o.left 1089 } ); 1090 } 1091 } ); 1092 1093 }, 1094 drag: function( event, ui, inst ) { 1095 1096 var ts, bs, ls, rs, l, r, t, b, i, first, 1097 o = inst.options, 1098 d = o.snapTolerance, 1099 x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width, 1100 y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height; 1101 1102 for ( i = inst.snapElements.length - 1; i >= 0; i-- ) { 1103 1104 l = inst.snapElements[ i ].left - inst.margins.left; 1105 r = l + inst.snapElements[ i ].width; 1106 t = inst.snapElements[ i ].top - inst.margins.top; 1107 b = t + inst.snapElements[ i ].height; 1108 1109 if ( x2 < l - d || x1 > r + d || y2 < t - d || y1 > b + d || 1110 !$.contains( inst.snapElements[ i ].item.ownerDocument, 1111 inst.snapElements[ i ].item ) ) { 1112 if ( inst.snapElements[ i ].snapping ) { 1113 ( inst.options.snap.release && 1114 inst.options.snap.release.call( 1115 inst.element, 1116 event, 1117 $.extend( inst._uiHash(), { snapItem: inst.snapElements[ i ].item } ) 1118 ) ); 1119 } 1120 inst.snapElements[ i ].snapping = false; 1121 continue; 1122 } 1123 1124 if ( o.snapMode !== "inner" ) { 1125 ts = Math.abs( t - y2 ) <= d; 1126 bs = Math.abs( b - y1 ) <= d; 1127 ls = Math.abs( l - x2 ) <= d; 1128 rs = Math.abs( r - x1 ) <= d; 1129 if ( ts ) { 1130 ui.position.top = inst._convertPositionTo( "relative", { 1131 top: t - inst.helperProportions.height, 1132 left: 0 1133 } ).top; 1134 } 1135 if ( bs ) { 1136 ui.position.top = inst._convertPositionTo( "relative", { 1137 top: b, 1138 left: 0 1139 } ).top; 1140 } 1141 if ( ls ) { 1142 ui.position.left = inst._convertPositionTo( "relative", { 1143 top: 0, 1144 left: l - inst.helperProportions.width 1145 } ).left; 1146 } 1147 if ( rs ) { 1148 ui.position.left = inst._convertPositionTo( "relative", { 1149 top: 0, 1150 left: r 1151 } ).left; 1152 } 1153 } 1154 1155 first = ( ts || bs || ls || rs ); 1156 1157 if ( o.snapMode !== "outer" ) { 1158 ts = Math.abs( t - y1 ) <= d; 1159 bs = Math.abs( b - y2 ) <= d; 1160 ls = Math.abs( l - x1 ) <= d; 1161 rs = Math.abs( r - x2 ) <= d; 1162 if ( ts ) { 1163 ui.position.top = inst._convertPositionTo( "relative", { 1164 top: t, 1165 left: 0 1166 } ).top; 1167 } 1168 if ( bs ) { 1169 ui.position.top = inst._convertPositionTo( "relative", { 1170 top: b - inst.helperProportions.height, 1171 left: 0 1172 } ).top; 1173 } 1174 if ( ls ) { 1175 ui.position.left = inst._convertPositionTo( "relative", { 1176 top: 0, 1177 left: l 1178 } ).left; 1179 } 1180 if ( rs ) { 1181 ui.position.left = inst._convertPositionTo( "relative", { 1182 top: 0, 1183 left: r - inst.helperProportions.width 1184 } ).left; 1185 } 1186 } 1187 1188 if ( !inst.snapElements[ i ].snapping && ( ts || bs || ls || rs || first ) ) { 1189 ( inst.options.snap.snap && 1190 inst.options.snap.snap.call( 1191 inst.element, 1192 event, 1193 $.extend( inst._uiHash(), { 1194 snapItem: inst.snapElements[ i ].item 1195 } ) ) ); 1196 } 1197 inst.snapElements[ i ].snapping = ( ts || bs || ls || rs || first ); 1198 1199 } 1200 1201 } 1202 } ); 1203 1204 $.ui.plugin.add( "draggable", "stack", { 1205 start: function( event, ui, instance ) { 1206 var min, 1207 o = instance.options, 1208 group = $.makeArray( $( o.stack ) ).sort( function( a, b ) { 1209 return ( parseInt( $( a ).css( "zIndex" ), 10 ) || 0 ) - 1210 ( parseInt( $( b ).css( "zIndex" ), 10 ) || 0 ); 1211 } ); 1212 1213 if ( !group.length ) { return; } 1214 1215 min = parseInt( $( group[ 0 ] ).css( "zIndex" ), 10 ) || 0; 1216 $( group ).each( function( i ) { 1217 $( this ).css( "zIndex", min + i ); 1218 } ); 1219 this.css( "zIndex", ( min + group.length ) ); 1220 } 1221 } ); 1222 1223 $.ui.plugin.add( "draggable", "zIndex", { 1224 start: function( event, ui, instance ) { 1225 var t = $( ui.helper ), 1226 o = instance.options; 1227 1228 if ( t.css( "zIndex" ) ) { 1229 o._zIndex = t.css( "zIndex" ); 1230 } 1231 t.css( "zIndex", o.zIndex ); 1232 }, 1233 stop: function( event, ui, instance ) { 1234 var o = instance.options; 1235 1236 if ( o._zIndex ) { 1237 $( ui.helper ).css( "zIndex", o._zIndex ); 1238 } 1239 } 1240 } ); 1241 1242 return $.ui.draggable; 1243 1244 } ) );