Hover over the active arrow tabs to scroll the items in the list:
/**
* add a function to an event, attached to a particular object
* @param {object} obj the object that will trigger the event
* @param {string} type the name of the event
* @param {function} fn the function to attach
**/
function addEvent( obj, type, fn ) {
if (obj.addEventListener) {
obj.addEventListener( type, fn, false );
}
else if (obj.attachEvent) {
obj["e"+type+fn] = fn;
obj[type+fn] = function() { obj["e"+type+fn]( window.event ); };
obj.attachEvent( "on"+type, obj[type+fn] );
}
else {
obj["on"+type] = obj["e"+type+fn];
}
}
/**
* Creates a delegate (callback) that sets the scope to obj.
* Call directly on any function. Example: this.myFunction.createDelegate(this)
* Will create a function that is automatically scoped to this.
* @param {object} obj (optional) The object for which the scope is set
* @param {array} args (optional) Overrides arguments for the call.
* (Defaults to the arguments passed by the caller)
* @param {bool/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
* if a number the args are inserted at the specified position
* @return {function} The new function
*/
Function.prototype.createDelegate = function(obj, args, appendArgs){
var method = this;
return function() {
var callArgs = args || arguments;
if(appendArgs === true){
callArgs = Array.prototype.slice.call(arguments, 0);
callArgs = callArgs.concat(args);
}else if(typeof appendArgs == "number"){
callArgs = Array.prototype.slice.call(arguments, 0); // copy arguments first
var applyArgs = [appendArgs, 0].concat(args); // create method call params
Array.prototype.splice.apply(callArgs, applyArgs); // splice them in
}
return method.apply(obj || window, callArgs);
};
};
/**
* left-pad a number or a string with a given character so that
* it's at least the given number of characters long
* @param {mixed} val the string or number to pad
* @param {int} len the desired length
* @param {string} pad the character to use for padding
* @return {string} out the newly formatted value as a string
**/
function leftPad(val, len, pad) {
return (new Array(len - String(val).length + 1)).join(pad).concat(val);
}
/**
* create a scrolling navigation pane
* @requires addEvent(obj,type,fn)
* @requires leftPad(val,len,pad)
* @requires Function.prototype.createDelegate(obj, args, appendArgs)
**/
var NavPane = {
px: 1, // number of pixels that the pane will move in each scrolling interval
rate: 5, // number of milliseconds between each position adjustment
boxes: 10, // TODO: get the content info and number of boxes from an API or page load data
intvl: [], // holder for the motion intervals
axis: { // enumeration for directions
right: 0,
left: 1
},
events: { // setup low level event tracking (normally would create events using a
rActive: false,
lActive: false,
rEnabled: true,
lEnabled: false
},
/**
* render the NavPane
* create DOM elements to represent panel boxes
**/
init: function() {
// gather pane skeleton (TODO: we could create this dynamically in this class)
this.navLeft = document.getElementById('navLeft');
this.navRight = document.getElementById('navRight');
this.navPaneWrap = document.getElementById('navPaneWrap');
this.navPane = document.getElementById('navPane');
// create pane
this.render();
// add events
addEvent(this.navLeft,'mouseover', this.setScrollInterval.createDelegate(this,[this.axis.left]));
addEvent(this.navRight,'mouseover', this.setScrollInterval.createDelegate(this,[this.axis.right]));
addEvent(this.navLeft,'mouseout',this.stop.createDelegate(this,[this.axis.left]));
addEvent(this.navRight,'mouseout',this.stop.createDelegate(this,[this.axis.right]));
},
/**
* render the NavPane
* create DOM elements to represent panel boxes
**/
render: function() {
var w = this.boxes*71;
this.navPane.style.width = w+'px'; // TODO: fetch the width of the boxes dynamically or define in JS
for(var i=1;i<=this.boxes;i++) {
var el = document.createElement('div');
el.innerHTML = 'box '+leftPad(i,3,'0');
this.navPane.appendChild(el);
}
this.minLeft = -(w - this.navPaneWrap.offsetWidth);
this.maxLeft = 0;
},
setScrollInterval: function(dir){
this.intvl[dir] = setInterval(this.scroll.createDelegate(this,[dir]),this.rate);
},
scroll: function(dir) {
var xpos = parseInt(this.navPane.offsetLeft || 0);
if(dir==this.axis.right) {
if(xpos > this.minLeft) {
xpos -= this.px;
this.navPane.style.left = xpos + 'px'; // move right
this.navLeft.className = "navBtn";
}else{ // stop it
this.stop(dir);
this.navRight.className = "navBtn inactive"; // turn inactive
}
}else{
if(xpos < this.maxLeft) {
xpos += this.px;
this.navPane.style.left = xpos + 'px'; // move left
this.navRight.className = "navBtn";
}else{ // stop it
this.stop(dir);
this.navLeft.className = "navBtn inactive"; // turn inactive
}
}
// if we want the menu to speed up more the longer we hover
// TODO: need some special handling
//this.intCount++;
//if(!(this.intCount%100)) this.px++;
},
stop: function(dir){
if(this.intvl[dir]) {
clearInterval(this.intvl[dir]);
this.intvl[dir] = 0;
//this.px = 1;
}
}
};
addEvent(window,'load',function() {
NavPane.init();
});