UNPKG

jquery-ui-sortable-only

Version:

jquery ui sortable

1,831 lines (1,478 loc) 73.5 kB
/*! jQuery UI - v1.12.1 - 2018-01-12 * http://jqueryui.com * Includes: widget.js, data.js, scroll-parent.js, widgets/sortable.js, widgets/mouse.js * Copyright jQuery Foundation and other contributors; Licensed MIT */ (function( factory ) { if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. define([ "jquery" ], factory ); } else { // Browser globals factory( jQuery ); } }(function( $ ) { $.ui = $.ui || {}; var version = $.ui.version = "1.12.1"; /*! * jQuery UI Widget 1.12.1 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license */ //>>label: Widget //>>group: Core //>>description: Provides a factory for creating stateful widgets with a common API. //>>docs: http://api.jqueryui.com/jQuery.widget/ //>>demos: http://jqueryui.com/widget/ var widgetUuid = 0; var widgetSlice = Array.prototype.slice; $.cleanData = ( function( orig ) { return function( elems ) { var events, elem, i; for ( i = 0; ( elem = elems[ i ] ) != null; i++ ) { try { // Only trigger remove when necessary to save time events = $._data( elem, "events" ); if ( events && events.remove ) { $( elem ).triggerHandler( "remove" ); } // Http://bugs.jquery.com/ticket/8235 } catch ( e ) {} } orig( elems ); }; } )( $.cleanData ); $.widget = function( name, base, prototype ) { var existingConstructor, constructor, basePrototype; // ProxiedPrototype allows the provided prototype to remain unmodified // so that it can be used as a mixin for multiple widgets (#8876) var proxiedPrototype = {}; var namespace = name.split( "." )[ 0 ]; name = name.split( "." )[ 1 ]; var fullName = namespace + "-" + name; if ( !prototype ) { prototype = base; base = $.Widget; } if ( $.isArray( prototype ) ) { prototype = $.extend.apply( null, [ {} ].concat( prototype ) ); } // Create selector for plugin $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) { return !!$.data( elem, fullName ); }; $[ namespace ] = $[ namespace ] || {}; existingConstructor = $[ namespace ][ name ]; constructor = $[ namespace ][ name ] = function( options, element ) { // Allow instantiation without "new" keyword if ( !this._createWidget ) { return new constructor( options, element ); } // Allow instantiation without initializing for simple inheritance // must use "new" keyword (the code above always passes args) if ( arguments.length ) { this._createWidget( options, element ); } }; // Extend with the existing constructor to carry over any static properties $.extend( constructor, existingConstructor, { version: prototype.version, // Copy the object used to create the prototype in case we need to // redefine the widget later _proto: $.extend( {}, prototype ), // Track widgets that inherit from this widget in case this widget is // redefined after a widget inherits from it _childConstructors: [] } ); basePrototype = new base(); // We need to make the options hash a property directly on the new instance // otherwise we'll modify the options hash on the prototype that we're // inheriting from basePrototype.options = $.widget.extend( {}, basePrototype.options ); $.each( prototype, function( prop, value ) { if ( !$.isFunction( value ) ) { proxiedPrototype[ prop ] = value; return; } proxiedPrototype[ prop ] = ( function() { function _super() { return base.prototype[ prop ].apply( this, arguments ); } function _superApply( args ) { return base.prototype[ prop ].apply( this, args ); } return function() { var __super = this._super; var __superApply = this._superApply; var returnValue; this._super = _super; this._superApply = _superApply; returnValue = value.apply( this, arguments ); this._super = __super; this._superApply = __superApply; return returnValue; }; } )(); } ); constructor.prototype = $.widget.extend( basePrototype, { // TODO: remove support for widgetEventPrefix // always use the name + a colon as the prefix, e.g., draggable:start // don't prefix for widgets that aren't DOM-based widgetEventPrefix: existingConstructor ? ( basePrototype.widgetEventPrefix || name ) : name }, proxiedPrototype, { constructor: constructor, namespace: namespace, widgetName: name, widgetFullName: fullName } ); // If this widget is being redefined then we need to find all widgets that // are inheriting from it and redefine all of them so that they inherit from // the new version of this widget. We're essentially trying to replace one // level in the prototype chain. if ( existingConstructor ) { $.each( existingConstructor._childConstructors, function( i, child ) { var childPrototype = child.prototype; // Redefine the child widget using the same prototype that was // originally used, but inherit from the new version of the base $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto ); } ); // Remove the list of existing child constructors from the old constructor // so the old child constructors can be garbage collected delete existingConstructor._childConstructors; } else { base._childConstructors.push( constructor ); } $.widget.bridge( name, constructor ); return constructor; }; $.widget.extend = function( target ) { var input = widgetSlice.call( arguments, 1 ); var inputIndex = 0; var inputLength = input.length; var key; var value; for ( ; inputIndex < inputLength; inputIndex++ ) { for ( key in input[ inputIndex ] ) { value = input[ inputIndex ][ key ]; if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) { // Clone objects if ( $.isPlainObject( value ) ) { target[ key ] = $.isPlainObject( target[ key ] ) ? $.widget.extend( {}, target[ key ], value ) : // Don't extend strings, arrays, etc. with objects $.widget.extend( {}, value ); // Copy everything else by reference } else { target[ key ] = value; } } } } return target; }; $.widget.bridge = function( name, object ) { var fullName = object.prototype.widgetFullName || name; $.fn[ name ] = function( options ) { var isMethodCall = typeof options === "string"; var args = widgetSlice.call( arguments, 1 ); var returnValue = this; if ( isMethodCall ) { // If this is an empty collection, we need to have the instance method // return undefined instead of the jQuery instance if ( !this.length && options === "instance" ) { returnValue = undefined; } else { this.each( function() { var methodValue; var instance = $.data( this, fullName ); if ( options === "instance" ) { returnValue = instance; return false; } if ( !instance ) { return $.error( "cannot call methods on " + name + " prior to initialization; " + "attempted to call method '" + options + "'" ); } if ( !$.isFunction( instance[ options ] ) || options.charAt( 0 ) === "_" ) { return $.error( "no such method '" + options + "' for " + name + " widget instance" ); } methodValue = instance[ options ].apply( instance, args ); if ( methodValue !== instance && methodValue !== undefined ) { returnValue = methodValue && methodValue.jquery ? returnValue.pushStack( methodValue.get() ) : methodValue; return false; } } ); } } else { // Allow multiple hashes to be passed on init if ( args.length ) { options = $.widget.extend.apply( null, [ options ].concat( args ) ); } this.each( function() { var instance = $.data( this, fullName ); if ( instance ) { instance.option( options || {} ); if ( instance._init ) { instance._init(); } } else { $.data( this, fullName, new object( options, this ) ); } } ); } return returnValue; }; }; $.Widget = function( /* options, element */ ) {}; $.Widget._childConstructors = []; $.Widget.prototype = { widgetName: "widget", widgetEventPrefix: "", defaultElement: "<div>", options: { classes: {}, disabled: false, // Callbacks create: null }, _createWidget: function( options, element ) { element = $( element || this.defaultElement || this )[ 0 ]; this.element = $( element ); this.uuid = widgetUuid++; this.eventNamespace = "." + this.widgetName + this.uuid; this.bindings = $(); this.hoverable = $(); this.focusable = $(); this.classesElementLookup = {}; if ( element !== this ) { $.data( element, this.widgetFullName, this ); this._on( true, this.element, { remove: function( event ) { if ( event.target === element ) { this.destroy(); } } } ); this.document = $( element.style ? // Element within the document element.ownerDocument : // Element is window or document element.document || element ); this.window = $( this.document[ 0 ].defaultView || this.document[ 0 ].parentWindow ); } this.options = $.widget.extend( {}, this.options, this._getCreateOptions(), options ); this._create(); if ( this.options.disabled ) { this._setOptionDisabled( this.options.disabled ); } this._trigger( "create", null, this._getCreateEventData() ); this._init(); }, _getCreateOptions: function() { return {}; }, _getCreateEventData: $.noop, _create: $.noop, _init: $.noop, destroy: function() { var that = this; this._destroy(); $.each( this.classesElementLookup, function( key, value ) { that._removeClass( value, key ); } ); // We can probably remove the unbind calls in 2.0 // all event bindings should go through this._on() this.element .off( this.eventNamespace ) .removeData( this.widgetFullName ); this.widget() .off( this.eventNamespace ) .removeAttr( "aria-disabled" ); // Clean up events and states this.bindings.off( this.eventNamespace ); }, _destroy: $.noop, widget: function() { return this.element; }, option: function( key, value ) { var options = key; var parts; var curOption; var i; if ( arguments.length === 0 ) { // Don't return a reference to the internal hash return $.widget.extend( {}, this.options ); } if ( typeof key === "string" ) { // Handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } } options = {}; parts = key.split( "." ); key = parts.shift(); if ( parts.length ) { curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] ); for ( i = 0; i < parts.length - 1; i++ ) { curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {}; curOption = curOption[ parts[ i ] ]; } key = parts.pop(); if ( arguments.length === 1 ) { return curOption[ key ] === undefined ? null : curOption[ key ]; } curOption[ key ] = value; } else { if ( arguments.length === 1 ) { return this.options[ key ] === undefined ? null : this.options[ key ]; } options[ key ] = value; } } this._setOptions( options ); return this; }, _setOptions: function( options ) { var key; for ( key in options ) { this._setOption( key, options[ key ] ); } return this; }, _setOption: function( key, value ) { if ( key === "classes" ) { this._setOptionClasses( value ); } this.options[ key ] = value; if ( key === "disabled" ) { this._setOptionDisabled( value ); } return this; }, _setOptionClasses: function( value ) { var classKey, elements, currentElements; for ( classKey in value ) { currentElements = this.classesElementLookup[ classKey ]; if ( value[ classKey ] === this.options.classes[ classKey ] || !currentElements || !currentElements.length ) { continue; } // We are doing this to create a new jQuery object because the _removeClass() call // on the next line is going to destroy the reference to the current elements being // tracked. We need to save a copy of this collection so that we can add the new classes // below. elements = $( currentElements.get() ); this._removeClass( currentElements, classKey ); // We don't use _addClass() here, because that uses this.options.classes // for generating the string of classes. We want to use the value passed in from // _setOption(), this is the new value of the classes option which was passed to // _setOption(). We pass this value directly to _classes(). elements.addClass( this._classes( { element: elements, keys: classKey, classes: value, add: true } ) ); } }, _setOptionDisabled: function( value ) { this._toggleClass( this.widget(), this.widgetFullName + "-disabled", null, !!value ); // If the widget is becoming disabled, then nothing is interactive if ( value ) { this._removeClass( this.hoverable, null, "ui-state-hover" ); this._removeClass( this.focusable, null, "ui-state-focus" ); } }, enable: function() { return this._setOptions( { disabled: false } ); }, disable: function() { return this._setOptions( { disabled: true } ); }, _classes: function( options ) { var full = []; var that = this; options = $.extend( { element: this.element, classes: this.options.classes || {} }, options ); function processClassString( classes, checkOption ) { var current, i; for ( i = 0; i < classes.length; i++ ) { current = that.classesElementLookup[ classes[ i ] ] || $(); if ( options.add ) { current = $( $.unique( current.get().concat( options.element.get() ) ) ); } else { current = $( current.not( options.element ).get() ); } that.classesElementLookup[ classes[ i ] ] = current; full.push( classes[ i ] ); if ( checkOption && options.classes[ classes[ i ] ] ) { full.push( options.classes[ classes[ i ] ] ); } } } this._on( options.element, { "remove": "_untrackClassesElement" } ); if ( options.keys ) { processClassString( options.keys.match( /\S+/g ) || [], true ); } if ( options.extra ) { processClassString( options.extra.match( /\S+/g ) || [] ); } return full.join( " " ); }, _untrackClassesElement: function( event ) { var that = this; $.each( that.classesElementLookup, function( key, value ) { if ( $.inArray( event.target, value ) !== -1 ) { that.classesElementLookup[ key ] = $( value.not( event.target ).get() ); } } ); }, _removeClass: function( element, keys, extra ) { return this._toggleClass( element, keys, extra, false ); }, _addClass: function( element, keys, extra ) { return this._toggleClass( element, keys, extra, true ); }, _toggleClass: function( element, keys, extra, add ) { add = ( typeof add === "boolean" ) ? add : extra; var shift = ( typeof element === "string" || element === null ), options = { extra: shift ? keys : extra, keys: shift ? element : keys, element: shift ? this.element : element, add: add }; options.element.toggleClass( this._classes( options ), add ); return this; }, _on: function( suppressDisabledCheck, element, handlers ) { var delegateElement; var instance = this; // No suppressDisabledCheck flag, shuffle arguments if ( typeof suppressDisabledCheck !== "boolean" ) { handlers = element; element = suppressDisabledCheck; suppressDisabledCheck = false; } // No element argument, shuffle and use this.element if ( !handlers ) { handlers = element; element = this.element; delegateElement = this.widget(); } else { element = delegateElement = $( element ); this.bindings = this.bindings.add( element ); } $.each( handlers, function( event, handler ) { function handlerProxy() { // Allow widgets to customize the disabled handling // - disabled as an array instead of boolean // - disabled class as method for disabling individual parts if ( !suppressDisabledCheck && ( instance.options.disabled === true || $( this ).hasClass( "ui-state-disabled" ) ) ) { return; } return ( typeof handler === "string" ? instance[ handler ] : handler ) .apply( instance, arguments ); } // Copy the guid so direct unbinding works if ( typeof handler !== "string" ) { handlerProxy.guid = handler.guid = handler.guid || handlerProxy.guid || $.guid++; } var match = event.match( /^([\w:-]*)\s*(.*)$/ ); var eventName = match[ 1 ] + instance.eventNamespace; var selector = match[ 2 ]; if ( selector ) { delegateElement.on( eventName, selector, handlerProxy ); } else { element.on( eventName, handlerProxy ); } } ); }, _off: function( element, eventName ) { eventName = ( eventName || "" ).split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace; element.off( eventName ).off( eventName ); // Clear the stack to avoid memory leaks (#10056) this.bindings = $( this.bindings.not( element ).get() ); this.focusable = $( this.focusable.not( element ).get() ); this.hoverable = $( this.hoverable.not( element ).get() ); }, _delay: function( handler, delay ) { function handlerProxy() { return ( typeof handler === "string" ? instance[ handler ] : handler ) .apply( instance, arguments ); } var instance = this; return setTimeout( handlerProxy, delay || 0 ); }, _hoverable: function( element ) { this.hoverable = this.hoverable.add( element ); this._on( element, { mouseenter: function( event ) { this._addClass( $( event.currentTarget ), null, "ui-state-hover" ); }, mouseleave: function( event ) { this._removeClass( $( event.currentTarget ), null, "ui-state-hover" ); } } ); }, _focusable: function( element ) { this.focusable = this.focusable.add( element ); this._on( element, { focusin: function( event ) { this._addClass( $( event.currentTarget ), null, "ui-state-focus" ); }, focusout: function( event ) { this._removeClass( $( event.currentTarget ), null, "ui-state-focus" ); } } ); }, _trigger: function( type, event, data ) { var prop, orig; var callback = this.options[ type ]; data = data || {}; event = $.Event( event ); event.type = ( type === this.widgetEventPrefix ? type : this.widgetEventPrefix + type ).toLowerCase(); // The original event may come from any element // so we need to reset the target on the new event event.target = this.element[ 0 ]; // Copy original event properties over to the new event orig = event.originalEvent; if ( orig ) { for ( prop in orig ) { if ( !( prop in event ) ) { event[ prop ] = orig[ prop ]; } } } this.element.trigger( event, data ); return !( $.isFunction( callback ) && callback.apply( this.element[ 0 ], [ event ].concat( data ) ) === false || event.isDefaultPrevented() ); } }; $.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { $.Widget.prototype[ "_" + method ] = function( element, options, callback ) { if ( typeof options === "string" ) { options = { effect: options }; } var hasOptions; var effectName = !options ? method : options === true || typeof options === "number" ? defaultEffect : options.effect || defaultEffect; options = options || {}; if ( typeof options === "number" ) { options = { duration: options }; } hasOptions = !$.isEmptyObject( options ); options.complete = callback; if ( options.delay ) { element.delay( options.delay ); } if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) { element[ method ]( options ); } else if ( effectName !== method && element[ effectName ] ) { element[ effectName ]( options.duration, options.easing, callback ); } else { element.queue( function( next ) { $( this )[ method ](); if ( callback ) { callback.call( element[ 0 ] ); } next(); } ); } }; } ); var widget = $.widget; /*! * jQuery UI :data 1.12.1 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license */ //>>label: :data Selector //>>group: Core //>>description: Selects elements which have data stored under the specified key. //>>docs: http://api.jqueryui.com/data-selector/ var data = $.extend( $.expr[ ":" ], { data: $.expr.createPseudo ? $.expr.createPseudo( function( dataName ) { return function( elem ) { return !!$.data( elem, dataName ); }; } ) : // Support: jQuery <1.8 function( elem, i, match ) { return !!$.data( elem, match[ 3 ] ); } } ); /*! * jQuery UI Scroll Parent 1.12.1 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license */ //>>label: scrollParent //>>group: Core //>>description: Get the closest ancestor element that is scrollable. //>>docs: http://api.jqueryui.com/scrollParent/ var scrollParent = $.fn.scrollParent = function( includeHidden ) { var position = this.css( "position" ), excludeStaticParent = position === "absolute", overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/, scrollParent = this.parents().filter( function() { var parent = $( this ); if ( excludeStaticParent && parent.css( "position" ) === "static" ) { return false; } return overflowRegex.test( parent.css( "overflow" ) + parent.css( "overflow-y" ) + parent.css( "overflow-x" ) ); } ).eq( 0 ); return position === "fixed" || !scrollParent.length ? $( this[ 0 ].ownerDocument || document ) : scrollParent; }; // This file is deprecated var ie = $.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() ); /*! * jQuery UI Mouse 1.12.1 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license */ //>>label: Mouse //>>group: Widgets //>>description: Abstracts mouse-based interactions to assist in creating certain widgets. //>>docs: http://api.jqueryui.com/mouse/ var mouseHandled = false; $( document ).on( "mouseup", function() { mouseHandled = false; } ); var widgetsMouse = $.widget( "ui.mouse", { version: "1.12.1", options: { cancel: "input, textarea, button, select, option", distance: 1, delay: 0 }, _mouseInit: function() { var that = this; this.element .on( "mousedown." + this.widgetName, function( event ) { return that._mouseDown( event ); } ) .on( "click." + this.widgetName, function( event ) { if ( true === $.data( event.target, that.widgetName + ".preventClickEvent" ) ) { $.removeData( event.target, that.widgetName + ".preventClickEvent" ); event.stopImmediatePropagation(); return false; } } ); this.started = false; }, // TODO: make sure destroying one instance of mouse doesn't mess with // other instances of mouse _mouseDestroy: function() { this.element.off( "." + this.widgetName ); if ( this._mouseMoveDelegate ) { this.document .off( "mousemove." + this.widgetName, this._mouseMoveDelegate ) .off( "mouseup." + this.widgetName, this._mouseUpDelegate ); } }, _mouseDown: function( event ) { // don't let more than one widget handle mouseStart if ( mouseHandled ) { return; } this._mouseMoved = false; // We may have missed mouseup (out of window) ( this._mouseStarted && this._mouseUp( event ) ); this._mouseDownEvent = event; var that = this, btnIsLeft = ( event.which === 1 ), // event.target.nodeName works around a bug in IE 8 with // disabled inputs (#7620) elIsCancel = ( typeof this.options.cancel === "string" && event.target.nodeName ? $( event.target ).closest( this.options.cancel ).length : false ); if ( !btnIsLeft || elIsCancel || !this._mouseCapture( event ) ) { return true; } this.mouseDelayMet = !this.options.delay; if ( !this.mouseDelayMet ) { this._mouseDelayTimer = setTimeout( function() { that.mouseDelayMet = true; }, this.options.delay ); } if ( this._mouseDistanceMet( event ) && this._mouseDelayMet( event ) ) { this._mouseStarted = ( this._mouseStart( event ) !== false ); if ( !this._mouseStarted ) { event.preventDefault(); return true; } } // Click event may never have fired (Gecko & Opera) if ( true === $.data( event.target, this.widgetName + ".preventClickEvent" ) ) { $.removeData( event.target, this.widgetName + ".preventClickEvent" ); } // These delegates are required to keep context this._mouseMoveDelegate = function( event ) { return that._mouseMove( event ); }; this._mouseUpDelegate = function( event ) { return that._mouseUp( event ); }; this.document .on( "mousemove." + this.widgetName, this._mouseMoveDelegate ) .on( "mouseup." + this.widgetName, this._mouseUpDelegate ); event.preventDefault(); mouseHandled = true; return true; }, _mouseMove: function( event ) { // Only check for mouseups outside the document if you've moved inside the document // at least once. This prevents the firing of mouseup in the case of IE<9, which will // fire a mousemove event if content is placed under the cursor. See #7778 // Support: IE <9 if ( this._mouseMoved ) { // IE mouseup check - mouseup happened when mouse was out of window if ( $.ui.ie && ( !document.documentMode || document.documentMode < 9 ) && !event.button ) { return this._mouseUp( event ); // Iframe mouseup check - mouseup occurred in another document } else if ( !event.which ) { // Support: Safari <=8 - 9 // Safari sets which to 0 if you press any of the following keys // during a drag (#14461) if ( event.originalEvent.altKey || event.originalEvent.ctrlKey || event.originalEvent.metaKey || event.originalEvent.shiftKey ) { this.ignoreMissingWhich = true; } else if ( !this.ignoreMissingWhich ) { return this._mouseUp( event ); } } } if ( event.which || event.button ) { this._mouseMoved = true; } if ( this._mouseStarted ) { this._mouseDrag( event ); return event.preventDefault(); } if ( this._mouseDistanceMet( event ) && this._mouseDelayMet( event ) ) { this._mouseStarted = ( this._mouseStart( this._mouseDownEvent, event ) !== false ); ( this._mouseStarted ? this._mouseDrag( event ) : this._mouseUp( event ) ); } return !this._mouseStarted; }, _mouseUp: function( event ) { this.document .off( "mousemove." + this.widgetName, this._mouseMoveDelegate ) .off( "mouseup." + this.widgetName, this._mouseUpDelegate ); if ( this._mouseStarted ) { this._mouseStarted = false; if ( event.target === this._mouseDownEvent.target ) { $.data( event.target, this.widgetName + ".preventClickEvent", true ); } this._mouseStop( event ); } if ( this._mouseDelayTimer ) { clearTimeout( this._mouseDelayTimer ); delete this._mouseDelayTimer; } this.ignoreMissingWhich = false; mouseHandled = false; event.preventDefault(); }, _mouseDistanceMet: function( event ) { return ( Math.max( Math.abs( this._mouseDownEvent.pageX - event.pageX ), Math.abs( this._mouseDownEvent.pageY - event.pageY ) ) >= this.options.distance ); }, _mouseDelayMet: function( /* event */ ) { return this.mouseDelayMet; }, // These are placeholder methods, to be overriden by extending plugin _mouseStart: function( /* event */ ) {}, _mouseDrag: function( /* event */ ) {}, _mouseStop: function( /* event */ ) {}, _mouseCapture: function( /* event */ ) { return true; } } ); /*! * jQuery UI Sortable 1.12.1 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license */ //>>label: Sortable //>>group: Interactions //>>description: Enables items in a list to be sorted using the mouse. //>>docs: http://api.jqueryui.com/sortable/ //>>demos: http://jqueryui.com/sortable/ //>>css.structure: ../../themes/base/sortable.css var widgetsSortable = $.widget( "ui.sortable", $.ui.mouse, { version: "1.12.1", widgetEventPrefix: "sort", ready: false, options: { appendTo: "parent", axis: false, connectWith: false, containment: false, cursor: "auto", cursorAt: false, dropOnEmpty: true, forcePlaceholderSize: false, forceHelperSize: false, grid: false, handle: false, helper: "original", items: "> *", opacity: false, placeholder: false, revert: false, scroll: true, scrollSensitivity: 20, scrollSpeed: 20, scope: "default", tolerance: "intersect", zIndex: 1000, // Callbacks activate: null, beforeStop: null, change: null, deactivate: null, out: null, over: null, receive: null, remove: null, sort: null, start: null, stop: null, update: null }, _isOverAxis: function( x, reference, size ) { return ( x >= reference ) && ( x < ( reference + size ) ); }, _isFloating: function( item ) { return ( /left|right/ ).test( item.css( "float" ) ) || ( /inline|table-cell/ ).test( item.css( "display" ) ); }, _create: function() { this.containerCache = {}; this._addClass( "ui-sortable" ); //Get the items this.refresh(); //Let's determine the parent's offset this.offset = this.element.offset(); //Initialize mouse events for interaction this._mouseInit(); this._setHandleClassName(); //We're ready to go this.ready = true; }, _setOption: function( key, value ) { this._super( key, value ); if ( key === "handle" ) { this._setHandleClassName(); } }, _setHandleClassName: function() { var that = this; this._removeClass( this.element.find( ".ui-sortable-handle" ), "ui-sortable-handle" ); $.each( this.items, function() { that._addClass( this.instance.options.handle ? this.item.find( this.instance.options.handle ) : this.item, "ui-sortable-handle" ); } ); }, _destroy: function() { this._mouseDestroy(); for ( var i = this.items.length - 1; i >= 0; i-- ) { this.items[ i ].item.removeData( this.widgetName + "-item" ); } return this; }, _mouseCapture: function( event, overrideHandle ) { var currentItem = null, validHandle = false, that = this; if ( this.reverting ) { return false; } if ( this.options.disabled || this.options.type === "static" ) { return false; } //We have to refresh the items data once first this._refreshItems( event ); //Find out if the clicked node (or one of its parents) is a actual item in this.items $( event.target ).parents().each( function() { if ( $.data( this, that.widgetName + "-item" ) === that ) { currentItem = $( this ); return false; } } ); if ( $.data( event.target, that.widgetName + "-item" ) === that ) { currentItem = $( event.target ); } if ( !currentItem ) { return false; } if ( this.options.handle && !overrideHandle ) { $( this.options.handle, currentItem ).find( "*" ).addBack().each( function() { if ( this === event.target ) { validHandle = true; } } ); if ( !validHandle ) { return false; } } this.currentItem = currentItem; this._removeCurrentsFromItems(); return true; }, _mouseStart: function( event, overrideHandle, noActivation ) { var i, body, o = this.options; this.currentContainer = this; //We only need to call refreshPositions, because the refreshItems call has been moved to // mouseCapture this.refreshPositions(); //Create and append the visible helper this.helper = this._createHelper( event ); //Cache the helper size this._cacheHelperProportions(); /* * - Position generation - * This block generates everything position related - it's the core of draggables. */ //Cache the margins of the original element this._cacheMargins(); //Get the next scrolling parent this.scrollParent = this.helper.scrollParent(); //The element's absolute position on the page minus margins this.offset = this.currentItem.offset(); this.offset = { top: this.offset.top - this.margins.top, left: this.offset.left - this.margins.left }; $.extend( this.offset, { click: { //Where the click happened, relative to the element left: event.pageX - this.offset.left, top: event.pageY - this.offset.top }, parent: this._getParentOffset(), // This is a relative to absolute position minus the actual position calculation - // only used for relative positioned helper relative: this._getRelativeOffset() } ); // Only after we got the offset, we can change the helper's position to absolute // TODO: Still need to figure out a way to make relative sorting possible this.helper.css( "position", "absolute" ); this.cssPosition = this.helper.css( "position" ); //Generate the original position this.originalPosition = this._generatePosition( event ); this.originalPageX = event.pageX; this.originalPageY = event.pageY; //Adjust the mouse offset relative to the helper if "cursorAt" is supplied ( o.cursorAt && this._adjustOffsetFromHelper( o.cursorAt ) ); //Cache the former DOM position this.domPosition = { prev: this.currentItem.prev()[ 0 ], parent: this.currentItem.parent()[ 0 ] }; // If the helper is not the original, hide the original so it's not playing any role during // the drag, won't cause anything bad this way if ( this.helper[ 0 ] !== this.currentItem[ 0 ] ) { this.currentItem.hide(); } //Create the placeholder this._createPlaceholder(); //Set a containment if given in the options if ( o.containment ) { this._setContainment(); } if ( o.cursor && o.cursor !== "auto" ) { // cursor option body = this.document.find( "body" ); // Support: IE this.storedCursor = body.css( "cursor" ); body.css( "cursor", o.cursor ); this.storedStylesheet = $( "<style>*{ cursor: " + o.cursor + " !important; }</style>" ).appendTo( body ); } if ( o.opacity ) { // opacity option if ( this.helper.css( "opacity" ) ) { this._storedOpacity = this.helper.css( "opacity" ); } this.helper.css( "opacity", o.opacity ); } if ( o.zIndex ) { // zIndex option if ( this.helper.css( "zIndex" ) ) { this._storedZIndex = this.helper.css( "zIndex" ); } this.helper.css( "zIndex", o.zIndex ); } //Prepare scrolling if ( this.scrollParent[ 0 ] !== this.document[ 0 ] && this.scrollParent[ 0 ].tagName !== "HTML" ) { this.overflowOffset = this.scrollParent.offset(); } //Call callbacks this._trigger( "start", event, this._uiHash() ); //Recache the helper size if ( !this._preserveHelperProportions ) { this._cacheHelperProportions(); } //Post "activate" events to possible containers if ( !noActivation ) { for ( i = this.containers.length - 1; i >= 0; i-- ) { this.containers[ i ]._trigger( "activate", event, this._uiHash( this ) ); } } //Prepare possible droppables if ( $.ui.ddmanager ) { $.ui.ddmanager.current = this; } if ( $.ui.ddmanager && !o.dropBehaviour ) { $.ui.ddmanager.prepareOffsets( this, event ); } this.dragging = true; this._addClass( this.helper, "ui-sortable-helper" ); // Execute the drag once - this causes the helper not to be visiblebefore getting its // correct position this._mouseDrag( event ); return true; }, _mouseDrag: function( event ) { var i, item, itemElement, intersection, o = this.options, scrolled = false; //Compute the helpers position this.position = this._generatePosition( event ); this.positionAbs = this._convertPositionTo( "absolute" ); if ( !this.lastPositionAbs ) { this.lastPositionAbs = this.positionAbs; } //Do scrolling if ( this.options.scroll ) { if ( this.scrollParent[ 0 ] !== this.document[ 0 ] && this.scrollParent[ 0 ].tagName !== "HTML" ) { if ( ( this.overflowOffset.top + this.scrollParent[ 0 ].offsetHeight ) - event.pageY < o.scrollSensitivity ) { this.scrollParent[ 0 ].scrollTop = scrolled = this.scrollParent[ 0 ].scrollTop + o.scrollSpeed; } else if ( event.pageY - this.overflowOffset.top < o.scrollSensitivity ) { this.scrollParent[ 0 ].scrollTop = scrolled = this.scrollParent[ 0 ].scrollTop - o.scrollSpeed; } if ( ( this.overflowOffset.left + this.scrollParent[ 0 ].offsetWidth ) - event.pageX < o.scrollSensitivity ) { this.scrollParent[ 0 ].scrollLeft = scrolled = this.scrollParent[ 0 ].scrollLeft + o.scrollSpeed; } else if ( event.pageX - this.overflowOffset.left < o.scrollSensitivity ) { this.scrollParent[ 0 ].scrollLeft = scrolled = this.scrollParent[ 0 ].scrollLeft - o.scrollSpeed; } } else { if ( event.pageY - this.document.scrollTop() < o.scrollSensitivity ) { scrolled = this.document.scrollTop( this.document.scrollTop() - o.scrollSpeed ); } else if ( this.window.height() - ( event.pageY - this.document.scrollTop() ) < o.scrollSensitivity ) { scrolled = this.document.scrollTop( this.document.scrollTop() + o.scrollSpeed ); } if ( event.pageX - this.document.scrollLeft() < o.scrollSensitivity ) { scrolled = this.document.scrollLeft( this.document.scrollLeft() - o.scrollSpeed ); } else if ( this.window.width() - ( event.pageX - this.document.scrollLeft() ) < o.scrollSensitivity ) { scrolled = this.document.scrollLeft( this.document.scrollLeft() + o.scrollSpeed ); } } if ( scrolled !== false && $.ui.ddmanager && !o.dropBehaviour ) { $.ui.ddmanager.prepareOffsets( this, event ); } } //Regenerate the absolute position used for position checks this.positionAbs = this._convertPositionTo( "absolute" ); //Set the helper position if ( !this.options.axis || this.options.axis !== "y" ) { this.helper[ 0 ].style.left = this.position.left + "px"; } if ( !this.options.axis || this.options.axis !== "x" ) { this.helper[ 0 ].style.top = this.position.top + "px"; } //Rearrange for ( i = this.items.length - 1; i >= 0; i-- ) { //Cache variables and intersection, continue if no intersection item = this.items[ i ]; itemElement = item.item[ 0 ]; intersection = this._intersectsWithPointer( item ); if ( !intersection ) { continue; } // Only put the placeholder inside the current Container, skip all // items from other containers. This works because when moving // an item from one container to another the // currentContainer is switched before the placeholder is moved. // // Without this, moving items in "sub-sortables" can cause // the placeholder to jitter between the outer and inner container. if ( item.instance !== this.currentContainer ) { continue; } // Cannot intersect with itself // no useless actions that have been done before // no action if the item moved is the parent of the item checked if ( itemElement !== this.currentItem[ 0 ] && this.placeholder[ intersection === 1 ? "next" : "prev" ]()[ 0 ] !== itemElement && !$.contains( this.placeholder[ 0 ], itemElement ) && ( this.options.type === "semi-dynamic" ? !$.contains( this.element[ 0 ], itemElement ) : true ) ) { this.direction = intersection === 1 ? "down" : "up"; if ( this.options.tolerance === "pointer" || this._intersectsWithSides( item ) ) { this._rearrange( event, item ); } else { break; } this._trigger( "change", event, this._uiHash() ); break; } } //Post events to containers this._contactContainers( event ); //Interconnect with droppables if ( $.ui.ddmanager ) { $.ui.ddmanager.drag( this, event ); } //Call callbacks this._trigger( "sort", event, this._uiHash() ); this.lastPositionAbs = this.positionAbs; return false; }, _mouseStop: function( event, noPropagation ) { if ( !event ) { return; } //If we are using droppables, inform the manager about the drop if ( $.ui.ddmanager && !this.options.dropBehaviour ) { $.ui.ddmanager.drop( this, event ); } if ( this.options.revert ) { var that = this, cur = this.placeholder.offset(), axis = this.options.axis, animation = {}; if ( !axis || axis === "x" ) { animation.left = cur.left - this.offset.parent.left - this.margins.left + ( this.offsetParent[ 0 ] === this.document[ 0 ].body ? 0 : this.offsetParent[ 0 ].scrollLeft ); } if ( !axis || axis === "y" ) { animation.top = cur.top - this.offset.parent.top - this.margins.top + ( this.offsetParent[ 0 ] === this.document[ 0 ].body ? 0 : this.offsetParent[ 0 ].scrollTop ); } this.reverting = true; $( this.helper ).animate( animation, parseInt( this.options.revert, 10 ) || 500, function() { that._clear( event ); } ); } else { this._clear( event, noPropagation ); } return false; }, cancel: function() { if ( this.dragging ) { this._mouseUp( new $.Event( "mouseup", { target: null } ) ); if ( this.options.helper === "original" ) { this.currentItem.css( this._storedCSS ); this._removeClass( this.currentItem, "ui-sortable-helper" ); } else { this.currentItem.show(); } //Post deactivating events to containers for ( var i = this.containers.length - 1; i >= 0; i-- ) { this.containers[ i ]._trigger( "deactivate", null, this._uiHash( this ) ); if ( this.containers[ i ].containerCache.over ) { this.containers[ i ]._trigger( "out", null, this._uiHash( this ) ); this.containers[ i ].containerCache.over = 0; } } } if ( this.placeholder ) { //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, // it unbinds ALL events from the original node! if ( this.placeholder[ 0 ].parentNode ) { this.placeholder[ 0 ].parentNode.removeChild( this.placeholder[ 0 ] ); } if ( this.options.helper !== "original" && this.helper && this.helper[ 0 ].parentNode ) { this.helper.remove(); } $.extend( this, { helper: null, dragging: false, reverting: false, _noFinalSort: null } ); if ( this.domPosition.prev ) { $( this.domPosition.prev ).after( this.currentItem ); } else { $( this.domPosition.parent ).prepend( this.currentItem ); } } return this; }, serialize: function( o ) { var items = this._getItemsAsjQuery( o && o.connected ), str = []; o = o || {}; $( items ).each( function() { var res = ( $( o.item || this ).attr( o.attribute || "id" ) || "" ) .match( o.expression || ( /(.+)[\-=_](.+)/ ) ); if ( res ) { str.push( ( o.key || res[ 1 ] + "[]" ) + "=" + ( o.key && o.expression ? res[ 1 ] : res[ 2 ] ) ); } } ); if ( !str.length && o.key ) { str.push( o.key + "=" ); } return str.join( "&" ); }, toArray: function( o ) { var items = this._getItemsAsjQuery( o && o.connected ), ret = []; o = o || {}; items.each( function() { ret.push( $( o.item || this ).attr( o.attribute || "id" ) || "" ); } ); return ret; }, /* Be careful with the following core functions */ _intersectsWith: function( item ) { var x1 = this.positionAbs.left, x2 = x1 + this.helperProportions.width, y1 = this.positionAbs.top, y2 = y1 + this.helperProportions.height, l = item.left, r = l + item.width, t = item.top, b = t + item.height, dyClick = this.offset.click.top, dxClick = this.offset.click.left, isOverElementHeight = ( this.options.axis === "x" ) || ( ( y1 + dyClick ) > t && ( y1 + dyClick ) < b ), isOverElementWidth = ( this.options.axis === "y" ) || ( ( x1 + dxClick ) > l && ( x1 + dxClick ) < r ), isOverElement = isOverElementHeight && isOverElementWidth; if ( this.options.tolerance === "pointer" || this.options.forcePointerForContainers || ( this.options.tolerance !== "pointer" && this.helperProportions[ this.floating ? "width" : "height" ] > item[ this.floating ? "width" : "height" ] ) ) { return isOverElement; } else { return ( l < x1 + ( this.helperProportions.width / 2 ) && // Right Half x2 - ( this.helperProportions.width / 2 ) < r && // Left Half t < y1 + ( this.helperProportions.height / 2 ) && // Bottom Half y2 - ( this.helperProportions.height / 2 ) < b ); // Top Half } }, _intersectsWithPointer: function( item ) { var verticalDirection, horizontalDirection, isOverElementHeight = ( this.options.axis === "x" ) || this._isOverAxis( this.positionAbs.top + this.offset.click.top, item.top, item.height ), isOverElementWidth = ( this.options.axis === "y" ) || this._isOverAxis( this.positionAbs.left + this.offset.click.left, item.left, item.width ), isOverElement = isOverElementHeight && isOverElementWidth; if ( !isOverElement ) { return false; } verticalDirection = this._getDragVerticalDirection(); horizontalDirection = this._getDragHorizontalDirection(); return this.floating ? ( ( horizontalDirection === "right" || verticalDirection === "down" ) ? 2 : 1 ) : ( verticalDirection && ( verticalDirection === "down" ? 2 : 1 ) ); }, _intersectsWithSides: function( item ) { var isOverBottomHalf = this._isOverAxis( this.positionAbs.top + this.offset.click.top, item.top + ( item.height / 2 ), item.height ), isOverRightHalf = this._isOverAxis( this.positionAbs.left + this.offset.click.left, item.left + ( item.width / 2 ), item.width ), verticalDirection = this._getDragVerticalDirection(), horizontalDirection = this._getDragHorizontalDirection(); if ( this.floating && horizontalDirection ) { return ( ( horizontalDirection === "right" && isOverRightHalf ) || ( horizontalDirection === "left" && !isOverRightHalf ) ); } else { return verticalDirection && ( ( verticalDirection === "down" && isOverBottomHalf ) || ( verticalDirection === "up" && !isOverBottomHalf ) ); } }, _getDragVerticalDirection: function() { var delta = this.positionAbs.top - this.lastPositionAbs.top; return delta !== 0 && ( delta > 0 ? "down" : "up" ); }, _getDragHorizontalDirection: function() { var delta = this.positionAbs.left - this.lastPositionAbs.left; return delta !== 0 && ( delta > 0 ? "right" : "left" ); }, refresh: function( event ) { this._refreshItems( event ); this._setHandleClassName(); this.refreshPositions(); return this; }, _connectWith: function() { var options = this.options; return options.connectWith.constructor === String ? [ options.connectWith ] : options.connectWith; }, _getItemsAsjQuery: function( connected ) { var i, j, cur, inst, items = [], queries = [], connectWith = this._connectWith(); if ( connectWith && connected ) { for ( i = connectWith.length - 1; i >= 0; i-- ) { cur = $( connectWith[ i ], this.document[ 0 ] ); for ( j = cur.length - 1; j >= 0; j-- ) { inst = $.data( cur[ j ], this.widgetFullName ); if ( inst && inst !== this && !inst.options.disabled ) { queries.push( [ $.isFunction( inst.options.items ) ? inst.options.items.call( inst.element ) : $( inst.options.items, inst.element ) .not( ".ui-sortable-helper" ) .not( ".ui-sortable-placeholder" ), inst ] ); } } } } queries.push( [ $.isFunction( this.options.items ) ? this.options.items .call( this.element, null, { options: this.options, item: this.currentItem } ) : $( this.options.items, this.element ) .not( ".ui-sortable-helper" ) .not( ".ui-sortable-placeholder" ), this ] ); function addItems() { items.push( this ); } for ( i = queries.length - 1; i >= 0; i-- ) { queries[ i ][ 0 ].each( addItems ); } return $( items ); }, _removeCurrentsFromItems: function() { var list = this.currentItem.find( ":data(" + this.widgetName + "-item)" ); this.items = $.grep( this.items, function( item ) { for ( var j = 0; j < list.length; j++ ) { if ( list[ j ] === item.item[ 0 ] ) { return false; } } return true; } ); }, _refreshItems: function( event ) { this.items = []; this.containers = [ this ]; var i, j, cur, inst, targetData, _queries, item, queriesLength, items = this.items, queries = [ [ $.isFunction( this.options.items ) ? this.options.items.call( this.element[ 0 ], event, { item: this.currentItem } ) : $( this.options.items, this.element ), this ] ], connectWith = this._connectWith(); //Shouldn't be run the first time through due to massive slow-down if ( connectWith && this.ready ) { for ( i = connectWith.length - 1; i >= 0; i-- ) { cur = $( connectWith[ i ], this.document[ 0 ] ); for ( j = cur.length - 1; j >= 0; j-- ) { inst = $.data( cur[ j ], this.widgetFullName ); if ( inst && inst !== this && !inst.options.disabled ) { queries.push( [ $.isFunction( inst.options.items ) ? inst.options.items .call( inst.element[ 0 ], event, { item: this.currentItem } ) : $( inst.options.items, inst.element ), inst ] ); this.containers.push( inst ); } } } } for ( i = queries.length - 1; i >= 0; i-- ) { targetData = queries[ i ][ 1 ]; _queries = queries[ i ][ 0 ]; for ( j = 0, queriesLength = _queries.length; j < queriesLength; j++ ) { item = $( _queries[ j ] ); // Data for target checking (mouse manager) item.data( this.widgetName + "-item", targetData ); items.push( { item: item, instance: targetData, width: 0, height: 0, left: 0, top: 0 } ); } } }, refreshPositions: function( fast ) { // Determine whether items are being displayed horizontally this.floating = this.items.length ? this.options.axis === "x" || this._isFloating( this.items[ 0 ].item ) : false; //This has to be redone because due to the item being moved out/int