123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694 |
- /* ===================================================
- * jquery-sortable.js v0.9.13
- * http://johnny.github.com/jquery-sortable/
- * ===================================================
- * Copyright (c) 2012 Jonas von Andrian
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * * The name of the author may not be used to endorse or promote products
- * derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- * ========================================================== */
- !function ( $, window, pluginName, undefined){
- var containerDefaults = {
- // If true, items can be dragged from this container
- drag: true,
- // If true, items can be droped onto this container
- drop: true,
- // Exclude items from being draggable, if the
- // selector matches the item
- exclude: "",
- // If true, search for nested containers within an item.If you nest containers,
- // either the original selector with which you call the plugin must only match the top containers,
- // or you need to specify a group (see the bootstrap nav example)
- nested: true,
- // If true, the items are assumed to be arranged vertically
- vertical: true
- }, // end container defaults
- groupDefaults = {
- // This is executed after the placeholder has been moved.
- // $closestItemOrContainer contains the closest item, the placeholder
- // has been put at or the closest empty Container, the placeholder has
- // been appended to.
- afterMove: function ($placeholder, container, $closestItemOrContainer) {
- },
- // The exact css path between the container and its items, e.g. "> tbody"
- containerPath: "",
- // The css selector of the containers
- containerSelector: "ol, ul",
- // Distance the mouse has to travel to start dragging
- distance: 0,
- // Time in milliseconds after mousedown until dragging should start.
- // This option can be used to prevent unwanted drags when clicking on an element.
- delay: 0,
- // The css selector of the drag handle
- handle: "",
- // The exact css path between the item and its subcontainers.
- // It should only match the immediate items of a container.
- // No item of a subcontainer should be matched. E.g. for ol>div>li the itemPath is "> div"
- itemPath: "",
- // The css selector of the items
- itemSelector: "li",
- // The class given to "body" while an item is being dragged
- bodyClass: "dragging",
- // The class giving to an item while being dragged
- draggedClass: "dragged",
- // Check if the dragged item may be inside the container.
- // Use with care, since the search for a valid container entails a depth first search
- // and may be quite expensive.
- isValidTarget: function ($item, container) {
- return true
- },
- // Executed before onDrop if placeholder is detached.
- // This happens if pullPlaceholder is set to false and the drop occurs outside a container.
- onCancel: function ($item, container, _super, event) {
- },
- // Executed at the beginning of a mouse move event.
- // The Placeholder has not been moved yet.
- onDrag: function ($item, position, _super, event) {
- $item.css(position)
- },
- // Called after the drag has been started,
- // that is the mouse button is being held down and
- // the mouse is moving.
- // The container is the closest initialized container.
- // Therefore it might not be the container, that actually contains the item.
- onDragStart: function ($item, container, _super, event) {
- $item.css({
- height: $item.outerHeight(),
- width: $item.outerWidth()
- })
- $item.addClass(container.group.options.draggedClass)
- $("body").addClass(container.group.options.bodyClass)
- },
- // Called when the mouse button is being released
- onDrop: function ($item, container, _super, event) {
- $item.removeClass(container.group.options.draggedClass).removeAttr("style")
- $("body").removeClass(container.group.options.bodyClass)
- },
- // Called on mousedown. If falsy value is returned, the dragging will not start.
- // Ignore if element clicked is input, select or textarea
- onMousedown: function ($item, _super, event) {
- if (!event.target.nodeName.match(/^(input|select|textarea)$/i)) {
- event.preventDefault()
- return true
- }
- },
- // The class of the placeholder (must match placeholder option markup)
- placeholderClass: "placeholder",
- // Template for the placeholder. Can be any valid jQuery input
- // e.g. a string, a DOM element.
- // The placeholder must have the class "placeholder"
- placeholder: '<li class="placeholder"></li>',
- // If true, the position of the placeholder is calculated on every mousemove.
- // If false, it is only calculated when the mouse is above a container.
- pullPlaceholder: true,
- // Specifies serialization of the container group.
- // The pair $parent/$children is either container/items or item/subcontainers.
- serialize: function ($parent, $children, parentIsContainer) {
- var result = $.extend({}, $parent.data())
- if(parentIsContainer)
- return [$children]
- else if ($children[0]){
- result.children = $children
- }
- delete result.subContainers
- delete result.sortable
- return result
- },
- // Set tolerance while dragging. Positive values decrease sensitivity,
- // negative values increase it.
- tolerance: 0
- }, // end group defaults
- containerGroups = {},
- groupCounter = 0,
- emptyBox = {
- left: 0,
- top: 0,
- bottom: 0,
- right:0
- },
- eventNames = {
- start: "touchstart.sortable mousedown.sortable",
- drop: "touchend.sortable touchcancel.sortable mouseup.sortable",
- drag: "touchmove.sortable mousemove.sortable",
- scroll: "scroll.sortable"
- },
- subContainerKey = "subContainers"
- /*
- * a is Array [left, right, top, bottom]
- * b is array [left, top]
- */
- function d(a,b) {
- var x = Math.max(0, a[0] - b[0], b[0] - a[1]),
- y = Math.max(0, a[2] - b[1], b[1] - a[3])
- return x+y;
- }
- function setDimensions(array, dimensions, tolerance, useOffset) {
- var i = array.length,
- offsetMethod = useOffset ? "offset" : "position"
- tolerance = tolerance || 0
- while(i--){
- var el = array[i].el ? array[i].el : $(array[i]),
- // use fitting method
- pos = el[offsetMethod]()
- pos.left += parseInt(el.css('margin-left'), 10)
- pos.top += parseInt(el.css('margin-top'),10)
- dimensions[i] = [
- pos.left - tolerance,
- pos.left + el.outerWidth() + tolerance,
- pos.top - tolerance,
- pos.top + el.outerHeight() + tolerance
- ]
- }
- }
- function getRelativePosition(pointer, element) {
- var offset = element.offset()
- return {
- left: pointer.left - offset.left,
- top: pointer.top - offset.top
- }
- }
- function sortByDistanceDesc(dimensions, pointer, lastPointer) {
- pointer = [pointer.left, pointer.top]
- lastPointer = lastPointer && [lastPointer.left, lastPointer.top]
- var dim,
- i = dimensions.length,
- distances = []
- while(i--){
- dim = dimensions[i]
- distances[i] = [i,d(dim,pointer), lastPointer && d(dim, lastPointer)]
- }
- distances = distances.sort(function (a,b) {
- return b[1] - a[1] || b[2] - a[2] || b[0] - a[0]
- })
- // last entry is the closest
- return distances
- }
- function ContainerGroup(options) {
- this.options = $.extend({}, groupDefaults, options)
- this.containers = []
- if(!this.options.rootGroup){
- this.scrollProxy = $.proxy(this.scroll, this)
- this.dragProxy = $.proxy(this.drag, this)
- this.dropProxy = $.proxy(this.drop, this)
- this.placeholder = $(this.options.placeholder)
- if(!options.isValidTarget)
- this.options.isValidTarget = undefined
- }
- }
- ContainerGroup.get = function (options) {
- if(!containerGroups[options.group]) {
- if(options.group === undefined)
- options.group = groupCounter ++
- containerGroups[options.group] = new ContainerGroup(options)
- }
- return containerGroups[options.group]
- }
- ContainerGroup.prototype = {
- dragInit: function (e, itemContainer) {
- this.$document = $(itemContainer.el[0].ownerDocument)
- // get item to drag
- var closestItem = $(e.target).closest(this.options.itemSelector);
- // using the length of this item, prevents the plugin from being started if there is no handle being clicked on.
- // this may also be helpful in instantiating multidrag.
- if (closestItem.length) {
- this.item = closestItem;
- this.itemContainer = itemContainer;
- if (this.item.is(this.options.exclude) || !this.options.onMousedown(this.item, groupDefaults.onMousedown, e)) {
- return;
- }
- this.setPointer(e);
- this.toggleListeners('on');
- this.setupDelayTimer();
- this.dragInitDone = true;
- }
- },
- drag: function (e) {
- if(!this.dragging){
- if(!this.distanceMet(e) || !this.delayMet)
- return
- this.options.onDragStart(this.item, this.itemContainer, groupDefaults.onDragStart, e)
- this.item.before(this.placeholder)
- this.dragging = true
- }
- this.setPointer(e)
- // place item under the cursor
- this.options.onDrag(this.item,
- getRelativePosition(this.pointer, this.item.offsetParent()),
- groupDefaults.onDrag,
- e)
- var p = this.getPointer(e),
- box = this.sameResultBox,
- t = this.options.tolerance
- if(!box || box.top - t > p.top || box.bottom + t < p.top || box.left - t > p.left || box.right + t < p.left)
- if(!this.searchValidTarget()){
- this.placeholder.detach()
- this.lastAppendedItem = undefined
- }
- },
- drop: function (e) {
- this.toggleListeners('off')
- this.dragInitDone = false
- if(this.dragging){
- // processing Drop, check if placeholder is detached
- if(this.placeholder.closest("html")[0]){
- this.placeholder.before(this.item).detach()
- } else {
- this.options.onCancel(this.item, this.itemContainer, groupDefaults.onCancel, e)
- }
- this.options.onDrop(this.item, this.getContainer(this.item), groupDefaults.onDrop, e)
- // cleanup
- this.clearDimensions()
- this.clearOffsetParent()
- this.lastAppendedItem = this.sameResultBox = undefined
- this.dragging = false
- }
- },
- searchValidTarget: function (pointer, lastPointer) {
- if(!pointer){
- pointer = this.relativePointer || this.pointer
- lastPointer = this.lastRelativePointer || this.lastPointer
- }
- var distances = sortByDistanceDesc(this.getContainerDimensions(),
- pointer,
- lastPointer),
- i = distances.length
- while(i--){
- var index = distances[i][0],
- distance = distances[i][1]
- if(!distance || this.options.pullPlaceholder){
- var container = this.containers[index]
- if(!container.disabled){
- if(!this.$getOffsetParent()){
- var offsetParent = container.getItemOffsetParent()
- pointer = getRelativePosition(pointer, offsetParent)
- lastPointer = getRelativePosition(lastPointer, offsetParent)
- }
- if(container.searchValidTarget(pointer, lastPointer))
- return true
- }
- }
- }
- if(this.sameResultBox)
- this.sameResultBox = undefined
- },
- movePlaceholder: function (container, item, method, sameResultBox) {
- var lastAppendedItem = this.lastAppendedItem
- if(!sameResultBox && lastAppendedItem && lastAppendedItem[0] === item[0])
- return;
- item[method](this.placeholder)
- this.lastAppendedItem = item
- this.sameResultBox = sameResultBox
- this.options.afterMove(this.placeholder, container, item)
- },
- getContainerDimensions: function () {
- if(!this.containerDimensions)
- setDimensions(this.containers, this.containerDimensions = [], this.options.tolerance, !this.$getOffsetParent())
- return this.containerDimensions
- },
- getContainer: function (element) {
- return element.closest(this.options.containerSelector).data(pluginName)
- },
- $getOffsetParent: function () {
- if(this.offsetParent === undefined){
- var i = this.containers.length - 1,
- offsetParent = this.containers[i].getItemOffsetParent()
- if(!this.options.rootGroup){
- while(i--){
- if(offsetParent[0] != this.containers[i].getItemOffsetParent()[0]){
- // If every container has the same offset parent,
- // use position() which is relative to this parent,
- // otherwise use offset()
- // compare #setDimensions
- offsetParent = false
- break;
- }
- }
- }
- this.offsetParent = offsetParent
- }
- return this.offsetParent
- },
- setPointer: function (e) {
- var pointer = this.getPointer(e)
- if(this.$getOffsetParent()){
- var relativePointer = getRelativePosition(pointer, this.$getOffsetParent())
- this.lastRelativePointer = this.relativePointer
- this.relativePointer = relativePointer
- }
- this.lastPointer = this.pointer
- this.pointer = pointer
- },
- distanceMet: function (e) {
- var currentPointer = this.getPointer(e)
- return (Math.max(
- Math.abs(this.pointer.left - currentPointer.left),
- Math.abs(this.pointer.top - currentPointer.top)
- ) >= this.options.distance)
- },
- getPointer: function(e) {
- var o = e.originalEvent || e.originalEvent.touches && e.originalEvent.touches[0]
- return {
- left: e.pageX || o.pageX,
- top: e.pageY || o.pageY
- }
- },
- setupDelayTimer: function () {
- var that = this
- this.delayMet = !this.options.delay
- // init delay timer if needed
- if (!this.delayMet) {
- clearTimeout(this._mouseDelayTimer);
- this._mouseDelayTimer = setTimeout(function() {
- that.delayMet = true
- }, this.options.delay)
- }
- },
- scroll: function (e) {
- this.clearDimensions()
- this.clearOffsetParent() // TODO is this needed?
- },
- toggleListeners: function (method) {
- var that = this,
- events = ['drag','drop','scroll']
- $.each(events,function (i,event) {
- that.$document[method](eventNames[event], that[event + 'Proxy'])
- })
- },
- clearOffsetParent: function () {
- this.offsetParent = undefined
- },
- // Recursively clear container and item dimensions
- clearDimensions: function () {
- this.traverse(function(object){
- object._clearDimensions()
- })
- },
- traverse: function(callback) {
- callback(this)
- var i = this.containers.length
- while(i--){
- this.containers[i].traverse(callback)
- }
- },
- _clearDimensions: function(){
- this.containerDimensions = undefined
- },
- _destroy: function () {
- containerGroups[this.options.group] = undefined
- }
- }
- function Container(element, options) {
- this.el = element
- this.options = $.extend( {}, containerDefaults, options)
- this.group = ContainerGroup.get(this.options)
- this.rootGroup = this.options.rootGroup || this.group
- this.handle = this.rootGroup.options.handle || this.rootGroup.options.itemSelector
- var itemPath = this.rootGroup.options.itemPath
- this.target = itemPath ? this.el.find(itemPath) : this.el
- this.target.on(eventNames.start, this.handle, $.proxy(this.dragInit, this))
- if(this.options.drop)
- this.group.containers.push(this)
- }
- Container.prototype = {
- dragInit: function (e) {
- var rootGroup = this.rootGroup
- if( !this.disabled &&
- !rootGroup.dragInitDone &&
- this.options.drag &&
- this.isValidDrag(e)) {
- rootGroup.dragInit(e, this)
- }
- },
- isValidDrag: function(e) {
- return e.which == 1 ||
- e.type == "touchstart" && e.originalEvent.touches.length == 1
- },
- searchValidTarget: function (pointer, lastPointer) {
- var distances = sortByDistanceDesc(this.getItemDimensions(),
- pointer,
- lastPointer),
- i = distances.length,
- rootGroup = this.rootGroup,
- validTarget = !rootGroup.options.isValidTarget ||
- rootGroup.options.isValidTarget(rootGroup.item, this)
- if(!i && validTarget){
- rootGroup.movePlaceholder(this, this.target, "append")
- return true
- } else
- while(i--){
- var index = distances[i][0],
- distance = distances[i][1]
- if(!distance && this.hasChildGroup(index)){
- var found = this.getContainerGroup(index).searchValidTarget(pointer, lastPointer)
- if(found)
- return true
- }
- else if(validTarget){
- this.movePlaceholder(index, pointer)
- return true
- }
- }
- },
- movePlaceholder: function (index, pointer) {
- var item = $(this.items[index]),
- dim = this.itemDimensions[index],
- method = "after",
- width = item.outerWidth(),
- height = item.outerHeight(),
- offset = item.offset(),
- sameResultBox = {
- left: offset.left,
- right: offset.left + width,
- top: offset.top,
- bottom: offset.top + height
- }
- if(this.options.vertical){
- var yCenter = (dim[2] + dim[3]) / 2,
- inUpperHalf = pointer.top <= yCenter
- if(inUpperHalf){
- method = "before"
- sameResultBox.bottom -= height / 2
- } else
- sameResultBox.top += height / 2
- } else {
- var xCenter = (dim[0] + dim[1]) / 2,
- inLeftHalf = pointer.left <= xCenter
- if(inLeftHalf){
- method = "before"
- sameResultBox.right -= width / 2
- } else
- sameResultBox.left += width / 2
- }
- if(this.hasChildGroup(index))
- sameResultBox = emptyBox
- this.rootGroup.movePlaceholder(this, item, method, sameResultBox)
- },
- getItemDimensions: function () {
- if(!this.itemDimensions){
- this.items = this.$getChildren(this.el, "item").filter(
- ":not(." + this.group.options.placeholderClass + ", ." + this.group.options.draggedClass + ")"
- ).get()
- setDimensions(this.items, this.itemDimensions = [], this.options.tolerance)
- }
- return this.itemDimensions
- },
- getItemOffsetParent: function () {
- var offsetParent,
- el = this.el
- // Since el might be empty we have to check el itself and
- // can not do something like el.children().first().offsetParent()
- if(el.css("position") === "relative" || el.css("position") === "absolute" || el.css("position") === "fixed")
- offsetParent = el
- else
- offsetParent = el.offsetParent()
- return offsetParent
- },
- hasChildGroup: function (index) {
- return this.options.nested && this.getContainerGroup(index)
- },
- getContainerGroup: function (index) {
- var childGroup = $.data(this.items[index], subContainerKey)
- if( childGroup === undefined){
- var childContainers = this.$getChildren(this.items[index], "container")
- childGroup = false
- if(childContainers[0]){
- var options = $.extend({}, this.options, {
- rootGroup: this.rootGroup,
- group: groupCounter ++
- })
- childGroup = childContainers[pluginName](options).data(pluginName).group
- }
- $.data(this.items[index], subContainerKey, childGroup)
- }
- return childGroup
- },
- $getChildren: function (parent, type) {
- var options = this.rootGroup.options,
- path = options[type + "Path"],
- selector = options[type + "Selector"]
- parent = $(parent)
- if(path)
- parent = parent.find(path)
- return parent.children(selector)
- },
- _serialize: function (parent, isContainer) {
- var that = this,
- childType = isContainer ? "item" : "container",
- children = this.$getChildren(parent, childType).not(this.options.exclude).map(function () {
- return that._serialize($(this), !isContainer)
- }).get()
- return this.rootGroup.options.serialize(parent, children, isContainer)
- },
- traverse: function(callback) {
- $.each(this.items || [], function(item){
- var group = $.data(this, subContainerKey)
- if(group)
- group.traverse(callback)
- });
- callback(this)
- },
- _clearDimensions: function () {
- this.itemDimensions = undefined
- },
- _destroy: function() {
- var that = this;
- this.target.off(eventNames.start, this.handle);
- this.el.removeData(pluginName)
- if(this.options.drop)
- this.group.containers = $.grep(this.group.containers, function(val){
- return val != that
- })
- $.each(this.items || [], function(){
- $.removeData(this, subContainerKey)
- })
- }
- }
- var API = {
- enable: function() {
- this.traverse(function(object){
- object.disabled = false
- })
- },
- disable: function (){
- this.traverse(function(object){
- object.disabled = true
- })
- },
- serialize: function () {
- return this._serialize(this.el, true)
- },
- refresh: function() {
- this.traverse(function(object){
- object._clearDimensions()
- })
- },
- destroy: function () {
- this.traverse(function(object){
- object._destroy();
- })
- }
- }
- $.extend(Container.prototype, API)
- /**
- * jQuery API
- *
- * Parameters are
- * either options on init
- * or a method name followed by arguments to pass to the method
- */
- $.fn[pluginName] = function(methodOrOptions) {
- var args = Array.prototype.slice.call(arguments, 1)
- return this.map(function(){
- var $t = $(this),
- object = $t.data(pluginName)
- if(object && API[methodOrOptions])
- return API[methodOrOptions].apply(object, args) || this
- else if(!object && (methodOrOptions === undefined ||
- typeof methodOrOptions === "object"))
- $t.data(pluginName, new Container($t, methodOrOptions))
- return this
- });
- };
- }(jQuery, window, 'sortable');
|