/*!

 * jScrollPane - v2.0.0beta11 - 2011-06-11

 * 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.0beta11, Last updated: 2011-06-11*

//

// 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) 2011 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.0beta11 - (in progress) 

// 2.0.0beta10 - (2011-04-17) cleaner required size calculation, improved keyboard support, stickToBottom/Left, other small fixes

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

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

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

				reinitialiseInterval, originalPadding, originalPaddingTotalWidth, previousContentWidth,

				wasAtTop = true, wasAtLeft = true, wasAtBottom = false, wasAtRight = 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 /*firstChild, lastChild, */isMaintainingPositon, lastContentX, lastContentY,

						hasContainingSpaceChanged, originalScrollTop, originalScrollLeft,

						maintainAtBottom = false, maintainAtRight = false;




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



					/*

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



					maintainAtBottom = settings.stickToBottom && isCloseToBottom();

					maintainAtRight  = settings.stickToRight  && isCloseToRight();



					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').remove().end();

				}



				pane.css('overflow', 'auto');

				if (s.contentWidth) {

					contentWidth = s.contentWidth;

				} else {

					contentWidth = pane[0].scrollWidth;

				}

				contentHeight = pane[0].scrollHeight;

				pane.css('overflow', '');



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

					if (isMaintainingPositon) {

						lastContentX = contentPositionX();

						lastContentY = contentPositionY();

					}



					initialiseVerticalScroll();

					initialiseHorizontalScroll();

					resizeScrollbars();



					if (isMaintainingPositon) {

						scrollToX(maintainAtRight  ? (contentWidth  - paneWidth ) : lastContentX, false);

						scrollToY(maintainAtBottom ? (contentHeight - paneHeight) : 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 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)

				try {

					if (verticalBar.position().left === 0) {

						pane.css('margin-left', scrollbarWidth + 'px');

					}

				} catch (err) {

				}

			}



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

				}

			}



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

							}

						}

					);

				}

			}



			function removeClickOnTrack()

			{

				if (horizontalTrack) {

					horizontalTrack.unbind('mousedown.jsp');

				}

				if (verticalTrack) {

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

				}

			}



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

				} 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]).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');

				}

			}



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

				}

				

				viewportLeft = contentPositionX();

	            maxVisibleEleLeft = viewportLeft + paneWidth;

	            if (eleLeft < viewportLeft || stickToTop) { // element is to the left of viewport

	                destX = eleLeft - settings.horizontalGutter;

	            } else if (eleLeft + eleWidth > maxVisibleEleLeft) { // element is to the right viewport

	                destX = eleLeft - paneWidth + eleWidth + settings.horizontalGutter;

	            }

	            if (destX) {

	                scrollToX(destX, animate);

	            }



			}



			function contentPositionX()

			{

				return -pane.position().left;

			}



			function contentPositionY()

			{

				return -pane.position().top;

			}



			function isCloseToBottom()

			{

				var scrollableHeight = contentHeight - paneHeight;

				return (scrollableHeight > 20) && (scrollableHeight - contentPositionY() < 10);

			}



			function isCloseToRight()

			{

				var scrollableWidth = contentWidth - paneWidth;

				return (scrollableWidth > 20) && (scrollableWidth - contentPositionX() < 10);

			}



			function initMousewheel()

			{

				container.unbind(mwEvent).bind(

					mwEvent,

					function (event, delta, deltaX, deltaY) {

						var dX = horizontalDragPosition, dY = verticalDragPosition;

						jsp.scrollBy(deltaX * settings.mouseWheelSpeed, -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.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, validParents = [];

				isScrollableH && validParents.push(horizontalBar[0]);

				isScrollableV && validParents.push(verticalBar[0]);

				

				// 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 && !(validParents.length && $(e.target).closest(validParents).length)){

								return;

							}

							var dX = horizontalDragPosition, dY = verticalDragPosition;

							switch(e.keyCode) {

								case 40: // down

								case 38: // up

								case 34: // page down

								case 32: // space

								case 33: // page up

								case 39: // right

								case 37: // left

									keyDown = e.keyCode;

									keyDownHandler();

									break;

								case 35: // end

									scrollToY(contentHeight - paneHeight);

									keyDown = null;

									break;

								case 36: // home

									scrollToY(0);

									keyDown = null;

									break;

							}



							elementHasScrolled = e.keyCode == keyDown && dX != horizontalDragPosition || dY != verticalDragPosition;

							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;

					switch(keyDown) {

						case 40: // down

							jsp.scrollByY(settings.keyboardSpeed, false);

							break;

						case 38: // up

							jsp.scrollByY(-settings.keyboardSpeed, false);

							break;

						case 34: // page down

						case 32: // space

							jsp.scrollByY(paneHeight * settings.scrollPagePercent, false);

							break;

						case 33: // page up

							jsp.scrollByY(-paneHeight * settings.scrollPagePercent, false);

							break;

						case 39: // right

							jsp.scrollByX(settings.keyboardSpeed, false);

							break;

						case 37: // left

							jsp.scrollByX(-settings.keyboardSpeed, false);

							break;

					}



					elementHasScrolled = dX != horizontalDragPosition || dY != verticalDragPosition;

					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,

						hash = escape(location.hash) // hash must be escaped to prevent XSS

						;

					try {

						e = $(hash);

					} catch (err) {

						return;

					}



					if (e.length && pane.find(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(hash, true);

										$(document).scrollTop(container.position().top);

										clearInterval(retryInt);

									}

								},

								50

							);

						} else {

							scrollToElement(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, Android

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

						

						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 scroll

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

					}

				).bind(

					'touchend.jsp',

					function(e)

					{

						moving = false;

						/*if(moved) {

							return 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() + Math[deltaX<0 ? 'floor' : 'ceil'](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() + Math[deltaY<0 ? 'floor' : 'ceil'](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)

					{

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

		});



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

				}

			}

		);

	};



	$.fn.jScrollPane.defaults = {

		showArrows					: false,

		maintainPosition			: true,

		stickToBottom				: false,

		stickToRight				: false,

		clickOnTrack				: true,

		autoReinitialise			: false,

		autoReinitialiseDelay		: 500,

		verticalDragMinHeight		: 0,

		verticalDragMaxHeight		: 99999,

		horizontalDragMinWidth		: 0,

		horizontalDragMaxWidth		: 99999,

		contentWidth				: undefined,

		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 repeating

		speed						: 30,		// Default speed when others falsey

		scrollPagePercent			: .8		// Percent of visible area scrolled when pageUp/Down or track area pressed

	};



})(jQuery,this);




