/* * jQuery UI 1.7.1 * * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT (MIT-LICENSE.txt) * and GPL (GPL-LICENSE.txt) licenses. * * http://docs.jquery.com/UI */ ;jQuery.ui || (function($) {
var _remove = $.fn.remove, isFF2 = $.browser.mozilla && (parseFloat($.browser.version) //Helper functions and ui object $.ui = { version: "1.7.1",
// $.ui.plugin is deprecated. Use the proxy pattern instead. plugin: { add: function(module, option, set) { var proto = $.ui[module].prototype; for(var i in set) { proto.plugins[i] = proto.plugins[i] || []; proto.plugins[i].push([option, set[i]]); } }, call: function(instance, name, args) { var set = instance.plugins[name]; if(!set || !instance.element[0].parentNode) { return; }
for (var i = 0; i if (instance.options[set[i][0]]) { set[i][1].apply(instance.element, args); } } } },
contains: function(a, b) { return document.compareDocumentPosition ? a.compareDocumentPosition(b) & 16 : a !== b && a.contains(b); },
hasScroll: function(el, a) {
//If overflow is hidden, the element might have extra content, but the user wants to hide it if ($(el).css('overflow') == 'hidden') { return false; }
var scroll = (a && a == 'left') ? 'scrollLeft' : 'scrollTop', has = false;
if (el[scroll] > 0) { return true; }
// TODO: determine which cases actually cause this to happen // if the element doesn't have the scroll set, see if it's possible to // set the scroll el[scroll] = 1; has = (el[scroll] > 0); el[scroll] = 0; return has; },
isOverAxis: function(x, reference, size) { //Determines when x coordinate is over "b" element axis return (x > reference) && (x },
isOver: function(y, x, top, left, height, width) { //Determines when x, y coordinates is over "b" element return $.ui.isOverAxis(y, top, height) && $.ui.isOverAxis(x, left, width); },
//jQuery plugins $.fn.extend({ remove: function() { // Safari has a native remove event which actually removes DOM elements, // so we have to use triggerHandler instead of trigger (#3037). $("*", this).add(this).each(function() { $(this).triggerHandler("remove"); }); return _remove.apply(this, arguments ); },
focusable: function(element) { var nodeName = element.nodeName.toLowerCase(), tabIndex = $.attr(element, 'tabindex'); return (/input|select|textarea|button|object/.test(nodeName) ? !element.disabled : 'a' == nodeName || 'area' == nodeName ? element.href || !isNaN(tabIndex) : !isNaN(tabIndex)) // the element and all of its ancestors must be visible // the browser may report that the area is hidden && !$(element)['area' == nodeName ? 'parents' : 'closest'](':hidden').length; },
// $.widget is a factory to create jQuery plugins // taking some boilerplate code out of the plugin code function getter(namespace, plugin, method, args) { function getMethods(type) { var methods = $[namespace][plugin][type] || []; return (typeof methods == 'string' ? methods.split(/,?\s+/) : methods); }
_trigger: function(type, event, data) { var callback = this.options[type], eventName = (type == this.widgetEventPrefix ? type : this.widgetEventPrefix + type);
event = $.Event(event); event.type = eventName;
// copy original event properties over to the new event // this would happen if we could call $.event.fix instead of $.Event // but we don't have a way to force an event to be fixed multiple times if (event.originalEvent) { for (var i = $.event.props.length, prop; i;) { prop = $.event.props[--i]; event[prop] = event.originalEvent[prop]; } }
// Prevent text selection in IE if ($.browser.msie) { this._mouseUnselectable = this.element.attr('unselectable'); this.element.attr('unselectable', 'on'); }
this.started = false; },
// TODO: make sure destroying one instance of mouse doesn't mess with // other instances of mouse _mouseDestroy: function() { this.element.unbind('.'+this.widgetName);
// Restore text selection in IE ($.browser.msie && this.element.attr('unselectable', this._mouseUnselectable)); },
_mouseDown: function(event) { // don't let more than one widget handle mouseStart // TODO: figure out why we have to use originalEvent event.originalEvent = event.originalEvent || {}; if (event.originalEvent.mouseHandled) { return; }
// we may have missed mouseup (out of window) (this._mouseStarted && this._mouseUp(event));
if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) { this._mouseStarted = (this._mouseStart(event) !== false); if (!this._mouseStarted) { event.preventDefault(); return true; } }
// these delegates are required to keep context this._mouseMoveDelegate = function(event) { return self._mouseMove(event); }; this._mouseUpDelegate = function(event) { return self._mouseUp(event); }; $(document) .bind('mousemove.'+this.widgetName, this._mouseMoveDelegate) .bind('mouseup.'+this.widgetName, this._mouseUpDelegate);
// preventDefault() is used to prevent the selection of text here - // however, in Safari, this causes select boxes not to be selectable // anymore, so this fix is needed ($.browser.safari || event.preventDefault());
_mouseMove: function(event) { // IE mouseup check - mouseup happened when mouse was out of window if ($.browser.msie && !event.button) { return this._mouseUp(event); }
if (this._mouseStarted) { this._mouseDrag(event); return event.preventDefault(); }
//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 var currentItem = null, self = this, nodes = $(event.target).parents().each(function() { if($.data(this, 'sortable-item') == self) { currentItem = $(this); return false; } }); if($.data(event.target, 'sortable-item') == self) currentItem = $(event.target);
var o = this.options, self = this; 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 };
// 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");
$.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 });
//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 if(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();
//Recache the helper size if(!this._preserveHelperProportions) this._cacheHelperProportions();
//Post 'activate' events to possible containers if(!noActivation) { for (var i = this.containers.length - 1; i >= 0; i--) { this.containers[i]._trigger("activate", event, self._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) {
//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) { var o = this.options, scrolled = false; if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML') {
//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 (var i = this.items.length - 1; i >= 0; i--) {
//Cache variables and intersection, continue if no intersection var item = this.items[i], itemElement = item.item[0], intersection = this._intersectsWithPointer(item); if (!intersection) continue;
if(itemElement != this.currentItem[0] //cannot intersect with itself && this.placeholder[intersection == 1 ? "next" : "prev"]()[0] != itemElement //no useless actions that have been done before && !$.ui.contains(this.placeholder[0], itemElement) //no action if the item moved is the parent of the item checked && (this.options.type == 'semi-dynamic' ? !$.ui.contains(this.element[0], itemElement) : true) ) {
//Post deactivating events to containers for (var i = this.containers.length - 1; i >= 0; i--){ this.containers[i]._trigger("deactivate", null, self._uiHash(this)); if(this.containers[i].containerCache.over) { this.containers[i]._trigger("out", null, self._uiHash(this)); this.containers[i].containerCache.over = 0; } }
}
//$(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();
//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(); }
for (var i = this.items.length - 1; i >= 0; i--){ var 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;
var t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item;
if (!fast) { item.width = t.outerWidth(); item.height = t.outerHeight(); }
var 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 (var i = this.containers.length - 1; i >= 0; i--){ var 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(); }; }
var el = $(document.createElement(self.currentItem[0].nodeName)) .addClass(className || self.currentItem[0].className+" ui-sortable-placeholder") .removeClass("ui-sortable-helper")[0];
if(!className) el.style.visibility = "hidden";
return el; }, update: function(container, p) {
// 1. If a className is set as 'placeholder option, we don't force sizes - the class is responsible for that // 2. The option 'forcePlaceholderSize can be enabled to force it even if a class name is specified if(className && !o.forcePlaceholderSize) return;
//If the element doesn't have a actual height by itself (without styles coming from a stylesheet), it receives the inline height from the dragged item if(!p.height()) { p.height(self.currentItem.innerHeight() - parseInt(self.currentItem.css('paddingTop')||0, 10) - parseInt(self.currentItem.css('paddingBottom')||0, 10)); }; if(!p.width()) { p.width(self.currentItem.innerWidth() - parseInt(self.currentItem.css('paddingLeft')||0, 10) - parseInt(self.currentItem.css('paddingRight')||0, 10)); }; } }; }
//Create the placeholder self.placeholder = $(o.placeholder.element.call(self.element, self.currentItem));
//Append it after the actual current item self.currentItem.after(self.placeholder);
//Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317) o.placeholder.update(self, self.placeholder);
},
_contactContainers: function(event) { for (var i = this.containers.length - 1; i >= 0; i--){
//When entering a new container, we will find the item with the least distance and append our item near it var dist = 10000; var itemWithLeastDistance = null; var base = this.positionAbs[this.containers[i].floating ? 'left' : 'top']; for (var j = this.items.length - 1; j >= 0; j--) { if(!$.ui.contains(this.containers[i].element[0], this.items[j].item[0])) continue; var cur = this.items[j][this.containers[i].floating ? 'left' : 'top']; if(Math.abs(cur - base) dist = Math.abs(cur - base); itemWithLeastDistance = this.items[j]; } }