/*!

 * jScrollPane - v2.0.0beta4 - 2010-09-17

 * 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.0beta4, Last updated: 2010-09-17*

//

// 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 - 1.4.2

// Browsers Tested - Firefox 3.6.8, Safari 5, Opera 10.6, Chrome 5.0, IE 6, 7, 8

//

// About: Release History

//

// 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, dragMaxX, horizontalDragPosition,

				verticalBar, verticalTrack, scrollbarWidth, verticalTrackHeight, verticalDragHeight, arrowUp, arrowDown,

				horizontalBar, horizontalTrack, horizontalTrackWidth, horizontalDragWidth, arrowLeft, arrowRight,

				reinitialiseInterval, originalPadding, originalPaddingTotalWidth, previousPaneWidth,

				wasAtTop = true, wasAtLeft = true, wasAtBottom = false, wasAtRight = false,

				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')) || 0) +

										(parseInt(elem.css('paddingRight')) || 0);



			initialise(s);



			function initialise(s)

			{



				var clonedElem, tempWrapper, /*firstChild, lastChild, */isMaintainingPositon, lastContentX, lastContentY,

						hasContainingSpaceChanged;



				settings = s;



				if (pane == undefined) {



					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" />').wrap(

						$('<div class="jspContainer" />')

							.css({

								'width': paneWidth + 'px',

								'height': paneHeight + 'px'

							}

						)

					);



					elem.wrapInner(pane.parent());

					// Need to get the vars after being added to the document, otherwise they reference weird

					// disconnected orphan elements...

					container = elem.find('>.jspContainer');

					pane = container.find('>.jspPane');

					pane.css('padding', originalPadding);



					/*

					// Move any margins from the first and last children up to the container so they can still

					// collapse with neighbouring elements as they would before jScrollPane 

					firstChild = pane.find(':first-child');

					lastChild = pane.find(':last-child');

					elem.css(

						{

							'margin-top': firstChild.css('margin-top'),

							'margin-bottom': lastChild.css('margin-bottom')

						}

					);

					firstChild.css('margin-top', 0);

					lastChild.css('margin-bottom', 0);

					*/

				} else {



					elem.css('width', null);



					hasContainingSpaceChanged = elem.outerWidth() + originalPaddingTotalWidth != paneWidth || elem.outerHeight() != paneHeight;



					if (hasContainingSpaceChanged) {

						paneWidth = elem.innerWidth() + originalPaddingTotalWidth;

						paneHeight = elem.innerHeight();

						container.css({

							'width': paneWidth + 'px',

							'height': paneHeight + 'px'

						});

					}



					previousPaneWidth = pane.innerWidth();



					if (!hasContainingSpaceChanged && pane.outerWidth() == contentWidth && pane.outerHeight() == contentHeight) {

						// Nothing has changed since we last initialised

						if (isScrollableH || isScrollableV) { // If we had already set a width then re-set it

							pane.css('width', previousPaneWidth + 'px');

							elem.css('width', (previousPaneWidth + originalPaddingTotalWidth) + 'px');

						}

						// Then abort...

						return;

					}

					

					pane.css('width', null);

					elem.css('width', (paneWidth ) + 'px');



					container.find('>.jspVerticalBar,>.jspHorizontalBar').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().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': null

					});

					removeMousewheel();

					removeFocusHandler();

					removeKeyboardNav();

					removeClickOnTrack();

					unhijackInternalLinks();

				} else {

					elem.addClass('jspScrollable');



					isMaintainingPositon = settings.maintainPosition && (verticalDragPosition || horizontalDragPosition);

					if (isMaintainingPositon) {

						lastContentX = contentPositionX();

						lastContentY = contentPositionY();

					}



					initialiseVerticalScroll();

					initialiseHorizontalScroll();

					resizeScrollbars();



					if (isMaintainingPositon) {

						scrollToX(lastContentX);

						scrollToY(lastContentY);

					}



					initFocusHandler();

					initMousewheel();

					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)

				}



				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', function() { return false; });



							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 scrollbar

				pane.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" />')

						)

					);



					horizontalBar = container.find('>.jspHorizontalBar');

					horizontalTrack = horizontalBar.find('>.jspTrack');

					horizontalDrag = horizontalTrack.find('>.jspDrag');



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

					}



					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', function() { return false; });



							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();

				} else {

					// no horizontal scroll

				}

			}



			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();

						}

					);

					horizontalTrackWidth -= verticalTrackWidth;

					paneHeight -= verticalTrackWidth;

					paneWidth -= horizontalTrackHeight;

					horizontalTrack.parent().append(

						$('<div class="jspCorner" />').css('width', horizontalTrackHeight + 'px')

					);

					sizeVerticalScrollbar();

					sizeHorizontalScrollbar();

				}

				// reflow content

				if (isScrollableH) {

					pane.width((container.outerWidth() - originalPaddingTotalWidth) + 'px');

				}

				contentHeight = pane.outerHeight();

				percentInViewV = contentHeight / paneHeight;



				if (isScrollableH) {

					horizontalDragWidth = 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); // To update the state for the arrow buttons

				}

				if (isScrollableV) {

					verticalDragHeight = 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, doScroll = function()

					{

						if (dirX != 0) {

							positionDragX(horizontalDragPosition + dirX * settings.arrowButtonSpeed, false);

						}

						if (dirY != 0) {

							positionDragY(verticalDragPosition + dirY * settings.arrowButtonSpeed, false);

						}

					},

					scrollInt = setInterval(doScroll, settings.arrowRepeatFreq);



				doScroll();



				eve = ele == undefined ? 'mouseup.jsp' : 'mouseout.jsp';

				ele = ele || $('html');

				ele.bind(

					eve,

					function()

					{

						arrow.removeClass('jspActive');

						clearInterval(scrollInt);

						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),

									scrollInt = setInterval(

										function()

										{

											var offset = clickedTrack.offset(), pos = e.pageY - offset.top;

											if (verticalDragPosition + verticalDragHeight < pos) {

												positionDragY(verticalDragPosition + settings.trackClickSpeed);

											} else if (pos < verticalDragPosition) {

												positionDragY(verticalDragPosition - settings.trackClickSpeed);

											} else {

												cancelClick();

											}

										},

										settings.trackClickRepeatFreq

									),

									cancelClick = function()

									{

										scrollInt && clearInterval(scrollInt);

										scrollInt = null;

										$(document).unbind('mouseup.jsp', cancelClick);

									};

								$(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),

									scrollInt = setInterval(

										function()

										{

											var offset = clickedTrack.offset(), pos = e.pageX - offset.left;

											if (horizontalDragPosition + horizontalDragWidth < pos) {

												positionDragX(horizontalDragPosition + settings.trackClickSpeed);

											} else if (pos < horizontalDragPosition) {

												positionDragX(horizontalDragPosition - settings.trackClickSpeed);

											} else {

												cancelClick();

											}

										},

										settings.trackClickRepeatFreq

									),

									cancelClick = function()

									{

										scrollInt && clearInterval(scrollInt);

										scrollInt = null;

										$(document).unbind('mouseup.jsp', cancelClick);

									};

								$(document).bind('mouseup.jsp', cancelClick);

								return false;

							}

						}

					);

				}

			}



			function removeClickOnTrack()

			{

				horizontalTrack && horizontalTrack.unbind('mousedown.jsp');

				verticalTrack && verticalTrack.unbind('mousedown.jsp');

			}



			function cancelDrag()

			{

				$('html').unbind('dragstart.jsp selectstart.jsp mousemove.jsp mouseup.jsp mouseleave.jsp');



				verticalDrag && verticalDrag.removeClass('jspActive');

				horizontalDrag && horizontalDrag.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]);

			}



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

				} else {

					horizontalDrag.css('left', destX);

					_positionDragX(destX);

				}

			}



			function _positionDragX(destX)

			{

				if (destX == undefined) {

					destX = horizontalDrag.position().left;

				}



				container.scrollTop(0);

				horizontalDragPosition = destX;



				var isAtLeft = horizontalDragPosition == 0,

					isAtRight = horizontalDragPosition == dragMaxX,

					percentScrolled = destX / dragMaxX,

					destLeft = -percentScrolled * (contentWidth - paneWidth);



				if (wasAtLeft != isAtLeft || wasAtRight != isAtRight) {

					wasAtLeft = isAtLeft;

					wasAtRight = isAtRight;

					elem.trigger('jsp-arrow-change', [wasAtTop, wasAtBottom, wasAtLeft, wasAtRight]);

				}

				

				updateHorizontalArrows(isAtLeft, isAtRight);

				pane.css('left', destLeft);

				elem.trigger('jsp-scroll-x', [-destLeft, isAtLeft, isAtRight]);

			}



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

				}

			}



			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, eleTop = 0, viewportTop, maxVisibleEleTop, destY;



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



				container.scrollTop(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;

					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 viewport

					destY = eleTop - settings.verticalGutter;

				} else if (eleTop + eleHeight > maxVisibleEleTop) { // element is below viewport

					destY = eleTop - paneHeight + eleHeight + settings.verticalGutter;

				}

				if (destY) {

					scrollToY(destY, animate);

				}

				// TODO: Implement automatic horizontal scrolling?

			}



			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;

						positionDragX(horizontalDragPosition + deltaX * settings.mouseWheelSpeed, false)

						positionDragY(verticalDragPosition - deltaY * settings.mouseWheelSpeed, false);

						// return true if there was no movement so rest of screen can scroll

						return dX == horizontalDragPosition && dY == verticalDragPosition;

					}

				);

			}



			function removeMousewheel()

			{

				container.unbind(mwEvent);

			}



			function nil()

			{

				return false;

			}



			function initFocusHandler()

			{

				pane.unbind('focusin.jsp').bind(

					'focusin.jsp',

					function(e)

					{

						if(e.target === pane[0]){return;}

						scrollToElement(e.target, false);

					}

				);

			}



			function removeFocusHandler()

			{



				pane.unbind('focusin.jsp');

			}

			

			function initKeyboardNav()

			{

				var pressed, pressedTimer;

				elem.attr('tabindex', 0)

					.unbind('keydown.jsp')

					.bind(

						'keydown.jsp',

						function(e)

						{

							if(e.target !== elem[0]){

								return;

							}

							var dX = horizontalDragPosition, dY = verticalDragPosition, step = pressed ? 2 : 16;

							switch(e.keyCode) {

								case 40: // down

									positionDragY(verticalDragPosition + step, false);

									break;

								case 38: // up

									positionDragY(verticalDragPosition - step, false);

									break;

								case 34: // page down

								case 32: // space

									scrollToY(contentPositionY() + Math.max(32, paneHeight) - 16);

									break;

								case 33: // page up

									scrollToY(contentPositionY() - paneHeight + 16);

									break;

								case 35: // end

									scrollToY(contentHeight - paneHeight);

									break;

								case 36: // home

									scrollToY(0);

									break;

								case 39: // right

									positionDragX(horizontalDragPosition + step, false);

									break;

								case 37: // left

									positionDragX(horizontalDragPosition - step, false);

									break;

							}



							if( !(dX == horizontalDragPosition && dY == verticalDragPosition) ){

								pressed = true;

								clearTimeout(pressedTimer);

								pressedTimer = setTimeout(function(){

									pressed = false;

								}, 260);

								return false;

							}

						}

					);

				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 removeKeyboardNav()

			{

				elem.attr('tabindex', '-1')

					.removeAttr('tabindex')

					.unbind('keydown.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(e)) {

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

							}

						}

					}

				)

			}



			// 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({}, s, settings);

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

					},

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

					}

				}

			);

		}



		// Pluginifying code...



		settings = $.extend({}, $.fn.jScrollPane.defaults, settings);



		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'			: 10,

		'arrowButtonSpeed'			: 10,

		'arrowRepeatFreq'			: 100,

		'arrowScrollOnHover'		: false,

		'trackClickSpeed'			: 30,

		'trackClickRepeatFreq'		: 100,

		'verticalArrowPositions'	: 'split',

		'horizontalArrowPositions'	: 'split',

		'enableKeyboardNavigation'	: true,

		'hideFocus'					: false

	};



})(jQuery,this);


