Changeset 485

Show
Ignore:
Timestamp:
08/18/06 13:53:26
Author:
piv
Message:

added sliding, url processing, visibility functionality under developing

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • qPloneTabs/tags/0.2.1/__init__.py

    r484 r485  
    88 
    99from Products.qPloneTabs.utils import getPortalTabs 
     10from Products.qPloneTabs.utils import getRootTabs 
  • qPloneTabs/tags/0.2.1/skins/qPloneTabs/javascripts/controls.js

    r484 r485  
    142142      } 
    143143     else  
    144       if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN)  
    145         return; 
     144      if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||  
     145        (navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) return; 
    146146 
    147147    this.changed = true; 
     
    151151      this.observer =  
    152152        setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); 
     153  }, 
     154 
     155  activate: function() { 
     156    this.changed = false; 
     157    this.hasFocus = true; 
     158    this.getUpdatedChoices(); 
    153159  }, 
    154160 
     
    222228      return; 
    223229    } 
    224  
    225     var value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); 
     230    var value = ''; 
     231    if (this.options.select) { 
     232      var nodes = document.getElementsByClassName(this.options.select, selectedElement) || []; 
     233      if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); 
     234    } else 
     235      value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); 
     236     
    226237    var lastTokenPos = this.findLastToken(); 
    227238    if (lastTokenPos != -1) { 
     
    306317Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), { 
    307318  initialize: function(element, update, url, options) { 
    308          this.baseInitialize(element, update, options); 
     319    this.baseInitialize(element, update, options); 
    309320    this.options.asynchronous  = true; 
    310321    this.options.onComplete    = this.onComplete.bind(this); 
     
    449460 
    450461    this.options = Object.extend({ 
     462      okButton: true, 
    451463      okText: "ok", 
     464      cancelLink: true, 
    452465      cancelText: "cancel", 
    453466      savingText: "Saving...", 
     
    471484      highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor, 
    472485      highlightendcolor: "#FFFFFF", 
    473       externalControl:  null, 
    474       ajaxOptions: {} 
     486      externalControl: null, 
     487      submitOnBlur: false, 
     488      ajaxOptions: {}, 
     489      evalScripts: false 
    475490    }, options || {}); 
    476491 
     
    537552    } 
    538553 
    539     okButton = document.createElement("input"); 
    540     okButton.type = "submit"; 
    541     okButton.value = this.options.okText; 
    542     this.form.appendChild(okButton); 
    543  
    544     cancelLink = document.createElement("a"); 
    545     cancelLink.href = "#"; 
    546     cancelLink.appendChild(document.createTextNode(this.options.cancelText)); 
    547     cancelLink.onclick = this.onclickCancel.bind(this); 
    548     this.form.appendChild(cancelLink); 
     554    if (this.options.okButton) { 
     555      okButton = document.createElement("input"); 
     556      okButton.type = "submit"; 
     557      okButton.value = this.options.okText; 
     558      okButton.className = 'editor_ok_button'; 
     559      this.form.appendChild(okButton); 
     560    } 
     561 
     562    if (this.options.cancelLink) { 
     563      cancelLink = document.createElement("a"); 
     564      cancelLink.href = "#"; 
     565      cancelLink.appendChild(document.createTextNode(this.options.cancelText)); 
     566      cancelLink.onclick = this.onclickCancel.bind(this); 
     567      cancelLink.className = 'editor_cancel';       
     568      this.form.appendChild(cancelLink); 
     569    } 
    549570  }, 
    550571  hasHTMLLineBreaks: function(string) { 
     
    562583      text = this.getText(); 
    563584    } 
     585 
     586    var obj = this; 
    564587     
    565588    if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) { 
    566589      this.options.textarea = false; 
    567590      var textField = document.createElement("input"); 
     591      textField.obj = this; 
    568592      textField.type = "text"; 
    569593      textField.name = "value"; 
    570594      textField.value = text; 
    571595      textField.style.backgroundColor = this.options.highlightcolor; 
     596      textField.className = 'editor_field'; 
    572597      var size = this.options.size || this.options.cols || 0; 
    573598      if (size != 0) textField.size = size; 
     599      if (this.options.submitOnBlur) 
     600        textField.onblur = this.onSubmit.bind(this); 
    574601      this.editField = textField; 
    575602    } else { 
    576603      this.options.textarea = true; 
    577604      var textArea = document.createElement("textarea"); 
     605      textArea.obj = this; 
    578606      textArea.name = "value"; 
    579607      textArea.value = this.convertHTMLLineBreaks(text); 
    580608      textArea.rows = this.options.rows; 
    581609      textArea.cols = this.options.cols || 40; 
     610      textArea.className = 'editor_field';       
     611      if (this.options.submitOnBlur) 
     612        textArea.onblur = this.onSubmit.bind(this); 
    582613      this.editField = textArea; 
    583614    } 
     
    630661    this.onLoading(); 
    631662     
    632     new Ajax.Updater( 
    633       {  
    634         success: this.element, 
    635          // don't update on failure (this could be an option) 
    636         failure: null 
    637       }, 
    638       this.url, 
    639       Object.extend({ 
    640         parameters: this.options.callback(form, value), 
    641         onComplete: this.onComplete.bind(this), 
    642         onFailure: this.onFailure.bind(this) 
    643       }, this.options.ajaxOptions) 
    644     ); 
     663    if (this.options.evalScripts) { 
     664      new Ajax.Request( 
     665        this.url, Object.extend({ 
     666          parameters: this.options.callback(form, value), 
     667          onComplete: this.onComplete.bind(this), 
     668          onFailure: this.onFailure.bind(this), 
     669          asynchronous:true,  
     670          evalScripts:true 
     671        }, this.options.ajaxOptions)); 
     672    } else  { 
     673      new Ajax.Updater( 
     674        { success: this.element, 
     675          // don't update on failure (this could be an option) 
     676          failure: null },  
     677        this.url, Object.extend({ 
     678          parameters: this.options.callback(form, value), 
     679          onComplete: this.onComplete.bind(this), 
     680          onFailure: this.onFailure.bind(this) 
     681        }, this.options.ajaxOptions)); 
     682    } 
    645683    // stop the event to avoid a page refresh in Safari 
    646684    if (arguments.length > 1) { 
     
    723761  } 
    724762}; 
     763 
     764Ajax.InPlaceCollectionEditor = Class.create(); 
     765Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype); 
     766Object.extend(Ajax.InPlaceCollectionEditor.prototype, { 
     767  createEditField: function() { 
     768    if (!this.cached_selectTag) { 
     769      var selectTag = document.createElement("select"); 
     770      var collection = this.options.collection || []; 
     771      var optionTag; 
     772      collection.each(function(e,i) { 
     773        optionTag = document.createElement("option"); 
     774        optionTag.value = (e instanceof Array) ? e[0] : e; 
     775        if(this.options.value==optionTag.value) optionTag.selected = true; 
     776        optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e)); 
     777        selectTag.appendChild(optionTag); 
     778      }.bind(this)); 
     779      this.cached_selectTag = selectTag; 
     780    } 
     781 
     782    this.editField = this.cached_selectTag; 
     783    if(this.options.loadTextURL) this.loadExternalText(); 
     784    this.form.appendChild(this.editField); 
     785    this.options.callback = function(form, value) { 
     786      return "value=" + encodeURIComponent(value); 
     787    } 
     788  } 
     789}); 
    725790 
    726791// Delayed observer, like Form.Element.Observer,  
  • qPloneTabs/tags/0.2.1/skins/qPloneTabs/javascripts/dragdrop.js

    r484 r485  
    11// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) 
     2//           (c) 2005 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz) 
    23//  
    34// See scriptaculous.js for full license. 
     
    1617    var options = Object.extend({ 
    1718      greedy:     true, 
    18       hoverclass: null   
     19      hoverclass: null, 
     20      tree:       false 
    1921    }, arguments[1] || {}); 
    2022 
     
    3840    this.drops.push(options); 
    3941  }, 
     42   
     43  findDeepestChild: function(drops) { 
     44    deepest = drops[0]; 
     45       
     46    for (i = 1; i < drops.length; ++i) 
     47      if (Element.isParent(drops[i].element, deepest.element)) 
     48        deepest = drops[i]; 
     49     
     50    return deepest; 
     51  }, 
    4052 
    4153  isContained: function(element, drop) { 
    42     var parentNode = element.parentNode; 
    43     return drop._containers.detect(function(c) { return parentNode == c }); 
    44   }, 
    45  
    46   isAffected: function(pX, pY, element, drop) { 
     54    var containmentNode; 
     55    if(drop.tree) { 
     56      containmentNode = element.treeNode;  
     57    } else { 
     58      containmentNode = element.parentNode; 
     59    } 
     60    return drop._containers.detect(function(c) { return containmentNode == c }); 
     61  }, 
     62   
     63  isAffected: function(point, element, drop) { 
    4764    return ( 
    4865      (drop.element!=element) && 
     
    5269        (Element.classNames(element).detect(  
    5370          function(v) { return drop.accept.include(v) } ) )) && 
    54       Position.within(drop.element, pX, pY) ); 
     71      Position.within(drop.element, point[0], point[1]) ); 
    5572  }, 
    5673 
     
    6279 
    6380  activate: function(drop) { 
    64     if(this.last_active) this.deactivate(this.last_active); 
    6581    if(drop.hoverclass) 
    6682      Element.addClassName(drop.element, drop.hoverclass); 
     
    6884  }, 
    6985 
    70   show: function(event, element) { 
     86  show: function(point, element) { 
    7187    if(!this.drops.length) return; 
    72     var pX = Event.pointerX(event); 
    73     var pY = Event.pointerY(event); 
    74     Position.prepare(); 
    75  
    76     var i = this.drops.length-1; do { 
    77       var drop = this.drops[i]; 
    78       if(this.isAffected(pX, pY, element, drop)) { 
    79         if(drop.onHover) 
    80            drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); 
    81         if(drop.greedy) {  
    82           this.activate(drop); 
    83           return; 
    84         } 
    85       } 
    86     } while (i--); 
     88    var affected = []; 
    8789     
    8890    if(this.last_active) this.deactivate(this.last_active); 
     91    this.drops.each( function(drop) { 
     92      if(Droppables.isAffected(point, element, drop)) 
     93        affected.push(drop); 
     94    }); 
     95         
     96    if(affected.length>0) { 
     97      drop = Droppables.findDeepestChild(affected); 
     98      Position.within(drop.element, point[0], point[1]); 
     99      if(drop.onHover) 
     100        drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); 
     101       
     102      Droppables.activate(drop); 
     103    } 
    89104  }, 
    90105 
     
    93108    Position.prepare(); 
    94109 
    95     if (this.isAffected(Event.pointerX(event), Event.pointerY(event), element, this.last_active)) 
     110    if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active)) 
    96111      if (this.last_active.onDrop)  
    97112        this.last_active.onDrop(element, this.last_active.element, event); 
     
    105120 
    106121var Draggables = { 
     122  drags: [], 
    107123  observers: [], 
     124   
     125  register: function(draggable) { 
     126    if(this.drags.length == 0) { 
     127      this.eventMouseUp   = this.endDrag.bindAsEventListener(this); 
     128      this.eventMouseMove = this.updateDrag.bindAsEventListener(this); 
     129      this.eventKeypress  = this.keyPress.bindAsEventListener(this); 
     130       
     131      Event.observe(document, "mouseup", this.eventMouseUp); 
     132      Event.observe(document, "mousemove", this.eventMouseMove); 
     133      Event.observe(document, "keypress", this.eventKeypress); 
     134    } 
     135    this.drags.push(draggable); 
     136  }, 
     137   
     138  unregister: function(draggable) { 
     139    this.drags = this.drags.reject(function(d) { return d==draggable }); 
     140    if(this.drags.length == 0) { 
     141      Event.stopObserving(document, "mouseup", this.eventMouseUp); 
     142      Event.stopObserving(document, "mousemove", this.eventMouseMove); 
     143      Event.stopObserving(document, "keypress", this.eventKeypress); 
     144    } 
     145  }, 
     146   
     147  activate: function(draggable) { 
     148    window.focus(); // allows keypress events if window isn't currently focused, fails for Safari 
     149    this.activeDraggable = draggable; 
     150  }, 
     151   
     152  deactivate: function() { 
     153    this.activeDraggable = null; 
     154  }, 
     155   
     156  updateDrag: function(event) { 
     157    if(!this.activeDraggable) return; 
     158    var pointer = [Event.pointerX(event), Event.pointerY(event)]; 
     159    // Mozilla-based browsers fire successive mousemove events with 
     160    // the same coordinates, prevent needless redrawing (moz bug?) 
     161    if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; 
     162    this._lastPointer = pointer; 
     163    this.activeDraggable.updateDrag(event, pointer); 
     164  }, 
     165   
     166  endDrag: function(event) { 
     167    if(!this.activeDraggable) return; 
     168    this._lastPointer = null; 
     169    this.activeDraggable.endDrag(event); 
     170    this.activeDraggable = null; 
     171  }, 
     172   
     173  keyPress: function(event) { 
     174    if(this.activeDraggable) 
     175      this.activeDraggable.keyPress(event); 
     176  }, 
     177   
    108178  addObserver: function(observer) { 
    109179    this.observers.push(observer); 
    110180    this._cacheObserverCallbacks(); 
    111181  }, 
     182   
    112183  removeObserver: function(element) {  // element instead of observer fixes mem leaks 
    113184    this.observers = this.observers.reject( function(o) { return o.element==element }); 
    114185    this._cacheObserverCallbacks(); 
    115186  }, 
     187   
    116188  notify: function(eventName, draggable, event) {  // 'onStart', 'onEnd', 'onDrag' 
    117189    if(this[eventName+'Count'] > 0) 
     
    120192      }); 
    121193  }, 
     194   
    122195  _cacheObserverCallbacks: function() { 
    123196    ['onStart','onEnd','onDrag'].each( function(eventName) { 
     
    136209    var options = Object.extend({ 
    137210      handle: false, 
    138       starteffect: function(element) {  
    139         new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7});  
     211      starteffect: function(element) { 
     212        element._opacity = Element.getOpacity(element);  
     213        new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});  
    140214      }, 
    141215      reverteffect: function(element, top_offset, left_offset) { 
    142216        var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; 
    143         new Effect.MoveBy(element, -top_offset, -left_offset, {duration:dur}); 
     217        element._revert = new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur}); 
    144218      }, 
    145       endeffect: function(element) {  
    146          new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0});  
     219      endeffect: function(element) { 
     220        var toOpacity = typeof element._opacity == 'number' ? element._opacity : 1.0 
     221        new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity});  
    147222      }, 
    148223      zindex: 1000, 
    149224      revert: false, 
     225      scroll: false, 
     226      scrollSensitivity: 20, 
     227      scrollSpeed: 15, 
    150228      snap: false   // false, or xy or [x,y] or function(x,y){ return [x,y] } 
    151229    }, arguments[1] || {}); 
    152230 
    153     this.element      = $(element); 
    154     if(options.handle && (typeof options.handle == 'string')) 
    155       this.handle = Element.childrenWithClassName(this.element, options.handle)[0]; 
    156        
     231    this.element = $(element); 
     232     
     233    if(options.handle && (typeof options.handle == 'string')) { 
     234      var h = Element.childrenWithClassName(this.element, options.handle, true); 
     235      if(h.length>0) this.handle = h[0]; 
     236    } 
    157237    if(!this.handle) this.handle = $(options.handle); 
    158238    if(!this.handle) this.handle = this.element; 
     239     
     240    if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) 
     241      options.scroll = $(options.scroll); 
    159242 
    160243    Element.makePositioned(this.element); // fix IE     
    161244 
    162     this.offsetX      = 0; 
    163     this.offsetY      = 0; 
    164     this.originalLeft = this.currentLeft(); 
    165     this.originalTop  = this.currentTop(); 
    166     this.originalX    = this.element.offsetLeft; 
    167     this.originalY    = this.element.offsetTop; 
    168  
    169     this.options      = options; 
    170  
    171     this.active       = false; 
    172     this.dragging     = false;    
    173  
    174     this.eventMouseDown = this.startDrag.bindAsEventListener(this); 
    175     this.eventMouseUp   = this.endDrag.bindAsEventListener(this); 
    176     this.eventMouseMove = this.update.bindAsEventListener(this); 
    177     this.eventKeypress  = this.keyPress.bindAsEventListener(this); 
    178      
    179     this.registerEvents(); 
    180   }, 
     245    this.delta    = this.currentDelta(); 
     246    this.options  = options; 
     247    this.dragging = false;    
     248 
     249    this.eventMouseDown = this.initDrag.bindAsEventListener(this); 
     250    Event.observe(this.handle, "mousedown", this.eventMouseDown); 
     251     
     252    Draggables.register(this); 
     253  }, 
     254   
    181255  destroy: function() { 
    182256    Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); 
    183     this.unregisterEvents(); 
    184   }, 
    185   registerEvents: function() { 
    186     Event.observe(document, "mouseup", this.eventMouseUp); 
    187     Event.observe(document, "mousemove", this.eventMouseMove); 
    188     Event.observe(document, "keypress", this.eventKeypress); 
    189     Event.observe(this.handle, "mousedown", this.eventMouseDown); 
    190   }, 
    191   unregisterEvents: function() { 
    192     //if(!this.active) return; 
    193     //Event.stopObserving(document, "mouseup", this.eventMouseUp); 
    194     //Event.stopObserving(document, "mousemove", this.eventMouseMove); 
    195     //Event.stopObserving(document, "keypress", this.eventKeypress); 
    196   }, 
    197   currentLeft: function() { 
    198     return parseInt(this.element.style.left || '0'); 
    199   }, 
    200   currentTop: function() { 
    201     return parseInt(this.element.style.top || '0') 
    202   }, 
    203   startDrag: function(event) { 
    204     if(Event.isLeftClick(event)) { 
    205        
     257    Draggables.unregister(this); 
     258  }, 
     259   
     260  currentDelta: function() { 
     261    return([ 
     262      parseInt(Element.getStyle(this.element,'left') || '0'), 
     263      parseInt(Element.getStyle(this.element,'top') || '0')]); 
     264  }, 
     265   
     266  initDrag: function(event) { 
     267    if(Event.isLeftClick(event)) {     
    206268      // abort on form elements, fixes a Firefox issue 
    207269      var src = Event.element(event); 
     
    209271        src.tagName=='INPUT' || 
    210272        src.tagName=='SELECT' || 
     273        src.tagName=='OPTION' || 
    211274        src.tagName=='BUTTON' || 
    212275        src.tagName=='TEXTAREA')) return; 
    213        
    214       // this.registerEvents(); 
    215       this.active = true; 
     276         
     277      if(this.element._revert) { 
     278        this.element._revert.cancel(); 
     279        this.element._revert = null; 
     280      } 
     281       
    216282      var pointer = [Event.pointerX(event), Event.pointerY(event)]; 
    217       var offsets = Position.cumulativeOffset(this.element); 
    218       this.offsetX =  (pointer[0] - offsets[0]); 
    219       this.offsetY =  (pointer[1] - offsets[1]); 
     283      var pos     = Position.cumulativeOffset(this.element); 
     284      this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); 
     285       
     286      Draggables.activate(this); 
    220287      Event.stop(event); 
    221288    } 
    222289  }, 
     290   
     291  startDrag: function(event) { 
     292    this.dragging = true; 
     293     
     294    if(this.options.zindex) { 
     295      this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); 
     296      this.element.style.zIndex = this.options.zindex; 
     297    } 
     298     
     299    if(this.options.ghosting) { 
     300      this._clone = this.element.cloneNode(true); 
     301      Position.absolutize(this.element); 
     302      this.element.parentNode.insertBefore(this._clone, this.element); 
     303    } 
     304     
     305    if(this.options.scroll) { 
     306      if (this.options.scroll == window) { 
     307        var where = this._getWindowScroll(this.options.scroll); 
     308        this.originalScrollLeft = where.left; 
     309        this.originalScrollTop = where.top; 
     310      } else { 
     311        this.originalScrollLeft = this.options.scroll.scrollLeft; 
     312        this.originalScrollTop = this.options.scroll.scrollTop; 
     313      } 
     314    } 
     315     
     316    Draggables.notify('onStart', this, event); 
     317    if(this.options.starteffect) this.options.starteffect(this.element); 
     318  }, 
     319   
     320  updateDrag: function(event, pointer) { 
     321    if(!this.dragging) this.startDrag(event); 
     322    Position.prepare(); 
     323    Droppables.show(pointer, this.element); 
     324    Draggables.notify('onDrag', this, event); 
     325    this.draw(pointer); 
     326    if(this.options.change) this.options.change(this); 
     327     
     328    if(this.options.scroll) { 
     329      this.stopScrolling(); 
     330       
     331      var p; 
     332      if (this.options.scroll == window) { 
     333        with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; } 
     334      } else { 
     335        p = Position.page(this.options.scroll); 
     336        p[0] += this.options.scroll.scrollLeft; 
     337        p[1] += this.options.scroll.scrollTop; 
     338        p.push(p[0]+this.options.scroll.offsetWidth); 
     339        p.push(p[1]+this.options.scroll.offsetHeight); 
     340      } 
     341      var speed = [0,0]; 
     342      if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); 
     343      if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); 
     344      if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); 
     345      if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); 
     346      this.startScrolling(speed); 
     347    } 
     348     
     349    // fix AppleWebKit rendering 
     350    if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); 
     351     
     352    Event.stop(event); 
     353  }, 
     354   
    223355  finishDrag: function(event, success) { 
    224     // this.unregisterEvents(); 
    225  
    226     this.active = false; 
    227356    this.dragging = false; 
    228357 
     
    238367    var revert = this.options.revert; 
    239368    if(revert && typeof revert == 'function') revert = revert(this.element); 
    240  
     369     
     370    var d = this.currentDelta(); 
    241371    if(revert && this.options.reverteffect) { 
    242372      this.options.reverteffect(this.element,  
    243       this.currentTop()-this.originalTop, 
    244       this.currentLeft()-this.originalLeft); 
     373        d[1]-this.delta[1], d[0]-this.delta[0]); 
    245374    } else { 
    246       this.originalLeft = this.currentLeft(); 
    247       this.originalTop  = this.currentTop(); 
     375      this.delta = d; 
    248376    } 
    249377 
     
    254382      this.options.endeffect(this.element); 
    255383 
    256  
     384    Draggables.deactivate(this); 
    257385    Droppables.reset(); 
    258386  }, 
     387   
    259388  keyPress: function(event) { 
    260     if(this.active) { 
    261       if(event.keyCode==Event.KEY_ESC) { 
    262         this.finishDrag(event, false); 
    263         Event.stop(event); 
    264       } 
    265     } 
    266   }, 
     389    if(event.keyCode!=Event.KEY_ESC) return; 
     390    this.finishDrag(event, false); 
     391    Event.stop(event); 
     392  }, 
     393   
    267394  endDrag: function(event) { 
    268     if(this.active && this.dragging) { 
    269       this.finishDrag(event, true); 
    270       Event.stop(event); 
    271     } 
    272     this.active = false; 
    273     this.dragging = false; 
    274   }, 
    275   draw: function(event) { 
    276     var pointer = [Event.pointerX(event), Event.pointerY(event)]; 
    277     var offsets = Position.cumulativeOffset(this.element); 
    278     offsets[0] -= this.currentLeft(); 
    279     offsets[1] -= this.currentTop(); 
    280     var style = this.element.style; 
    281      
    282     var pos = [ 
    283       (pointer[0] - offsets[0] - this.offsetX), 
    284       (pointer[1] - offsets[1] - this.offsetY)]; 
     395    if(!this.dragging) return; 
     396    this.stopScrolling(); 
     397    this.finishDrag(event, true); 
     398    Event.stop(event); 
     399  }, 
     400   
     401  draw: function(point) { 
     402    var pos = Position.cumulativeOffset(this.element); 
     403    var d = this.currentDelta(); 
     404    pos[0] -= d[0]; pos[1] -= d[1]; 
     405     
     406    if(this.options.scroll && (this.options.scroll != window)) { 
     407      pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft; 
     408      pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; 
     409    } 
     410     
     411    var p = [0,1].map(function(i){  
     412      return (point[i]-pos[i]-this.offset[i])  
     413    }.bind(this)); 
    285414     
    286415    if(this.options.snap) { 
    287416      if(typeof this.options.snap == 'function') { 
    288         pos = this.options.snap(pos[0],pos[1]); 
     417        p = this.options.snap(p[0],p[1],this); 
    289418      } else { 
    290       var draggable = this; 
    291419      if(this.options.snap instanceof Array) { 
    292         pos = pos.collect( function(v, i) { 
    293           return Math.round(v/draggable.options.snap[i])*draggable.options.snap[i] }
     420        p = p.map( function(v, i) { 
     421          return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this)
    294422      } else { 
    295         pos = pos.collect( function(v) { 
    296           return Math.round(v/draggable.options.snap)*draggable.options.snap }
     423        p = p.map( function(v) { 
     424          return Math.round(v/this.options.snap)*this.options.snap }.bind(this)
    297425      } 
    298426    }} 
    299427     
     428    var style = this.element.style; 
    300429    if((!this.options.constraint) || (this.options.constraint=='horizontal')) 
    301       style.left = pos[0] + "px"; 
     430      style.left = p[0] + "px"; 
    302431    if((!this.options.constraint) || (this.options.constraint=='vertical')) 
    303       style.top  = pos[1] + "px"; 
     432      style.top  = p[1] + "px"; 
    304433    if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering 
    305434  }, 
    306   update: function(event) { 
    307    if(this.active) { 
    308       if(!this.dragging) { 
    309         var style = this.element.style; 
    310         this.dragging = true; 
    311          
    312         if(Element.getStyle(this.element,'position')=='')  
    313           style.position = "relative"; 
    314          
    315         if(this.options.zindex) { 
    316           this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); 
    317           style.zIndex = this.options.zindex; 
     435   
     436  stopScrolling: function() { 
     437    if(this.scrollInterval) { 
     438      clearInterval(this.scrollInterval); 
     439      this.scrollInterval = null; 
     440      Draggables._lastScrollPointer = null; 
     441    } 
     442  }, 
     443   
     444  startScrolling: function(speed) { 
     445    this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed]; 
     446    this.lastScrolled = new Date(); 
     447    this.scrollInterval = setInterval(this.scroll.bind(this), 10); 
     448  }, 
     449   
     450  scroll: function() { 
     451    var current = new Date(); 
     452    var delta = current - this.lastScrolled; 
     453    this.lastScrolled = current; 
     454    if(this.options.scroll == window) { 
     455      with (this._getWindowScroll(this.options.scroll)) { 
     456        if (this.scrollSpeed[0] || this.scrollSpeed[1]) { 
     457          var d = delta / 1000; 
     458          this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); 
    318459        } 
    319  
    320         if(this.options.ghosting) { 
    321           this._clone = this.element.cloneNode(true); 
    322           Position.absolutize(this.element); 
    323           this.element.parentNode.insertBefore(this._clone, this.element); 
    324         } 
    325  
    326         Draggables.notify('onStart', this, event); 
    327         if(this.options.starteffect) this.options.starteffect(this.element); 
    328       } 
    329  
    330       Droppables.show(event, this.element); 
    331       Draggables.notify('onDrag', this, event); 
    332       this.draw(event); 
    333       if(this.options.change) this.options.change(this); 
    334  
    335       // fix AppleWebKit rendering 
    336       if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);  
    337  
    338       Event.stop(event); 
    339    } 
     460      } 
     461    } else { 
     462      this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; 
     463      this.options.scroll.scrollTop  += this.scrollSpeed[1] * delta / 1000; 
     464    } 
     465     
     466    Position.prepare(); 
     467    Droppables.show(Draggables._lastPointer, this.element); 
     468    Draggables.notify('onDrag', this); 
     469    Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); 
     470    Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; 
     471    Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; 
     472    if (Draggables._lastScrollPointer[0] < 0) 
     473      Draggables._lastScrollPointer[0] = 0; 
     474    if (Draggables._lastScrollPointer[1] < 0) 
     475      Draggables._lastScrollPointer[1] = 0; 
     476    this.draw(Draggables._lastScrollPointer); 
     477     
     478    if(this.options.change) this.options.change(this); 
     479  }, 
     480   
     481  _getWindowScroll: function(w) { 
     482    var T, L, W, H; 
     483    with (w.document) { 
     484      if (w.document.documentElement && documentElement.scrollTop) { 
     485        T = documentElement.scrollTop; 
     486        L = documentElement.scrollLeft; 
     487      } else if (w.document.body) { 
     488        T = body.scrollTop; 
     489        L = body.scrollLeft; 
     490      } 
     491      if (w.innerWidth) { 
     492        W = w.innerWidth; 
     493        H = w.innerHeight; 
     494      } else if (w.document.documentElement && documentElement.clientWidth) { 
     495        W = documentElement.clientWidth; 
     496        H = documentElement.clientHeight; 
     497      } else { 
     498        W = body.offsetWidth; 
     499        H = body.offsetHeight 
     500      } 
     501    } 
     502    return { top: T, left: L, width: W, height: H }; 
    340503  } 
    341504} 
     
    350513    this.lastValue = Sortable.serialize(this.element); 
    351514  }, 
     515   
    352516  onStart: function() { 
    353517    this.lastValue = Sortable.serialize(this.element); 
    354518  }, 
     519   
    355520  onEnd: function() { 
    356521    Sortable.unmark(); 
     
    361526 
    362527var Sortable = { 
    363   sortables: new Array(), 
    364   options: function(element){ 
    365     element = $(element); 
    366     return this.sortables.detect(function(s) { return s.element == element }); 
    367   }, 
     528  sortables: {}, 
     529   
     530  _findRootElement: function(element) { 
     531    while (element.tagName != "BODY") {   
     532      if(element.id && Sortable.sortables[element.id]) return element; 
     533      element = element.parentNode; 
     534    } 
     535  }, 
     536 
     537  options: function(element) { 
     538    element = Sortable._findRootElement($(element)); 
     539    if(!element) return; 
     540    return Sortable.sortables[element.id]; 
     541  }, 
     542   
    368543  destroy: function(element){ 
    369     element = $(element); 
    370     this.sortables.findAll(function(s) { return s.element == element }).each(function(s){ 
     544    var s = Sortable.options(element); 
     545     
     546    if(s) { 
    371547      Draggables.removeObserver(s.element); 
    372548      s.droppables.each(function(d){ Droppables.remove(d) }); 
    373549      s.draggables.invoke('destroy'); 
    374     }); 
    375     this.sortables = this.sortables.reject(function(s) { return s.element == element }); 
    376   }, 
     550       
     551      delete Sortable.sortables[s.element.id]; 
     552    } 
     553  }, 
     554 
    377555  create: function(element) { 
    378556    element = $(element); 
     
    381559      tag:         'li',       // assumes li children, override with tag: 'tagname' 
    382560      dropOnEmpty: false, 
    383       tree:        false,      // fixme: unimplemented 
     561      tree:        false, 
     562      treeTag:     'ul', 
    384563      overlap:     'vertical', // one of 'vertical', 'horizontal' 
    385564      constraint:  'vertical', // one of 'vertical', 'horizontal', false 
     
    389568      hoverclass:  null, 
    390569      ghosting:    false, 
    391       format:      null, 
     570      scroll:      false, 
     571      scrollSensitivity: 20, 
     572      scrollSpeed: 15, 
     573      format:      /^[^_]*_(.*)$/, 
    392574      onChange:    Prototype.emptyFunction, 
    393575      onUpdate:    Prototype.emptyFunction 
     
    400582    var options_for_draggable = { 
    401583      revert:      true, 
     584      scroll:      options.scroll, 
     585      scrollSpeed: options.scrollSpeed, 
     586      scrollSensitivity: options.scrollSensitivity, 
    402587      ghosting:    options.ghosting, 
    403588      constraint:  options.constraint, 
     
    425610      overlap:     options.overlap, 
    426611      containment: options.containment, 
     612      tree:        options.tree, 
    427613      hoverclass:  options.hoverclass, 
    428       onHover:     Sortable.onHover, 
    429       greedy:      !options.dropOnEmpty 
     614      onHover:     Sortable.onHover 
     615      //greedy:      !options.dropOnEmpty 
     616    } 
     617     
     618    var options_for_tree = { 
     619      onHover:      Sortable.onEmptyHover, 
     620      overlap:      options.overlap, 
     621      containment:  options.containment, 
     622      hoverclass:   options.hoverclass 
    430623    } 
    431624 
     
    436629    options.droppables = []; 
    437630 
    438     // make it so 
    439  
    440631    // drop on empty handling 
    441     if(options.dropOnEmpty) { 
    442       Droppables.add(element, 
    443         {containment: options.containment, onHover: Sortable.onEmptyHover, greedy: false}); 
     632    if(options.dropOnEmpty || options.tree) { 
     633      Droppables.add(element, options_for_tree); 
    444634      options.droppables.push(element); 
    445635    } 
     
    452642        new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); 
    453643      Droppables.add(e, options_for_droppable); 
     644      if(options.tree) e.treeNode = element; 
    454645      options.droppables.push(e);       
    455646    }); 
     647     
     648    if(options.tree) { 
     649      (Sortable.findTreeElements(element, options) || []).each( function(e) { 
     650        Droppables.add(e, options_for_tree); 
     651        e.treeNode = element; 
     652        options.droppables.push(e); 
     653      }); 
     654    } 
    456655 
    457656    // keep reference 
    458     this.sortables.push(options)
     657    this.sortables[element.id] = options
    459658 
    460659    // for onupdate 
     
    465664  // return all suitable-for-sortable elements in a guaranteed order 
    466665  findElements: function(element, options) { 
    467     if(!element.hasChildNodes()) return null; 
    468     var elements = []; 
    469     $A(element.childNodes).each( function(e) { 
    470       if(e.tagName && e.tagName==options.tag.toUpperCase() && 
    471         (!options.only || (Element.hasClassName(e, options.only)))) 
    472           elements.push(e); 
    473       if(options.tree) { 
    474         var grandchildren = this.findElements(e, options); 
    475         if(grandchildren) elements.push(grandchildren); 
    476       } 
    477     }); 
    478  
    479     return (elements.length>0 ? elements.flatten() : null); 
     666    return Element.findChildren( 
     667      element, options.only, options.tree ? true : false, options.tag); 
     668  }, 
     669   
     670  findTreeElements: function(element, options) { 
     671    return Element.findChildren( 
     672      element, options.only, options.tree ? true : false, options.treeTag); 
    480673  }, 
    481674 
    482675  onHover: function(element, dropon, overlap) { 
    483     if(overlap>0.5) { 
     676    if(Element.isParent(dropon, element)) return; 
     677 
     678    if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { 
     679      return; 
     680    } else if(overlap>0.5) { 
    484681      Sortable.mark(dropon, 'before'); 
    485682      if(dropon.previousSibling != element) { 
     
    504701    } 
    505702  }, 
    506  
    507   onEmptyHover: function(element, dropon) { 
    508     if(element.parentNode!=dropon) { 
    509       var oldParentNode = element.parentNode; 
    510       dropon.appendChild(element); 
     703   
     704  onEmptyHover: function(element, dropon, overlap) { 
     705    var oldParentNode = element.parentNode; 
     706    var droponOptions = Sortable.options(dropon); 
     707         
     708    if(!Element.isParent(dropon, element)) { 
     709      var index; 
     710       
     711      var children = Sortable.findElements(dropon, {tag: droponOptions.tag}); 
     712      var child = null; 
     713             
     714      if(children) { 
     715        var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap); 
     716         
     717        for (index = 0; index < children.length; index += 1) { 
     718          if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) { 
     719            offset -= Element.offsetSize (children[index], droponOptions.overlap); 
     720          } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { 
     721            child = index + 1 < children.length ? children[index + 1] : null; 
     722            break; 
     723          } else { 
     724            child = children[index]; 
     725            break; 
     726          } 
     727        } 
     728      } 
     729       
     730      dropon.insertBefore(element, child); 
     731       
    511732      Sortable.options(oldParentNode).onChange(element); 
    512       Sortable.options(dropon)</