/**
* @author Ryan Johnson
* @copyright 2008 PersonalGrid Corporation
* @package LivePipe UI
* @license MIT
* @url http://livepipe.net/control/scrollbar
* @require prototype.js, slider.js, livepipe.js
*/
if(typeof(Prototype) == "undefined")
throw "Control.ScrollBar requires Prototype to be loaded.";
if(typeof(Control.Slider) == "undefined")
throw "Control.ScrollBar requires Control.Slider to be loaded.";
if(typeof(Object.Event) == "undefined")
throw "Control.ScrollBar requires Object.Event to be loaded.";
Control.ScrollBar = Class.create({
initialize: function(container,track,options){
Control.ScrollBar.instances.push(this);
this.enabled = false;
this.notificationTimeout = false;
this.container = $(container);
this.boundMouseWheelEvent = this.onMouseWheel.bindAsEventListener(this);
this.boundResizeObserver = this.onWindowResize.bind(this);
this.track = $(track);
this.handle = this.track.firstDescendant();
this.options = Object.extend({
active_class_name: 'scrolling',
apply_active_class_name_to: this.container,
notification_timeout_length: 125,
handle_minimum_length: 25,
scroll_to_smoothing: 0.01,
scroll_to_steps: 15,
scroll_to_precision: 100,
proportional: true,
custom_event: null,
custom_event_handler: null,
scroll_axis: 'vertical',
/**
* If this value is larger than 0 (e.g. 50), one mouse wheel event will scroll the page
* by the given number of pixels (default behaviour in any program). If it is lower than 0,
* the scrollbar's default behaviour will be used (scrolls down 1/20 of the page, no matter
* how long it is). Initialization with e.g.:
* new Control.ScrollBar(
document.getElementById('scrollbar_content'),
document.getElementById('scrollbar_track'),
{ fixed_scroll_distance: 50 });
*/
fixed_scroll_distance: -1,
slider_options: {}
},options || {});
this.slider = new Control.Slider(this.handle,this.track,Object.extend({
axis: this.options.scroll_axis,
onSlide: this.onChange.bind(this),
onChange: this.onChange.bind(this)
},this.options.slider_options));
this.recalculateLayout();
Event.observe(window,'resize',this.boundResizeObserver);
if (this.options.custom_event) {
if (Object.isFunction(this.options.custom_event_handler)) {
this.container.observe(this.options.custom_event, this.options.custom_event_handler);
} else {
this.container.observe(this.options.custom_event, this.boundResizeObserver);
}
}
this.handle.observe('mousedown',function(){
if(this.auto_sliding_executer)
this.auto_sliding_executer.stop();
}.bind(this));
},
destroy: function(){
Event.stopObserving(window,'resize',this.boundResizeObserver);
if(this.options.active_class_name)
$(this.options.apply_active_class_name_to).removeClassName(this.options.active_class_name);
if (this.options.custom_event) {
this.container.stopObserving(this.options.custom_event);
}
},
scrollLength: function(){
return (this.options.scroll_axis == 'vertical') ? this.container.scrollHeight : this.container.scrollWidth;
},
offsetLength: function(){
return (this.options.scroll_axis == 'vertical') ? this.container.offsetHeight : this.container.offsetWidth;
},
enable: function(){
this.enabled = true;
this.container.observe('mouse:wheel',this.boundMouseWheelEvent);
this.slider.setEnabled();
this.track.show();
if(this.options.active_class_name)
$(this.options.apply_active_class_name_to).addClassName(this.options.active_class_name);
this.notify('enabled');
},
disable: function(){
this.enabled = false;
this.container.stopObserving('mouse:wheel',this.boundMouseWheelEvent);
this.slider.setDisabled();
this.track.hide();
if(this.options.active_class_name)
$(this.options.apply_active_class_name_to).removeClassName(this.options.active_class_name);
this.notify('disabled');
this.reset();
},
reset: function(){
this.slider.setValue(0);
},
recalculateLayout: function(){
if(this.scrollLength() <= this.offsetLength())
this.disable();
else{
this.enable();
this.slider.trackLength = this.slider.maximumOffset() - this.slider.minimumOffset();
if(this.options.proportional){
this.slider.handleLength = Math.max(this.offsetLength() * (this.offsetLength() / this.scrollLength()),this.options.handle_minimum_length);
if (this.options.scroll_axis == 'vertical')
this.handle.style.height = this.slider.handleLength + 'px';
else
this.handle.style.width = this.slider.handleLength + 'px';
}
this.scrollBy(0);
}
},
onWindowResize: function(){
this.recalculateLayout();
this.scrollBy(0);
},
onMouseWheel: function(event){
if(this.auto_sliding_executer) {
this.auto_sliding_executer.stop();
}
if (this.options.fixed_scroll_distance > 0) {
// Move the content by the given number of pixels each wheel event
this.slider.setValueBy(-(this.options.fixed_scroll_distance * event.memo.delta / (this.scrollLength()-this.slider.trackLength)));
} else {
// Move the content by 1/20 of the page
this.slider.setValueBy(-(event.memo.delta / 20));
}
event.stop();
return false;
},
onChange: function(value){
var scroll_pos = Math.round(value / this.slider.maximum * (this.scrollLength() - this.offsetLength()));
if (this.options.scroll_axis == 'vertical')
this.container.scrollTop = scroll_pos;
else
this.container.scrollLeft = scroll_pos;
if(this.notification_timeout)
window.clearTimeout(this.notificationTimeout);
this.notificationTimeout = window.setTimeout(function(){
this.notify('change',value);
}.bind(this),this.options.notification_timeout_length);
},
getCurrentMaximumDelta: function(){
return this.slider.maximum * (this.scrollLength() - this.offsetLength());
},
getContainerOffset: function(element) {
var offset = element.positionedOffset();
while (element.getOffsetParent() != this.container)
{
element = element.getOffsetParent();
offset[0] += element.positionedOffset()[0];
offset[1] += element.positionedOffset()[1];
offset.top += element.positionedOffset().top;
offset.left += element.positionedOffset().left;
}
return offset;
},
getDeltaToElement: function(element){
if (this.options.scroll_axis == 'vertical')
return this.slider.maximum * ((this.getContainerOffset(element).top + (element.getHeight() / 2)) - (this.container.getHeight() / 2));
else
return this.slider.maximum * ((this.getContainerOffset(element).left + (element.getWidth() / 2)) - (this.container.getWidth() / 2));
},
scrollTo: function(y,animate){
var precision = this.options.scroll_to_precision,
current_maximum_delta = this.getCurrentMaximumDelta();
if (precision == 'auto')
precision = Math.pow(10, Math.ceil(Math.log(current_maximum_delta)/Math.log(10)));
if(y == 'top')
y = 0;
else if(y == 'bottom')
y = current_maximum_delta;
else if(typeof(y) != "number")
y = this.getDeltaToElement($(y));
if(this.enabled){
y = Math.max(0,Math.min(y,current_maximum_delta));
if(this.auto_sliding_executer)
this.auto_sliding_executer.stop();
var target_value = y / current_maximum_delta;
var original_slider_value = this.slider.value;
var delta = (target_value - original_slider_value) * current_maximum_delta;
if(animate){
this.auto_sliding_executer = new PeriodicalExecuter(function(){
if(Math.round(this.slider.value * precision) / precision < Math.round(target_value * precision) / precision || Math.round(this.slider.value * precision) / precision > Math.round(target_value * precision) / precision){
this.scrollBy(delta / this.options.scroll_to_steps);
}else{
this.auto_sliding_executer.stop();
this.auto_sliding_executer = null;
if(typeof(animate) == "function")
animate();
}
}.bind(this),this.options.scroll_to_smoothing);
}else
this.scrollBy(delta);
}else if(typeof(animate) == "function")
animate();
},
scrollBy: function(y){
if(!this.enabled)
return false;
this.slider.setValueBy(y / (this.getCurrentMaximumDelta() == 0 ? 1 : this.getCurrentMaximumDelta()) );
}
});
Object.extend(Control.ScrollBar,
{
instances: [],
findByElementId: function(id)
{
return Control.ScrollBar.instances.find(function(instance)
{
return (instance.container.id && instance.container.id == id);
});
}
});
Object.Event.extend(Control.ScrollBar);