Generic-user-small Dave Vernon 1 post

Hi,

I am using a Scriptaculous slider to scroll a div but I want to be able to control the position of the scroller using buttons too. See my example here:

http://www.stickycreation.co.uk/laurus/scroll_testX.html

Here’s the code from that page:


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<title>Use a Slider as a Scrollbar Demo</title>
        <script src="prototype.js" type="text/javascript"></script>
        <script src="slider.js" type="text/javascript"></script>
    <style type="text/css" media="screen" >

            /* scrollable div area */
            #scrollable1 {
                float: left;
                width: 300px;
                height: 200px;
                border: 1px solid #ccc;
                overflow: hidden;
                white-space: nowrap;
            }

            /* vertical track */
            #track1 {
                float: left;
                width: 15px;
                height: 200px;
                margin-left: 10px;
                background-color: #ccc;
            }

            /* vertical track handle */
            #handle1 {
                width: 15px;
                height: 20px;
                background-color: #f00;
                cursor: move;
            }

            /* horizontal track */
            #track2 {
                float: left;
                clear: both;
                width: 300px;
                height: 15px;
                margin-top: 10px;
                background-color:#ccc;
            }

            /* horizontal track handle */
            #handle2 {
                width: 20px;
                height: 15px;
                background-color: #f00;
                cursor: move;
            }

            /* scrollable div area */
            #scrollable2 {
                float: left;
                width: 284px;
                height: 184px;
                padding: 8px;
                border-top: 1px solid #ccc;
                border-left: 1px solid #ccc;
                overflow: hidden;
                white-space: nowrap;
            }

            /* wrap to make sure that image area is clickable */
            #wrap3 {
                float: left;
                width: 15px;
                height: 201px;
                background: transparent url(images/scrollbar-track-bottom.png) no-repeat bottom left;
            }

            /* top image of track */
            #track3-top {
                position: absolute;
                width: 15px;
                height: 15px;
                background: transparent url(images/scrollbar-track-top.png) no-repeat top left;
            }

            /* vertical track */
            #track3 {
                position: absolute;
                width: 15px;
                height: 201px;
            }

            /* vertical track handle */
            #handle3 {
                width: 15px;
                height: 37px;
            }

            /* wrap to make sure that image area is clickable */
            #wrap4 {
                float: left;
                clear: both;
                width: 301px;
                height: 15px;
                background: transparent url(images/scrollbar-track-right.png) no-repeat top right;
            }

            /* left image of track */
            #track4-left {
                position: absolute;
                width: 15px;
                height: 15px;
                background: transparent url(images/scrollbar-track-left.png) no-repeat top left;
            }

            /* horizontal track */
            #track4 {
                position: absolute;
                width: 301px;
                height: 15px;
            }

            /* horizontal track handle */
            #handle4 {
                width: 37px;
                height: 15px;
            }

        </style>

</head>
    <body>
        <h1>Using a Slider as a Scrollbar</h1>

        <h2>Using Standard Slider Controls</h2>
        <div id="scrollable1">
            This is an example of using a slider as a vertical and horizontal scrollbar.

            <ol>
                <li>Line item</li>

                <li>Line item</li>
                <li>Line item</li>
                <li>Line item</li>
                <li>Line item</li>
                <li>Line item</li>
                <li>Line item</li>

            </ol>

            This is a demo using a slider as a vertical and horizontal scrollbar.

            <ul>
                <li>Line item</li>
                <li>Line item</li>
                <li>Line item</li>
                <li>Line item</li>

                <li>Line item</li>
                <li>Line item</li>
                <li>Line item</li>
            </ul>

            This is an example of using a slider as a vertical and horizontal scrollbar.
        </div>

        <div id="track1"><div id="handle1"></div></div>

        <div id="track2"><div id="handle2"></div></div>

        <h2><br style="clear: both;" /></h2>

        <a href="#">scroll left</a>  |  <a href="#">scroll right</a>
    <script type="text/javascript" language="javascript">
        // <![CDATA[

            // vertical slider control
            var slider1 = new Control.Slider('handle1', 'track1', {
                axis: 'vertical',
                onSlide: function(v) { scrollVertical(v, $('scrollable1'), slider1);  },
                onChange: function(v) { scrollVertical(v, $('scrollable1'), slider1); }
            });

            // horizontal slider control
            var slider2 = new Control.Slider('handle2', 'track2', {
                onSlide: function(v) { scrollHorizontal(v, $('scrollable1'), slider2);  },
                onChange: function(v) { scrollHorizontal(v, $('scrollable1'), slider2); }
            });

            // vertical slider control
            var slider3 = new Control.Slider('handle3', 'track3', {
                axis: 'vertical',
                onSlide: function(v) { scrollVertical(v, $('scrollable2'), slider3);  },
                onChange: function(v) { scrollVertical(v, $('scrollable2'), slider3); }
            });

            // horizontal slider control
            var slider4 = new Control.Slider('handle4', 'track4', {
                onSlide: function(v) { scrollHorizontal(v, $('scrollable2'), slider4);  },
                onChange: function(v) { scrollHorizontal(v, $('scrollable2'), slider4); }
            });

            // scroll the element vertically based on its width and the slider maximum value
            function scrollVertical(value, element, slider) {
                element.scrollTop = Math.round(value/slider.maximum*(element.scrollHeight-element.offsetHeight));
            }

            // scroll the element horizontally based on its width and the slider maximum value
            function scrollHorizontal(value, element, slider) {
                element.scrollLeft = Math.round(value/slider.maximum*(element.scrollWidth-element.offsetWidth));
            }

            // disable vertical scrolling if text doesn't overflow the div
            if ($('scrollable1').scrollHeight <= $('scrollable1').offsetHeight) {
                slider1.setDisabled();
                $('track1').hide();
            }

            // disable horizontal scrolling if text doesn't overflow the div
            if ($('scrollable1').scrollWidth <= $('scrollable1').offsetWidth) {
                slider2.setDisabled();
                $('track2').hide();
            }

            // disable vertical scrolling if text doesn't overflow the div
            if ($('scrollable2').scrollHeight <= $('scrollable2').offsetHeight) {
                slider3.setDisabled();
                $('wrap3').hide();
            }

            // disable horizontal scrolling if text doesn't overflow the div
            if ($('scrollable2').scrollWidth <= $('scrollable2').offsetWidth) {
                slider4.setDisabled();
                $('wrap4').hide();
            }

        // ]]>
          </script>

    </body>
</html>

This file links to the prototype framework and the js file slider.js, which contains the following code:


// script.aculo.us slider.js v1.6.5, Wed Nov 08 14:17:49 CET 2006

// Copyright (c) 2005, 2006 Marty Haught, Thomas Fuchs 
//
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: <a href="http://script.aculo.us/">http://script.aculo.us/</a>

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

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

    if(handle instanceof Array) {
      this.handles = handle.collect( function(e) { return $(e) });
    } else {
      this.handles = [$(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.range     = this.options.range || $R(0,1);

    this.value     = 0; // assure backwards compat
    this.values    = this.handles.map( function() { return 0 });
    this.spans     = this.options.spans ? this.options.spans.map(function(s){ return $(s) }) : false;
    this.options.startSpan = $(this.options.startSpan || null);
    this.options.endSpan   = $(this.options.endSpan || null);

    this.restricted = this.options.restricted || false;

    this.maximum   = this.options.maximum || this.range.end;
    this.minimum   = this.options.minimum || this.range.start;

    // 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');

    this.trackLength = this.maximumOffset() - this.minimumOffset();

    this.handleLength = this.isVertical() ? 
      (this.handles[0].offsetHeight != 0 ? 
        this.handles[0].offsetHeight : this.handles[0].style.height.replace(/px$/,"")) : 
      (this.handles[0].offsetWidth != 0 ? this.handles[0].offsetWidth : 
        this.handles[0].style.width.replace(/px$/,""));

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

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

    // Allowed values array
    this.allowedValues = this.options.values ? this.options.values.sortBy(Prototype.K) : false;
    if(this.allowedValues) {
      this.minimum = this.allowedValues.min();
      this.maximum = this.allowedValues.max();
    }

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

    // Initialize handles in reverse (make sure first handle is active)
    this.handles.each( function(h,i) {
      i = slider.handles.length-1-i;
      slider.setValue(parseFloat(
        (slider.options.sliderValue instanceof Array ? 
          slider.options.sliderValue[i] : slider.options.sliderValue) || 
         slider.range.start), i);
      Element.makePositioned(h); // fix IE
      Event.observe(h, "mousedown", slider.eventMouseDown);
    });

    Event.observe(this.track, "mousedown", this.eventMouseDown);
    Event.observe(document, "mouseup", this.eventMouseUp);
    Event.observe(document, "mousemove", this.eventMouseMove);

    this.initialized = true;
  },
  dispose: function() {
    var slider = this;    
    Event.stopObserving(this.track, "mousedown", this.eventMouseDown);
    Event.stopObserving(document, "mouseup", this.eventMouseUp);
    Event.stopObserving(document, "mousemove", this.eventMouseMove);
    this.handles.each( function(h) {
      Event.stopObserving(h, "mousedown", slider.eventMouseDown);
    });
  },
  setDisabled: function(){
    this.disabled = true;
  },
  setEnabled: function(){
    this.disabled = false;
  },  
  getNearestValue: function(value){
    if(this.allowedValues){
      if(value >= this.allowedValues.max()) return(this.allowedValues.max());
      if(value <= this.allowedValues.min()) return(this.allowedValues.min());

      var offset = Math.abs(this.allowedValues[0] - value);
      var newValue = this.allowedValues[0];
      this.allowedValues.each( function(v) {
        var currentOffset = Math.abs(v - value);
        if(currentOffset <= offset){
          newValue = v;
          offset = currentOffset;
        } 
      });
      return newValue;
    }
    if(value > this.range.end) return this.range.end;
    if(value < this.range.start) return this.range.start;
    return value;
  },
  setValue: function(sliderValue, handleIdx){
    if(!this.active) {
      this.activeHandleIdx = handleIdx || 0;
      this.activeHandle    = this.handles[this.activeHandleIdx];
      this.updateStyles();
    }
    handleIdx = handleIdx || this.activeHandleIdx || 0;
    if(this.initialized && this.restricted) {
      if((handleIdx>0) && (sliderValue<this.values[handleIdx-1]))
        sliderValue = this.values[handleIdx-1];
      if((handleIdx < (this.handles.length-1)) && (sliderValue>this.values[handleIdx+1]))
        sliderValue = this.values[handleIdx+1];
    }
    sliderValue = this.getNearestValue(sliderValue);
    this.values[handleIdx] = sliderValue;
    this.value = this.values[0]; // assure backwards compat

    this.handles[handleIdx].style[this.isVertical() ? 'top' : 'left'] = 
      this.translateToPx(sliderValue);

    this.drawSpans();
    if(!this.dragging || !this.event) this.updateFinished();
  },
  setValueBy: function(delta, handleIdx) {
    this.setValue(this.values[handleIdx || this.activeHandleIdx || 0] + delta, 
      handleIdx || this.activeHandleIdx || 0);
  },
  translateToPx: function(value) {
    return Math.round(
      ((this.trackLength-this.handleLength)/(this.range.end-this.range.start)) * 
      (value - this.range.start)) + "px";
  },
  translateToValue: function(offset) {
    return ((offset/(this.trackLength-this.handleLength) * 
      (this.range.end-this.range.start)) + this.range.start);
  },
  getRange: function(range) {
    var v = this.values.sortBy(Prototype.K); 
    range = range || 0;
    return $R(v[range],v[range+1]);
  },
  minimumOffset: function(){
    return(this.isVertical() ? this.alignY : this.alignX);
  },
  maximumOffset: function(){
    return(this.isVertical() ? 
      (this.track.offsetHeight != 0 ? this.track.offsetHeight :
        this.track.style.height.replace(/px$/,"")) - this.alignY : 
      (this.track.offsetWidth != 0 ? this.track.offsetWidth : 
        this.track.style.width.replace(/px$/,"")) - this.alignY);
  },  
  isVertical:  function(){
    return (this.axis == 'vertical');
  },
  drawSpans: function() {
    var slider = this;
    if(this.spans)
      $R(0, this.spans.length-1).each(function(r) { slider.setSpan(slider.spans[r], slider.getRange(r)) });
    if(this.options.startSpan)
      this.setSpan(this.options.startSpan,
        $R(0, this.values.length>1 ? this.getRange(0).min() : this.value ));
    if(this.options.endSpan)
      this.setSpan(this.options.endSpan, 
        $R(this.values.length>1 ? this.getRange(this.spans.length-1).max() : this.value, this.maximum));
  },
  setSpan: function(span, range) {
    if(this.isVertical()) {
      span.style.top = this.translateToPx(range.start);
      span.style.height = this.translateToPx(range.end - range.start + this.range.start);
    } else {
      span.style.left = this.translateToPx(range.start);
      span.style.width = this.translateToPx(range.end - range.start + this.range.start);
    }
  },
  updateStyles: function() {
    this.handles.each( function(h){ Element.removeClassName(h, 'selected') });
    Element.addClassName(this.activeHandle, 'selected');
  },
  startDrag: function(event) {
    if(Event.isLeftClick(event)) {
      if(!this.disabled){
        this.active = true;

        var handle = Event.element(event);
        var pointer  = [Event.pointerX(event), Event.pointerY(event)];
        var track = handle;
        if(track==this.track) {
          var offsets  = Position.cumulativeOffset(this.track); 
          this.event = event;
          this.setValue(this.translateToValue( 
           (this.isVertical() ? pointer[1]-offsets[1] : pointer[0]-offsets[0])-(this.handleLength/2)
          ));
          var offsets  = Position.cumulativeOffset(this.activeHandle);
          this.offsetX = (pointer[0] - offsets[0]);
          this.offsetY = (pointer[1] - offsets[1]);
        } else {
          // find the handle (prevents issues with Safari)
          while((this.handles.indexOf(handle) == -1) && handle.parentNode) 
            handle = handle.parentNode;

          if(this.handles.indexOf(handle)!=-1) {
            this.activeHandle    = handle;
            this.activeHandleIdx = this.handles.indexOf(this.activeHandle);
            this.updateStyles();

            var offsets  = Position.cumulativeOffset(this.activeHandle);
            this.offsetX = (pointer[0] - offsets[0]);
            this.offsetY = (pointer[1] - offsets[1]);
          }
        }
      }
      Event.stop(event);
    }
  },
  update: function(event) {
   if(this.active) {
      if(!this.dragging) this.dragging = true;
      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.track);
    pointer[0] -= this.offsetX + offsets[0];
    pointer[1] -= this.offsetY + offsets[1];
    this.event = event;
    this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] ));
    if(this.initialized && this.options.onSlide)
      this.options.onSlide(this.values.length>1 ? this.values : this.value, this);
  },
  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.updateFinished();
  },
  updateFinished: function() {
    if(this.initialized && this.options.onChange) 
      this.options.onChange(this.values.length>1 ? this.values : this.value, this);
    this.event = null;
  }
}

My question is how can I enable the text-link buttons I have created (scroll left/scroll right) to move the scroll bar together with the div content along? If it’s not possible with the code I’ve used is there a better solution I could use? Any help would be greatly appreciated!

Dave Vernon

 
Me_fb_small Tony Amoyal 1 post

Have you made any progress on this issue? I am working on the same thing right now. If you have a solution, it would be great if you could post it. If not, we could work together using this discussion to post progress.

Thanks,

Tony

 
Headshot_120px_small Christophe P... 52 posts

Hey guys, sorry I didn’t reply earlier.

Slider allows programmatic positioning through its setValue method, which takes the value for the handle, and optionally the handle index (starting from zero), if there are multiple handles.

So based on the code here, and considering the slider2 variable is the horizontal slider, all we need to do is:

a) Add proper classes to the left/right links, like so:

<a class="scroll left" href="#">scroll left</a>  |  <a class="scroll right" href="#">scroll right</a>

b) Follow slider2’s creation with code such as the following:


document.observe('click', function(e) {
  var activator = e.findElement('a.scroll');
  if (!activator) return;
  e.stop();
  var sign    = activator.hasClassName('left') ? -1 : 1;
  var offset  = sign * ((slider2.maximum - slider2.minimum) / 10);
  slider2.setValue(slider2.value + offset);
});

This requests sliding of 10%, left or right. Slider’s internal controls will prevent overshooting.

‘HTH

3 posts, 3 voices