Subversion Repositories SmartDukaan

Rev

Blame | Last modification | View Log | RSS feed

/*!
 * jScrollPane - v2.0.0beta2 - 2010-08-21
 * 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.0beta2, Last updated: 2010-08-21*
//
// Project Home - http://jscrollpane.kelvinluck.com/
// GitHub       - http://github.com/vitch/jScrollPane
// Source       - http://github.com/vitch/jScrollPane/raw/master/script/jquery.jscrollpane.js
// (Minified)   - http://github.com/vitch/jScrollPane/raw/master/script/jquery.jscrollpane.min.js
//
// About: License
//
// Copyright (c) 2010 Kelvin Luck
// Dual licensed under the MIT or GPL Version 2 licenses.
// http://jscrollpane.kelvinluck.com/MIT-LICENSE.txt
// http://jscrollpane.kelvinluck.com/GPL-LICENSE.txt
//
// About: Examples
//
// All examples and demos are available through the jScrollPane example site at:
// http://jscrollpane.kelvinluck.com/
//
// About: Support and Testing
//
// This plugin is tested on the browsers below and has been found to work reliably on them. If you run
// into a problem on one of the supported browsers then please visit the support section on the jScrollPane
// website (http://jscrollpane.kelvinluck.com/) for more information on getting support. You are also
// welcome to fork the project on GitHub if you can contribute a fix for a given issue. 
//
// jQuery Versions - 1.4.2
// Browsers Tested - Firefox 3.6.8, Safari 5, Opera 10.6, Chrome 5.0, IE 6, 7, 8
//
// About: Release History
//
// 2.0.0beta2 - (2010-08-21) Bug fixes
// 2.0.0beta1 - (2010-08-17) Rewrite to follow modern best practices and enable horizontal scrolling, initially hidden
//                                                       elements and dynamically sized elements.
// 1.x - (2006-12-31 - 2010-07-31) Initial version, hosted at googlecode, deprecated

(function($,window,undefined){

        $.fn.jScrollPane = function(settings)
        {
                // JScrollPane "class" - public methods are available through $('selector').data('jsp')
                function JScrollPane(elem, s)
                {

                        var settings, jsp = this, pane, paneWidth, paneHeight, container, contentWidth, contentHeight,
                                percentInViewH, percentInViewV, isScrollableV, isScrollableH, verticalDrag, dragMaxY,
                                verticalDragPosition, horizontalDrag, dragMaxX, horizontalDragPosition,
                                verticalBar, verticalTrack, scrollbarWidth, verticalTrackHeight, verticalDragHeight, arrowUp, arrowDown,
                                horizontalBar, horizontalTrack, horizontalTrackWidth, horizontalDragWidth, arrowLeft, arrowRight,
                                reinitialiseInterval, originalPadding, originalPaddingTotalWidth, previousPaneWidth,
                                wasAtTop = wasAtLeft = true, wasAtBottom = wasAtRight = false;

                        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 + originalPaddingTotalWidth) + '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() + 'px'
                                        });
                                        removeMousewheel();
                                        removeFocusHandler();
                                        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();
                                        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();
                                        updateVerticalArrows();
                                }
                        }

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

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

                        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 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 == dragMaxY,
                                        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('mousewheel.jsp').bind(
                                        'mousewheel.jsp',
                                        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('mousewheel.jsp');
                        }

                        function nil()
                        {
                                return false;
                        }

                        function initFocusHandler()
                        {
                                pane.unbind('focusin.jsp').bind(
                                        'focusin.jsp',
                                        function(e)
                                        {
                                                scrollToElement(e.target, false);
                                        }
                                );
                        }

                        function removeFocusHandler()
                        {

                                pane.unbind('focusin.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,
                '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,
                'verticalArrowPositions'        : 'split',
                'horizontalArrowPositions'      : 'split'
        };

})(jQuery,this);