Subversion Repositories SmartDukaan

Rev

Blame | Last modification | View Log | RSS feed

/*!
 * jScrollPane - v2.0.0beta9 - 2011-02-04
 * http://jscrollpane.kelvinluck.com/
 *
 * Copyright (c) 2010 Kelvin Luck
 * Dual licensed under the MIT and GPL licenses.
 */

// Script: jScrollPane - cross browser customisable scrollbars
//
// *Version: 2.0.0beta10, Last updated: 2011-02-04*
//
// Project Home - http://jscrollpane.kelvinluck.com/
// GitHub       - http://github.com/vitch/jScrollPane
// Source       - http://github.com/vitch/jScrollPane/raw/master/script/jquery.jscrollpane.js
// (Minified)   - http://github.com/vitch/jScrollPane/raw/master/script/jquery.jscrollpane.min.js
//
// About: License
//
// Copyright (c) 2010 Kelvin Luck
// Dual licensed under the MIT or GPL Version 2 licenses.
// http://jscrollpane.kelvinluck.com/MIT-LICENSE.txt
// http://jscrollpane.kelvinluck.com/GPL-LICENSE.txt
//
// About: Examples
//
// All examples and demos are available through the jScrollPane example site at:
// http://jscrollpane.kelvinluck.com/
//
// About: Support and Testing
//
// This plugin is tested on the browsers below and has been found to work reliably on them. If you run
// into a problem on one of the supported browsers then please visit the support section on the jScrollPane
// website (http://jscrollpane.kelvinluck.com/) for more information on getting support. You are also
// welcome to fork the project on GitHub if you can contribute a fix for a given issue. 
//
// jQuery Versions - tested in 1.4.2+ - reported to work in 1.3.x
// Browsers Tested - Firefox 3.6.8, Safari 5, Opera 10.6, Chrome 5.0, IE 6, 7, 8
//
// About: Release History
//
// 2.0.0beta10 - (in progress)
// 2.0.0beta9 - (2011-01-31) new API methods, bug fixes and correct keyboard support for FF/OSX
// 2.0.0beta8 - (2011-01-29) touchscreen support, improved keyboard support
// 2.0.0beta7 - (2011-01-23) scroll speed consistent (thanks Aivo Paas)
// 2.0.0beta6 - (2010-12-07) scrollToElement horizontal support
// 2.0.0beta5 - (2010-10-18) jQuery 1.4.3 support, various bug fixes
// 2.0.0beta4 - (2010-09-17) clickOnTrack support, bug fixes
// 2.0.0beta3 - (2010-08-27) Horizontal mousewheel, mwheelIntent, keyboard support, bug fixes
// 2.0.0beta2 - (2010-08-21) Bug fixes
// 2.0.0beta1 - (2010-08-17) Rewrite to follow modern best practices and enable horizontal scrolling, initially hidden
//                                                       elements and dynamically sized elements.
// 1.x - (2006-12-31 - 2010-07-31) Initial version, hosted at googlecode, deprecated

(function($,window,undefined){

  $.fn.jScrollPane = function(settings)
  {
    // JScrollPane "class" - public methods are available through $('selector').data('jsp')
    function JScrollPane(elem, s)
    {
      var settings, jsp = this, pane, paneWidth, paneHeight, container, contentWidth, contentHeight,
      percentInViewH, percentInViewV, isScrollableV, isScrollableH, verticalDrag, dragMaxY,
      verticalDragPosition, horizontalDrag, horizontalDragbottom, dragMaxX, horizontalDragPosition, horizontalDragPositionbottom,
      verticalBar, verticalTrack, scrollbarWidth, verticalTrackHeight, verticalDragHeight, arrowUp, arrowDown,
      horizontalBar, horizontalTrack, horizontalTrackWidth, horizontalDragWidth, horizontalBarbottom, horizontalTrackbottom, horizontalTrackWidthbottom, horizontalDragWidthbottom, arrowLeft, arrowRight,arrowLeftbottom, arrowRightbottom, reinitialiseInterval, originalPadding, originalPaddingTotalWidth, previousContentWidth,
      wasAtTop = true, wasAtLeft = true, wasAtBottom = false, wasAtRight = false, wasAtTopbottom = true, wasAtLeftbottom = true, wasAtBottombottom = false, wasAtRightbottom = false,
      originalElement = elem.clone(false, false).empty(),
      mwEvent = $.fn.mwheelIntent ? 'mwheelIntent.jsp' : 'mousewheel.jsp';

      originalPadding = elem.css('paddingTop') + ' ' +
      elem.css('paddingRight') + ' ' +
      elem.css('paddingBottom') + ' ' +
      elem.css('paddingLeft');
      originalPaddingTotalWidth = (parseInt(elem.css('paddingLeft'), 10) || 0) +
      (parseInt(elem.css('paddingRight'), 10) || 0);

      function initialise(s)
      {

        var clonedElem, tempWrapper, /*firstChild, lastChild, */isMaintainingPositon, lastContentX, lastContentY,
        hasContainingSpaceChanged, originalScrollTop, originalScrollLeft;

        settings = s;

        if (pane === undefined) {
          originalScrollTop = elem.scrollTop();
          originalScrollLeft = elem.scrollLeft();
          elem.css(
          {
            overflow: 'hidden',
            padding: 0
          }
          );
          // TODO: Deal with where width/ height is 0 as it probably means the element is hidden and we should
          // come back to it later and check once it is unhidden...
          paneWidth = elem.innerWidth() + originalPaddingTotalWidth;
          paneHeight = elem.innerHeight();

          elem.width(paneWidth);
                                        
          pane = $('<div class="jspPane" />').css('padding', originalPadding).append(elem.children());
          container = $('<div class="jspContainer" />')
          .css({
            'width': paneWidth + 'px',
            'height': paneHeight + 'px'
          }
          ).append(pane).appendTo(elem);
        } else {
          elem.css('width', '');

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

          if (hasContainingSpaceChanged) {
            paneWidth = elem.innerWidth() + originalPaddingTotalWidth;
            paneHeight = elem.innerHeight();
            container.css({
              width: paneWidth + 'px',
              height: paneHeight + 'px'
            });
          }

          // If nothing changed since last check...
          if (!hasContainingSpaceChanged && previousContentWidth == contentWidth && pane.outerHeight() == contentHeight) {
            elem.width(paneWidth);
            return;
          }
          previousContentWidth = contentWidth;
                                        
          pane.css('width', '');
          elem.width(paneWidth);

          container.find('>.jspVerticalBar,>.jspHorizontalBar,>.jspHorizontalBarbottom').remove().end();
        }

        // Unfortunately it isn't that easy to find out the width of the element as it will always report the
        // width as allowed by its container, regardless of overflow settings.
        // A cunning workaround is to clone the element, set its position to absolute and place it in a narrow
        // container. Now it will push outwards to its maxium real width...
        clonedElem = pane.clone(false, false).css('position', 'absolute');
        tempWrapper = $('<div style="width:1px; position: relative;" />').append(clonedElem);
        $('body').append(tempWrapper);
        contentWidth = Math.max(pane.outerWidth(), clonedElem.outerWidth());
        tempWrapper.remove();
                                
        contentHeight = pane.outerHeight();
        percentInViewH = contentWidth / paneWidth;
        percentInViewV = contentHeight / paneHeight;
        isScrollableV = percentInViewV > 1;

        isScrollableH = percentInViewH > 1;

        //console.log(paneWidth, paneHeight, contentWidth, contentHeight, percentInViewH, percentInViewV, isScrollableH, isScrollableV);

        if (!(isScrollableH || isScrollableV)) {
          elem.removeClass('jspScrollable');
          pane.css({
            top: 0,
            width: container.width() - originalPaddingTotalWidth
          });
          removeMousewheel();
          removeFocusHandler();
          removeKeyboardNav();
          removeClickOnTrack();
          unhijackInternalLinks();
        } else {
          elem.addClass('jspScrollable');

          isMaintainingPositon = settings.maintainPosition && (verticalDragPosition || horizontalDragPosition || horizontalDragPositionbottom);
          if (isMaintainingPositon) {
            lastContentX = contentPositionX();
            lastContentY = contentPositionY();
          }

          initialiseVerticalScroll();
          initialiseHorizontalScroll();
          resizeScrollbars();

          if (isMaintainingPositon) {
            scrollToX(lastContentX, false);
            scrollToY(lastContentY, false);
          }

          initFocusHandler();
          initMousewheel();
          initTouch();
                                        
          if (settings.enableKeyboardNavigation) {
            initKeyboardNav();
          }
          if (settings.clickOnTrack) {
            initClickOnTrack();
          }
                                        
          observeHash();
          if (settings.hijackInternalLinks) {
            hijackInternalLinks();
          }
        }

        if (settings.autoReinitialise && !reinitialiseInterval) {
          reinitialiseInterval = setInterval(
            function()
            {
              initialise(settings);
            },
            settings.autoReinitialiseDelay
            );
        } else if (!settings.autoReinitialise && reinitialiseInterval) {
          clearInterval(reinitialiseInterval);
        }

        originalScrollTop && elem.scrollTop(0) && scrollToY(originalScrollTop, false);
        originalScrollLeft && elem.scrollLeft(0) && scrollToX(originalScrollLeft, false);

        elem.trigger('jsp-initialised', [isScrollableH || isScrollableV]);
      }

      function initialiseVerticalScroll()
      {
        if (isScrollableV) {

          container.append(
            $('<div class="jspVerticalBar" />').append(
              $('<div class="jspCap jspCapTop" />'),
              $('<div class="jspTrack" />').append(
                $('<div class="jspDrag" />').append(
                  $('<div class="jspDragTop" />'),
                  $('<div class="jspDragBottom" />')
                  )
                ),
              $('<div class="jspCap jspCapBottom" />')
              )
            );

          verticalBar = container.find('>.jspVerticalBar');
          verticalTrack = verticalBar.find('>.jspTrack');
          verticalDrag = verticalTrack.find('>.jspDrag');

          if (settings.showArrows) {
            arrowUp = $('<a class="jspArrow jspArrowUp" />').bind(
              'mousedown.jsp', getArrowScroll(0, -1)
              ).bind('click.jsp', nil);
            arrowDown = $('<a class="jspArrow jspArrowDown" />').bind(
              'mousedown.jsp', getArrowScroll(0, 1)
              ).bind('click.jsp', nil);
            if (settings.arrowScrollOnHover) {
              arrowUp.bind('mouseover.jsp', getArrowScroll(0, -1, arrowUp));
              arrowDown.bind('mouseover.jsp', getArrowScroll(0, 1, arrowDown));
            }

            appendArrows(verticalTrack, settings.verticalArrowPositions, arrowUp, arrowDown);
          }

          verticalTrackHeight = paneHeight;
          container.find('>.jspVerticalBar>.jspCap:visible,>.jspVerticalBar>.jspArrow').each(
            function()
            {
              verticalTrackHeight -= $(this).outerHeight();
            }
            );


          verticalDrag.hover(
            function()
            {
              verticalDrag.addClass('jspHover');
            },
            function()
            {
              verticalDrag.removeClass('jspHover');
            }
            ).bind(
            'mousedown.jsp',
            function(e)
            {
              // Stop IE from allowing text selection
              $('html').bind('dragstart.jsp selectstart.jsp', nil);

              verticalDrag.addClass('jspActive');

              var startY = e.pageY - verticalDrag.position().top;

              $('html').bind(
                'mousemove.jsp',
                function(e)
                {
                  positionDragY(e.pageY - startY, false);
                }
                ).bind('mouseup.jsp mouseleave.jsp', cancelDrag);
              return false;
            }
            );
          sizeVerticalScrollbar();
        }
      }

      function sizeVerticalScrollbar()
      {
        verticalTrack.height(verticalTrackHeight + 'px');
        verticalDragPosition = 0;
        scrollbarWidth = settings.verticalGutter + verticalTrack.outerWidth();

        // Make the pane thinner to allow for the vertical 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" />')
              )
            );
          container.append(
            $('<div class="jspHorizontalBarbottom" />').append(
              $('<div class="jspCap jspCapLeftbottom" />'),
              $('<div class="jspTrackbottom" />').append(
                $('<div class="jspDragbottom" />').append(
                  $('<div class="jspDragLeft" />'),
                  $('<div class="jspDragRight" />')
                  )
                ),
              $('<div class="jspCap jspCapRight" />')
              )
            );

          horizontalBar = container.find('>.jspHorizontalBar');
          horizontalTrack = horizontalBar.find('>.jspTrack');
          horizontalDrag = horizontalTrack.find('>.jspDrag');
                                        
          horizontalBarbottom = container.find('>.jspHorizontalBarbottom');
          horizontalTrackbottom = horizontalBarbottom.find('>.jspTrackbottom');
          horizontalDragbottom = horizontalTrackbottom.find('>.jspDragbottom');

          if (settings.showArrows) {
            arrowLeft = $('<a class="jspArrow jspArrowLeft" />').bind(
              'mousedown.jsp', getArrowScroll(-1, 0)
              ).bind('click.jsp', nil);
            arrowRight = $('<a class="jspArrow jspArrowRight" />').bind(
              'mousedown.jsp', getArrowScroll(1, 0)
              ).bind('click.jsp', nil);
            if (settings.arrowScrollOnHover) {
              arrowLeft.bind('mouseover.jsp', getArrowScroll(-1, 0, arrowLeft));
              arrowRight.bind('mouseover.jsp', getArrowScroll(1, 0, arrowRight));
            }
            appendArrows(horizontalTrack, settings.horizontalArrowPositions, arrowLeft, arrowRight);

            arrowLeftbottom = $('<a class="jspArrow jspArrowLeft" />').bind(
              'mousedown.jsp', getArrowScroll(-1, 0)
              ).bind('click.jsp', nil);
            arrowRightbottom = $('<a class="jspArrow jspArrowRight" />').bind(
              'mousedown.jsp', getArrowScroll(1, 0)
              ).bind('click.jsp', nil);
            if (settings.arrowScrollOnHover) {
              arrowLeftbottom.bind('mouseover.jsp', getArrowScroll(-1, 0, arrowLeftbottom));
              arrowRightbottom.bind('mouseover.jsp', getArrowScroll(1, 0, arrowRightbottom));
            }
            appendArrows(horizontalTrackbottom, settings.horizontalArrowPositions, arrowLeftbottom, arrowRightbottom);

          }

          horizontalDrag.hover(
            function()
            {
              horizontalDrag.addClass('jspHover');
            },
            function()
            {
              horizontalDrag.removeClass('jspHover');
            }
            ).bind(
            'mousedown.jsp',
            function(e)
            {
              // Stop IE from allowing text selection
              $('html').bind('dragstart.jsp selectstart.jsp', nil);

              horizontalDrag.addClass('jspActive');

              var startX = e.pageX - horizontalDrag.position().left;

              $('html').bind(
                'mousemove.jsp',
                function(e)
                {
                  positionDragX(e.pageX - startX, false);
                }
                ).bind('mouseup.jsp mouseleave.jsp', cancelDrag);
              return false;
            }
            );

          horizontalTrackWidth = container.innerWidth();
          sizeHorizontalScrollbar();

          horizontalDragbottom.hover(
            function()
            {
              horizontalDragbottom.addClass('jspHover');
            },
            function()
            {
              horizontalDragbottom.removeClass('jspHover');
            }
            ).bind(
            'mousedown.jsp',
            function(e)
            {
              // Stop IE from allowing text selection
              $('html').bind('dragstart.jsp selectstart.jsp', nil);

              horizontalDragbottom.addClass('jspActive');

              var startX = e.pageX - horizontalDragbottom.position().left;

              $('html').bind(
                'mousemove.jsp',
                function(e)
                {
                  positionDragX(e.pageX - startX, false);
                }
                ).bind('mouseup.jsp mouseleave.jsp', cancelDrag);
              return false;
            }
            );

          horizontalTrackWidthbottom = container.innerWidth();
          sizeHorizontalScrollbarbottom();
        }
      }

      function sizeHorizontalScrollbarbottom() {
        container.find('>.jspHorizontalBarbottom>.jspCap:visible,>.jspHorizontalBarbottom>.jspArrow').each(
          function()
          {
            horizontalTrackWidthbottom -= $(this).outerWidth();
          }
          );
        horizontalTrack.width(horizontalTrackWidthbottom + 'px');
        horizontalDragPositionbottom = 0;
      }

      function sizeHorizontalScrollbar() {
        container.find('>.jspHorizontalBar>.jspCap:visible,>.jspHorizontalBar>.jspArrow').each(
          function()
          {
            horizontalTrackWidth -= $(this).outerWidth();
          }
          );
        horizontalTrack.width(horizontalTrackWidth + 'px');
        horizontalDragPosition = 0;
      }

      function resizeScrollbars()
      {
        if (isScrollableH && isScrollableV) {
          var horizontalTrackHeight = horizontalTrack.outerHeight(),
          verticalTrackWidth = verticalTrack.outerWidth();
          verticalTrackHeight -= horizontalTrackHeight;
          $(horizontalBar).find('>.jspCap:visible,>.jspArrow').each(
            function()
            {
              horizontalTrackWidth += $(this).outerWidth();
            }
            );
          $(horizontalBarbottom).find('>.jspCapbottom:visible,>.jspArrowbottom').each(
            function()
            {
              horizontalTrackWidthbottom += $(this).outerWidth();
            }
            );
          horizontalTrackWidth -= verticalTrackWidth;
          horizontalTrackWidthbottom -= verticalTrackWidth;
          paneHeight -= verticalTrackWidth;
          paneWidth -= horizontalTrackHeight;
          horizontalTrack.parent().append(
            $('<div class="jspCorner" />').css('width', horizontalTrackHeight + 'px')
            );
          horizontalTrackbottom.parent().append(
            $('<div class="jspCorner" />').css('width', horizontalTrackHeight + 'px')
            );
          sizeVerticalScrollbar();
          sizeHorizontalScrollbar();
        }
        // reflow content
        if (isScrollableH) {
          pane.width((container.outerWidth() - originalPaddingTotalWidth) + 'px');
        }
        contentHeight = pane.outerHeight();
        percentInViewV = contentHeight / paneHeight;

        if (isScrollableH) {
          horizontalDragWidth = Math.ceil(1 / percentInViewH * horizontalTrackWidth);
          if (horizontalDragWidth > settings.horizontalDragMaxWidth) {
            horizontalDragWidth = settings.horizontalDragMaxWidth;
          } else if (horizontalDragWidth < settings.horizontalDragMinWidth) {
            horizontalDragWidth = settings.horizontalDragMinWidth;
          }
          horizontalDrag.width(horizontalDragWidth + 'px');
          dragMaxX = horizontalTrackWidth - horizontalDragWidth;
          _positionDragX(horizontalDragPosition);
          _positionDragX(horizontalDragPositionbottom); // To update the state for the arrow buttons

        }
        if (isScrollableV) {
          verticalDragHeight = Math.ceil(1 / percentInViewV * verticalTrackHeight);
          if (verticalDragHeight > settings.verticalDragMaxHeight) {
            verticalDragHeight = settings.verticalDragMaxHeight;
          } else if (verticalDragHeight < settings.verticalDragMinHeight) {
            verticalDragHeight = settings.verticalDragMinHeight;
          }
          verticalDrag.height(verticalDragHeight + 'px');
          dragMaxY = verticalTrackHeight - verticalDragHeight;
          _positionDragY(verticalDragPosition); // To update the state for the arrow buttons
        }
      }

      function appendArrows(ele, p, a1, a2)
      {
        var p1 = "before", p2 = "after", aTemp;
                                
        // Sniff for mac... Is there a better way to determine whether the arrows would naturally appear
        // at the top or the bottom of the bar?
        if (p == "os") {
          p = /Mac/.test(navigator.platform) ? "after" : "split";
        }
        if (p == p1) {
          p2 = p;
        } else if (p == p2) {
          p1 = p;
          aTemp = a1;
          a1 = a2;
          a2 = aTemp;
        }

        ele[p1](a1)[p2](a2);
      }

      function getArrowScroll(dirX, dirY, ele)
      {
        return function()
        {
          arrowScroll(dirX, dirY, this, ele);
          this.blur();
          return false;
        };
      }

      function arrowScroll(dirX, dirY, arrow, ele)
      {
        arrow = $(arrow).addClass('jspActive');

        var eve,
        scrollTimeout,
        isFirst = true,
        doScroll = function()
        {
          if (dirX !== 0) {
            jsp.scrollByX(dirX * settings.arrowButtonSpeed);
          }
          if (dirY !== 0) {
            jsp.scrollByY(dirY * settings.arrowButtonSpeed);
          }
          scrollTimeout = setTimeout(doScroll, isFirst ? settings.initialDelay : settings.arrowRepeatFreq);
          isFirst = false;
        };

        doScroll();

        eve = ele ? 'mouseout.jsp' : 'mouseup.jsp';
        ele = ele || $('html');
        ele.bind(
          eve,
          function()
          {
            arrow.removeClass('jspActive');
            scrollTimeout && clearTimeout(scrollTimeout);
            scrollTimeout = null;
            ele.unbind(eve);
          }
          );
      }

      function initClickOnTrack()
      {
        removeClickOnTrack();
        if (isScrollableV) {
          verticalTrack.bind(
            'mousedown.jsp',
            function(e)
            {
              if (e.originalTarget === undefined || e.originalTarget == e.currentTarget) {
                var clickedTrack = $(this),
                offset = clickedTrack.offset(),
                direction = e.pageY - offset.top - verticalDragPosition,
                scrollTimeout,
                isFirst = true,
                doScroll = function()
                {
                  var offset = clickedTrack.offset(),
                  pos = e.pageY - offset.top - verticalDragHeight / 2,
                  contentDragY = paneHeight * settings.scrollPagePercent,
                  dragY = dragMaxY * contentDragY / (contentHeight - paneHeight);
                  if (direction < 0) {
                    if (verticalDragPosition - dragY > pos) {
                      jsp.scrollByY(-contentDragY);
                    } else {
                      positionDragY(pos);
                    }
                  } else if (direction > 0) {
                    if (verticalDragPosition + dragY < pos) {
                      jsp.scrollByY(contentDragY);
                    } else {
                      positionDragY(pos);
                    }
                  } else {
                    cancelClick();
                    return;
                  }
                  scrollTimeout = setTimeout(doScroll, isFirst ? settings.initialDelay : settings.trackClickRepeatFreq);
                  isFirst = false;
                },
                cancelClick = function()
                {
                  scrollTimeout && clearTimeout(scrollTimeout);
                  scrollTimeout = null;
                  $(document).unbind('mouseup.jsp', cancelClick);
                };
                doScroll();
                $(document).bind('mouseup.jsp', cancelClick);
                return false;
              }
            }
            );
        }
                                
        if (isScrollableH) {
          horizontalTrack.bind(
            'mousedown.jsp',
            function(e)
            {
              if (e.originalTarget === undefined || e.originalTarget == e.currentTarget) {
                var clickedTrack = $(this),
                offset = clickedTrack.offset(),
                direction = e.pageX - offset.left - horizontalDragPosition,
                scrollTimeout,
                isFirst = true,
                doScroll = function()
                {
                  var offset = clickedTrack.offset(),
                  pos = e.pageX - offset.left - horizontalDragWidth / 2,
                  contentDragX = paneWidth * settings.scrollPagePercent,
                  dragX = dragMaxX * contentDragX / (contentWidth - paneWidth);
                  if (direction < 0) {
                    if (horizontalDragPosition - dragX > pos) {
                      jsp.scrollByX(-contentDragX);
                    } else {
                      positionDragX(pos);
                    }
                  } else if (direction > 0) {
                    if (horizontalDragPosition + dragX < pos) {
                      jsp.scrollByX(contentDragX);
                    } else {
                      positionDragX(pos);
                    }
                  } else {
                    cancelClick();
                    return;
                  }
                  scrollTimeout = setTimeout(doScroll, isFirst ? settings.initialDelay : settings.trackClickRepeatFreq);
                  isFirst = false;
                },
                cancelClick = function()
                {
                  scrollTimeout && clearTimeout(scrollTimeout);
                  scrollTimeout = null;
                  $(document).unbind('mouseup.jsp', cancelClick);
                };
                doScroll();
                $(document).bind('mouseup.jsp', cancelClick);
                return false;
              }
            }
            );
          horizontalTrackbottom.bind(
            'mousedown.jsp',
            function(e)
            {
              if (e.originalTarget === undefined || e.originalTarget == e.currentTarget) {
                var clickedTrack = $(this),
                offset = clickedTrack.offset(),
                direction = e.pageX - offset.left - horizontalDragPositionbottom,
                scrollTimeout,
                isFirst = true,
                doScroll = function()
                {
                  var offset = clickedTrack.offset(),
                  pos = e.pageX - offset.left - horizontalDragWidth / 2,
                  contentDragX = paneWidth * settings.scrollPagePercent,
                  dragX = dragMaxX * contentDragX / (contentWidth - paneWidth);
                  if (direction < 0) {
                    if (horizontalDragPositionbottom - dragX > pos) {
                      jsp.scrollByX(-contentDragX);
                    } else {
                      positionDragX(pos);
                    }
                  } else if (direction > 0) {
                    if (horizontalDragPositionbottom + dragX < pos) {
                      jsp.scrollByX(contentDragX);
                    } else {
                      positionDragX(pos);
                    }
                  } else {
                    cancelClick();
                    return;
                  }
                  scrollTimeout = setTimeout(doScroll, isFirst ? settings.initialDelay : settings.trackClickRepeatFreq);
                  isFirst = false;
                },
                cancelClick = function()
                {
                  scrollTimeout && clearTimeout(scrollTimeout);
                  scrollTimeout = null;
                  $(document).unbind('mouseup.jsp', cancelClick);
                };
                doScroll();
                $(document).bind('mouseup.jsp', cancelClick);
                return false;
              }
            }
            );
        }
      }

      function removeClickOnTrack()
      {
        if (horizontalTrack) {
          horizontalTrack.unbind('mousedown.jsp');
        }
        if (verticalTrack) {
          verticalTrack.unbind('mousedown.jsp');
        }
        if (horizontalTrackbottom) {
          horizontalTrackbottom.unbind('mousedown.jsp');
        }
      }

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

        if (verticalDrag) {
          verticalDrag.removeClass('jspActive');
        }
        if (horizontalDrag) {
          horizontalDrag.removeClass('jspActive');
        }
        if (horizontalDragbottom) {
          horizontalDragbottom.removeClass('jspActive');
        }
      }

      function positionDragY(destY, animate)
      {
        if (!isScrollableV) {
          return;
        }
        if (destY < 0) {
          destY = 0;
        } else if (destY > dragMaxY) {
          destY = dragMaxY;
        }

        // can't just check if(animate) because false is a valid value that could be passed in...
        if (animate === undefined) {
          animate = settings.animateScroll;
        }
        if (animate) {
          jsp.animate(verticalDrag, 'top', destY,       _positionDragY);
        } else {
          verticalDrag.css('top', destY);
          _positionDragY(destY);
        }

      }

      function _positionDragY(destY)
      {
        if (destY === undefined) {
          destY = verticalDrag.position().top;
        }

        container.scrollTop(0);
        verticalDragPosition = destY;

        var isAtTop = verticalDragPosition === 0,
        isAtBottom = verticalDragPosition == dragMaxY,
        percentScrolled = destY/ dragMaxY,
        destTop = -percentScrolled * (contentHeight - paneHeight);

        if (wasAtTop != isAtTop || wasAtBottom != isAtBottom) {
          wasAtTop = isAtTop;
          wasAtBottom = isAtBottom;
          elem.trigger('jsp-arrow-change', [wasAtTop, wasAtBottom, wasAtLeft, wasAtRight]);
        }
                                
        updateVerticalArrows(isAtTop, isAtBottom);
        pane.css('top', destTop);
        elem.trigger('jsp-scroll-y', [-destTop, isAtTop, isAtBottom]).trigger('scroll');
      }

      function positionDragX(destX, animate)
      {
        if (!isScrollableH) {
          return;
        }
        if (destX < 0) {
          destX = 0;
        } else if (destX > dragMaxX) {
          destX = dragMaxX;
        }

        if (animate === undefined) {
          animate = settings.animateScroll;
        }
        if (animate) {
          jsp.animate(horizontalDrag, 'left', destX,    _positionDragX);
          jsp.animate(horizontalDragbottom, 'left', destX,      _positionDragX);
        } else {
          horizontalDrag.css('left', destX);
          horizontalDragbottom.css('left', destX);
          _positionDragX(destX);
        }
      }

      function _positionDragX(destX)
      {
        if (destX === undefined) {
          destX = horizontalDrag.position().left;
          destX = horizontalDragbottom.position().left;
        }

        container.scrollTop(0);
        horizontalDragPosition = destX;
        horizontalDragPositionbottom = destX;

        var isAtLeft = horizontalDragPosition === 0,
        isAtRight = horizontalDragPosition == dragMaxX,
        percentScrolled = destX / dragMaxX,
        destLeft = -percentScrolled * (contentWidth - paneWidth);
        var isAtLeftbottom = horizontalDragPositionbottom === 0,
        isAtRightbottom = horizontalDragPositionbottom == dragMaxX,
        percentScrolledbottom = destX / dragMaxX,
        destLeftbottom = -percentScrolledbottom * (contentWidth - paneWidth);
        if (wasAtLeft != isAtLeft || wasAtRight != isAtRight) {
          wasAtLeft = isAtLeft;
          wasAtRight = isAtRight;
          elem.trigger('jsp-arrow-change', [wasAtTop, wasAtBottom, wasAtLeft, wasAtRight]);
        }
        if (wasAtLeftbottom != isAtLeftbottom || wasAtRightbottom != isAtRightbottom) {
          wasAtLeftbottom = isAtLeftbottom;
          wasAtRightbottom = isAtRightbottom;
          elem.trigger('jsp-arrow-change', [wasAtTop, wasAtBottom, wasAtLeftbottom, wasAtRightbottom]);
        }
                                
        updateHorizontalArrows(isAtLeft, isAtRight);
        updateHorizontalArrows(isAtLeftbottom, isAtRightbottom);
        pane.css('left', destLeft);
        pane.css('left', destLeftbottom);
        elem.trigger('jsp-scroll-x', [-destLeft, isAtLeft, isAtRight]).trigger('scroll');
        elem.trigger('jsp-scroll-x', [-destLeftbottom, isAtLeftbottom, isAtRightbottom]).trigger('scroll');
      }

      function updateVerticalArrows(isAtTop, isAtBottom)
      {
        if (settings.showArrows) {
          arrowUp[isAtTop ? 'addClass' : 'removeClass']('jspDisabled');
          arrowDown[isAtBottom ? 'addClass' : 'removeClass']('jspDisabled');
        }
      }

      function updateHorizontalArrows(isAtLeft, isAtRight)
      {
        if (settings.showArrows) {
          arrowLeft[isAtLeft ? 'addClass' : 'removeClass']('jspDisabled');
          arrowRight[isAtRight ? 'addClass' : 'removeClass']('jspDisabled');
          arrowLeftbottom[isAtLeft ? 'addClass' : 'removeClass']('jspDisabled');
          arrowRightbottom[isAtRight ? 'addClass' : 'removeClass']('jspDisabled');
        }
      }

      function scrollToY(destY, animate)
      {
        var percentScrolled = destY / (contentHeight - paneHeight);
        positionDragY(percentScrolled * dragMaxY, animate);
      }

      function scrollToX(destX, animate)
      {
        var percentScrolled = destX / (contentWidth - paneWidth);
        positionDragX(percentScrolled * dragMaxX, animate);
      }

      function scrollToElement(ele, stickToTop, animate)
      {
        var e, eleHeight, eleWidth, eleTop = 0, eleLeft = 0, viewportTop, maxVisibleEleTop, maxVisibleEleLeft, destY, destX;

        // Legal hash values aren't necessarily legal jQuery selectors so we need to catch any
        // errors from the lookup...
        try {
          e = $(ele);
        } catch (err) {
          return;
        }
        eleHeight = e.outerHeight();
        eleWidth= e.outerWidth();

        container.scrollTop(0);
        container.scrollLeft(0);
                                
        // loop through parents adding the offset top of any elements that are relatively positioned between
        // the focused element and the jspPane so we can get the true distance from the top
        // of the focused element to the top of the scrollpane...
        while (!e.is('.jspPane')) {
          eleTop += e.position().top;
          eleLeft += e.position().left;
          e = e.offsetParent();
          if (/^body|html$/i.test(e[0].nodeName)) {
            // we ended up too high in the document structure. Quit!
            return;
          }
        }

        viewportTop = contentPositionY();
        maxVisibleEleTop = viewportTop + paneHeight;
        if (eleTop < viewportTop || stickToTop) { // element is above viewport
          destY = eleTop - settings.verticalGutter;
        } else if (eleTop + eleHeight > maxVisibleEleTop) { // element is below viewport
          destY = eleTop - paneHeight + eleHeight + settings.verticalGutter;
        }
        if (destY) {
          scrollToY(destY, animate);
        }
                                
        viewportLeft = contentPositionX();
        maxVisibleEleLeft = viewportLeft + paneWidth;
        if (eleLeft < viewportLeft || stickToTop) { // element is to the left of viewport
          destX = eleLeft - settings.horizontalGutter;
        } else if (eleLeft + eleWidth > maxVisibleEleLeft) { // element is to the right viewport
          destX = eleLeft - paneWidth + eleWidth + settings.horizontalGutter;
        }
        if (destX) {
          scrollToX(destX, animate);
        }

      }

      function contentPositionX()
      {
        return -pane.position().left;
      }

      function contentPositionY()
      {
        return -pane.position().top;
      }

      function initMousewheel()
      {
        container.unbind(mwEvent).bind(
          mwEvent,
          function (event, delta, deltaX, deltaY) {
            var dX = horizontalDragPosition, dY = verticalDragPosition, dXb = horizontalDragPositionbottom;
            jsp.scrollBy(deltaX * settings.mouseWheelSpeed, -deltaY * settings.mouseWheelSpeed, false);
            // return true if there was no movement so rest of screen can scroll
            return dX == horizontalDragPosition && dY == verticalDragPosition && dXb == horizontalDragPositionbottom;
          }
          );
      }

      function removeMousewheel()
      {
        container.unbind(mwEvent);
      }

      function nil()
      {
        return false;
      }

      function initFocusHandler()
      {
        pane.find(':input,a').unbind('focus.jsp').bind(
          'focus.jsp',
          function(e)
          {
            scrollToElement(e.target, false);
          }
          );
      }

      function removeFocusHandler()
      {
        pane.find(':input,a').unbind('focus.jsp');
      }
                        
      function initKeyboardNav()
      {
        var keyDown, elementHasScrolled;
        // IE also focuses elements that don't have tabindex set.
        pane.focus(
          function()
          {
            elem.focus();
          }
          );
                                
        elem.attr('tabindex', 0)
        .unbind('keydown.jsp keypress.jsp')
        .bind(
          'keydown.jsp',
          function(e)
          {
            if (e.target !== this){
              return;
            }
            var dX = horizontalDragPosition, dY = verticalDragPosition, dXb = horizontalDragPositionbottom;
            switch(e.keyCode) {
              case 40: // down
              case 38: // up
              case 34: // page down
              case 32: // space
              case 33: // page up
              case 39: // right
              case 37: // left
                keyDown = e.keyCode;
                keyDownHandler();
                break;
              case 35: // end
                scrollToY(contentHeight - paneHeight);
                keyDown = null;
                break;
              case 36: // home
                scrollToY(0);
                keyDown = null;
                break;
            }

            elementHasScrolled = e.keyCode == keyDown && dX != horizontalDragPosition || dY != verticalDragPosition || dX != horizontalDragPosition;
            return !elementHasScrolled;
          }
          ).bind(
          'keypress.jsp', // For FF/ OSX so that we can cancel the repeat key presses if the JSP scrolls...
          function(e)
          {
            if (e.keyCode == keyDown) {
              keyDownHandler();
            }
            return !elementHasScrolled;
          }
          );
                                
        if (settings.hideFocus) {
          elem.css('outline', 'none');
          if ('hideFocus' in container[0]){
            elem.attr('hideFocus', true);
          }
        } else {
          elem.css('outline', '');
          if ('hideFocus' in container[0]){
            elem.attr('hideFocus', false);
          }
        }
                                
        function keyDownHandler()
        {
          var dX = horizontalDragPosition, dY = verticalDragPosition, dXb = horizontalDragPositionbottom;
          switch(keyDown) {
            case 40: // down
              jsp.scrollByY(settings.keyboardSpeed, false);
              break;
            case 38: // up
              jsp.scrollByY(-settings.keyboardSpeed, false);
              break;
            case 34: // page down
            case 32: // space
              jsp.scrollByY(paneHeight * settings.scrollPagePercent, false);
              break;
            case 33: // page up
              jsp.scrollByY(-paneHeight * settings.scrollPagePercent, false);
              break;
            case 39: // right
              jsp.scrollByX(settings.keyboardSpeed, false);
              break;
            case 37: // left
              jsp.scrollByX(-settings.keyboardSpeed, false);
              break;
          }

          elementHasScrolled = dX != horizontalDragPosition || dY != verticalDragPosition || dXb != horizontalDragPositionbottom;
          return elementHasScrolled;
        }
      }
                        
      function removeKeyboardNav()
      {
        elem.attr('tabindex', '-1')
        .removeAttr('tabindex')
        .unbind('keydown.jsp keypress.jsp');
      }

      function observeHash()
      {
        if (location.hash && location.hash.length > 1) {
          var e, retryInt;
          try {
            e = $(location.hash);
          } catch (err) {
            return;
          }

          if (e.length && pane.find(location.hash)) {
            // nasty workaround but it appears to take a little while before the hash has done its thing
            // to the rendered page so we just wait until the container's scrollTop has been messed up.
            if (container.scrollTop() === 0) {
              retryInt = setInterval(
                function()
                {
                  if (container.scrollTop() > 0) {
                    scrollToElement(location.hash, true);
                    $(document).scrollTop(container.position().top);
                    clearInterval(retryInt);
                  }
                },
                50
                );
            } else {
              scrollToElement(location.hash, true);
              $(document).scrollTop(container.position().top);
            }
          }
        }
      }

      function unhijackInternalLinks()
      {
        $('a.jspHijack').unbind('click.jsp-hijack').removeClass('jspHijack');
      }

      function hijackInternalLinks()
      {
        unhijackInternalLinks();
        $('a[href^=#]').addClass('jspHijack').bind(
          'click.jsp-hijack',
          function()
          {
            var uriParts = this.href.split('#'), hash;
            if (uriParts.length > 1) {
              hash = uriParts[1];
              if (hash.length > 0 && pane.find('#' + hash).length > 0) {
                scrollToElement('#' + hash, true);
                // Need to return false otherwise things mess up... Would be nice to maybe also scroll
                // the window to the top of the scrollpane?
                return false;
              }
            }
          }
          );
      }
                        
      // Init touch on iPad, iPhone, iPod, Android
      function initTouch()
      {
        var startX,
        startY,
        touchStartX,
        touchStartY,
        moved,
        moving = false;
  
        container.unbind('touchstart.jsp touchmove.jsp touchend.jsp click.jsp-touchclick').bind(
          'touchstart.jsp',
          function(e)
          {
            var touch = e.originalEvent.touches[0];
            startX = contentPositionX();
            startY = contentPositionY();
            touchStartX = touch.pageX;
            touchStartY = touch.pageY;
            moved = false;
            moving = true;
          }
          ).bind(
          'touchmove.jsp',
          function(ev)
          {
            if(!moving) {
              return;
            }
                                                
            var touchPos = ev.originalEvent.touches[0],
            dX = horizontalDragPosition, dY = verticalDragPosition, dXb = horizontalDragPositionbottom;
                                                
            jsp.scrollTo(startX + touchStartX - touchPos.pageX, startY + touchStartY - touchPos.pageY);
                                                
            moved = moved || Math.abs(touchStartX - touchPos.pageX) > 5 || Math.abs(touchStartY - touchPos.pageY) > 5;
                                                
            // return true if there was no movement so rest of screen can scroll
            return dX == horizontalDragPosition && dY == verticalDragPosition && dXb == horizontalDragPositionbottom;
          }
          ).bind(
          'touchend.jsp',
          function(e)
          {
            moving = false;
          }
          ).bind(
          'click.jsp-touchclick',
          function(e)
          {
            if(moved) {
              moved = false;
              return false;
            }
          }
          );
      }
                        
      function destroy(){
        var currentY = contentPositionY(),
        currentX = contentPositionX();
        elem.removeClass('jspScrollable').unbind('.jsp');
        elem.replaceWith(originalElement.append(pane.children()));
        originalElement.scrollTop(currentY);
        originalElement.scrollLeft(currentX);
      }

      // Public API
      $.extend(
        jsp,
        {
          // Reinitialises the scroll pane (if it's internal dimensions have changed since the last time it
          // was initialised). The settings object which is passed in will override any settings from the
          // previous time it was initialised - if you don't pass any settings then the ones from the previous
          // initialisation will be used.
          reinitialise: function(s)
          {
            s = $.extend({}, settings, s);
            initialise(s);
          },
          // Scrolls the specified element (a jQuery object, DOM node or jQuery selector string) into view so
          // that it can be seen within the viewport. If stickToTop is true then the element will appear at
          // the top of the viewport, if it is false then the viewport will scroll as little as possible to
          // show the element. You can also specify if you want animation to occur. If you don't provide this
          // argument then the animateScroll value from the settings object is used instead.
          scrollToElement: function(ele, stickToTop, animate)
          {
            scrollToElement(ele, stickToTop, animate);
          },
          // Scrolls the pane so that the specified co-ordinates within the content are at the top left
          // of the viewport. animate is optional and if not passed then the value of animateScroll from
          // the settings object this jScrollPane was initialised with is used.
          scrollTo: function(destX, destY, animate)
          {
            scrollToX(destX, animate);
            scrollToY(destY, animate);
          },
          // Scrolls the pane so that the specified co-ordinate within the content is at the left of the
          // viewport. animate is optional and if not passed then the value of animateScroll from the settings
          // object this jScrollPane was initialised with is used.
          scrollToX: function(destX, animate)
          {
            scrollToX(destX, animate);
          },
          // Scrolls the pane so that the specified co-ordinate within the content is at the top of the
          // viewport. animate is optional and if not passed then the value of animateScroll from the settings
          // object this jScrollPane was initialised with is used.
          scrollToY: function(destY, animate)
          {
            scrollToY(destY, animate);
          },
          // Scrolls the pane to the specified percentage of its maximum horizontal scroll position. animate
          // is optional and if not passed then the value of animateScroll from the settings object this
          // jScrollPane was initialised with is used.
          scrollToPercentX: function(destPercentX, animate)
          {
            scrollToX(destPercentX * (contentWidth - paneWidth), animate);
          },
          // Scrolls the pane to the specified percentage of its maximum vertical scroll position. animate
          // is optional and if not passed then the value of animateScroll from the settings object this
          // jScrollPane was initialised with is used.
          scrollToPercentY: function(destPercentY, animate)
          {
            scrollToY(destPercentY * (contentHeight - paneHeight), animate);
          },
          // Scrolls the pane by the specified amount of pixels. animate is optional and if not passed then
          // the value of animateScroll from the settings object this jScrollPane was initialised with is used.
          scrollBy: function(deltaX, deltaY, animate)
          {
            jsp.scrollByX(deltaX, animate);
            jsp.scrollByY(deltaY, animate);
          },
          // Scrolls the pane by the specified amount of pixels. animate is optional and if not passed then
          // the value of animateScroll from the settings object this jScrollPane was initialised with is used.
          scrollByX: function(deltaX, animate)
          {
            var destX = contentPositionX() + deltaX,
            percentScrolled = destX / (contentWidth - paneWidth);
            positionDragX(percentScrolled * dragMaxX, animate);
          },
          // Scrolls the pane by the specified amount of pixels. animate is optional and if not passed then
          // the value of animateScroll from the settings object this jScrollPane was initialised with is used.
          scrollByY: function(deltaY, animate)
          {
            var destY = contentPositionY() + deltaY,
            percentScrolled = destY / (contentHeight - paneHeight);
            positionDragY(percentScrolled * dragMaxY, animate);
          },
          // Positions the horizontal drag at the specified x position (and updates the viewport to reflect
          // this). animate is optional and if not passed then the value of animateScroll from the settings
          // object this jScrollPane was initialised with is used.
          positionDragX: function(x, animate)
          {
            positionDragX(x, animate);
          },
          // Positions the vertical drag at the specified y position (and updates the viewport to reflect
          // this). animate is optional and if not passed then the value of animateScroll from the settings
          // object this jScrollPane was initialised with is used.
          positionDragY: function(y, animate)
          {
            positionDragX(y, animate);
          },
          // This method is called when jScrollPane is trying to animate to a new position. You can override
          // it if you want to provide advanced animation functionality. It is passed the following arguments:
          //  * ele          - the element whose position is being animated
          //  * prop         - the property that is being animated
          //  * value        - the value it's being animated to
          //  * stepCallback - a function that you must execute each time you update the value of the property
          // You can use the default implementation (below) as a starting point for your own implementation.
          animate: function(ele, prop, value, stepCallback)
          {
            var params = {};
            params[prop] = value;
            ele.animate(
              params,
              {
                'duration'      : settings.animateDuration,
                'ease'          : settings.animateEase,
                'queue'         : false,
                'step'          : stepCallback
              }
              );
          },
          // Returns the current x position of the viewport with regards to the content pane.
          getContentPositionX: function()
          {
            return contentPositionX();
          },
          // Returns the current y position of the viewport with regards to the content pane.
          getContentPositionY: function()
          {
            return contentPositionY();
          },
          // Returns the width of the content within the scroll pane.
          getContentWidth: function()
          {
            return contentWidth();
          },
          // Returns the height of the content within the scroll pane.
          getContentHeight: function()
          {
            return contentHeight();
          },
          // Returns the horizontal position of the viewport within the pane content.
          getPercentScrolledX: function()
          {
            return contentPositionX() / (contentWidth - paneWidth);
          },
          // Returns the vertical position of the viewport within the pane content.
          getPercentScrolledY: function()
          {
            return contentPositionY() / (contentHeight - paneHeight);
          },
          // Returns whether or not this scrollpane has a horizontal scrollbar.
          getIsScrollableH: function()
          {
            return isScrollableH;
          },
          // Returns whether or not this scrollpane has a vertical scrollbar.
          getIsScrollableV: function()
          {
            return isScrollableV;
          },
          // Gets a reference to the content pane. It is important that you use this method if you want to
          // edit the content of your jScrollPane as if you access the element directly then you may have some
          // problems (as your original element has had additional elements for the scrollbars etc added into
          // it).
          getContentPane: function()
          {
            return pane;
          },
          // Scrolls this jScrollPane down as far as it can currently scroll. If animate isn't passed then the
          // animateScroll value from settings is used instead.
          scrollToBottom: function(animate)
          {
            positionDragY(dragMaxY, animate);
          },
          // Hijacks the links on the page which link to content inside the scrollpane. If you have changed
          // the content of your page (e.g. via AJAX) and want to make sure any new anchor links to the
          // contents of your scroll pane will work then call this function.
          hijackInternalLinks: function()
          {
            hijackInternalLinks();
          },
          // Removes the jScrollPane and returns the page to the state it was in before jScrollPane was
          // initialised.
          destroy: function()
          {
            destroy();
          }
        }
        );
                        
      initialise(s);
    }

    // Pluginifying code...
    settings = $.extend({}, $.fn.jScrollPane.defaults, settings);
                
    // Apply default speed
    $.each(['mouseWheelSpeed', 'arrowButtonSpeed', 'trackClickSpeed', 'keyboardSpeed'], function() {
      settings[this] = settings[this] || settings.speed;
    });

    var ret;
    this.each(
      function()
      {
        var elem = $(this), jspApi = elem.data('jsp');
        if (jspApi) {
          jspApi.reinitialise(settings);
        } else {
          jspApi = new JScrollPane(elem, settings);
          elem.data('jsp', jspApi);
        }
        ret = ret ? ret.add(elem) : elem;
      }
      );
    return ret;
  };

  $.fn.jScrollPane.defaults = {
    showArrows                  : false,
    maintainPosition            : true,
    clickOnTrack                : true,
    autoReinitialise            : false,
    autoReinitialiseDelay       : 500,
    verticalDragMinHeight       : 0,
    verticalDragMaxHeight       : 99999,
    horizontalDragMinWidth      : 0,
    horizontalDragMaxWidth      : 99999,
    animateScroll               : false,
    animateDuration             : 300,
    animateEase                 : 'linear',
    hijackInternalLinks         : false,
    verticalGutter              : 4,
    horizontalGutter            : 4,
    mouseWheelSpeed             : 0,
    arrowButtonSpeed            : 0,
    arrowRepeatFreq             : 50,
    arrowScrollOnHover          : false,
    trackClickSpeed             : 0,
    trackClickRepeatFreq        : 70,
    verticalArrowPositions      : 'split',
    horizontalArrowPositions    : 'split',
    enableKeyboardNavigation    : true,
    hideFocus                   : false,
    keyboardSpeed               : 0,
    initialDelay                : 300,        // Delay before starting repeating
    speed                       : 30,           // Default speed when others falsey
    scrollPagePercent           : .8            // Percent of visible area scrolled when pageUp/Down or track area pressed
  };

})(jQuery,this);