// supplements.js v1.0
//
// Copyright (c) 2007 stickmanlabs
// Author: Kevin P Miller | http://www.stickmanlabs.com
// 
// Supplements is freely distributable under the terms of an MIT-style license.
//
// This file contains class extensions and new methods for Prototype/Scriptaculous
//

/*-----------------------------------------------------------------------------------------------*/

//
//  Extends Draggable to allow for dragging an item out of a parent container with the overflow set to 'hidden' or 'scroll'
//
//	Usage:
//		overflow: true 		// Just add this option
//
Draggable.prototype = Object.extend(Draggable.prototype, {
  initialize: function(element) {
    var defaults = {
      handle: false,
      reverteffect: function(element, top_offset, left_offset) {
        var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
        new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
          queue: {scope:'_draggable', position:'end'}
        });
      },
      endeffect: function(element) {
        var toOpacity = typeof element._opacity == 'number' ? element._opacity : 1.0;
        new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, 
          queue: {scope:'_draggable', position:'end'},
          afterFinish: function(){ 
            Draggable._dragging[element] = false 
          }
        }); 
      },
      zindex: 1000,
      revert: false,
      overflow: false,
      scroll: false,
      scrollSensitivity: 20,
      scrollSpeed: 15,
      snap: false,  // false, or xy or [x,y] or function(x,y){ return [x,y] }
      delay: 0
    };

    if(!arguments[1] || typeof arguments[1].endeffect == 'undefined')
      Object.extend(defaults, {
        starteffect: function(element) {
          element._opacity = Element.getOpacity(element);
          Draggable._dragging[element] = true;
          new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); 
        }
      });

    var options = Object.extend(defaults, arguments[1] || {});

    this.element = $(element);

    if(options.handle && (typeof options.handle == 'string'))
      this.handle = this.element.down('.'+options.handle, 0);

    if(!this.handle) this.handle = $(options.handle);
    if(!this.handle) this.handle = this.element;

    if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
      options.scroll = $(options.scroll);
      this._isScrollChild = Element.childOf(this.element, options.scroll);
    }

    Element.makePositioned(this.element); // fix IE    

    this.delta    = this.currentDelta();
    this.options  = options;
    this.dragging = false;   

    this.eventMouseDown = this.initDrag.bindAsEventListener(this);
    Event.observe(this.handle, "mousedown", this.eventMouseDown);

    Draggables.register(this);
  },

  startDrag: function(event) {
    this.dragging = true;

    if(this.options.zindex) {
      this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
      this.element.style.zIndex = this.options.zindex;
    }

    if (this.options.overflow) {
      var element = this.element;
      this.deltaOverflow = Position.cumulativeOffset($(element));
      this._clone = $(element).cloneNode(true);
      this._clone.setStyle({
        position: 'absolute',
        top: this.deltaOverflow[1]+'px',
        left: this.deltaOverflow[0]+'px'
      });
      
      document.getElementsByTagName('body')[0].appendChild(this._clone);
      
      this.element = this._clone;
      this._clone = $(element);
      
      if (!this.options.ghosting) this._clone.style.visibility = 'hidden';
    } else {
      if(this.options.ghosting) {
        this._clone = this.element.cloneNode(true);
        Position.absolutize(this.element);
        this.element.parentNode.insertBefore(this._clone, this.element);
      }				
    }

    if(this.options.scroll) {
      if (this.options.scroll == window) {
        var where = this._getWindowScroll(this.options.scroll);
        this.originalScrollLeft = where.left;
        this.originalScrollTop = where.top;
      } else {
        this.originalScrollLeft = this.options.scroll.scrollLeft;
        this.originalScrollTop = this.options.scroll.scrollTop;
      }
    }

    Draggables.notify('onStart', this, event);

    if(this.options.starteffect) this.options.starteffect(this.element);
  },

  finishDrag: function(event, success) {
    this.dragging = false;

    if(this.options.ghosting && !this.options.overflow) {
      Position.relativize(this.element);
      Element.remove(this._clone);
      this._clone = null;
    }

    if(success) Droppables.fire(event, this.element);
    Draggables.notify('onEnd', this, event);

    var revert = this.options.revert;
    if(revert && typeof revert == 'function') revert = revert(this.element);

    var d = this.currentDelta();
    if(revert && this.options.reverteffect) {
      if (this.options.overflow) {
        this.options.reverteffect(this.element, 
        d[1]-this.deltaOverflow[1], d[0]-this.deltaOverflow[0]);
		
        this.checkDraggableEffects = new PeriodicalExecuter(function() {
          if (!Draggable._dragging[this.element]) {
            this.checkDraggableEffects.stop();
            if (!this.options.ghosting) this._clone.style.visibility = 'visible';
            
            $(this.element).remove();
            this.element = this._clone;
            $(this._clone).style.zIndex = '';
            this._clone = null;
            Position.relativize(this.element);
          }
        }.bind(this), .0001);
      } else {
        this.options.reverteffect(this.element, 
        d[1]-this.delta[1], d[0]-this.delta[0]);
      }
    } else {
      this.delta = d;
    }

    if(this.options.zindex)
      this.element.style.zIndex = this.originalZ;

    if(this.options.endeffect) 
      this.options.endeffect(this.element);

	Draggables.deactivate(this);
	Droppables.reset();
  }
});

//
//  Uses a Slider to create a custom scrollbar
//
Control.Scrollbar = Class.create();
Control.Scrollbar.prototype = {
  initialize: function(element, options) {
    this.options = Object.extend({
      axis: 'vertical',
      bothAxis: false,
      verticalClassName: {
        container: 'vertical_scrollbar_container',
        track: 'vertical_scrollbar_track',
        handle: 'vertical_scrollbar_handle',
        up: 'vertical_scrollbar_up',
        down: 'vertical_scrollbar_down'
      },
      horizontalClassName: {
        container: 'vertical_scrollbar_container',
        track: 'horizontal_scrollbar_track',
        handle: 'horizontal_scrollbar_handle',
        up: 'horizontal_scrollbar_left',
        down: 'horizontal_scrollbar_right'
      },
      minimalHandleSize: 20,
      verticalWidth: 15, 
      horizontalHeight: 15,
      increment: 15,
      mouseWheelIncrement: 40,
      defaultCursor: 'pointer',
      disabledClass: 'scrollbar_disabled',
      disable: false
    }, options || {});

    this.element = $(element);

  	this.element.scrollTop = this.element.scrollLeft = 0;

    this._wrapElement();

    // Safari won't let us dynamically change the overflow of an element, so we plant this puppy on top
    if (!Prototype.Browser.WebKit) {
  	  this.element.setStyle({
  		  overflow: 'hidden'
  		});

  		if (this.options.axis == 'vertical') {
  		  this.element.setStyle({
        paddingRight: this.options.verticalWidth/2+'px',
  		    width: this.element.getWidth()-this.options.verticalWidth +'px'
  		  });
  		} else if (this.options.axis == 'horizontal') {
  		  this.element.setStyle({
  		    paddingBottom: this.options.horizontalHeight/2+'px',
  		    height: this.element.getHeight()-this.options.horizontalHeight+'px' 
  		  });    			  
  		}
  	}		  
	
  	// IE and image flicker's suck so lets not do that!
  	if (Prototype.Browser.IE) {
  	  try {
      	document.execCommand("BackgroundImageCache", false, true);
      } catch(e) {}
  	}

    this._renderScrollbar();

  	this.slider = new Control.Slider(this.element.id+'_'+this.options.axis+'_handle', this.element.id+'_'+this.options.axis+'_track', {
  		axis: this.options.axis,
  		onSlide: function(value) { 
  			this.handleScroll(value);
  		}.bind(this),
  		onChange: function(value) { 
  			this.handleScroll(value);
  		}.bind(this)
  	});
	
  	this.eventMouseDownUp = this.startScroll.bindAsEventListener(this, -this.options.increment);
    this.eventMouseDownDown = this.startScroll.bindAsEventListener(this, this.options.increment);
    this.eventMouseUp = this.stopScroll.bindAsEventListener(this);
    this.eventMouseHheel = this.wheelScroll.bindAsEventListener(this);
    this.eventHandle = this.handleScroll;

    if (!this.options.disabled) {
      this.setObservers();
    }
  },
  
  startScroll: function(event, increment) {
    this.callback = setInterval(this.updateScroll.bind(this, increment), 10);
  },
  
  handleScroll: function(increment) {
    if (this.options.axis == 'vertical') {
      this.element.scrollTop = Math.round(increment/this.slider.maximum*(this.element.scrollHeight-this.element.offsetHeight));
    } else {
      this.element.scrollLeft = Math.round(increment/this.slider.maximum*(this.element.scrollWidth-this.element.offsetWidth)); 
    }
  },
  
  wheelScroll: function(event) {
  	var delta = 0;
	
  	if (!event) {
  		event = window.event;
  	}
	
  	if (event.wheelDelta) { 
  		delta = event.wheelDelta/120;
  	} else if (event.detail) {
  		delta = -event.detail/3;
  	}

  	if (delta) {
  		if (delta < 0) {
  			this.updateScroll(this.options.mouseWheelIncrement);
  		} else {
  			this.updateScroll(-this.options.mouseWheelIncrement);
  		}
  	}

  	if (event.preventDefault) {
  		event.preventDefault();
  	}
  },
  
  updateScroll: function(increment) {
  	if (this.options.axis == 'vertical') {
      this.slider.setValueBy(increment/$(this.element.id+'_'+this.options.axis+'_track').getHeight());
  		this.element.scrollTop = this.element.scrollTop+increment;
  	} else {
      this.slider.setValueBy(increment/$(this.element.id+'_'+this.options.axis+'_track').getWidth());
  		this.element.scrollLeft = this.element.scrollLeft+increment;
  	}
  },
  
  stopScroll: function() {
    clearInterval(this.callback);
  },
  
  setObservers: function() {
  	Event.observe(this.element.id+'_'+this.button.first, 'mousedown', this.eventMouseDownUp);
  	Event.observe(this.element.id+'_'+this.button.first, 'mouseup', this.eventMouseUp);
  	Event.observe(this.element.id+'_'+this.button.last, 'mousedown', this.eventMouseDownDown);
  	Event.observe(this.element.id+'_'+this.button.last, 'mouseup', this.eventMouseUp);
  	if (this.options.axis == 'vertical') {
  	  Event.observe(this.element.id, 'DOMMouseScroll', this.eventMouseHheel);
  		Event.observe(this.element.id, 'mousewheel', this.eventMouseHheel);
  	}
  },
  
  stopObservers: function() {
		Event.stopObserving(this.element.id+'_'+this.button.first, 'mousedown', this.eventMouseDownUp);
  	Event.stopObserving(this.element.id+'_'+this.button.first, 'mouseup', this.eventMouseUp);
  	Event.stopObserving(this.element.id+'_'+this.button.last, 'mousedown', this.eventMouseDownDown);
  	Event.stopObserving(this.element.id+'_'+this.button.last, 'mouseup', this.eventMouseUp);
  	if (this.options.axis == 'vertical') {
  		Event.stopObserving(this.element.id, 'DOMMouseScroll', this.eventMouseHheel);
  		Event.stopObserving(this.element.id, 'mousewheel', this.eventMouseHheel);
  	} 
  },
  
  setDisabled: function(){
    this.slider.setDisabled();
    this.stopObservers();
    $(this.element.id+'_'+this.options.axis+'_scrollbar').className = this.options.disabledClass;
    },

    setEnabled: function(){
    this.slider.setEnabled();
    this.setObservers();
    $(this.element.id+'_'+this.options.axis+'_scrollbar').className = '';
  },
  
  _renderScrollbar: function() {
    if (this.options.axis == 'vertical') {
      if (this.options.bothAxis) {
        var offset = this.options.verticalWidth;
      } else {
        var offset = 0;
      }
  
      var className = this.options.verticalClassName;
      this.button = {first: 'up', last: 'down'}
  
      if (Prototype.Browser.Opera) {
        var elementPosition = Position.page(this.element);    
        var scrollbarPosition = 'position: absolute; top: '+elementPosition[1]+'px; left: '+(elementPosition[0]+this.element.getWidth()-this.options.verticalWidth/2)+'px;';
      } else {
        var scrollbarPosition = 'position: absolute; top: 0px; right: 0px;';
      }
    } else if (this.options.axis == 'horizontal') {
      if (this.options.bothAxis) {
        var offset = this.options.horizontalHeight;
      } else {
        var offset = 0;
      }
  
      var className = this.options.horizontalClassName;
      this.button = {first: 'left', last: 'right'}
 
      if (Prototype.Browser.Opera) {
        var elementPosition = Position.page(this.element);    
        var scrollbarPosition = 'position: absolute; left: '+elementPosition[0]+'px; top: '+(elementPosition[1]+this.element.getHeight()-this.options.horizontalHeight/2)+'px;';
      } else {
        var scrollbarPosition = 'position: absolute; bottom: 0px; left: 0px;';
      }
    }

    var scrollbar = Builder.node('div', {id: this.element.id+'_'+this.options.axis+'_scrollbar', className: className.container, style: scrollbarPosition}, [
                    Builder.node('a', {id: this.element.id+'_'+this.button.first, className: className.up, style: 'cursor: '+this.options.defaultCursor+';'}),
                    Builder.node('div', {id: this.element.id+'_'+this.options.axis+'_track', className: className.track}, [
                      Builder.node('div', {id: this.element.id+'_'+this.options.axis+'_handle', className: className.handle, style: 'cursor: '+this.options.defaultCursor+';'})
                    ]),                          
                    Builder.node('a', {id: this.element.id+'_'+this.button.last, className: className.down, style: 'cursor: '+this.options.defaultCursor+';'}),
                  ]);

    if (Prototype.Browser.Opera) {
      document.getElementsByTagName('body')[0].appendChild(scrollbar);
    } else {
      this.element.appendChild(scrollbar);
    }

    if (!Prototype.Browser.WebKit && this.options.axis == 'horizontal') {
      offset = offset/2;
    }

    if (this.options.axis == 'vertical') {
    	$(this.element.id+'_'+this.options.axis+'_scrollbar').style.height = this.element.getHeight()+'px';
    	$(this.element.id+'_'+this.options.axis+'_scrollbar').style.width = this.options.verticalWidth+'px';
    	$(this.element.id+'_'+this.options.axis+'_track').style.height = this.element.getHeight()-$(this.element.id+'_'+this.button.first).getHeight()-$(this.element.id+'_'+this.button.last).getHeight()-offset+'px';
  	} else if (this.options.axis == 'horizontal') {
    	$(this.element.id+'_'+this.options.axis+'_scrollbar').style.height = this.options.horizontalWidth+'px';
    	$(this.element.id+'_'+this.options.axis+'_scrollbar').style.width = this.element.getWidth()+'px';
  		$(this.element.id+'_'+this.options.axis+'_track').style.width = this.element.getWidth()-$(this.element.id+'_'+this.button.first).getWidth()-$(this.element.id+'_'+this.button.last).getWidth()-offset+'px';
  	}	
	
  	this.updateScrollbar();	  			
  },
  
  updateScrollbar: function() {
    if ($(this.element.id+'_vertical_handle')) {
  		var handleSize = this.element.getHeight()*(this.element.getHeight()/this.element.scrollHeight);
  		if (handleSize >= this.options.minimalHandleSize) {
  		  $(this.element.id+'_vertical_handle').style.height = handleSize+'px';
  		} else {
  		  $(this.element.id+'_vertical_handle').style.height = this.options.minimalHandleSize+'px';
  		}
  	} 
	
  	if ($(this.element.id+'_horizontal_handle')) {
  		var handleSize = this.element.getWidth()*(this.element.getWidth()/this.element.scrollWidth);
  		if (handleSize >= this.options.minimalHandleSize) {
  		  $(this.element.id+'_horizontal_handle').style.width = handleSize+'px';
  		} else {
  		  $(this.element.id+'_horizontal_handle').style.width = this.options.minimalHandleSize+'px';
  		}
  	}		    
  },
  
  destroy: function() {
    this.element.style.overflow = 'auto';
    $(this.element.id+'_'+this.options.axis+'_scrollbar').remove();
  },
  
  _wrapElement: function() { 
    if (!$(this.element.id+'_scrollbar_container')) {
      var container = document.createElement('div');
      container.setAttribute('id', this.element.id+'_scrollbar_container');
      container.setAttribute('style', 'position: relative; width: '+this.element.getWidth()+'px; height: '+this.element.getHeight()+'px; float:'+this.element.getStyle('float')+';');
      var element = $(this.element); 
      var wrapper = $(container); 
      var e = element.cloneNode(true); 
      element.parentNode.replaceChild(e, element) 
      wrapper.appendChild(element); 
      e.parentNode.replaceChild(wrapper, e); 
    }
  }
}
