aboutsummaryrefslogblamecommitdiffstats
path: root/actionpack/lib/action_view/helpers/javascripts/slider.js
blob: 1712b98943ca27dd00649fc2d74283ee1d33b6dd (plain) (tree)

































































































































































































































































                                                                                                                            
// Copyright (c) 2005 Marty Haught
// 
// See scriptaculous.js for full license.

if(!Control) var Control = {};
Control.Slider = Class.create();

// options:
//  axis: 'vertical', or 'horizontal' (default)
//  increment: (default: 1)
//  step: (default: 1)
//
// callbacks:
//  onChange(value)
//  onSlide(value)
Control.Slider.prototype = {
  initialize: function(handle, track, options) {
    this.handle  = $(handle);
    this.track   = $(track);

    this.options = options || {};

    this.axis      = this.options.axis || 'horizontal';
    this.increment = this.options.increment || 1;
    this.step      = parseInt(this.options.step) || 1;
    this.value     = 0;

    var defaultMaximum = Math.round(this.track.offsetWidth / this.increment);
    if(this.isVertical()) defaultMaximum = Math.round(this.track.offsetHeight / this.increment);   
    
    this.maximum = this.options.maximum || defaultMaximum;
    this.minimum = this.options.minimum || 0;

    // Will be used to align the handle onto the track, if necessary
    this.alignX = parseInt (this.options.alignX) || 0;
    this.alignY = parseInt (this.options.alignY) || 0;

    // Zero out the slider position	
    this.setCurrentLeft(Position.cumulativeOffset(this.track)[0] - Position.cumulativeOffset(this.handle)[0] + this.alignX);
    this.setCurrentTop(this.trackTop() - Position.cumulativeOffset(this.handle)[1] + this.alignY);

    this.offsetX = 0;
    this.offsetY = 0;

    this.originalLeft = this.currentLeft();
    this.originalTop  = this.currentTop();
    this.originalZ    = parseInt(this.handle.style.zIndex || "0");

    // Prepopulate Slider value
    this.setSliderValue(parseInt(this.options.sliderValue) || 0);

    this.active   = false;
    this.dragging = false;
    this.disabled = false;

    // FIXME: use css
    this.handleImage    = $(this.options.handleImage) || false; 
    this.handleDisabled = this.options.handleDisabled || false;
    this.handleEnabled  = false;
    if(this.handleImage)
      this.handleEnabled  = this.handleImage.src || false;

    if(this.options.disabled)
      this.setDisabled();

    // Value Array
    this.values = this.options.values || false;  // Add method to validate and sort??

    Element.makePositioned(this.handle); // fix IE

    this.eventMouseDown = this.startDrag.bindAsEventListener(this);
    this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
    this.eventMouseMove = this.update.bindAsEventListener(this);
    this.eventKeypress  = this.keyPress.bindAsEventListener(this);

    Event.observe(this.handle, "mousedown", this.eventMouseDown);
    Event.observe(document, "mouseup", this.eventMouseUp);
    Event.observe(document, "mousemove", this.eventMouseMove);
    Event.observe(document, "keypress", this.eventKeypress);
  },
  dispose: function() {
    Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
    Event.stopObserving(document, "mouseup", this.eventMouseUp);
    Event.stopObserving(document, "mousemove", this.eventMouseMove);
    Event.stopObserving(document, "keypress", this.eventKeypress);
  },
  setDisabled: function(){
    this.disabled = true;
    if(this.handleDisabled)
      this.handleImage.src = this.handleDisabled;
  },
  setEnabled: function(){
    this.disabled = false;
    if(this.handleEnabled)
      this.handleImage.src = this.handleEnabled;
  },  
  currentLeft: function() {
    return parseInt(this.handle.style.left || '0');
  },
  currentTop: function() {
    return parseInt(this.handle.style.top || '0');
  },
  setCurrentLeft: function(left) {
    this.handle.style.left = left +"px";
  },
  setCurrentTop: function(top) {
    this.handle.style.top = top +"px";
  },
  trackLeft: function(){
    return Position.cumulativeOffset(this.track)[0];
  },
  trackTop: function(){
    return Position.cumulativeOffset(this.track)[1];
  }, 
  getNearestValue: function(value){
    if(this.values){
      var i = 0;
      var offset = Math.abs(this.values[0] - value);
      var newValue = this.values[0];

      for(i=0; i < this.values.length; i++){
        var currentOffset = Math.abs(this.values[i] - value);
        if(currentOffset < offset){
          newValue = this.values[i];
          offset = currentOffset;
        }
      }
      return newValue;
    }
    return value;
  },
  setSliderValue:  function(sliderValue){
    // First check our max and minimum and nearest values
    sliderValue = this.getNearestValue(sliderValue);	
    if(sliderValue > this.maximum) sliderValue = this.maximum;
    if(sliderValue < this.minimum) sliderValue = this.minimum;
    var offsetDiff = (sliderValue - (this.value||this.minimum)) * this.increment;
    
    if(this.isVertical()){
      this.setCurrentTop(offsetDiff + this.currentTop());
    } else {
      this.setCurrentLeft(offsetDiff + this.currentLeft());
    }
    this.value = sliderValue;
    this.updateFinished();
  },  
  minimumOffset: function(){
    return(this.isVertical() ? 
      this.trackTop() + this.alignY :
      this.trackLeft() + this.alignX);
  },
  maximumOffset: function(){
    return(this.isVertical() ?
      this.trackTop() + this.alignY + (this.maximum - this.minimum) * this.increment :
      this.trackLeft() + this.alignX + (this.maximum - this.minimum) * this.increment);
  },  
  isVertical:  function(){
    return (this.axis == 'vertical');
  },
  startDrag: function(event) {
    if(Event.isLeftClick(event)) {
      if(!this.disabled){
        this.active = true;
        var pointer = [Event.pointerX(event), Event.pointerY(event)];
        var offsets = Position.cumulativeOffset(this.handle);
        this.offsetX =  (pointer[0] - offsets[0]);
        this.offsetY =  (pointer[1] - offsets[1]);
        this.originalLeft = this.currentLeft();
        this.originalTop = this.currentTop();
      }
      Event.stop(event);
    }
  },
  update: function(event) {
   if(this.active) {
      if(!this.dragging) {
        var style = this.handle.style;
        this.dragging = true;
        if(style.position=="") style.position = "relative";
        style.zIndex = this.options.zindex;
      }
      this.draw(event);
      // fix AppleWebKit rendering
      if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
      Event.stop(event);
   }
  },
  draw: function(event) {
    var pointer = [Event.pointerX(event), Event.pointerY(event)];
    var offsets = Position.cumulativeOffset(this.handle);

    offsets[0] -= this.currentLeft();
    offsets[1] -= this.currentTop();
        
    // Adjust for the pointer's position on the handle
    pointer[0] -= this.offsetX;
    pointer[1] -= this.offsetY;
    var style = this.handle.style;

    if(this.isVertical()){
      if(pointer[1] > this.maximumOffset())
        pointer[1] = this.maximumOffset();
      if(pointer[1] < this.minimumOffset())
        pointer[1] =  this.minimumOffset();

    // Increment by values
    if(this.values){
      this.value = this.getNearestValue(Math.round((pointer[1] - this.minimumOffset()) / this.increment) + this.minimum);
      pointer[1] = this.trackTop() + this.alignY + (this.value - this.minimum) * this.increment;
    } else {
      this.value = Math.round((pointer[1] - this.minimumOffset()) / this.increment) + this.minimum;
    }
    style.top  = pointer[1] - offsets[1] + "px";
    } else {
      if(pointer[0] > this.maximumOffset()) pointer[0] = this.maximumOffset();
      if(pointer[0] < this.minimumOffset())	pointer[0] =  this.minimumOffset();
      // Increment by values
      if(this.values){
        this.value = this.getNearestValue(Math.round((pointer[0] - this.minimumOffset()) / this.increment) + this.minimum);
        pointer[0] = this.trackLeft() + this.alignX + (this.value - this.minimum) * this.increment;
      } else {
        this.value = Math.round((pointer[0] - this.minimumOffset()) / this.increment) + this.minimum;
      }
      style.left = (pointer[0] - offsets[0]) + "px";
    }
    if(this.options.onSlide) this.options.onSlide(this.value);
  },
  endDrag: function(event) {
    if(this.active && this.dragging) {
      this.finishDrag(event, true);
      Event.stop(event);
    }
    this.active = false;
    this.dragging = false;
  },  
  finishDrag: function(event, success) {
    this.active = false;
    this.dragging = false;
    this.handle.style.zIndex = this.originalZ;
    this.originalLeft = this.currentLeft();
    this.originalTop  = this.currentTop();
    this.updateFinished();
  },
  updateFinished: function() {
    if(this.options.onChange) this.options.onChange(this.value);
  },
  keyPress: function(event) {
    if(this.active && !this.disabled) {
      switch(event.keyCode) {
        case Event.KEY_ESC:
          this.finishDrag(event, false);
          Event.stop(event); 
          break;
      }
      if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
    }
  }
}