﻿//jScrollPane
(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': 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(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);

//Mousewheel
/*! Copyright (c) 2010 Brandon Aaron (http://brandonaaron.net)
* Licensed under the MIT License (LICENSE.txt).
*
* Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.
* Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.
* Thanks to: Seamus Leahy for adding deltaX and deltaY
*
* Version: 3.0.4
* 
* Requires: 1.2.2+
*/

(function ($) {

    var types = ['DOMMouseScroll', 'mousewheel'];

    $.event.special.mousewheel = {
        setup: function () {
            if (this.addEventListener) {
                for (var i = types.length; i; ) {
                    this.addEventListener(types[--i], handler, false);
                }
            } else {
                this.onmousewheel = handler;
            }
        },

        teardown: function () {
            if (this.removeEventListener) {
                for (var i = types.length; i; ) {
                    this.removeEventListener(types[--i], handler, false);
                }
            } else {
                this.onmousewheel = null;
            }
        }
    };

    $.fn.extend({
        mousewheel: function (fn) {
            return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel");
        },

        unmousewheel: function (fn) {
            return this.unbind("mousewheel", fn);
        }
    });


    function handler(event) {
        var orgEvent = event || window.event, args = [].slice.call(arguments, 1), delta = 0, returnValue = true, deltaX = 0, deltaY = 0;
        event = $.event.fix(orgEvent);
        event.type = "mousewheel";

        // Old school scrollwheel delta
        if (event.wheelDelta) { delta = event.wheelDelta / 120; }
        if (event.detail) { delta = -event.detail / 3; }

        // New school multidimensional scroll (touchpads) deltas
        deltaY = delta;

        // Gecko
        if (orgEvent.axis !== undefined && orgEvent.axis === orgEvent.HORIZONTAL_AXIS) {
            deltaY = 0;
            deltaX = -1 * delta;
        }

        // Webkit
        if (orgEvent.wheelDeltaY !== undefined) { deltaY = orgEvent.wheelDeltaY / 120; }
        if (orgEvent.wheelDeltaX !== undefined) { deltaX = -1 * orgEvent.wheelDeltaX / 120; }

        // Add event and delta to the front of the arguments
        args.unshift(event, delta, deltaX, deltaY);

        return $.event.handle.apply(this, args);
    }

})(jQuery);

//Flash SWFObject
// jQuery SWFObject v1.1.1 MIT/GPL @jon_neal
// http://jquery.thewikies.com/swfobject
(function (f, h, i) { function k(a, c) { var b = (a[0] || 0) - (c[0] || 0); return b > 0 || !b && a.length > 0 && k(a.slice(1), c.slice(1)) } function l(a) { if (typeof a != g) return a; var c = [], b = ""; for (var d in a) { b = typeof a[d] == g ? l(a[d]) : [d, m ? encodeURI(a[d]) : a[d]].join("="); c.push(b) } return c.join("&") } function n(a) { var c = []; for (var b in a) a[b] && c.push([b, '="', a[b], '"'].join("")); return c.join(" ") } function o(a) { var c = []; for (var b in a) c.push(['<param name="', b, '" value="', l(a[b]), '" />'].join("")); return c.join("") } var g = "object", m = true; try { var j = i.description || function () { return (new i("ShockwaveFlash.ShockwaveFlash")).GetVariable("$version") } () } catch (p) { j = "Unavailable" } var e = j.match(/\d+/g) || [0]; f[h] = { available: e[0] > 0, activeX: i && !i.name, version: { original: j, array: e, string: e.join("."), major: parseInt(e[0], 10) || 0, minor: parseInt(e[1], 10) || 0, release: parseInt(e[2], 10) || 0 }, hasVersion: function (a) { a = /string|number/.test(typeof a) ? a.toString().split(".") : /object/.test(typeof a) ? [a.major, a.minor] : a || [0, 0]; return k(e, a) }, encodeParams: true, expressInstall: "expressInstall.swf", expressInstallIsActive: false, create: function (a) { if (!a.swf || this.expressInstallIsActive || !this.available && !a.hasVersionFail) return false; if (!this.hasVersion(a.hasVersion || 1)) { this.expressInstallIsActive = true; if (typeof a.hasVersionFail == "function") if (!a.hasVersionFail.apply(a)) return false; a = { swf: a.expressInstall || this.expressInstall, height: 137, width: 214, flashvars: { MMredirectURL: location.href, MMplayerType: this.activeX ? "ActiveX" : "PlugIn", MMdoctitle: document.title.slice(0, 47) + " - Flash Player Installation"}} } attrs = { data: a.swf, type: "application/x-shockwave-flash", id: a.id || "flash_" + Math.floor(Math.random() * 999999999), width: a.width || 320, height: a.height || 180, style: a.style || "" }; m = typeof a.useEncode !== "undefined" ? a.useEncode : this.encodeParams; a.movie = a.swf; a.wmode = a.wmode || "opaque"; delete a.fallback; delete a.hasVersion; delete a.hasVersionFail; delete a.height; delete a.id; delete a.swf; delete a.useEncode; delete a.width; var c = document.createElement("div"); c.innerHTML = ["<object ", n(attrs), ">", o(a), "</object>"].join(""); return c.firstChild } }; f.fn[h] = function (a) { var c = this.find(g).andSelf().filter(g); /string|object/.test(typeof a) && this.each(function () { var b = f(this), d; a = typeof a == g ? a : { swf: a }; a.fallback = this; if (d = f[h].create(a)) { b.children().remove(); b.html(d) } }); typeof a == "function" && c.each(function () { var b = this; b.jsInteractionTimeoutMs = b.jsInteractionTimeoutMs || 0; if (b.jsInteractionTimeoutMs < 660) b.clientWidth || b.clientHeight ? a.call(b) : setTimeout(function () { f(b)[h](a) }, b.jsInteractionTimeoutMs + 66) }); return c } })(jQuery, "flash", navigator.plugins["Shockwave Flash"] || window.ActiveXObject);

/**
* hoverIntent r5 // 2007.03.27 // jQuery 1.1.2+
* <http://cherne.net/brian/resources/jquery.hoverIntent.html>
* 
* @param  f  onMouseOver function || An object with configuration options
* @param  g  onMouseOut function  || Nothing (use configuration options object)
* @author    Brian Cherne <brian@cherne.net>
*/
(function($){$.fn.hoverIntent=function(f,g){var cfg={sensitivity:7,interval:100,timeout:0};cfg=$.extend(cfg,g?{over:f,out:g}:f);var cX,cY,pX,pY;var track=function(ev){cX=ev.pageX;cY=ev.pageY;};var compare=function(ev,ob){ob.hoverIntent_t=clearTimeout(ob.hoverIntent_t);if((Math.abs(pX-cX)+Math.abs(pY-cY))<cfg.sensitivity){$(ob).unbind("mousemove",track);ob.hoverIntent_s=1;return cfg.over.apply(ob,[ev]);}else{pX=cX;pY=cY;ob.hoverIntent_t=setTimeout(function(){compare(ev,ob);},cfg.interval);}};var delay=function(ev,ob){ob.hoverIntent_t=clearTimeout(ob.hoverIntent_t);ob.hoverIntent_s=0;return cfg.out.apply(ob,[ev]);};var handleHover=function(e){var p=(e.type=="mouseover"?e.fromElement:e.toElement)||e.relatedTarget;while(p&&p!=this){try{p=p.parentNode;}catch(e){p=this;}}if(p==this){return false;}var ev=jQuery.extend({},e);var ob=this;if(ob.hoverIntent_t){ob.hoverIntent_t=clearTimeout(ob.hoverIntent_t);}if(e.type=="mouseover"){pX=ev.pageX;pY=ev.pageY;$(ob).bind("mousemove",track);if(ob.hoverIntent_s!=1){ob.hoverIntent_t=setTimeout(function(){compare(ev,ob);},cfg.interval);}}else{$(ob).unbind("mousemove",track);if(ob.hoverIntent_s==1){ob.hoverIntent_t=setTimeout(function(){delay(ev,ob);},cfg.timeout);}}};return this.mouseover(handleHover).mouseout(handleHover);};})(jQuery);
