3,554
edits
mNo edit summary |
(Test script) |
||
Line 1: | Line 1: | ||
/* | |||
Class: Scroller | |||
Adds a scrollbar to a specific div. The scrollbar is implemented using a Script.aculo.us slider. | |||
The class reparents the original div, creates a slider and ties the reparented div to the slider, | |||
setting any properties necessary on the divs to make it all work. The scrollbar can be styled using | |||
CSS. The track of the scrollbar has class 'scroll-track', 'scroll-track-top' and 'scroll-track-bot', | |||
the thumb has class 'scroll-handle', 'scroll-handle-top' and 'scroll-handle-bot'. | |||
properties: | |||
myIndex - an integer used to generate a unique ID for use in, for example, div ids. | |||
outerBox - the div that holds the scrollpane + scrollbar | |||
innerBox - the div that holds the scrollpane | |||
innerHeight - the height of the inner box. | |||
viewportHeight - the height of the view onto the scrolled div. | |||
track - a div that holds the script.aculo.us slider (the scrollbar) | |||
trackHeight - the height of the slider | |||
handle - the div for the 'thumb' of the scrollbar | |||
handleHeight - the height of the thumb | |||
slider - the script.aculo.us slider itself | |||
ieDecreaseBy - a fudge factor used when calculating the width of innerBox | |||
*/ | |||
var Scroller = Class.create(); | |||
/* | |||
$('. | property: Scroller.ids | ||
}); | A cache of Scrollers indexed by the ID of the original div. | ||
*/ | |||
$('. | Scroller.ids = new Object(); | ||
}); | |||
/* | |||
property: Scroller.i | |||
A unique ID generator. | |||
*/ | |||
Scroller.i = 0; | |||
Scroller.prototype = { | |||
/* | |||
constructor: initialize | |||
Wrap the passed div in a scrollpane. | |||
parameters: | |||
el - the div to add a scrollbar to. | |||
*/ | |||
initialize: function(el) { | |||
this.outerBox = el; | |||
this.decorate(); | |||
}, | |||
/* | |||
function: decorate | |||
create the necessary elements to implement the scrollbar and wire up events. | |||
*/ | |||
decorate: function() { | |||
$(this.outerBox).makePositioned(); // Fix IE | |||
// Seed a unique ID | |||
Scroller.i = Scroller.i + 1; | |||
this.myIndex = Scroller.i; | |||
//wrap the existing content in an intermediate inner box | |||
this.innerBox = document.createElement("DIV"); | |||
this.innerBox.className="scroll-innerBox"; | |||
$(this.innerBox).makePositioned(); // Fix IE | |||
this.innerBox.style.cssFloat=this.innerBox.style.styleFloat='left'; // Need the scrollbar to appear next to the scrollpane | |||
this.innerBox.id="scroll-innerBox-"+Scroller.i; | |||
this.innerBox.style.top = "0px"; | |||
//Transfer the contents of Outer Box to Inner Box | |||
while (this.outerBox.hasChildNodes()) { | |||
this.innerBox.appendChild(this.outerBox.firstChild); | |||
} | |||
this.innerBox.style.overflow="hidden"; | |||
//turn off scrolling on the outer div | |||
this.outerBox.style.overflow="hidden"; | |||
// create a track | |||
this.track=document.createElement('div'); | |||
this.track.className="scroll-track"; | |||
$(this.track).makePositioned(); | |||
this.track.style.cssFloat=this.track.style.styleFloat='left'; | |||
this.track.id="scroll-track-"+Scroller.i; | |||
// Fix IE line-height bug. Sigh. | |||
this.track.appendChild(document.createComment('')); | |||
// Create the top button | |||
this.tracktop=document.createElement('div'); | |||
this.tracktop.className="scroll-track-top"; | |||
$(this.tracktop).makePositioned(); | |||
this.tracktop.style.cssFloat=this.tracktop.style.styleFloat='left'; | |||
this.tracktop.id="scroll-track-top-"+Scroller.i; | |||
// Fix IE line-height bug. Sigh. | |||
this.tracktop.appendChild(document.createComment('')); | |||
// Create the bottom button | |||
this.trackbot=document.createElement('div'); | |||
this.trackbot.className="scroll-track-bot"; | |||
$(this.trackbot).makePositioned(); | |||
this.trackbot.style.cssFloat=this.trackbot.style.styleFloat='left'; | |||
this.trackbot.id="scroll-track-bot-"+Scroller.i; | |||
// Fix IE line-height bug. Sigh. | |||
this.trackbot.appendChild(document.createComment('')); | |||
// Create the handle | |||
this.handle=document.createElement('div'); | |||
this.handle.className="scroll-handle-container"; | |||
this.handle.id="scroll-handle-container"+Scroller.i; | |||
// Create the handle middle | |||
this.handle_middle=document.createElement('div'); | |||
this.handle_middle.className="scroll-handle"; | |||
$(this.handle_middle).makePositioned(); | |||
this.handle_middle.id="scroll-handle-"+Scroller.i; | |||
// Fix IE line-height bug. Sigh. | |||
this.handle_middle.appendChild(document.createComment('')); | |||
// Create the handle top cap | |||
this.handletop=document.createElement('div'); | |||
this.handletop.className="scroll-handle-top"; | |||
$(this.handletop).makePositioned(); | |||
this.handletop.id="scroll-handle-top-"+Scroller.i; | |||
// Fix IE line-height bug. Sigh. | |||
this.handletop.appendChild(document.createComment('')); | |||
// Create the handle bottom cap | |||
this.handlebot=document.createElement('div'); | |||
this.handlebot.className="scroll-handle-bot"; | |||
$(this.handlebot).makePositioned(); | |||
this.handlebot.id="scroll-handle-bot-"+Scroller.i; | |||
// Fix IE line-height bug. Sigh. | |||
this.handlebot.appendChild(document.createComment('')); | |||
this.track.hide(); | |||
this.tracktop.hide(); | |||
this.trackbot.hide(); | |||
this.outerBox.appendChild(this.innerBox); | |||
this.outerBox.appendChild(this.tracktop); | |||
this.handle.appendChild(this.handletop); | |||
this.handle.appendChild(this.handle_middle); | |||
this.handle.appendChild(this.handlebot); | |||
this.track.appendChild(this.handle); | |||
this.outerBox.appendChild(this.track); | |||
this.outerBox.appendChild(this.trackbot); | |||
this.slider = new Control.Slider($(this.handle).id, $(this.track).id, {axis:'vertical', | |||
minimum: 0, | |||
maximum: $(this.outerBox).clientHeight}); | |||
this.slider.options.onSlide = this.slider.options.onChange = this.onChange.bind(this); | |||
setTimeout(this.resetScrollbar.bind(this, false), 10); | |||
this.domMouseCB = this.MouseWheelEvent.bindAsEventListener(this, this.slider); | |||
this.mouseWheelCB = this.MouseWheelEvent.bindAsEventListener(this, this.slider); | |||
this.trackTopCB = this.tracktopEvent.bindAsEventListener(this, this.slider); | |||
this.trackBotCB = this.trackbotEvent.bindAsEventListener(this, this.slider); | |||
//Events control | |||
$(this.outerBox).observe('DOMMouseScroll', this.domMouseCB); // Mozilla | |||
$(this.outerBox).observe('mousewheel', this.mouseWheelCB);// IE/Opera | |||
$(this.tracktop).observe('mousedown', this.trackTopCB); | |||
$(this.trackbot).observe('mousedown', this.trackBotCB); | |||
}, | |||
release: function() { | |||
$(this.outerBox).stopObserving('DOMMouseScroll', this.domMouseCB); | |||
$(this.outerBox).stopObserving('mousewheel', this.mouseWheelCB);// IE/Opera | |||
$(this.tracktop).stopObserving('mousedown', this.trackTopCB); | |||
$(this.trackbot).stopObserving('mousedown', this.trackBotCB); | |||
}, | |||
/* | |||
function: resetScrollbar | |||
Re-calculate the geometry of the scrollbar. Typically called from an event handler. | |||
args: | |||
repeat - if true, set timer to re-calculate to fix IE bug on resize window. | |||
*/ | |||
resetScrollbar: function(repeat) { | |||
this.track.hide(); | |||
this.tracktop.hide(); | |||
this.trackbot.hide(); | |||
this.enableScroll = false; | |||
this.innerHeight = $(this.outerBox).clientHeight; | |||
this.innerBox.style.height = this.innerHeight + "px"; | |||
var newWidth = $(this.outerBox).clientWidth; | |||
var tth = Element.getStyle(this.tracktop,"height"); | |||
if (tth) | |||
tth = tth.replace("px",""); | |||
else | |||
tth = 0; | |||
var hth = Element.getStyle(this.handletop,"height"); | |||
if (hth) | |||
hth = hth.replace("px",""); | |||
else | |||
hth = 0; | |||
if (this.innerHeight < this.innerBox.scrollHeight) { | |||
this.viewportHeight = this.innerHeight - tth*2; | |||
this.slider.trackLength = this.viewportHeight; | |||
this.track.style.height = this.viewportHeight + "px"; | |||
this.handleHeight = Math.round(this.viewportHeight * this.innerHeight / this.innerBox.scrollHeight); | |||
if(this.handleHeight < (hth*2)) | |||
this.handleHeight = (hth*2); | |||
if (this.handleHeight < 10) | |||
this.handleHeight = 10; | |||
this.handle.style.height = this.handleHeight + "px"; | |||
this.handle_middle.style.height = this.handleHeight - hth*2 + "px"; | |||
this.handletop.style.height = hth + "px"; | |||
this.slider.handleLength = this.handleHeight; | |||
this.track.style.display = 'inline'; | |||
this.tracktop.style.display = 'inline'; | |||
this.trackbot.style.display = 'inline'; | |||
this.ieDecreaseBy = 1; // Firefox seems to have an off-by one error, so allow for it. | |||
if (this.outerBox.currentStyle) { | |||
var borderWidth = this.outerBox.currentStyle["borderWidth"].replace("px",""); | |||
if(!isNaN(borderWidth)) { | |||
this.ieDecreaseBy = (borderWidth) * 2; | |||
} | |||
} | |||
newWidth = ($(this.outerBox).clientWidth - $(this.track).clientWidth - this.ieDecreaseBy); | |||
this.enableScroll = true; | |||
} | |||
//Set the width of of the scrollpane (aka innerBox). | |||
this.innerBox.style.width = newWidth + "px"; | |||
//Fix IE resize event Bug | |||
if(repeat) { | |||
setTimeout(this.resetScrollbar.bind(this, false), 10); | |||
} | |||
}, | |||
//Mouse wheel code from http://adomas.org/javascript-mouse-wheel/ | |||
MouseWheelEvent: function(event, slider) { | |||
var delta = 0; | |||
if (!event) //For IE. | |||
event = window.event; | |||
if (event.wheelDelta) { //IE/Opera. | |||
delta = event.wheelDelta / 120; | |||
/*if (window.opera) //In Opera 9, delta differs in sign as compared to IE | |||
delta = -delta; But it isn't necessary with Opera v9.51*/ | |||
} else if (event.detail) { //Mozilla case | |||
delta = -event.detail / 3; | |||
} | |||
if (delta) | |||
slider.setValueBy(-delta / 10); | |||
Event.stop(event); | |||
}, | |||
trackbotEvent: function(event, slider) { | |||
if (Event.isLeftClick(event)) { | |||
slider.setValueBy(0.2); | |||
Event.stop(event); | |||
} | |||
}, | |||
tracktopEvent: function(event, slider) { | |||
if (Event.isLeftClick(event)) { | |||
slider.setValueBy(-0.2); | |||
Event.stop(event); | |||
} | |||
}, | |||
/* | |||
function: onChange | |||
Called when the script.aculo.us slider has changed (i.e. when it has been dragged). Scroll the inner box. | |||
args: | |||
val - not used. | |||
*/ | |||
onChange: function(val) { | |||
if(this.enableScroll) | |||
this.innerBox.scrollTop = Math.round (val * (this.innerBox.scrollHeight-this.innerBox.offsetHeight)); | |||
} | |||
} | |||
/* | |||
function: Scroller.setAll | |||
Search for divs of the class 'makeScroll' and wrap them in a Scroller. | |||
*/ | |||
Scroller.setAll = function () { | |||
$$('.makeScroll').each(function(item) { | |||
Scroller.ids[item.id] = new Scroller(item); | |||
}); | |||
} | |||
/* | |||
function: Scroller.reset | |||
If the passed element has class 'makeScroll', wrap it in a Scroller. | |||
*/ | |||
Scroller.reset = function (body_id) { | |||
if ($(body_id).className.match(new RegExp("(^|\\s)makeScroll(\\s|$)"))) { | |||
if (Scroller.ids[body_id]) | |||
Scroller.ids[body_id].release(); | |||
Scroller.ids[body_id] = new Scroller($(body_id)); | |||
} | |||
} | |||
/* | |||
property: Scroller.updateAll | |||
Reset all of the scrollbars. | |||
*/ | |||
Scroller.updateAll = function () { | |||
$H(Scroller.ids).each(function(pair) { | |||
Scroller.ids[pair.key].resetScrollbar(true); | |||
}); | |||
} | |||
/* | |||
Hook up some global event handlers. | |||
*/ | |||
Event.observe(window, "load", Scroller.setAll); | |||
Event.observe(window, "resize", Scroller.updateAll); |