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