Blame | Last modification | View Log | RSS feed
/*!* jScrollPane - v2.0.0beta9 - 2011-02-04* http://jscrollpane.kelvinluck.com/** Copyright (c) 2010 Kelvin Luck* Dual licensed under the MIT and GPL licenses.*/// Script: jScrollPane - cross browser customisable scrollbars//// *Version: 2.0.0beta10, Last updated: 2011-02-04*//// Project Home - http://jscrollpane.kelvinluck.com/// GitHub - http://github.com/vitch/jScrollPane// Source - http://github.com/vitch/jScrollPane/raw/master/script/jquery.jscrollpane.js// (Minified) - http://github.com/vitch/jScrollPane/raw/master/script/jquery.jscrollpane.min.js//// About: License//// Copyright (c) 2010 Kelvin Luck// Dual licensed under the MIT or GPL Version 2 licenses.// http://jscrollpane.kelvinluck.com/MIT-LICENSE.txt// http://jscrollpane.kelvinluck.com/GPL-LICENSE.txt//// About: Examples//// All examples and demos are available through the jScrollPane example site at:// http://jscrollpane.kelvinluck.com///// About: Support and Testing//// This plugin is tested on the browsers below and has been found to work reliably on them. If you run// into a problem on one of the supported browsers then please visit the support section on the jScrollPane// website (http://jscrollpane.kelvinluck.com/) for more information on getting support. You are also// welcome to fork the project on GitHub if you can contribute a fix for a given issue.//// jQuery Versions - tested in 1.4.2+ - reported to work in 1.3.x// Browsers Tested - Firefox 3.6.8, Safari 5, Opera 10.6, Chrome 5.0, IE 6, 7, 8//// About: Release History//// 2.0.0beta10 - (in progress)// 2.0.0beta9 - (2011-01-31) new API methods, bug fixes and correct keyboard support for FF/OSX// 2.0.0beta8 - (2011-01-29) touchscreen support, improved keyboard support// 2.0.0beta7 - (2011-01-23) scroll speed consistent (thanks Aivo Paas)// 2.0.0beta6 - (2010-12-07) scrollToElement horizontal support// 2.0.0beta5 - (2010-10-18) jQuery 1.4.3 support, various bug fixes// 2.0.0beta4 - (2010-09-17) clickOnTrack support, bug fixes// 2.0.0beta3 - (2010-08-27) Horizontal mousewheel, mwheelIntent, keyboard support, bug fixes// 2.0.0beta2 - (2010-08-21) Bug fixes// 2.0.0beta1 - (2010-08-17) Rewrite to follow modern best practices and enable horizontal scrolling, initially hidden// elements and dynamically sized elements.// 1.x - (2006-12-31 - 2010-07-31) Initial version, hosted at googlecode, deprecated(function($,window,undefined){$.fn.jScrollPane = function(settings){// JScrollPane "class" - public methods are available through $('selector').data('jsp')function JScrollPane(elem, s){var settings, jsp = this, pane, paneWidth, paneHeight, container, contentWidth, contentHeight,percentInViewH, percentInViewV, isScrollableV, isScrollableH, verticalDrag, dragMaxY,verticalDragPosition, horizontalDrag, horizontalDragbottom, dragMaxX, horizontalDragPosition, horizontalDragPositionbottom,verticalBar, verticalTrack, scrollbarWidth, verticalTrackHeight, verticalDragHeight, arrowUp, arrowDown,horizontalBar, horizontalTrack, horizontalTrackWidth, horizontalDragWidth, horizontalBarbottom, horizontalTrackbottom, horizontalTrackWidthbottom, horizontalDragWidthbottom, arrowLeft, arrowRight,arrowLeftbottom, arrowRightbottom, reinitialiseInterval, originalPadding, originalPaddingTotalWidth, previousContentWidth,wasAtTop = true, wasAtLeft = true, wasAtBottom = false, wasAtRight = false, wasAtTopbottom = true, wasAtLeftbottom = true, wasAtBottombottom = false, wasAtRightbottom = false,originalElement = elem.clone(false, false).empty(),mwEvent = $.fn.mwheelIntent ? 'mwheelIntent.jsp' : 'mousewheel.jsp';originalPadding = elem.css('paddingTop') + ' ' +elem.css('paddingRight') + ' ' +elem.css('paddingBottom') + ' ' +elem.css('paddingLeft');originalPaddingTotalWidth = (parseInt(elem.css('paddingLeft'), 10) || 0) +(parseInt(elem.css('paddingRight'), 10) || 0);function initialise(s){var clonedElem, tempWrapper, /*firstChild, lastChild, */isMaintainingPositon, lastContentX, lastContentY,hasContainingSpaceChanged, originalScrollTop, originalScrollLeft;settings = s;if (pane === undefined) {originalScrollTop = elem.scrollTop();originalScrollLeft = elem.scrollLeft();elem.css({overflow: 'hidden',padding: 0});// TODO: Deal with where width/ height is 0 as it probably means the element is hidden and we should// come back to it later and check once it is unhidden...paneWidth = elem.innerWidth() + originalPaddingTotalWidth;paneHeight = elem.innerHeight();elem.width(paneWidth);pane = $('<div class="jspPane" />').css('padding', originalPadding).append(elem.children());container = $('<div class="jspContainer" />').css({'width': paneWidth + 'px','height': paneHeight + 'px'}).append(pane).appendTo(elem);} else {elem.css('width', '');hasContainingSpaceChanged = elem.innerWidth() + originalPaddingTotalWidth != paneWidth || elem.outerHeight() != paneHeight;if (hasContainingSpaceChanged) {paneWidth = elem.innerWidth() + originalPaddingTotalWidth;paneHeight = elem.innerHeight();container.css({width: paneWidth + 'px',height: paneHeight + 'px'});}// If nothing changed since last check...if (!hasContainingSpaceChanged && previousContentWidth == contentWidth && pane.outerHeight() == contentHeight) {elem.width(paneWidth);return;}previousContentWidth = contentWidth;pane.css('width', '');elem.width(paneWidth);container.find('>.jspVerticalBar,>.jspHorizontalBar,>.jspHorizontalBarbottom').remove().end();}// Unfortunately it isn't that easy to find out the width of the element as it will always report the// width as allowed by its container, regardless of overflow settings.// A cunning workaround is to clone the element, set its position to absolute and place it in a narrow// container. Now it will push outwards to its maxium real width...clonedElem = pane.clone(false, false).css('position', 'absolute');tempWrapper = $('<div style="width:1px; position: relative;" />').append(clonedElem);$('body').append(tempWrapper);contentWidth = Math.max(pane.outerWidth(), clonedElem.outerWidth());tempWrapper.remove();contentHeight = pane.outerHeight();percentInViewH = contentWidth / paneWidth;percentInViewV = contentHeight / paneHeight;isScrollableV = percentInViewV > 1;isScrollableH = percentInViewH > 1;//console.log(paneWidth, paneHeight, contentWidth, contentHeight, percentInViewH, percentInViewV, isScrollableH, isScrollableV);if (!(isScrollableH || isScrollableV)) {elem.removeClass('jspScrollable');pane.css({top: 0,width: container.width() - originalPaddingTotalWidth});removeMousewheel();removeFocusHandler();removeKeyboardNav();removeClickOnTrack();unhijackInternalLinks();} else {elem.addClass('jspScrollable');isMaintainingPositon = settings.maintainPosition && (verticalDragPosition || horizontalDragPosition || horizontalDragPositionbottom);if (isMaintainingPositon) {lastContentX = contentPositionX();lastContentY = contentPositionY();}initialiseVerticalScroll();initialiseHorizontalScroll();resizeScrollbars();if (isMaintainingPositon) {scrollToX(lastContentX, false);scrollToY(lastContentY, false);}initFocusHandler();initMousewheel();initTouch();if (settings.enableKeyboardNavigation) {initKeyboardNav();}if (settings.clickOnTrack) {initClickOnTrack();}observeHash();if (settings.hijackInternalLinks) {hijackInternalLinks();}}if (settings.autoReinitialise && !reinitialiseInterval) {reinitialiseInterval = setInterval(function(){initialise(settings);},settings.autoReinitialiseDelay);} else if (!settings.autoReinitialise && reinitialiseInterval) {clearInterval(reinitialiseInterval);}originalScrollTop && elem.scrollTop(0) && scrollToY(originalScrollTop, false);originalScrollLeft && elem.scrollLeft(0) && scrollToX(originalScrollLeft, false);elem.trigger('jsp-initialised', [isScrollableH || isScrollableV]);}function initialiseVerticalScroll(){if (isScrollableV) {container.append($('<div class="jspVerticalBar" />').append($('<div class="jspCap jspCapTop" />'),$('<div class="jspTrack" />').append($('<div class="jspDrag" />').append($('<div class="jspDragTop" />'),$('<div class="jspDragBottom" />'))),$('<div class="jspCap jspCapBottom" />')));verticalBar = container.find('>.jspVerticalBar');verticalTrack = verticalBar.find('>.jspTrack');verticalDrag = verticalTrack.find('>.jspDrag');if (settings.showArrows) {arrowUp = $('<a class="jspArrow jspArrowUp" />').bind('mousedown.jsp', getArrowScroll(0, -1)).bind('click.jsp', nil);arrowDown = $('<a class="jspArrow jspArrowDown" />').bind('mousedown.jsp', getArrowScroll(0, 1)).bind('click.jsp', nil);if (settings.arrowScrollOnHover) {arrowUp.bind('mouseover.jsp', getArrowScroll(0, -1, arrowUp));arrowDown.bind('mouseover.jsp', getArrowScroll(0, 1, arrowDown));}appendArrows(verticalTrack, settings.verticalArrowPositions, arrowUp, arrowDown);}verticalTrackHeight = paneHeight;container.find('>.jspVerticalBar>.jspCap:visible,>.jspVerticalBar>.jspArrow').each(function(){verticalTrackHeight -= $(this).outerHeight();});verticalDrag.hover(function(){verticalDrag.addClass('jspHover');},function(){verticalDrag.removeClass('jspHover');}).bind('mousedown.jsp',function(e){// Stop IE from allowing text selection$('html').bind('dragstart.jsp selectstart.jsp', nil);verticalDrag.addClass('jspActive');var startY = e.pageY - verticalDrag.position().top;$('html').bind('mousemove.jsp',function(e){positionDragY(e.pageY - startY, false);}).bind('mouseup.jsp mouseleave.jsp', cancelDrag);return false;});sizeVerticalScrollbar();}}function sizeVerticalScrollbar(){verticalTrack.height(verticalTrackHeight + 'px');verticalDragPosition = 0;scrollbarWidth = settings.verticalGutter + verticalTrack.outerWidth();// Make the pane thinner to allow for the vertical scrollbarpane.width(paneWidth - scrollbarWidth - originalPaddingTotalWidth);// Add margin to the left of the pane if scrollbars are on that side (to position// the scrollbar on the left or right set it's left or right property in CSS)if (verticalBar.position().left === 0) {pane.css('margin-left', scrollbarWidth + 'px');}}function initialiseHorizontalScroll(){if (isScrollableH) {container.append($('<div class="jspHorizontalBar" />').append($('<div class="jspCap jspCapLeft" />'),$('<div class="jspTrack" />').append($('<div class="jspDrag" />').append($('<div class="jspDragLeft" />'),$('<div class="jspDragRight" />'))),$('<div class="jspCap jspCapRight" />')));container.append($('<div class="jspHorizontalBarbottom" />').append($('<div class="jspCap jspCapLeftbottom" />'),$('<div class="jspTrackbottom" />').append($('<div class="jspDragbottom" />').append($('<div class="jspDragLeft" />'),$('<div class="jspDragRight" />'))),$('<div class="jspCap jspCapRight" />')));horizontalBar = container.find('>.jspHorizontalBar');horizontalTrack = horizontalBar.find('>.jspTrack');horizontalDrag = horizontalTrack.find('>.jspDrag');horizontalBarbottom = container.find('>.jspHorizontalBarbottom');horizontalTrackbottom = horizontalBarbottom.find('>.jspTrackbottom');horizontalDragbottom = horizontalTrackbottom.find('>.jspDragbottom');if (settings.showArrows) {arrowLeft = $('<a class="jspArrow jspArrowLeft" />').bind('mousedown.jsp', getArrowScroll(-1, 0)).bind('click.jsp', nil);arrowRight = $('<a class="jspArrow jspArrowRight" />').bind('mousedown.jsp', getArrowScroll(1, 0)).bind('click.jsp', nil);if (settings.arrowScrollOnHover) {arrowLeft.bind('mouseover.jsp', getArrowScroll(-1, 0, arrowLeft));arrowRight.bind('mouseover.jsp', getArrowScroll(1, 0, arrowRight));}appendArrows(horizontalTrack, settings.horizontalArrowPositions, arrowLeft, arrowRight);arrowLeftbottom = $('<a class="jspArrow jspArrowLeft" />').bind('mousedown.jsp', getArrowScroll(-1, 0)).bind('click.jsp', nil);arrowRightbottom = $('<a class="jspArrow jspArrowRight" />').bind('mousedown.jsp', getArrowScroll(1, 0)).bind('click.jsp', nil);if (settings.arrowScrollOnHover) {arrowLeftbottom.bind('mouseover.jsp', getArrowScroll(-1, 0, arrowLeftbottom));arrowRightbottom.bind('mouseover.jsp', getArrowScroll(1, 0, arrowRightbottom));}appendArrows(horizontalTrackbottom, settings.horizontalArrowPositions, arrowLeftbottom, arrowRightbottom);}horizontalDrag.hover(function(){horizontalDrag.addClass('jspHover');},function(){horizontalDrag.removeClass('jspHover');}).bind('mousedown.jsp',function(e){// Stop IE from allowing text selection$('html').bind('dragstart.jsp selectstart.jsp', nil);horizontalDrag.addClass('jspActive');var startX = e.pageX - horizontalDrag.position().left;$('html').bind('mousemove.jsp',function(e){positionDragX(e.pageX - startX, false);}).bind('mouseup.jsp mouseleave.jsp', cancelDrag);return false;});horizontalTrackWidth = container.innerWidth();sizeHorizontalScrollbar();horizontalDragbottom.hover(function(){horizontalDragbottom.addClass('jspHover');},function(){horizontalDragbottom.removeClass('jspHover');}).bind('mousedown.jsp',function(e){// Stop IE from allowing text selection$('html').bind('dragstart.jsp selectstart.jsp', nil);horizontalDragbottom.addClass('jspActive');var startX = e.pageX - horizontalDragbottom.position().left;$('html').bind('mousemove.jsp',function(e){positionDragX(e.pageX - startX, false);}).bind('mouseup.jsp mouseleave.jsp', cancelDrag);return false;});horizontalTrackWidthbottom = container.innerWidth();sizeHorizontalScrollbarbottom();}}function sizeHorizontalScrollbarbottom() {container.find('>.jspHorizontalBarbottom>.jspCap:visible,>.jspHorizontalBarbottom>.jspArrow').each(function(){horizontalTrackWidthbottom -= $(this).outerWidth();});horizontalTrack.width(horizontalTrackWidthbottom + 'px');horizontalDragPositionbottom = 0;}function sizeHorizontalScrollbar() {container.find('>.jspHorizontalBar>.jspCap:visible,>.jspHorizontalBar>.jspArrow').each(function(){horizontalTrackWidth -= $(this).outerWidth();});horizontalTrack.width(horizontalTrackWidth + 'px');horizontalDragPosition = 0;}function resizeScrollbars(){if (isScrollableH && isScrollableV) {var horizontalTrackHeight = horizontalTrack.outerHeight(),verticalTrackWidth = verticalTrack.outerWidth();verticalTrackHeight -= horizontalTrackHeight;$(horizontalBar).find('>.jspCap:visible,>.jspArrow').each(function(){horizontalTrackWidth += $(this).outerWidth();});$(horizontalBarbottom).find('>.jspCapbottom:visible,>.jspArrowbottom').each(function(){horizontalTrackWidthbottom += $(this).outerWidth();});horizontalTrackWidth -= verticalTrackWidth;horizontalTrackWidthbottom -= verticalTrackWidth;paneHeight -= verticalTrackWidth;paneWidth -= horizontalTrackHeight;horizontalTrack.parent().append($('<div class="jspCorner" />').css('width', horizontalTrackHeight + 'px'));horizontalTrackbottom.parent().append($('<div class="jspCorner" />').css('width', horizontalTrackHeight + 'px'));sizeVerticalScrollbar();sizeHorizontalScrollbar();}// reflow contentif (isScrollableH) {pane.width((container.outerWidth() - originalPaddingTotalWidth) + 'px');}contentHeight = pane.outerHeight();percentInViewV = contentHeight / paneHeight;if (isScrollableH) {horizontalDragWidth = Math.ceil(1 / percentInViewH * horizontalTrackWidth);if (horizontalDragWidth > settings.horizontalDragMaxWidth) {horizontalDragWidth = settings.horizontalDragMaxWidth;} else if (horizontalDragWidth < settings.horizontalDragMinWidth) {horizontalDragWidth = settings.horizontalDragMinWidth;}horizontalDrag.width(horizontalDragWidth + 'px');dragMaxX = horizontalTrackWidth - horizontalDragWidth;_positionDragX(horizontalDragPosition);_positionDragX(horizontalDragPositionbottom); // To update the state for the arrow buttons}if (isScrollableV) {verticalDragHeight = Math.ceil(1 / percentInViewV * verticalTrackHeight);if (verticalDragHeight > settings.verticalDragMaxHeight) {verticalDragHeight = settings.verticalDragMaxHeight;} else if (verticalDragHeight < settings.verticalDragMinHeight) {verticalDragHeight = settings.verticalDragMinHeight;}verticalDrag.height(verticalDragHeight + 'px');dragMaxY = verticalTrackHeight - verticalDragHeight;_positionDragY(verticalDragPosition); // To update the state for the arrow buttons}}function appendArrows(ele, p, a1, a2){var p1 = "before", p2 = "after", aTemp;// Sniff for mac... Is there a better way to determine whether the arrows would naturally appear// at the top or the bottom of the bar?if (p == "os") {p = /Mac/.test(navigator.platform) ? "after" : "split";}if (p == p1) {p2 = p;} else if (p == p2) {p1 = p;aTemp = a1;a1 = a2;a2 = aTemp;}ele[p1](a1)[p2](a2);}function getArrowScroll(dirX, dirY, ele){return function(){arrowScroll(dirX, dirY, this, ele);this.blur();return false;};}function arrowScroll(dirX, dirY, arrow, ele){arrow = $(arrow).addClass('jspActive');var eve,scrollTimeout,isFirst = true,doScroll = function(){if (dirX !== 0) {jsp.scrollByX(dirX * settings.arrowButtonSpeed);}if (dirY !== 0) {jsp.scrollByY(dirY * settings.arrowButtonSpeed);}scrollTimeout = setTimeout(doScroll, isFirst ? settings.initialDelay : settings.arrowRepeatFreq);isFirst = false;};doScroll();eve = ele ? 'mouseout.jsp' : 'mouseup.jsp';ele = ele || $('html');ele.bind(eve,function(){arrow.removeClass('jspActive');scrollTimeout && clearTimeout(scrollTimeout);scrollTimeout = null;ele.unbind(eve);});}function initClickOnTrack(){removeClickOnTrack();if (isScrollableV) {verticalTrack.bind('mousedown.jsp',function(e){if (e.originalTarget === undefined || e.originalTarget == e.currentTarget) {var clickedTrack = $(this),offset = clickedTrack.offset(),direction = e.pageY - offset.top - verticalDragPosition,scrollTimeout,isFirst = true,doScroll = function(){var offset = clickedTrack.offset(),pos = e.pageY - offset.top - verticalDragHeight / 2,contentDragY = paneHeight * settings.scrollPagePercent,dragY = dragMaxY * contentDragY / (contentHeight - paneHeight);if (direction < 0) {if (verticalDragPosition - dragY > pos) {jsp.scrollByY(-contentDragY);} else {positionDragY(pos);}} else if (direction > 0) {if (verticalDragPosition + dragY < pos) {jsp.scrollByY(contentDragY);} else {positionDragY(pos);}} else {cancelClick();return;}scrollTimeout = setTimeout(doScroll, isFirst ? settings.initialDelay : settings.trackClickRepeatFreq);isFirst = false;},cancelClick = function(){scrollTimeout && clearTimeout(scrollTimeout);scrollTimeout = null;$(document).unbind('mouseup.jsp', cancelClick);};doScroll();$(document).bind('mouseup.jsp', cancelClick);return false;}});}if (isScrollableH) {horizontalTrack.bind('mousedown.jsp',function(e){if (e.originalTarget === undefined || e.originalTarget == e.currentTarget) {var clickedTrack = $(this),offset = clickedTrack.offset(),direction = e.pageX - offset.left - horizontalDragPosition,scrollTimeout,isFirst = true,doScroll = function(){var offset = clickedTrack.offset(),pos = e.pageX - offset.left - horizontalDragWidth / 2,contentDragX = paneWidth * settings.scrollPagePercent,dragX = dragMaxX * contentDragX / (contentWidth - paneWidth);if (direction < 0) {if (horizontalDragPosition - dragX > pos) {jsp.scrollByX(-contentDragX);} else {positionDragX(pos);}} else if (direction > 0) {if (horizontalDragPosition + dragX < pos) {jsp.scrollByX(contentDragX);} else {positionDragX(pos);}} else {cancelClick();return;}scrollTimeout = setTimeout(doScroll, isFirst ? settings.initialDelay : settings.trackClickRepeatFreq);isFirst = false;},cancelClick = function(){scrollTimeout && clearTimeout(scrollTimeout);scrollTimeout = null;$(document).unbind('mouseup.jsp', cancelClick);};doScroll();$(document).bind('mouseup.jsp', cancelClick);return false;}});horizontalTrackbottom.bind('mousedown.jsp',function(e){if (e.originalTarget === undefined || e.originalTarget == e.currentTarget) {var clickedTrack = $(this),offset = clickedTrack.offset(),direction = e.pageX - offset.left - horizontalDragPositionbottom,scrollTimeout,isFirst = true,doScroll = function(){var offset = clickedTrack.offset(),pos = e.pageX - offset.left - horizontalDragWidth / 2,contentDragX = paneWidth * settings.scrollPagePercent,dragX = dragMaxX * contentDragX / (contentWidth - paneWidth);if (direction < 0) {if (horizontalDragPositionbottom - dragX > pos) {jsp.scrollByX(-contentDragX);} else {positionDragX(pos);}} else if (direction > 0) {if (horizontalDragPositionbottom + dragX < pos) {jsp.scrollByX(contentDragX);} else {positionDragX(pos);}} else {cancelClick();return;}scrollTimeout = setTimeout(doScroll, isFirst ? settings.initialDelay : settings.trackClickRepeatFreq);isFirst = false;},cancelClick = function(){scrollTimeout && clearTimeout(scrollTimeout);scrollTimeout = null;$(document).unbind('mouseup.jsp', cancelClick);};doScroll();$(document).bind('mouseup.jsp', cancelClick);return false;}});}}function removeClickOnTrack(){if (horizontalTrack) {horizontalTrack.unbind('mousedown.jsp');}if (verticalTrack) {verticalTrack.unbind('mousedown.jsp');}if (horizontalTrackbottom) {horizontalTrackbottom.unbind('mousedown.jsp');}}function cancelDrag(){$('html').unbind('dragstart.jsp selectstart.jsp mousemove.jsp mouseup.jsp mouseleave.jsp');if (verticalDrag) {verticalDrag.removeClass('jspActive');}if (horizontalDrag) {horizontalDrag.removeClass('jspActive');}if (horizontalDragbottom) {horizontalDragbottom.removeClass('jspActive');}}function positionDragY(destY, animate){if (!isScrollableV) {return;}if (destY < 0) {destY = 0;} else if (destY > dragMaxY) {destY = dragMaxY;}// can't just check if(animate) because false is a valid value that could be passed in...if (animate === undefined) {animate = settings.animateScroll;}if (animate) {jsp.animate(verticalDrag, 'top', destY, _positionDragY);} else {verticalDrag.css('top', destY);_positionDragY(destY);}}function _positionDragY(destY){if (destY === undefined) {destY = verticalDrag.position().top;}container.scrollTop(0);verticalDragPosition = destY;var isAtTop = verticalDragPosition === 0,isAtBottom = verticalDragPosition == dragMaxY,percentScrolled = destY/ dragMaxY,destTop = -percentScrolled * (contentHeight - paneHeight);if (wasAtTop != isAtTop || wasAtBottom != isAtBottom) {wasAtTop = isAtTop;wasAtBottom = isAtBottom;elem.trigger('jsp-arrow-change', [wasAtTop, wasAtBottom, wasAtLeft, wasAtRight]);}updateVerticalArrows(isAtTop, isAtBottom);pane.css('top', destTop);elem.trigger('jsp-scroll-y', [-destTop, isAtTop, isAtBottom]).trigger('scroll');}function positionDragX(destX, animate){if (!isScrollableH) {return;}if (destX < 0) {destX = 0;} else if (destX > dragMaxX) {destX = dragMaxX;}if (animate === undefined) {animate = settings.animateScroll;}if (animate) {jsp.animate(horizontalDrag, 'left', destX, _positionDragX);jsp.animate(horizontalDragbottom, 'left', destX, _positionDragX);} else {horizontalDrag.css('left', destX);horizontalDragbottom.css('left', destX);_positionDragX(destX);}}function _positionDragX(destX){if (destX === undefined) {destX = horizontalDrag.position().left;destX = horizontalDragbottom.position().left;}container.scrollTop(0);horizontalDragPosition = destX;horizontalDragPositionbottom = destX;var isAtLeft = horizontalDragPosition === 0,isAtRight = horizontalDragPosition == dragMaxX,percentScrolled = destX / dragMaxX,destLeft = -percentScrolled * (contentWidth - paneWidth);var isAtLeftbottom = horizontalDragPositionbottom === 0,isAtRightbottom = horizontalDragPositionbottom == dragMaxX,percentScrolledbottom = destX / dragMaxX,destLeftbottom = -percentScrolledbottom * (contentWidth - paneWidth);if (wasAtLeft != isAtLeft || wasAtRight != isAtRight) {wasAtLeft = isAtLeft;wasAtRight = isAtRight;elem.trigger('jsp-arrow-change', [wasAtTop, wasAtBottom, wasAtLeft, wasAtRight]);}if (wasAtLeftbottom != isAtLeftbottom || wasAtRightbottom != isAtRightbottom) {wasAtLeftbottom = isAtLeftbottom;wasAtRightbottom = isAtRightbottom;elem.trigger('jsp-arrow-change', [wasAtTop, wasAtBottom, wasAtLeftbottom, wasAtRightbottom]);}updateHorizontalArrows(isAtLeft, isAtRight);updateHorizontalArrows(isAtLeftbottom, isAtRightbottom);pane.css('left', destLeft);pane.css('left', destLeftbottom);elem.trigger('jsp-scroll-x', [-destLeft, isAtLeft, isAtRight]).trigger('scroll');elem.trigger('jsp-scroll-x', [-destLeftbottom, isAtLeftbottom, isAtRightbottom]).trigger('scroll');}function updateVerticalArrows(isAtTop, isAtBottom){if (settings.showArrows) {arrowUp[isAtTop ? 'addClass' : 'removeClass']('jspDisabled');arrowDown[isAtBottom ? 'addClass' : 'removeClass']('jspDisabled');}}function updateHorizontalArrows(isAtLeft, isAtRight){if (settings.showArrows) {arrowLeft[isAtLeft ? 'addClass' : 'removeClass']('jspDisabled');arrowRight[isAtRight ? 'addClass' : 'removeClass']('jspDisabled');arrowLeftbottom[isAtLeft ? 'addClass' : 'removeClass']('jspDisabled');arrowRightbottom[isAtRight ? 'addClass' : 'removeClass']('jspDisabled');}}function scrollToY(destY, animate){var percentScrolled = destY / (contentHeight - paneHeight);positionDragY(percentScrolled * dragMaxY, animate);}function scrollToX(destX, animate){var percentScrolled = destX / (contentWidth - paneWidth);positionDragX(percentScrolled * dragMaxX, animate);}function scrollToElement(ele, stickToTop, animate){var e, eleHeight, eleWidth, eleTop = 0, eleLeft = 0, viewportTop, maxVisibleEleTop, maxVisibleEleLeft, destY, destX;// Legal hash values aren't necessarily legal jQuery selectors so we need to catch any// errors from the lookup...try {e = $(ele);} catch (err) {return;}eleHeight = e.outerHeight();eleWidth= e.outerWidth();container.scrollTop(0);container.scrollLeft(0);// loop through parents adding the offset top of any elements that are relatively positioned between// the focused element and the jspPane so we can get the true distance from the top// of the focused element to the top of the scrollpane...while (!e.is('.jspPane')) {eleTop += e.position().top;eleLeft += e.position().left;e = e.offsetParent();if (/^body|html$/i.test(e[0].nodeName)) {// we ended up too high in the document structure. Quit!return;}}viewportTop = contentPositionY();maxVisibleEleTop = viewportTop + paneHeight;if (eleTop < viewportTop || stickToTop) { // element is above viewportdestY = eleTop - settings.verticalGutter;} else if (eleTop + eleHeight > maxVisibleEleTop) { // element is below viewportdestY = eleTop - paneHeight + eleHeight + settings.verticalGutter;}if (destY) {scrollToY(destY, animate);}viewportLeft = contentPositionX();maxVisibleEleLeft = viewportLeft + paneWidth;if (eleLeft < viewportLeft || stickToTop) { // element is to the left of viewportdestX = eleLeft - settings.horizontalGutter;} else if (eleLeft + eleWidth > maxVisibleEleLeft) { // element is to the right viewportdestX = eleLeft - paneWidth + eleWidth + settings.horizontalGutter;}if (destX) {scrollToX(destX, animate);}}function contentPositionX(){return -pane.position().left;}function contentPositionY(){return -pane.position().top;}function initMousewheel(){container.unbind(mwEvent).bind(mwEvent,function (event, delta, deltaX, deltaY) {var dX = horizontalDragPosition, dY = verticalDragPosition, dXb = horizontalDragPositionbottom;jsp.scrollBy(deltaX * settings.mouseWheelSpeed, -deltaY * settings.mouseWheelSpeed, false);// return true if there was no movement so rest of screen can scrollreturn dX == horizontalDragPosition && dY == verticalDragPosition && dXb == horizontalDragPositionbottom;});}function removeMousewheel(){container.unbind(mwEvent);}function nil(){return false;}function initFocusHandler(){pane.find(':input,a').unbind('focus.jsp').bind('focus.jsp',function(e){scrollToElement(e.target, false);});}function removeFocusHandler(){pane.find(':input,a').unbind('focus.jsp');}function initKeyboardNav(){var keyDown, elementHasScrolled;// IE also focuses elements that don't have tabindex set.pane.focus(function(){elem.focus();});elem.attr('tabindex', 0).unbind('keydown.jsp keypress.jsp').bind('keydown.jsp',function(e){if (e.target !== this){return;}var dX = horizontalDragPosition, dY = verticalDragPosition, dXb = horizontalDragPositionbottom;switch(e.keyCode) {case 40: // downcase 38: // upcase 34: // page downcase 32: // spacecase 33: // page upcase 39: // rightcase 37: // leftkeyDown = e.keyCode;keyDownHandler();break;case 35: // endscrollToY(contentHeight - paneHeight);keyDown = null;break;case 36: // homescrollToY(0);keyDown = null;break;}elementHasScrolled = e.keyCode == keyDown && dX != horizontalDragPosition || dY != verticalDragPosition || dX != horizontalDragPosition;return !elementHasScrolled;}).bind('keypress.jsp', // For FF/ OSX so that we can cancel the repeat key presses if the JSP scrolls...function(e){if (e.keyCode == keyDown) {keyDownHandler();}return !elementHasScrolled;});if (settings.hideFocus) {elem.css('outline', 'none');if ('hideFocus' in container[0]){elem.attr('hideFocus', true);}} else {elem.css('outline', '');if ('hideFocus' in container[0]){elem.attr('hideFocus', false);}}function keyDownHandler(){var dX = horizontalDragPosition, dY = verticalDragPosition, dXb = horizontalDragPositionbottom;switch(keyDown) {case 40: // downjsp.scrollByY(settings.keyboardSpeed, false);break;case 38: // upjsp.scrollByY(-settings.keyboardSpeed, false);break;case 34: // page downcase 32: // spacejsp.scrollByY(paneHeight * settings.scrollPagePercent, false);break;case 33: // page upjsp.scrollByY(-paneHeight * settings.scrollPagePercent, false);break;case 39: // rightjsp.scrollByX(settings.keyboardSpeed, false);break;case 37: // leftjsp.scrollByX(-settings.keyboardSpeed, false);break;}elementHasScrolled = dX != horizontalDragPosition || dY != verticalDragPosition || dXb != horizontalDragPositionbottom;return elementHasScrolled;}}function removeKeyboardNav(){elem.attr('tabindex', '-1').removeAttr('tabindex').unbind('keydown.jsp keypress.jsp');}function observeHash(){if (location.hash && location.hash.length > 1) {var e, retryInt;try {e = $(location.hash);} catch (err) {return;}if (e.length && pane.find(location.hash)) {// nasty workaround but it appears to take a little while before the hash has done its thing// to the rendered page so we just wait until the container's scrollTop has been messed up.if (container.scrollTop() === 0) {retryInt = setInterval(function(){if (container.scrollTop() > 0) {scrollToElement(location.hash, true);$(document).scrollTop(container.position().top);clearInterval(retryInt);}},50);} else {scrollToElement(location.hash, true);$(document).scrollTop(container.position().top);}}}}function unhijackInternalLinks(){$('a.jspHijack').unbind('click.jsp-hijack').removeClass('jspHijack');}function hijackInternalLinks(){unhijackInternalLinks();$('a[href^=#]').addClass('jspHijack').bind('click.jsp-hijack',function(){var uriParts = this.href.split('#'), hash;if (uriParts.length > 1) {hash = uriParts[1];if (hash.length > 0 && pane.find('#' + hash).length > 0) {scrollToElement('#' + hash, true);// Need to return false otherwise things mess up... Would be nice to maybe also scroll// the window to the top of the scrollpane?return false;}}});}// Init touch on iPad, iPhone, iPod, Androidfunction initTouch(){var startX,startY,touchStartX,touchStartY,moved,moving = false;container.unbind('touchstart.jsp touchmove.jsp touchend.jsp click.jsp-touchclick').bind('touchstart.jsp',function(e){var touch = e.originalEvent.touches[0];startX = contentPositionX();startY = contentPositionY();touchStartX = touch.pageX;touchStartY = touch.pageY;moved = false;moving = true;}).bind('touchmove.jsp',function(ev){if(!moving) {return;}var touchPos = ev.originalEvent.touches[0],dX = horizontalDragPosition, dY = verticalDragPosition, dXb = horizontalDragPositionbottom;jsp.scrollTo(startX + touchStartX - touchPos.pageX, startY + touchStartY - touchPos.pageY);moved = moved || Math.abs(touchStartX - touchPos.pageX) > 5 || Math.abs(touchStartY - touchPos.pageY) > 5;// return true if there was no movement so rest of screen can scrollreturn dX == horizontalDragPosition && dY == verticalDragPosition && dXb == horizontalDragPositionbottom;}).bind('touchend.jsp',function(e){moving = false;}).bind('click.jsp-touchclick',function(e){if(moved) {moved = false;return false;}});}function destroy(){var currentY = contentPositionY(),currentX = contentPositionX();elem.removeClass('jspScrollable').unbind('.jsp');elem.replaceWith(originalElement.append(pane.children()));originalElement.scrollTop(currentY);originalElement.scrollLeft(currentX);}// Public API$.extend(jsp,{// Reinitialises the scroll pane (if it's internal dimensions have changed since the last time it// was initialised). The settings object which is passed in will override any settings from the// previous time it was initialised - if you don't pass any settings then the ones from the previous// initialisation will be used.reinitialise: function(s){s = $.extend({}, settings, s);initialise(s);},// Scrolls the specified element (a jQuery object, DOM node or jQuery selector string) into view so// that it can be seen within the viewport. If stickToTop is true then the element will appear at// the top of the viewport, if it is false then the viewport will scroll as little as possible to// show the element. You can also specify if you want animation to occur. If you don't provide this// argument then the animateScroll value from the settings object is used instead.scrollToElement: function(ele, stickToTop, animate){scrollToElement(ele, stickToTop, animate);},// Scrolls the pane so that the specified co-ordinates within the content are at the top left// of the viewport. animate is optional and if not passed then the value of animateScroll from// the settings object this jScrollPane was initialised with is used.scrollTo: function(destX, destY, animate){scrollToX(destX, animate);scrollToY(destY, animate);},// Scrolls the pane so that the specified co-ordinate within the content is at the left of the// viewport. animate is optional and if not passed then the value of animateScroll from the settings// object this jScrollPane was initialised with is used.scrollToX: function(destX, animate){scrollToX(destX, animate);},// Scrolls the pane so that the specified co-ordinate within the content is at the top of the// viewport. animate is optional and if not passed then the value of animateScroll from the settings// object this jScrollPane was initialised with is used.scrollToY: function(destY, animate){scrollToY(destY, animate);},// Scrolls the pane to the specified percentage of its maximum horizontal scroll position. animate// is optional and if not passed then the value of animateScroll from the settings object this// jScrollPane was initialised with is used.scrollToPercentX: function(destPercentX, animate){scrollToX(destPercentX * (contentWidth - paneWidth), animate);},// Scrolls the pane to the specified percentage of its maximum vertical scroll position. animate// is optional and if not passed then the value of animateScroll from the settings object this// jScrollPane was initialised with is used.scrollToPercentY: function(destPercentY, animate){scrollToY(destPercentY * (contentHeight - paneHeight), animate);},// Scrolls the pane by the specified amount of pixels. animate is optional and if not passed then// the value of animateScroll from the settings object this jScrollPane was initialised with is used.scrollBy: function(deltaX, deltaY, animate){jsp.scrollByX(deltaX, animate);jsp.scrollByY(deltaY, animate);},// Scrolls the pane by the specified amount of pixels. animate is optional and if not passed then// the value of animateScroll from the settings object this jScrollPane was initialised with is used.scrollByX: function(deltaX, animate){var destX = contentPositionX() + deltaX,percentScrolled = destX / (contentWidth - paneWidth);positionDragX(percentScrolled * dragMaxX, animate);},// Scrolls the pane by the specified amount of pixels. animate is optional and if not passed then// the value of animateScroll from the settings object this jScrollPane was initialised with is used.scrollByY: function(deltaY, animate){var destY = contentPositionY() + deltaY,percentScrolled = destY / (contentHeight - paneHeight);positionDragY(percentScrolled * dragMaxY, animate);},// Positions the horizontal drag at the specified x position (and updates the viewport to reflect// this). animate is optional and if not passed then the value of animateScroll from the settings// object this jScrollPane was initialised with is used.positionDragX: function(x, animate){positionDragX(x, animate);},// Positions the vertical drag at the specified y position (and updates the viewport to reflect// this). animate is optional and if not passed then the value of animateScroll from the settings// object this jScrollPane was initialised with is used.positionDragY: function(y, animate){positionDragX(y, animate);},// This method is called when jScrollPane is trying to animate to a new position. You can override// it if you want to provide advanced animation functionality. It is passed the following arguments:// * ele - the element whose position is being animated// * prop - the property that is being animated// * value - the value it's being animated to// * stepCallback - a function that you must execute each time you update the value of the property// You can use the default implementation (below) as a starting point for your own implementation.animate: function(ele, prop, value, stepCallback){var params = {};params[prop] = value;ele.animate(params,{'duration' : settings.animateDuration,'ease' : settings.animateEase,'queue' : false,'step' : stepCallback});},// Returns the current x position of the viewport with regards to the content pane.getContentPositionX: function(){return contentPositionX();},// Returns the current y position of the viewport with regards to the content pane.getContentPositionY: function(){return contentPositionY();},// Returns the width of the content within the scroll pane.getContentWidth: function(){return contentWidth();},// Returns the height of the content within the scroll pane.getContentHeight: function(){return contentHeight();},// Returns the horizontal position of the viewport within the pane content.getPercentScrolledX: function(){return contentPositionX() / (contentWidth - paneWidth);},// Returns the vertical position of the viewport within the pane content.getPercentScrolledY: function(){return contentPositionY() / (contentHeight - paneHeight);},// Returns whether or not this scrollpane has a horizontal scrollbar.getIsScrollableH: function(){return isScrollableH;},// Returns whether or not this scrollpane has a vertical scrollbar.getIsScrollableV: function(){return isScrollableV;},// Gets a reference to the content pane. It is important that you use this method if you want to// edit the content of your jScrollPane as if you access the element directly then you may have some// problems (as your original element has had additional elements for the scrollbars etc added into// it).getContentPane: function(){return pane;},// Scrolls this jScrollPane down as far as it can currently scroll. If animate isn't passed then the// animateScroll value from settings is used instead.scrollToBottom: function(animate){positionDragY(dragMaxY, animate);},// Hijacks the links on the page which link to content inside the scrollpane. If you have changed// the content of your page (e.g. via AJAX) and want to make sure any new anchor links to the// contents of your scroll pane will work then call this function.hijackInternalLinks: function(){hijackInternalLinks();},// Removes the jScrollPane and returns the page to the state it was in before jScrollPane was// initialised.destroy: function(){destroy();}});initialise(s);}// Pluginifying code...settings = $.extend({}, $.fn.jScrollPane.defaults, settings);// Apply default speed$.each(['mouseWheelSpeed', 'arrowButtonSpeed', 'trackClickSpeed', 'keyboardSpeed'], function() {settings[this] = settings[this] || settings.speed;});var ret;this.each(function(){var elem = $(this), jspApi = elem.data('jsp');if (jspApi) {jspApi.reinitialise(settings);} else {jspApi = new JScrollPane(elem, settings);elem.data('jsp', jspApi);}ret = ret ? ret.add(elem) : elem;});return ret;};$.fn.jScrollPane.defaults = {showArrows : false,maintainPosition : true,clickOnTrack : true,autoReinitialise : false,autoReinitialiseDelay : 500,verticalDragMinHeight : 0,verticalDragMaxHeight : 99999,horizontalDragMinWidth : 0,horizontalDragMaxWidth : 99999,animateScroll : false,animateDuration : 300,animateEase : 'linear',hijackInternalLinks : false,verticalGutter : 4,horizontalGutter : 4,mouseWheelSpeed : 0,arrowButtonSpeed : 0,arrowRepeatFreq : 50,arrowScrollOnHover : false,trackClickSpeed : 0,trackClickRepeatFreq : 70,verticalArrowPositions : 'split',horizontalArrowPositions : 'split',enableKeyboardNavigation : true,hideFocus : false,keyboardSpeed : 0,initialDelay : 300, // Delay before starting repeatingspeed : 30, // Default speed when others falseyscrollPagePercent : .8 // Percent of visible area scrolled when pageUp/Down or track area pressed};})(jQuery,this);