home *** CD-ROM | disk | FTP | other *** search
/ Cricao de Sites - 650 Layouts Prontos / WebMasters.iso / Blogs / wordpress2.6.exe / wordpress2.6 / wp-includes / js / scriptaculous / dragdrop.js < prev    next >
Encoding:
JavaScript  |  2008-03-03  |  30.9 KB  |  975 lines

  1. // script.aculo.us dragdrop.js v1.8.0, Tue Nov 06 15:01:40 +0300 2007
  2.  
  3. // Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
  4. //           (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
  5. // 
  6. // script.aculo.us is freely distributable under the terms of an MIT-style license.
  7. // For details, see the script.aculo.us web site: http://script.aculo.us/
  8.  
  9. if(Object.isUndefined(Effect))
  10.   throw("dragdrop.js requires including script.aculo.us' effects.js library");
  11.  
  12. var Droppables = {
  13.   drops: [],
  14.  
  15.   remove: function(element) {
  16.     this.drops = this.drops.reject(function(d) { return d.element==$(element) });
  17.   },
  18.  
  19.   add: function(element) {
  20.     element = $(element);
  21.     var options = Object.extend({
  22.       greedy:     true,
  23.       hoverclass: null,
  24.       tree:       false
  25.     }, arguments[1] || { });
  26.  
  27.     // cache containers
  28.     if(options.containment) {
  29.       options._containers = [];
  30.       var containment = options.containment;
  31.       if(Object.isArray(containment)) {
  32.         containment.each( function(c) { options._containers.push($(c)) });
  33.       } else {
  34.         options._containers.push($(containment));
  35.       }
  36.     }
  37.     
  38.     if(options.accept) options.accept = [options.accept].flatten();
  39.  
  40.     Element.makePositioned(element); // fix IE
  41.     options.element = element;
  42.  
  43.     this.drops.push(options);
  44.   },
  45.   
  46.   findDeepestChild: function(drops) {
  47.     deepest = drops[0];
  48.       
  49.     for (i = 1; i < drops.length; ++i)
  50.       if (Element.isParent(drops[i].element, deepest.element))
  51.         deepest = drops[i];
  52.     
  53.     return deepest;
  54.   },
  55.  
  56.   isContained: function(element, drop) {
  57.     var containmentNode;
  58.     if(drop.tree) {
  59.       containmentNode = element.treeNode; 
  60.     } else {
  61.       containmentNode = element.parentNode;
  62.     }
  63.     return drop._containers.detect(function(c) { return containmentNode == c });
  64.   },
  65.   
  66.   isAffected: function(point, element, drop) {
  67.     return (
  68.       (drop.element!=element) &&
  69.       ((!drop._containers) ||
  70.         this.isContained(element, drop)) &&
  71.       ((!drop.accept) ||
  72.         (Element.classNames(element).detect( 
  73.           function(v) { return drop.accept.include(v) } ) )) &&
  74.       Position.within(drop.element, point[0], point[1]) );
  75.   },
  76.  
  77.   deactivate: function(drop) {
  78.     if(drop.hoverclass)
  79.       Element.removeClassName(drop.element, drop.hoverclass);
  80.     this.last_active = null;
  81.   },
  82.  
  83.   activate: function(drop) {
  84.     if(drop.hoverclass)
  85.       Element.addClassName(drop.element, drop.hoverclass);
  86.     this.last_active = drop;
  87.   },
  88.  
  89.   show: function(point, element) {
  90.     if(!this.drops.length) return;
  91.     var drop, affected = [];
  92.     
  93.     this.drops.each( function(drop) {
  94.       if(Droppables.isAffected(point, element, drop))
  95.         affected.push(drop);
  96.     });
  97.         
  98.     if(affected.length>0)
  99.       drop = Droppables.findDeepestChild(affected);
  100.  
  101.     if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);
  102.     if (drop) {
  103.       Position.within(drop.element, point[0], point[1]);
  104.       if(drop.onHover)
  105.         drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
  106.       
  107.       if (drop != this.last_active) Droppables.activate(drop);
  108.     }
  109.   },
  110.  
  111.   fire: function(event, element) {
  112.     if(!this.last_active) return;
  113.     Position.prepare();
  114.  
  115.     if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
  116.       if (this.last_active.onDrop) {
  117.         this.last_active.onDrop(element, this.last_active.element, event); 
  118.         return true; 
  119.       }
  120.   },
  121.  
  122.   reset: function() {
  123.     if(this.last_active)
  124.       this.deactivate(this.last_active);
  125.   }
  126. }
  127.  
  128. var Draggables = {
  129.   drags: [],
  130.   observers: [],
  131.   
  132.   register: function(draggable) {
  133.     if(this.drags.length == 0) {
  134.       this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
  135.       this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
  136.       this.eventKeypress  = this.keyPress.bindAsEventListener(this);
  137.       
  138.       Event.observe(document, "mouseup", this.eventMouseUp);
  139.       Event.observe(document, "mousemove", this.eventMouseMove);
  140.       Event.observe(document, "keypress", this.eventKeypress);
  141.     }
  142.     this.drags.push(draggable);
  143.   },
  144.   
  145.   unregister: function(draggable) {
  146.     this.drags = this.drags.reject(function(d) { return d==draggable });
  147.     if(this.drags.length == 0) {
  148.       Event.stopObserving(document, "mouseup", this.eventMouseUp);
  149.       Event.stopObserving(document, "mousemove", this.eventMouseMove);
  150.       Event.stopObserving(document, "keypress", this.eventKeypress);
  151.     }
  152.   },
  153.   
  154.   activate: function(draggable) {
  155.     if(draggable.options.delay) { 
  156.       this._timeout = setTimeout(function() { 
  157.         Draggables._timeout = null; 
  158.         window.focus(); 
  159.         Draggables.activeDraggable = draggable; 
  160.       }.bind(this), draggable.options.delay); 
  161.     } else {
  162.       window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
  163.       this.activeDraggable = draggable;
  164.     }
  165.   },
  166.   
  167.   deactivate: function() {
  168.     this.activeDraggable = null;
  169.   },
  170.   
  171.   updateDrag: function(event) {
  172.     if(!this.activeDraggable) return;
  173.     var pointer = [Event.pointerX(event), Event.pointerY(event)];
  174.     // Mozilla-based browsers fire successive mousemove events with
  175.     // the same coordinates, prevent needless redrawing (moz bug?)
  176.     if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
  177.     this._lastPointer = pointer;
  178.     
  179.     this.activeDraggable.updateDrag(event, pointer);
  180.   },
  181.   
  182.   endDrag: function(event) {
  183.     if(this._timeout) { 
  184.       clearTimeout(this._timeout); 
  185.       this._timeout = null; 
  186.     }
  187.     if(!this.activeDraggable) return;
  188.     this._lastPointer = null;
  189.     this.activeDraggable.endDrag(event);
  190.     this.activeDraggable = null;
  191.   },
  192.   
  193.   keyPress: function(event) {
  194.     if(this.activeDraggable)
  195.       this.activeDraggable.keyPress(event);
  196.   },
  197.   
  198.   addObserver: function(observer) {
  199.     this.observers.push(observer);
  200.     this._cacheObserverCallbacks();
  201.   },
  202.   
  203.   removeObserver: function(element) {  // element instead of observer fixes mem leaks
  204.     this.observers = this.observers.reject( function(o) { return o.element==element });
  205.     this._cacheObserverCallbacks();
  206.   },
  207.   
  208.   notify: function(eventName, draggable, event) {  // 'onStart', 'onEnd', 'onDrag'
  209.     if(this[eventName+'Count'] > 0)
  210.       this.observers.each( function(o) {
  211.         if(o[eventName]) o[eventName](eventName, draggable, event);
  212.       });
  213.     if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
  214.   },
  215.   
  216.   _cacheObserverCallbacks: function() {
  217.     ['onStart','onEnd','onDrag'].each( function(eventName) {
  218.       Draggables[eventName+'Count'] = Draggables.observers.select(
  219.         function(o) { return o[eventName]; }
  220.       ).length;
  221.     });
  222.   }
  223. }
  224.  
  225. /*--------------------------------------------------------------------------*/
  226.  
  227. var Draggable = Class.create({
  228.   initialize: function(element) {
  229.     var defaults = {
  230.       handle: false,
  231.       reverteffect: function(element, top_offset, left_offset) {
  232.         var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
  233.         new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
  234.           queue: {scope:'_draggable', position:'end'}
  235.         });
  236.       },
  237.       endeffect: function(element) {
  238.         var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;
  239.         new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, 
  240.           queue: {scope:'_draggable', position:'end'},
  241.           afterFinish: function(){ 
  242.             Draggable._dragging[element] = false 
  243.           }
  244.         }); 
  245.       },
  246.       zindex: 1000,
  247.       revert: false,
  248.       quiet: false,
  249.       scroll: false,
  250.       scrollSensitivity: 20,
  251.       scrollSpeed: 15,
  252.       snap: false,  // false, or xy or [x,y] or function(x,y){ return [x,y] }
  253.       delay: 0
  254.     };
  255.     
  256.     if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))
  257.       Object.extend(defaults, {
  258.         starteffect: function(element) {
  259.           element._opacity = Element.getOpacity(element);
  260.           Draggable._dragging[element] = true;
  261.           new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); 
  262.         }
  263.       });
  264.     
  265.     var options = Object.extend(defaults, arguments[1] || { });
  266.  
  267.     this.element = $(element);
  268.     
  269.     if(options.handle && Object.isString(options.handle))
  270.       this.handle = this.element.down('.'+options.handle, 0);
  271.     
  272.     if(!this.handle) this.handle = $(options.handle);
  273.     if(!this.handle) this.handle = this.element;
  274.     
  275.     if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
  276.       options.scroll = $(options.scroll);
  277.       this._isScrollChild = Element.childOf(this.element, options.scroll);
  278.     }
  279.  
  280.     Element.makePositioned(this.element); // fix IE    
  281.  
  282.     this.options  = options;
  283.     this.dragging = false;   
  284.  
  285.     this.eventMouseDown = this.initDrag.bindAsEventListener(this);
  286.     Event.observe(this.handle, "mousedown", this.eventMouseDown);
  287.     
  288.     Draggables.register(this);
  289.   },
  290.   
  291.   destroy: function() {
  292.     Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
  293.     Draggables.unregister(this);
  294.   },
  295.   
  296.   currentDelta: function() {
  297.     return([
  298.       parseInt(Element.getStyle(this.element,'left') || '0'),
  299.       parseInt(Element.getStyle(this.element,'top') || '0')]);
  300.   },
  301.   
  302.   initDrag: function(event) {
  303.     if(!Object.isUndefined(Draggable._dragging[this.element]) &&
  304.       Draggable._dragging[this.element]) return;
  305.     if(Event.isLeftClick(event)) {    
  306.       // abort on form elements, fixes a Firefox issue
  307.       var src = Event.element(event);
  308.       if((tag_name = src.tagName.toUpperCase()) && (
  309.         tag_name=='INPUT' ||
  310.         tag_name=='SELECT' ||
  311.         tag_name=='OPTION' ||
  312.         tag_name=='BUTTON' ||
  313.         tag_name=='TEXTAREA')) return;
  314.         
  315.       var pointer = [Event.pointerX(event), Event.pointerY(event)];
  316.       var pos     = Position.cumulativeOffset(this.element);
  317.       this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
  318.       
  319.       Draggables.activate(this);
  320.       Event.stop(event);
  321.     }
  322.   },
  323.   
  324.   startDrag: function(event) {
  325.     this.dragging = true;
  326.     if(!this.delta)
  327.       this.delta = this.currentDelta();
  328.     
  329.     if(this.options.zindex) {
  330.       this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
  331.       this.element.style.zIndex = this.options.zindex;
  332.     }
  333.     
  334.     if(this.options.ghosting) {
  335.       this._clone = this.element.cloneNode(true);
  336.       this.element._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
  337.       if (!this.element._originallyAbsolute)
  338.         Position.absolutize(this.element);
  339.       this.element.parentNode.insertBefore(this._clone, this.element);
  340.     }
  341.     
  342.     if(this.options.scroll) {
  343.       if (this.options.scroll == window) {
  344.         var where = this._getWindowScroll(this.options.scroll);
  345.         this.originalScrollLeft = where.left;
  346.         this.originalScrollTop = where.top;
  347.       } else {
  348.         this.originalScrollLeft = this.options.scroll.scrollLeft;
  349.         this.originalScrollTop = this.options.scroll.scrollTop;
  350.       }
  351.     }
  352.     
  353.     Draggables.notify('onStart', this, event);
  354.         
  355.     if(this.options.starteffect) this.options.starteffect(this.element);
  356.   },
  357.   
  358.   updateDrag: function(event, pointer) {
  359.     if(!this.dragging) this.startDrag(event);
  360.     
  361.     if(!this.options.quiet){
  362.       Position.prepare();
  363.       Droppables.show(pointer, this.element);
  364.     }
  365.     
  366.     Draggables.notify('onDrag', this, event);
  367.     
  368.     this.draw(pointer);
  369.     if(this.options.change) this.options.change(this);
  370.     
  371.     if(this.options.scroll) {
  372.       this.stopScrolling();
  373.       
  374.       var p;
  375.       if (this.options.scroll == window) {
  376.         with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
  377.       } else {
  378.         p = Position.page(this.options.scroll);
  379.         p[0] += this.options.scroll.scrollLeft + Position.deltaX;
  380.         p[1] += this.options.scroll.scrollTop + Position.deltaY;
  381.         p.push(p[0]+this.options.scroll.offsetWidth);
  382.         p.push(p[1]+this.options.scroll.offsetHeight);
  383.       }
  384.       var speed = [0,0];
  385.       if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
  386.       if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
  387.       if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
  388.       if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
  389.       this.startScrolling(speed);
  390.     }
  391.     
  392.     // fix AppleWebKit rendering
  393.     if(Prototype.Browser.WebKit) window.scrollBy(0,0);
  394.     
  395.     Event.stop(event);
  396.   },
  397.   
  398.   finishDrag: function(event, success) {
  399.     this.dragging = false;
  400.     
  401.     if(this.options.quiet){
  402.       Position.prepare();
  403.       var pointer = [Event.pointerX(event), Event.pointerY(event)];
  404.       Droppables.show(pointer, this.element);
  405.     }
  406.  
  407.     if(this.options.ghosting) {
  408.       if (!this.element._originallyAbsolute)
  409.         Position.relativize(this.element);
  410.       delete this.element._originallyAbsolute;
  411.       Element.remove(this._clone);
  412.       this._clone = null;
  413.     }
  414.  
  415.     var dropped = false; 
  416.     if(success) { 
  417.       dropped = Droppables.fire(event, this.element); 
  418.       if (!dropped) dropped = false; 
  419.     }
  420.     if(dropped && this.options.onDropped) this.options.onDropped(this.element);
  421.     Draggables.notify('onEnd', this, event);
  422.  
  423.     var revert = this.options.revert;
  424.     if(revert && Object.isFunction(revert)) revert = revert(this.element);
  425.     
  426.     var d = this.currentDelta();
  427.     if(revert && this.options.reverteffect) {
  428.       if (dropped == 0 || revert != 'failure')
  429.         this.options.reverteffect(this.element,
  430.           d[1]-this.delta[1], d[0]-this.delta[0]);
  431.     } else {
  432.       this.delta = d;
  433.     }
  434.  
  435.     if(this.options.zindex)
  436.       this.element.style.zIndex = this.originalZ;
  437.  
  438.     if(this.options.endeffect) 
  439.       this.options.endeffect(this.element);
  440.       
  441.     Draggables.deactivate(this);
  442.     Droppables.reset();
  443.   },
  444.   
  445.   keyPress: function(event) {
  446.     if(event.keyCode!=Event.KEY_ESC) return;
  447.     this.finishDrag(event, false);
  448.     Event.stop(event);
  449.   },
  450.   
  451.   endDrag: function(event) {
  452.     if(!this.dragging) return;
  453.     this.stopScrolling();
  454.     this.finishDrag(event, true);
  455.     Event.stop(event);
  456.   },
  457.   
  458.   draw: function(point) {
  459.     var pos = Position.cumulativeOffset(this.element);
  460.     if(this.options.ghosting) {
  461.       var r   = Position.realOffset(this.element);
  462.       pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
  463.     }
  464.     
  465.     var d = this.currentDelta();
  466.     pos[0] -= d[0]; pos[1] -= d[1];
  467.     
  468.     if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
  469.       pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
  470.       pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
  471.     }
  472.     
  473.     var p = [0,1].map(function(i){ 
  474.       return (point[i]-pos[i]-this.offset[i]) 
  475.     }.bind(this));
  476.     
  477.     if(this.options.snap) {
  478.       if(Object.isFunction(this.options.snap)) {
  479.         p = this.options.snap(p[0],p[1],this);
  480.       } else {
  481.       if(Object.isArray(this.options.snap)) {
  482.         p = p.map( function(v, i) {
  483.           return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this))
  484.       } else {
  485.         p = p.map( function(v) {
  486.           return (v/this.options.snap).round()*this.options.snap }.bind(this))
  487.       }
  488.     }}
  489.     
  490.     var style = this.element.style;
  491.     if((!this.options.constraint) || (this.options.constraint=='horizontal'))
  492.       style.left = p[0] + "px";
  493.     if((!this.options.constraint) || (this.options.constraint=='vertical'))
  494.       style.top  = p[1] + "px";
  495.     
  496.     if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
  497.   },
  498.   
  499.   stopScrolling: function() {
  500.     if(this.scrollInterval) {
  501.       clearInterval(this.scrollInterval);
  502.       this.scrollInterval = null;
  503.       Draggables._lastScrollPointer = null;
  504.     }
  505.   },
  506.   
  507.   startScrolling: function(speed) {
  508.     if(!(speed[0] || speed[1])) return;
  509.     this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
  510.     this.lastScrolled = new Date();
  511.     this.scrollInterval = setInterval(this.scroll.bind(this), 10);
  512.   },
  513.   
  514.   scroll: function() {
  515.     var current = new Date();
  516.     var delta = current - this.lastScrolled;
  517.     this.lastScrolled = current;
  518.     if(this.options.scroll == window) {
  519.       with (this._getWindowScroll(this.options.scroll)) {
  520.         if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
  521.           var d = delta / 1000;
  522.           this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
  523.         }
  524.       }
  525.     } else {
  526.       this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
  527.       this.options.scroll.scrollTop  += this.scrollSpeed[1] * delta / 1000;
  528.     }
  529.     
  530.     Position.prepare();
  531.     Droppables.show(Draggables._lastPointer, this.element);
  532.     Draggables.notify('onDrag', this);
  533.     if (this._isScrollChild) {
  534.       Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
  535.       Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
  536.       Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
  537.       if (Draggables._lastScrollPointer[0] < 0)
  538.         Draggables._lastScrollPointer[0] = 0;
  539.       if (Draggables._lastScrollPointer[1] < 0)
  540.         Draggables._lastScrollPointer[1] = 0;
  541.       this.draw(Draggables._lastScrollPointer);
  542.     }
  543.     
  544.     if(this.options.change) this.options.change(this);
  545.   },
  546.   
  547.   _getWindowScroll: function(w) {
  548.     var T, L, W, H;
  549.     with (w.document) {
  550.       if (w.document.documentElement && documentElement.scrollTop) {
  551.         T = documentElement.scrollTop;
  552.         L = documentElement.scrollLeft;
  553.       } else if (w.document.body) {
  554.         T = body.scrollTop;
  555.         L = body.scrollLeft;
  556.       }
  557.       if (w.innerWidth) {
  558.         W = w.innerWidth;
  559.         H = w.innerHeight;
  560.       } else if (w.document.documentElement && documentElement.clientWidth) {
  561.         W = documentElement.clientWidth;
  562.         H = documentElement.clientHeight;
  563.       } else {
  564.         W = body.offsetWidth;
  565.         H = body.offsetHeight
  566.       }
  567.     }
  568.     return { top: T, left: L, width: W, height: H };
  569.   }
  570. });
  571.  
  572. Draggable._dragging = { };
  573.  
  574. /*--------------------------------------------------------------------------*/
  575.  
  576. var SortableObserver = Class.create({
  577.   initialize: function(element, observer) {
  578.     this.element   = $(element);
  579.     this.observer  = observer;
  580.     this.lastValue = Sortable.serialize(this.element);
  581.   },
  582.   
  583.   onStart: function() {
  584.     this.lastValue = Sortable.serialize(this.element);
  585.   },
  586.   
  587.   onEnd: function() {
  588.     Sortable.unmark();
  589.     if(this.lastValue != Sortable.serialize(this.element))
  590.       this.observer(this.element)
  591.   }
  592. });
  593.  
  594. var Sortable = {
  595.   SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
  596.   
  597.   sortables: { },
  598.   
  599.   _findRootElement: function(element) {
  600.     while (element.tagName.toUpperCase() != "BODY") {  
  601.       if(element.id && Sortable.sortables[element.id]) return element;
  602.       element = element.parentNode;
  603.     }
  604.   },
  605.  
  606.   options: function(element) {
  607.     element = Sortable._findRootElement($(element));
  608.     if(!element) return;
  609.     return Sortable.sortables[element.id];
  610.   },
  611.   
  612.   destroy: function(element){
  613.     var s = Sortable.options(element);
  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.id] = 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 = Position.cumulativeOffset(dropon);
  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. }
  975.