JAVASCRIPT 27
Dragdrop.js Guest on 14th August 2020 01:14:48 AM
  1. // script.aculo.us dragdrop.js v1.9.0, Thu Dec 23 16:54:48 -0500 2010
  2.  
  3. // Copyright (c) 2005-2010 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
  4. //
  5. // script.aculo.us is freely distributable under the terms of an MIT-style license.
  6. // For details, see the script.aculo.us web site: http://script.aculo.us/
  7.  
  8. if(Object.isUndefined(Effect))
  9.   throw("dragdrop.js requires including script.aculo.us' effects.js library");
  10.  
  11. var Droppables = {
  12.   drops: [],
  13.  
  14.   remove: function(element) {
  15.     this.drops = this.drops.reject(function(d) { return d.element==$(element) });
  16.   },
  17.  
  18.   add: function(element) {
  19.     element = $(element);
  20.     var options = Object.extend({
  21.       greedy:     true,
  22.       hoverclass: null,
  23.       tree:       false
  24.     }, arguments[1] || { });
  25.  
  26.     // cache containers
  27.     if(options.containment) {
  28.       options._containers = [];
  29.       var containment = options.containment;
  30.       if(Object.isArray(containment)) {
  31.         containment.each( function(c) { options._containers.push($(c)) });
  32.       } else {
  33.         options._containers.push($(containment));
  34.       }
  35.     }
  36.  
  37.     if(options.accept) options.accept = [options.accept].flatten();
  38.  
  39.     Element.makePositioned(element); // fix IE
  40.     options.element = element;
  41.  
  42.     this.drops.push(options);
  43.   },
  44.  
  45.   findDeepestChild: function(drops) {
  46.     deepest = drops[0];
  47.  
  48.     for (i = 1; i < drops.length; ++i)
  49.       if (Element.isParent(drops[i].element, deepest.element))
  50.         deepest = drops[i];
  51.  
  52.     return deepest;
  53.   },
  54.  
  55.   isContained: function(element, drop) {
  56.     var containmentNode;
  57.     if(drop.tree) {
  58.       containmentNode = element.treeNode;
  59.     } else {
  60.       containmentNode = element.parentNode;
  61.     }
  62.     return drop._containers.detect(function(c) { return containmentNode == c });
  63.   },
  64.  
  65.   isAffected: function(point, element, drop) {
  66.     return (
  67.       (drop.element!=element) &&
  68.       ((!drop._containers) ||
  69.         this.isContained(element, drop)) &&
  70.       ((!drop.accept) ||
  71.         (Element.classNames(element).detect(
  72.           function(v) { return drop.accept.include(v) } ) )) &&
  73.       Position.within(drop.element, point[0], point[1]) );
  74.   },
  75.  
  76.   deactivate: function(drop) {
  77.     if(drop.hoverclass)
  78.       Element.removeClassName(drop.element, drop.hoverclass);
  79.     this.last_active = null;
  80.   },
  81.  
  82.   activate: function(drop) {
  83.     if(drop.hoverclass)
  84.       Element.addClassName(drop.element, drop.hoverclass);
  85.     this.last_active = drop;
  86.   },
  87.  
  88.   show: function(point, element) {
  89.     if(!this.drops.length) return;
  90.     var drop, affected = [];
  91.  
  92.     this.drops.each( function(drop) {
  93.       if(Droppables.isAffected(point, element, drop))
  94.         affected.push(drop);
  95.     });
  96.  
  97.     if(affected.length>0)
  98.       drop = Droppables.findDeepestChild(affected);
  99.  
  100.     if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);
  101.     if (drop) {
  102.       Position.within(drop.element, point[0], point[1]);
  103.       if(drop.onHover)
  104.         drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
  105.  
  106.       if (drop != this.last_active) Droppables.activate(drop);
  107.     }
  108.   },
  109.  
  110.   fire: function(event, element) {
  111.     if(!this.last_active) return;
  112.     Position.prepare();
  113.  
  114.     if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
  115.       if (this.last_active.onDrop) {
  116.         this.last_active.onDrop(element, this.last_active.element, event);
  117.         return true;
  118.       }
  119.   },
  120.  
  121.   reset: function() {
  122.     if(this.last_active)
  123.       this.deactivate(this.last_active);
  124.   }
  125. };
  126.  
  127. var Draggables = {
  128.   drags: [],
  129.   observers: [],
  130.  
  131.   register: function(draggable) {
  132.     if(this.drags.length == 0) {
  133.       this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
  134.       this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
  135.       this.eventKeypress  = this.keyPress.bindAsEventListener(this);
  136.  
  137.       Event.observe(document, "mouseup", this.eventMouseUp);
  138.       Event.observe(document, "mousemove", this.eventMouseMove);
  139.       Event.observe(document, "keypress", this.eventKeypress);
  140.     }
  141.     this.drags.push(draggable);
  142.   },
  143.  
  144.   unregister: function(draggable) {
  145.     this.drags = this.drags.reject(function(d) { return d==draggable });
  146.     if(this.drags.length == 0) {
  147.       Event.stopObserving(document, "mouseup", this.eventMouseUp);
  148.       Event.stopObserving(document, "mousemove", this.eventMouseMove);
  149.       Event.stopObserving(document, "keypress", this.eventKeypress);
  150.     }
  151.   },
  152.  
  153.   activate: function(draggable) {
  154.     if(draggable.options.delay) {
  155.       this._timeout = setTimeout(function() {
  156.         Draggables._timeout = null;
  157.         window.focus();
  158.         Draggables.activeDraggable = draggable;
  159.       }.bind(this), draggable.options.delay);
  160.     } else {
  161.       window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
  162.       this.activeDraggable = draggable;
  163.     }
  164.   },
  165.  
  166.   deactivate: function() {
  167.     this.activeDraggable = null;
  168.   },
  169.  
  170.   updateDrag: function(event) {
  171.     if(!this.activeDraggable) return;
  172.     var pointer = [Event.pointerX(event), Event.pointerY(event)];
  173.     // Mozilla-based browsers fire successive mousemove events with
  174.     // the same coordinates, prevent needless redrawing (moz bug?)
  175.     if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
  176.     this._lastPointer = pointer;
  177.  
  178.     this.activeDraggable.updateDrag(event, pointer);
  179.   },
  180.  
  181.   endDrag: function(event) {
  182.     if(this._timeout) {
  183.       clearTimeout(this._timeout);
  184.       this._timeout = null;
  185.     }
  186.     if(!this.activeDraggable) return;
  187.     this._lastPointer = null;
  188.     this.activeDraggable.endDrag(event);
  189.     this.activeDraggable = null;
  190.   },
  191.  
  192.   keyPress: function(event) {
  193.     if(this.activeDraggable)
  194.       this.activeDraggable.keyPress(event);
  195.   },
  196.  
  197.   addObserver: function(observer) {
  198.     this.observers.push(observer);
  199.     this._cacheObserverCallbacks();
  200.   },
  201.  
  202.   removeObserver: function(element) {  // element instead of observer fixes mem leaks
  203.     this.observers = this.observers.reject( function(o) { return o.element==element });
  204.     this._cacheObserverCallbacks();
  205.   },
  206.  
  207.   notify: function(eventName, draggable, event) {  // 'onStart', 'onEnd', 'onDrag'
  208.     if(this[eventName+'Count'] > 0)
  209.       this.observers.each( function(o) {
  210.         if(o[eventName]) o[eventName](eventName, draggable, event);
  211.       });
  212.     if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
  213.   },
  214.  
  215.   _cacheObserverCallbacks: function() {
  216.     ['onStart','onEnd','onDrag'].each( function(eventName) {
  217.       Draggables[eventName+'Count'] = Draggables.observers.select(
  218.         function(o) { return o[eventName]; }
  219.       ).length;
  220.     });
  221.   }
  222. };
  223.  
  224. /*--------------------------------------------------------------------------*/
  225.  
  226. var Draggable = Class.create({
  227.   initialize: function(element) {
  228.     var defaults = {
  229.       handle: false,
  230.       reverteffect: function(element, top_offset, left_offset) {
  231.         var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
  232.         new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
  233.           queue: {scope:'_draggable', position:'end'}
  234.         });
  235.       },
  236.       endeffect: function(element) {
  237.         var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;
  238.         new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
  239.           queue: {scope:'_draggable', position:'end'},
  240.           afterFinish: function(){
  241.             Draggable._dragging[element] = false
  242.           }
  243.         });
  244.       },
  245.       zindex: 1000,
  246.       revert: false,
  247.       quiet: false,
  248.       scroll: false,
  249.       scrollSensitivity: 20,
  250.       scrollSpeed: 15,
  251.       snap: false,  // false, or xy or [x,y] or function(x,y){ return [x,y] }
  252.       delay: 0
  253.     };
  254.  
  255.     if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))
  256.       Object.extend(defaults, {
  257.         starteffect: function(element) {
  258.           element._opacity = Element.getOpacity(element);
  259.           Draggable._dragging[element] = true;
  260.           new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
  261.         }
  262.       });
  263.  
  264.     var options = Object.extend(defaults, arguments[1] || { });
  265.  
  266.     this.element = $(element);
  267.  
  268.     if(options.handle && Object.isString(options.handle))
  269.       this.handle = this.element.down('.'+options.handle, 0);
  270.  
  271.     if(!this.handle) this.handle = $(options.handle);
  272.     if(!this.handle) this.handle = this.element;
  273.  
  274.     if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
  275.       options.scroll = $(options.scroll);
  276.       this._isScrollChild = Element.childOf(this.element, options.scroll);
  277.     }
  278.  
  279.     Element.makePositioned(this.element); // fix IE
  280.  
  281.     this.options  = options;
  282.     this.dragging = false;
  283.  
  284.     this.eventMouseDown = this.initDrag.bindAsEventListener(this);
  285.     Event.observe(this.handle, "mousedown", this.eventMouseDown);
  286.  
  287.     Draggables.register(this);
  288.   },
  289.  
  290.   destroy: function() {
  291.     Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
  292.     Draggables.unregister(this);
  293.   },
  294.  
  295.   currentDelta: function() {
  296.     return([
  297.       parseInt(Element.getStyle(this.element,'left') || '0'),
  298.       parseInt(Element.getStyle(this.element,'top') || '0')]);
  299.   },
  300.  
  301.   initDrag: function(event) {
  302.     if(!Object.isUndefined(Draggable._dragging[this.element]) &&
  303.       Draggable._dragging[this.element]) return;
  304.     if(Event.isLeftClick(event)) {
  305.       // abort on form elements, fixes a Firefox issue
  306.       var src = Event.element(event);
  307.       if((tag_name = src.tagName.toUpperCase()) && (
  308.         tag_name=='INPUT' ||
  309.         tag_name=='SELECT' ||
  310.         tag_name=='OPTION' ||
  311.         tag_name=='BUTTON' ||
  312.         tag_name=='TEXTAREA')) return;
  313.  
  314.       var pointer = [Event.pointerX(event), Event.pointerY(event)];
  315.       var pos     = this.element.cumulativeOffset();
  316.       this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
  317.  
  318.       Draggables.activate(this);
  319.       Event.stop(event);
  320.     }
  321.   },
  322.  
  323.   startDrag: function(event) {
  324.     this.dragging = true;
  325.     if(!this.delta)
  326.       this.delta = this.currentDelta();
  327.  
  328.     if(this.options.zindex) {
  329.       this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
  330.       this.element.style.zIndex = this.options.zindex;
  331.     }
  332.  
  333.     if(this.options.ghosting) {
  334.       this._clone = this.element.cloneNode(true);
  335.       this._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
  336.       if (!this._originallyAbsolute)
  337.         Position.absolutize(this.element);
  338.       this.element.parentNode.insertBefore(this._clone, this.element);
  339.     }
  340.  
  341.     if(this.options.scroll) {
  342.       if (this.options.scroll == window) {
  343.         var where = this._getWindowScroll(this.options.scroll);
  344.         this.originalScrollLeft = where.left;
  345.         this.originalScrollTop = where.top;
  346.       } else {
  347.         this.originalScrollLeft = this.options.scroll.scrollLeft;
  348.         this.originalScrollTop = this.options.scroll.scrollTop;
  349.       }
  350.     }
  351.  
  352.     Draggables.notify('onStart', this, event);
  353.  
  354.     if(this.options.starteffect) this.options.starteffect(this.element);
  355.   },
  356.  
  357.   updateDrag: function(event, pointer) {
  358.     if(!this.dragging) this.startDrag(event);
  359.  
  360.     if(!this.options.quiet){
  361.       Position.prepare();
  362.       Droppables.show(pointer, this.element);
  363.     }
  364.  
  365.     Draggables.notify('onDrag', this, event);
  366.  
  367.     this.draw(pointer);
  368.     if(this.options.change) this.options.change(this);
  369.  
  370.     if(this.options.scroll) {
  371.       this.stopScrolling();
  372.  
  373.       var p;
  374.       if (this.options.scroll == window) {
  375.         with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
  376.       } else {
  377.         p = Position.page(this.options.scroll).toArray();
  378.         p[0] += this.options.scroll.scrollLeft + Position.deltaX;
  379.         p[1] += this.options.scroll.scrollTop + Position.deltaY;
  380.         p.push(p[0]+this.options.scroll.offsetWidth);
  381.         p.push(p[1]+this.options.scroll.offsetHeight);
  382.       }
  383.       var speed = [0,0];
  384.       if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
  385.       if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
  386.       if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
  387.       if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
  388.       this.startScrolling(speed);
  389.     }
  390.  
  391.     // fix AppleWebKit rendering
  392.     if(Prototype.Browser.WebKit) window.scrollBy(0,0);
  393.  
  394.     Event.stop(event);
  395.   },
  396.  
  397.   finishDrag: function(event, success) {
  398.     this.dragging = false;
  399.  
  400.     if(this.options.quiet){
  401.       Position.prepare();
  402.       var pointer = [Event.pointerX(event), Event.pointerY(event)];
  403.       Droppables.show(pointer, this.element);
  404.     }
  405.  
  406.     if(this.options.ghosting) {
  407.       if (!this._originallyAbsolute)
  408.         Position.relativize(this.element);
  409.       delete this._originallyAbsolute;
  410.       Element.remove(this._clone);
  411.       this._clone = null;
  412.     }
  413.  
  414.     var dropped = false;
  415.     if(success) {
  416.       dropped = Droppables.fire(event, this.element);
  417.       if (!dropped) dropped = false;
  418.     }
  419.     if(dropped && this.options.onDropped) this.options.onDropped(this.element);
  420.     Draggables.notify('onEnd', this, event);
  421.  
  422.     var revert = this.options.revert;
  423.     if(revert && Object.isFunction(revert)) revert = revert(this.element);
  424.  
  425.     var d = this.currentDelta();
  426.     if(revert && this.options.reverteffect) {
  427.       if (dropped == 0 || revert != 'failure')
  428.         this.options.reverteffect(this.element,
  429.           d[1]-this.delta[1], d[0]-this.delta[0]);
  430.     } else {
  431.       this.delta = d;
  432.     }
  433.  
  434.     if(this.options.zindex)
  435.       this.element.style.zIndex = this.originalZ;
  436.  
  437.     if(this.options.endeffect)
  438.       this.options.endeffect(this.element);
  439.  
  440.     Draggables.deactivate(this);
  441.     Droppables.reset();
  442.   },
  443.  
  444.   keyPress: function(event) {
  445.     if(event.keyCode!=Event.KEY_ESC) return;
  446.     this.finishDrag(event, false);
  447.     Event.stop(event);
  448.   },
  449.  
  450.   endDrag: function(event) {
  451.     if(!this.dragging) return;
  452.     this.stopScrolling();
  453.     this.finishDrag(event, true);
  454.     Event.stop(event);
  455.   },
  456.  
  457.   draw: function(point) {
  458.     var pos = this.element.cumulativeOffset();
  459.     if(this.options.ghosting) {
  460.       var r   = Position.realOffset(this.element);
  461.       pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
  462.     }
  463.  
  464.     var d = this.currentDelta();
  465.     pos[0] -= d[0]; pos[1] -= d[1];
  466.  
  467.     if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
  468.       pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
  469.       pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
  470.     }
  471.  
  472.     var p = [0,1].map(function(i){
  473.       return (point[i]-pos[i]-this.offset[i])
  474.     }.bind(this));
  475.  
  476.     if(this.options.snap) {
  477.       if(Object.isFunction(this.options.snap)) {
  478.         p = this.options.snap(p[0],p[1],this);
  479.       } else {
  480.       if(Object.isArray(this.options.snap)) {
  481.         p = p.map( function(v, i) {
  482.           return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this));
  483.       } else {
  484.         p = p.map( function(v) {
  485.           return (v/this.options.snap).round()*this.options.snap }.bind(this));
  486.       }
  487.     }}
  488.  
  489.     var style = this.element.style;
  490.     if((!this.options.constraint) || (this.options.constraint=='horizontal'))
  491.       style.left = p[0] + "px";
  492.     if((!this.options.constraint) || (this.options.constraint=='vertical'))
  493.       style.top  = p[1] + "px";
  494.  
  495.     if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
  496.   },
  497.  
  498.   stopScrolling: function() {
  499.     if(this.scrollInterval) {
  500.       clearInterval(this.scrollInterval);
  501.       this.scrollInterval = null;
  502.       Draggables._lastScrollPointer = null;
  503.     }
  504.   },
  505.  
  506.   startScrolling: function(speed) {
  507.     if(!(speed[0] || speed[1])) return;
  508.     this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
  509.     this.lastScrolled = new Date();
  510.     this.scrollInterval = setInterval(this.scroll.bind(this), 10);
  511.   },
  512.  
  513.   scroll: function() {
  514.     var current = new Date();
  515.     var delta = current - this.lastScrolled;
  516.     this.lastScrolled = current;
  517.     if(this.options.scroll == window) {
  518.       with (this._getWindowScroll(this.options.scroll)) {
  519.         if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
  520.           var d = delta / 1000;
  521.           this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
  522.         }
  523.       }
  524.     } else {
  525.       this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
  526.       this.options.scroll.scrollTop  += this.scrollSpeed[1] * delta / 1000;
  527.     }
  528.  
  529.     Position.prepare();
  530.     Droppables.show(Draggables._lastPointer, this.element);
  531.     Draggables.notify('onDrag', this);
  532.     if (this._isScrollChild) {
  533.       Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
  534.       Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
  535.       Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
  536.       if (Draggables._lastScrollPointer[0] < 0)
  537.         Draggables._lastScrollPointer[0] = 0;
  538.       if (Draggables._lastScrollPointer[1] < 0)
  539.         Draggables._lastScrollPointer[1] = 0;
  540.       this.draw(Draggables._lastScrollPointer);
  541.     }
  542.  
  543.     if(this.options.change) this.options.change(this);
  544.   },
  545.  
  546.   _getWindowScroll: function(w) {
  547.     var T, L, W, H;
  548.     with (w.document) {
  549.       if (w.document.documentElement && documentElement.scrollTop) {
  550.         T = documentElement.scrollTop;
  551.         L = documentElement.scrollLeft;
  552.       } else if (w.document.body) {
  553.         T = body.scrollTop;
  554.         L = body.scrollLeft;
  555.       }
  556.       if (w.innerWidth) {
  557.         W = w.innerWidth;
  558.         H = w.innerHeight;
  559.       } else if (w.document.documentElement && documentElement.clientWidth) {
  560.         W = documentElement.clientWidth;
  561.         H = documentElement.clientHeight;
  562.       } else {
  563.         W = body.offsetWidth;
  564.         H = body.offsetHeight;
  565.       }
  566.     }
  567.     return { top: T, left: L, width: W, height: H };
  568.   }
  569. });
  570.  
  571. Draggable._dragging = { };
  572.  
  573. /*--------------------------------------------------------------------------*/
  574.  
  575. var SortableObserver = Class.create({
  576.   initialize: function(element, observer) {
  577.     this.element   = $(element);
  578.     this.observer  = observer;
  579.     this.lastValue = Sortable.serialize(this.element);
  580.   },
  581.  
  582.   onStart: function() {
  583.     this.lastValue = Sortable.serialize(this.element);
  584.   },
  585.  
  586.   onEnd: function() {
  587.     Sortable.unmark();
  588.     if(this.lastValue != Sortable.serialize(this.element))
  589.       this.observer(this.element)
  590.   }
  591. });
  592.  
  593. var Sortable = {
  594.   SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
  595.  
  596.   sortables: { },
  597.  
  598.   _findRootElement: function(element) {
  599.     while (element.tagName.toUpperCase() != "BODY") {
  600.       if(element.id && Sortable.sortables[element.id]) return element;
  601.       element = element.parentNode;
  602.     }
  603.   },
  604.  
  605.   options: function(element) {
  606.     element = Sortable._findRootElement($(element));
  607.     if(!element) return;
  608.     return Sortable.sortables[element.id];
  609.   },
  610.  
  611.   destroy: function(element){
  612.     element = $(element);
  613.     var s = Sortable.sortables[element.id];
  614.  
  615.     if(s) {
  616.       Draggables.removeObserver(s.element);
  617.       s.droppables.each(function(d){ Droppables.remove(d) });
  618.       s.draggables.invoke('destroy');
  619.  
  620.       delete Sortable.sortables[s.element.id];
  621.     }
  622.   },
  623.  
  624.   create: function(element) {
  625.     element = $(element);
  626.     var options = Object.extend({
  627.       element:     element,
  628.       tag:         'li',       // assumes li children, override with tag: 'tagname'
  629.       dropOnEmpty: false,
  630.       tree:        false,
  631.       treeTag:     'ul',
  632.       overlap:     'vertical', // one of 'vertical', 'horizontal'
  633.       constraint:  'vertical', // one of 'vertical', 'horizontal', false
  634.       containment: element,    // also takes array of elements (or id's); or false
  635.       handle:      false,      // or a CSS class
  636.       only:        false,
  637.       delay:       0,
  638.       hoverclass:  null,
  639.       ghosting:    false,
  640.       quiet:       false,
  641.       scroll:      false,
  642.       scrollSensitivity: 20,
  643.       scrollSpeed: 15,
  644.       format:      this.SERIALIZE_RULE,
  645.  
  646.       // these take arrays of elements or ids and can be
  647.       // used for better initialization performance
  648.       elements:    false,
  649.       handles:     false,
  650.  
  651.       onChange:    Prototype.emptyFunction,
  652.       onUpdate:    Prototype.emptyFunction
  653.     }, arguments[1] || { });
  654.  
  655.     // clear any old sortable with same element
  656.     this.destroy(element);
  657.  
  658.     // build options for the draggables
  659.     var options_for_draggable = {
  660.       revert:      true,
  661.       quiet:       options.quiet,
  662.       scroll:      options.scroll,
  663.       scrollSpeed: options.scrollSpeed,
  664.       scrollSensitivity: options.scrollSensitivity,
  665.       delay:       options.delay,
  666.       ghosting:    options.ghosting,
  667.       constraint:  options.constraint,
  668.       handle:      options.handle };
  669.  
  670.     if(options.starteffect)
  671.       options_for_draggable.starteffect = options.starteffect;
  672.  
  673.     if(options.reverteffect)
  674.       options_for_draggable.reverteffect = options.reverteffect;
  675.     else
  676.       if(options.ghosting) options_for_draggable.reverteffect = function(element) {
  677.         element.style.top  = 0;
  678.         element.style.left = 0;
  679.       };
  680.  
  681.     if(options.endeffect)
  682.       options_for_draggable.endeffect = options.endeffect;
  683.  
  684.     if(options.zindex)
  685.       options_for_draggable.zindex = options.zindex;
  686.  
  687.     // build options for the droppables
  688.     var options_for_droppable = {
  689.       overlap:     options.overlap,
  690.       containment: options.containment,
  691.       tree:        options.tree,
  692.       hoverclass:  options.hoverclass,
  693.       onHover:     Sortable.onHover
  694.     };
  695.  
  696.     var options_for_tree = {
  697.       onHover:      Sortable.onEmptyHover,
  698.       overlap:      options.overlap,
  699.       containment:  options.containment,
  700.       hoverclass:   options.hoverclass
  701.     };
  702.  
  703.     // fix for gecko engine
  704.     Element.cleanWhitespace(element);
  705.  
  706.     options.draggables = [];
  707.     options.droppables = [];
  708.  
  709.     // drop on empty handling
  710.     if(options.dropOnEmpty || options.tree) {
  711.       Droppables.add(element, options_for_tree);
  712.       options.droppables.push(element);
  713.     }
  714.  
  715.     (options.elements || this.findElements(element, options) || []).each( function(e,i) {
  716.       var handle = options.handles ? $(options.handles[i]) :
  717.         (options.handle ? $(e).select('.' + options.handle)[0] : e);
  718.       options.draggables.push(
  719.         new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
  720.       Droppables.add(e, options_for_droppable);
  721.       if(options.tree) e.treeNode = element;
  722.       options.droppables.push(e);
  723.     });
  724.  
  725.     if(options.tree) {
  726.       (Sortable.findTreeElements(element, options) || []).each( function(e) {
  727.         Droppables.add(e, options_for_tree);
  728.         e.treeNode = element;
  729.         options.droppables.push(e);
  730.       });
  731.     }
  732.  
  733.     // keep reference
  734.     this.sortables[element.identify()] = options;
  735.  
  736.     // for onupdate
  737.     Draggables.addObserver(new SortableObserver(element, options.onUpdate));
  738.  
  739.   },
  740.  
  741.   // return all suitable-for-sortable elements in a guaranteed order
  742.   findElements: function(element, options) {
  743.     return Element.findChildren(
  744.       element, options.only, options.tree ? true : false, options.tag);
  745.   },
  746.  
  747.   findTreeElements: function(element, options) {
  748.     return Element.findChildren(
  749.       element, options.only, options.tree ? true : false, options.treeTag);
  750.   },
  751.  
  752.   onHover: function(element, dropon, overlap) {
  753.     if(Element.isParent(dropon, element)) return;
  754.  
  755.     if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
  756.       return;
  757.     } else if(overlap>0.5) {
  758.       Sortable.mark(dropon, 'before');
  759.       if(dropon.previousSibling != element) {
  760.         var oldParentNode = element.parentNode;
  761.         element.style.visibility = "hidden"; // fix gecko rendering
  762.         dropon.parentNode.insertBefore(element, dropon);
  763.         if(dropon.parentNode!=oldParentNode)
  764.           Sortable.options(oldParentNode).onChange(element);
  765.         Sortable.options(dropon.parentNode).onChange(element);
  766.       }
  767.     } else {
  768.       Sortable.mark(dropon, 'after');
  769.       var nextElement = dropon.nextSibling || null;
  770.       if(nextElement != element) {
  771.         var oldParentNode = element.parentNode;
  772.         element.style.visibility = "hidden"; // fix gecko rendering
  773.         dropon.parentNode.insertBefore(element, nextElement);
  774.         if(dropon.parentNode!=oldParentNode)
  775.           Sortable.options(oldParentNode).onChange(element);
  776.         Sortable.options(dropon.parentNode).onChange(element);
  777.       }
  778.     }
  779.   },
  780.  
  781.   onEmptyHover: function(element, dropon, overlap) {
  782.     var oldParentNode = element.parentNode;
  783.     var droponOptions = Sortable.options(dropon);
  784.  
  785.     if(!Element.isParent(dropon, element)) {
  786.       var index;
  787.  
  788.       var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
  789.       var child = null;
  790.  
  791.       if(children) {
  792.         var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
  793.  
  794.         for (index = 0; index < children.length; index += 1) {
  795.           if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
  796.             offset -= Element.offsetSize (children[index], droponOptions.overlap);
  797.           } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
  798.             child = index + 1 < children.length ? children[index + 1] : null;
  799.             break;
  800.           } else {
  801.             child = children[index];
  802.             break;
  803.           }
  804.         }
  805.       }
  806.  
  807.       dropon.insertBefore(element, child);
  808.  
  809.       Sortable.options(oldParentNode).onChange(element);
  810.       droponOptions.onChange(element);
  811.     }
  812.   },
  813.  
  814.   unmark: function() {
  815.     if(Sortable._marker) Sortable._marker.hide();
  816.   },
  817.  
  818.   mark: function(dropon, position) {
  819.     // mark on ghosting only
  820.     var sortable = Sortable.options(dropon.parentNode);
  821.     if(sortable && !sortable.ghosting) return;
  822.  
  823.     if(!Sortable._marker) {
  824.       Sortable._marker =
  825.         ($('dropmarker') || Element.extend(document.createElement('DIV'))).
  826.           hide().addClassName('dropmarker').setStyle({position:'absolute'});
  827.       document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
  828.     }
  829.     var offsets = dropon.cumulativeOffset();
  830.     Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
  831.  
  832.     if(position=='after')
  833.       if(sortable.overlap == 'horizontal')
  834.         Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
  835.       else
  836.         Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
  837.  
  838.     Sortable._marker.show();
  839.   },
  840.  
  841.   _tree: function(element, options, parent) {
  842.     var children = Sortable.findElements(element, options) || [];
  843.  
  844.     for (var i = 0; i < children.length; ++i) {
  845.       var match = children[i].id.match(options.format);
  846.  
  847.       if (!match) continue;
  848.  
  849.       var child = {
  850.         id: encodeURIComponent(match ? match[1] : null),
  851.         element: element,
  852.         parent: parent,
  853.         children: [],
  854.         position: parent.children.length,
  855.         container: $(children[i]).down(options.treeTag)
  856.       };
  857.  
  858.       /* Get the element containing the children and recurse over it */
  859.       if (child.container)
  860.         this._tree(child.container, options, child);
  861.  
  862.       parent.children.push (child);
  863.     }
  864.  
  865.     return parent;
  866.   },
  867.  
  868.   tree: function(element) {
  869.     element = $(element);
  870.     var sortableOptions = this.options(element);
  871.     var options = Object.extend({
  872.       tag: sortableOptions.tag,
  873.       treeTag: sortableOptions.treeTag,
  874.       only: sortableOptions.only,
  875.       name: element.id,
  876.       format: sortableOptions.format
  877.     }, arguments[1] || { });
  878.  
  879.     var root = {
  880.       id: null,
  881.       parent: null,
  882.       children: [],
  883.       container: element,
  884.       position: 0
  885.     };
  886.  
  887.     return Sortable._tree(element, options, root);
  888.   },
  889.  
  890.   /* Construct a [i] index for a particular node */
  891.   _constructIndex: function(node) {
  892.     var index = '';
  893.     do {
  894.       if (node.id) index = '[' + node.position + ']' + index;
  895.     } while ((node = node.parent) != null);
  896.     return index;
  897.   },
  898.  
  899.   sequence: function(element) {
  900.     element = $(element);
  901.     var options = Object.extend(this.options(element), arguments[1] || { });
  902.  
  903.     return $(this.findElements(element, options) || []).map( function(item) {
  904.       return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
  905.     });
  906.   },
  907.  
  908.   setSequence: function(element, new_sequence) {
  909.     element = $(element);
  910.     var options = Object.extend(this.options(element), arguments[2] || { });
  911.  
  912.     var nodeMap = { };
  913.     this.findElements(element, options).each( function(n) {
  914.         if (n.id.match(options.format))
  915.             nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
  916.         n.parentNode.removeChild(n);
  917.     });
  918.  
  919.     new_sequence.each(function(ident) {
  920.       var n = nodeMap[ident];
  921.       if (n) {
  922.         n[1].appendChild(n[0]);
  923.         delete nodeMap[ident];
  924.       }
  925.     });
  926.   },
  927.  
  928.   serialize: function(element) {
  929.     element = $(element);
  930.     var options = Object.extend(Sortable.options(element), arguments[1] || { });
  931.     var name = encodeURIComponent(
  932.       (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
  933.  
  934.     if (options.tree) {
  935.       return Sortable.tree(element, arguments[1]).children.map( function (item) {
  936.         return [name + Sortable._constructIndex(item) + "[id]=" +
  937.                 encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
  938.       }).flatten().join('&');
  939.     } else {
  940.       return Sortable.sequence(element, arguments[1]).map( function(item) {
  941.         return name + "[]=" + encodeURIComponent(item);
  942.       }).join('&');
  943.     }
  944.   }
  945. };
  946.  
  947. // Returns true if child is contained within element
  948. Element.isParent = function(child, element) {
  949.   if (!child.parentNode || child == element) return false;
  950.   if (child.parentNode == element) return true;
  951.   return Element.isParent(child.parentNode, element);
  952. };
  953.  
  954. Element.findChildren = function(element, only, recursive, tagName) {
  955.   if(!element.hasChildNodes()) return null;
  956.   tagName = tagName.toUpperCase();
  957.   if(only) only = [only].flatten();
  958.   var elements = [];
  959.   $A(element.childNodes).each( function(e) {
  960.     if(e.tagName && e.tagName.toUpperCase()==tagName &&
  961.       (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
  962.         elements.push(e);
  963.     if(recursive) {
  964.       var grandchildren = Element.findChildren(e, only, recursive, tagName);
  965.       if(grandchildren) elements.push(grandchildren);
  966.     }
  967.   });
  968.  
  969.   return (elements.length>0 ? elements.flatten() : []);
  970. };
  971.  
  972. Element.offsetSize = function (element, type) {
  973.   return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
  974. };

Paste is for source code and general debugging text.

Login or Register to edit, delete and keep track of your pastes and more.

Raw Paste

Login or Register to edit or fork this paste. It's free.