text: /* ===================================================
   *  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');
  0
  