/*! * jQuery UI Sortable - v1.10.4 * http://jqueryui.com * * Copyright 2014 jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license * * http://api.jqueryui.com/sortable/ */ define([ 'jquery', 'jquery-ui-modules/core', 'jquery-ui-modules/mouse' ], function ($, undefined) { function isOverAxis(x, reference, size) { return (x > reference) && (x < (reference + size)); } function isFloating(item) { return (/left|right/).test(item.css("float")) || (/inline|table-cell/).test(item.css("display")); } $.widget("ui.sortable", $.ui.mouse, { version: "1.10.4", 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 }, _create: function () { var o = this.options; this.containerCache = {}; this.element.addClass("ui-sortable"); //Get the items this.refresh(); //Let's determine if the items are being displayed horizontally this.floating = this.items.length ? o.axis === "x" || isFloating(this.items[0].item) : false; //Let's determine the parent's offset this.offset = this.element.offset(); //Initialize mouse events for interaction this._mouseInit(); //We're ready to go this.ready = true; }, _destroy: function () { this.element .removeClass("ui-sortable ui-sortable-disabled"); this._mouseDestroy(); for (var i = this.items.length - 1; i >= 0; i--) { this.items[i].item.removeData(this.widgetName + "-item"); } return this; }, _setOption: function (key, value) { if (key === "disabled") { this.options[key] = value; this.widget().toggleClass("ui-sortable-disabled", !!value); } else { // Don't call widget base _setOption for disable as it adds ui-state-disabled class $.Widget.prototype._setOption.apply(this, arguments); } }, _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(), relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper }); // 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 = $("").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] !== document && 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.helper.addClass("ui-sortable-helper"); this._mouseDrag(event); //Execute the drag once - this causes the helper not to be visible before getting its correct position 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] !== document && 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 - $(document).scrollTop() < o.scrollSensitivity) { scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed); } else if ($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) { scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed); } if (event.pageX - $(document).scrollLeft() < o.scrollSensitivity) { scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed); } else if ($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) { scrolled = $(document).scrollLeft($(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 beetween 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] === document.body ? 0 : this.offsetParent[0].scrollLeft); } if (!axis || axis === "y") { animation.top = cur.top - this.offset.parent.top - this.margins.top + (this.offsetParent[0] === document.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({target: null}); if (this.options.helper === "original") { this.currentItem.css(this._storedCSS).removeClass("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 isOverElementHeight = (this.options.axis === "x") || isOverAxis(this.positionAbs.top + this.offset.click.top, item.top, item.height), isOverElementWidth = (this.options.axis === "y") || isOverAxis(this.positionAbs.left + this.offset.click.left, item.left, item.width), isOverElement = isOverElementHeight && isOverElementWidth, verticalDirection = this._getDragVerticalDirection(), horizontalDirection = this._getDragHorizontalDirection(); if (!isOverElement) { return false; } return this.floating ? (((horizontalDirection && horizontalDirection === "right") || verticalDirection === "down") ? 2 : 1) : (verticalDirection && (verticalDirection === "down" ? 2 : 1)); }, _intersectsWithSides: function (item) { var isOverBottomHalf = isOverAxis(this.positionAbs.top + this.offset.click.top, item.top + (item.height / 2), item.height), isOverRightHalf = 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.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]); 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(); if (connectWith && this.ready) { //Shouldn't be run the first time through due to massive slow-down for (i = connectWith.length - 1; i >= 0; i--) { cur = $(connectWith[i]); 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]); item.data(this.widgetName + "-item", targetData); // Data for target checking (mouse manager) items.push({ item: item, instance: targetData, width: 0, height: 0, left: 0, top: 0 }); } } }, refreshPositions: function (fast) { //This has to be redone because due to the item being moved out/into the offsetParent, the offsetParent's position will change if (this.offsetParent && this.helper) { this.offset.parent = this._getParentOffset(); } var i, item, t, p; for (i = this.items.length - 1; i >= 0; i--) { item = this.items[i]; //We ignore calculating positions of all connected containers when we're not over them if (item.instance !== this.currentContainer && this.currentContainer && item.item[0] !== this.currentItem[0]) { continue; } t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item; if (!fast) { item.width = t.outerWidth(); item.height = t.outerHeight(); } p = t.offset(); item.left = p.left; item.top = p.top; } if (this.options.custom && this.options.custom.refreshContainers) { this.options.custom.refreshContainers.call(this); } else { for (i = this.containers.length - 1; i >= 0; i--) { p = this.containers[i].element.offset(); this.containers[i].containerCache.left = p.left; this.containers[i].containerCache.top = p.top; this.containers[i].containerCache.width = this.containers[i].element.outerWidth(); this.containers[i].containerCache.height = this.containers[i].element.outerHeight(); } } return this; }, _createPlaceholder: function (that) { that = that || this; var className, o = that.options; if (!o.placeholder || o.placeholder.constructor === String) { className = o.placeholder; o.placeholder = { element: function () { var nodeName = that.currentItem[0].nodeName.toLowerCase(), element = $("<" + nodeName + ">", that.document[0]) .addClass(className || that.currentItem[0].className + " ui-sortable-placeholder") .removeClass("ui-sortable-helper"); if (nodeName === "tr") { that.currentItem.children().each(function () { $("