diff --git a/Gruntfile.js b/Gruntfile.js index abde193..df44d8f 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -3,47 +3,60 @@ * http://jscrollpane.kelvinluck.com/ * * Copyright (c) 2013 Kelvin Luck + * Copyright (c) 2020 Tuukka Pasanen * Licensed under the MIT license. */ 'use strict'; -module.exports = function(grunt) { - - // Project configuration. - grunt.initConfig({ - - uglify: { - jsp: { - files: { - 'script/jquery.jscrollpane.min.js': 'script/jquery.jscrollpane.js' +module.exports = function (grunt) { + // Project configuration. + grunt.initConfig({ + uglify: { + jsp: { + files: { + 'script/jquery.jscrollpane.min.js': 'script/jquery.jscrollpane.js', + }, + options: { + preserveComments: 'some', + }, + }, }, - options: { - preserveComments: 'some' - } - } - }, - watch: { - content: { - files: ['script/jquery.jscrollpane.js'], - tasks: 'uglify' - } - }, - connect: { - site: { - options: { - base: '.' - } - } - } - - }); - - grunt.loadNpmTasks('grunt-contrib-connect'); - grunt.loadNpmTasks('grunt-contrib-watch'); - grunt.loadNpmTasks('grunt-contrib-uglify'); + watch: { + content: { + files: ['script/jquery.jscrollpane.js'], + tasks: 'uglify', + }, + }, + connect: { + site: { + options: { + base: '.', + }, + }, + }, + prettier: { + pretty: { + options: { + printWidth: 140, + tabWidth: 4, + useTabs: false, + semi: true, + singleQuote: true, + trailingComma: 'all', + bracketSpacing: true, + progress: true, + }, + src: ['Gruntfile.js', 'script/jquery.jscrollpane.js'], + }, + }, + }); - grunt.registerTask('default', ['uglify']); - grunt.registerTask('serve', ['uglify', 'connect', 'watch']); + grunt.loadNpmTasks('grunt-contrib-connect'); + grunt.loadNpmTasks('grunt-contrib-watch'); + grunt.loadNpmTasks('grunt-contrib-uglify'); + grunt.loadNpmTasks('grunt-prettier'); + grunt.registerTask('default', ['uglify', 'prettier']); + grunt.registerTask('serve', ['uglify', 'connect', 'watch']); }; diff --git a/package-lock.json b/package-lock.json index 9d1db03..be1c20d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "jscrollpane", - "version": "2.2.3", + "version": "2.2.3-rc.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1198,6 +1198,16 @@ "which": "~1.3.0" } }, + "grunt-prettier": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/grunt-prettier/-/grunt-prettier-2.1.0.tgz", + "integrity": "sha512-PKPz5Qa+xJsoN30DNvjsmozA3ej/YsRXJGnwGK5Rk3XwuuBUIzDkd6tpfmp2xlxQBW91FgDy1EdESclIkB0GPA==", + "dev": true, + "requires": { + "prettier": "^2.0.5", + "progress": "^2.0.0" + } + }, "gzip-size": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-3.0.0.tgz", @@ -2174,6 +2184,12 @@ "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", "dev": true }, + "prettier": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz", + "integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==", + "dev": true + }, "pretty-bytes": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-3.0.1.tgz", @@ -2189,6 +2205,12 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, "punycode": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", diff --git a/package.json b/package.json index bb6a433..9cd458e 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "grunt-cli": "^1.3.2", "grunt-contrib-connect": "^2.1.0", "grunt-contrib-uglify": "^4.0.1", - "grunt-contrib-watch": "^1.1.0" + "grunt-contrib-watch": "^1.1.0", + "grunt-prettier": "^2.1.0" } } diff --git a/script/jquery.jscrollpane.js b/script/jquery.jscrollpane.js index 4da0a41..799c9df 100644 --- a/script/jquery.jscrollpane.js +++ b/script/jquery.jscrollpane.js @@ -64,94 +64,130 @@ // - #359 Use npm scripts and local dev dependencies to build the project (function (factory) { - if ( typeof define === 'function' && define.amd ) { - // AMD. Register as an anonymous module. - define(['jquery'], factory); - } else if (typeof exports === 'object') { - // Node/CommonJS style for Browserify - module.exports = factory(jQuery || require('jquery')); - } else { - // Browser globals - factory(jQuery); - } -}(function($){ - - $.fn.jScrollPane = function(settings) - { - // JScrollPane "class" - public methods are available through $('selector').data('jsp') - function JScrollPane(elem, s) - { - var settings, jsp = this, pane, paneWidth, paneHeight, container, contentWidth, contentHeight, - percentInViewH, percentInViewV, isScrollableV, isScrollableH, verticalDrag, dragMaxY, - verticalDragPosition, horizontalDrag, dragMaxX, horizontalDragPosition, - verticalBar, verticalTrack, scrollbarWidth, verticalTrackHeight, verticalDragHeight, arrowUp, arrowDown, - horizontalBar, horizontalTrack, horizontalTrackWidth, horizontalDragWidth, arrowLeft, arrowRight, - reinitialiseInterval, originalPadding, originalPaddingTotalWidth, previousContentWidth, - wasAtTop = true, wasAtLeft = true, wasAtBottom = false, wasAtRight = false, - originalElement = elem.clone(false, false).empty(), resizeEventsAdded = false, - mwEvent = $.fn.mwheelIntent ? 'mwheelIntent.jsp' : 'mousewheel.jsp'; - - var reinitialiseFn = function() { - // if size has changed then reinitialise - if (settings.resizeSensorDelay > 0) { - setTimeout(function() { - initialise(settings); - }, settings.resizeSensorDelay); - } - else { - initialise(settings); - } - }; - - if (elem.css('box-sizing') === 'border-box') { - originalPadding = 0; - originalPaddingTotalWidth = 0; - } else { - originalPadding = elem.css('paddingTop') + ' ' + - elem.css('paddingRight') + ' ' + - elem.css('paddingBottom') + ' ' + - elem.css('paddingLeft'); - originalPaddingTotalWidth = (parseInt(elem.css('paddingLeft'), 10) || 0) + - (parseInt(elem.css('paddingRight'), 10) || 0); - } - - function initialise(s) - { - - var /*firstChild, lastChild, */isMaintainingPositon, lastContentX, lastContentY, - hasContainingSpaceChanged, originalScrollTop, originalScrollLeft, - newPaneWidth, newPaneHeight, maintainAtBottom = false, maintainAtRight = false; - - settings = s; + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['jquery'], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS style for Browserify + module.exports = factory(jQuery || require('jquery')); + } else { + // Browser globals + factory(jQuery); + } +})(function ($) { + $.fn.jScrollPane = function (settings) { + // JScrollPane "class" - public methods are available through $('selector').data('jsp') + function JScrollPane(elem, s) { + var settings, + jsp = this, + pane, + paneWidth, + paneHeight, + container, + contentWidth, + contentHeight, + percentInViewH, + percentInViewV, + isScrollableV, + isScrollableH, + verticalDrag, + dragMaxY, + verticalDragPosition, + horizontalDrag, + dragMaxX, + horizontalDragPosition, + verticalBar, + verticalTrack, + scrollbarWidth, + verticalTrackHeight, + verticalDragHeight, + arrowUp, + arrowDown, + horizontalBar, + horizontalTrack, + horizontalTrackWidth, + horizontalDragWidth, + arrowLeft, + arrowRight, + reinitialiseInterval, + originalPadding, + originalPaddingTotalWidth, + previousContentWidth, + wasAtTop = true, + wasAtLeft = true, + wasAtBottom = false, + wasAtRight = false, + originalElement = elem.clone(false, false).empty(), + resizeEventsAdded = false, + mwEvent = $.fn.mwheelIntent ? 'mwheelIntent.jsp' : 'mousewheel.jsp'; + + var reinitialiseFn = function () { + // if size has changed then reinitialise + if (settings.resizeSensorDelay > 0) { + setTimeout(function () { + initialise(settings); + }, settings.resizeSensorDelay); + } else { + initialise(settings); + } + }; + + if (elem.css('box-sizing') === 'border-box') { + originalPadding = 0; + originalPaddingTotalWidth = 0; + } else { + originalPadding = + elem.css('paddingTop') + + ' ' + + elem.css('paddingRight') + + ' ' + + elem.css('paddingBottom') + + ' ' + + elem.css('paddingLeft'); + originalPaddingTotalWidth = (parseInt(elem.css('paddingLeft'), 10) || 0) + (parseInt(elem.css('paddingRight'), 10) || 0); + } + + function initialise(s) { + var /*firstChild, lastChild, */ isMaintainingPositon, + lastContentX, + lastContentY, + hasContainingSpaceChanged, + originalScrollTop, + originalScrollLeft, + newPaneWidth, + newPaneHeight, + maintainAtBottom = false, + maintainAtRight = false; + + settings = s; lastContentX = 0; lastContentY = 0; - 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 = $('
').css('padding', originalPadding).append(elem.children()); - container = $('') - .css({ - 'width': paneWidth + 'px', - 'height': paneHeight + 'px' - } - ).append(pane).appendTo(elem); - - /* + 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 = $('').css('padding', originalPadding).append(elem.children()); + container = $('') + .css({ + width: paneWidth + 'px', + height: paneHeight + 'px', + }) + .append(pane) + .appendTo(elem); + + /* // Move any margins from the first and last children up to the container so they can still // collapse with neighbouring elements as they would before jScrollPane firstChild = pane.find(':first-child'); @@ -165,1500 +201,1355 @@ firstChild.css('margin-top', 0); lastChild.css('margin-bottom', 0); */ - } else { - elem.css('width', ''); - - // To measure the required dimensions accurately, temporarily override the CSS positioning - // of the container and pane. - container.css({width: 'auto', height: 'auto'}); - pane.css('position', 'static'); - - newPaneWidth = elem.innerWidth() + originalPaddingTotalWidth; - newPaneHeight = elem.innerHeight(); - pane.css('position', 'absolute'); - - maintainAtBottom = settings.stickToBottom && isCloseToBottom(); - maintainAtRight = settings.stickToRight && isCloseToRight(); - - hasContainingSpaceChanged = newPaneWidth !== paneWidth || newPaneHeight !== paneHeight; - - paneWidth = newPaneWidth; - paneHeight = newPaneHeight; - container.css({width: paneWidth + "px", height: paneHeight + "px"}); - - // If nothing changed since last check... - if (!hasContainingSpaceChanged && previousContentWidth == contentWidth && pane.outerHeight() == contentHeight) { - elem.width(paneWidth); - return; - } - previousContentWidth = contentWidth; - - pane.css('width', ''); - elem.width(paneWidth); - - container.find('>.jspVerticalBar,>.jspHorizontalBar').remove().end(); - } - - pane.css('overflow', 'auto'); - if (s.contentWidth) { - contentWidth = s.contentWidth; - } else { - contentWidth = pane[0].scrollWidth; - } - contentHeight = pane[0].scrollHeight; - pane.css('overflow', ''); - - percentInViewH = contentWidth / paneWidth; - percentInViewV = contentHeight / paneHeight; - isScrollableV = percentInViewV > 1 || settings.alwaysShowVScroll; - isScrollableH = percentInViewH > 1 || settings.alwaysShowHScroll; - - if (!(isScrollableH || isScrollableV)) { - elem.removeClass('jspScrollable'); - pane.css({ - top: '0', - left: '0', - width: container.width() - originalPaddingTotalWidth - }); - removeMousewheel(); - removeFocusHandler(); - removeKeyboardNav(); - removeClickOnTrack(); - } else { - elem.addClass('jspScrollable'); - - isMaintainingPositon = settings.maintainPosition && (verticalDragPosition || horizontalDragPosition); - - if (isMaintainingPositon) { - lastContentX = contentPositionX(); - lastContentY = contentPositionY(); - } - - initialiseVerticalScroll(); - initialiseHorizontalScroll(); - resizeScrollbars(); - - if (settings.stickToBottom || settings.stickToRight) { - scrollToX(maintainAtRight ? (contentWidth - paneWidth ) : lastContentX, false); - scrollToY(maintainAtBottom ? (contentHeight - paneHeight) : lastContentY, false); - } - - initFocusHandler(); - initMousewheel(); - initTouch(); - - if (settings.enableKeyboardNavigation) { - initKeyboardNav(); - } - if (settings.clickOnTrack) { - initClickOnTrack(); - } - - observeHash(); - if (settings.hijackInternalLinks) { - hijackInternalLinks(); - } - } - - if (!settings.resizeSensor && settings.autoReinitialise && !reinitialiseInterval) { - reinitialiseInterval = setInterval( - function() - { - initialise(settings); - }, - settings.autoReinitialiseDelay - ); - } else if (!settings.resizeSensor && !settings.autoReinitialise && reinitialiseInterval) { - clearInterval(reinitialiseInterval); - } + } else { + elem.css('width', ''); - if(settings.resizeSensor && !resizeEventsAdded) { + // To measure the required dimensions accurately, temporarily override the CSS positioning + // of the container and pane. + container.css({ width: 'auto', height: 'auto' }); + pane.css('position', 'static'); - // detect size change in content - detectSizeChanges(pane, reinitialiseFn); + newPaneWidth = elem.innerWidth() + originalPaddingTotalWidth; + newPaneHeight = elem.innerHeight(); + pane.css('position', 'absolute'); - // detect size changes of scroll element - detectSizeChanges(elem, reinitialiseFn); + maintainAtBottom = settings.stickToBottom && isCloseToBottom(); + maintainAtRight = settings.stickToRight && isCloseToRight(); - // detect size changes of container - detectSizeChanges(elem.parent(), reinitialiseFn); + hasContainingSpaceChanged = newPaneWidth !== paneWidth || newPaneHeight !== paneHeight; - // add a reinit on window resize also for safety - window.addEventListener('resize', reinitialiseFn); + paneWidth = newPaneWidth; + paneHeight = newPaneHeight; + container.css({ width: paneWidth + 'px', height: paneHeight + 'px' }); - resizeEventsAdded = true; - } + // If nothing changed since last check... + if (!hasContainingSpaceChanged && previousContentWidth == contentWidth && pane.outerHeight() == contentHeight) { + elem.width(paneWidth); + return; + } + previousContentWidth = contentWidth; + + pane.css('width', ''); + elem.width(paneWidth); + + container.find('>.jspVerticalBar,>.jspHorizontalBar').remove().end(); + } + + pane.css('overflow', 'auto'); + if (s.contentWidth) { + contentWidth = s.contentWidth; + } else { + contentWidth = pane[0].scrollWidth; + } + contentHeight = pane[0].scrollHeight; + pane.css('overflow', ''); + + percentInViewH = contentWidth / paneWidth; + percentInViewV = contentHeight / paneHeight; + isScrollableV = percentInViewV > 1 || settings.alwaysShowVScroll; + isScrollableH = percentInViewH > 1 || settings.alwaysShowHScroll; + + if (!(isScrollableH || isScrollableV)) { + elem.removeClass('jspScrollable'); + pane.css({ + top: '0', + left: '0', + width: container.width() - originalPaddingTotalWidth, + }); + removeMousewheel(); + removeFocusHandler(); + removeKeyboardNav(); + removeClickOnTrack(); + } else { + elem.addClass('jspScrollable'); + + isMaintainingPositon = settings.maintainPosition && (verticalDragPosition || horizontalDragPosition); + + if (isMaintainingPositon) { + lastContentX = contentPositionX(); + lastContentY = contentPositionY(); + } - if(originalScrollTop && elem.scrollTop(0)) { - scrollToY(originalScrollTop, false); - } + initialiseVerticalScroll(); + initialiseHorizontalScroll(); + resizeScrollbars(); - if(originalScrollLeft && elem.scrollLeft(0)) { - scrollToX(originalScrollLeft, false); - } + if (settings.stickToBottom || settings.stickToRight) { + scrollToX(maintainAtRight ? contentWidth - paneWidth : lastContentX, false); + scrollToY(maintainAtBottom ? contentHeight - paneHeight : lastContentY, false); + } - elem.trigger('jsp-initialised', [isScrollableH || isScrollableV]); - } - - function detectSizeChanges(element, callback) { - - // create resize event elements - based on resize sensor: https://github.com/flowkey/resize-sensor/ - var resizeWidth, resizeHeight; - var resizeElement = document.createElement('div'); - var resizeGrowElement = document.createElement('div'); - var resizeGrowChildElement = document.createElement('div'); - var resizeShrinkElement = document.createElement('div'); - var resizeShrinkChildElement = document.createElement('div'); - - // add necessary styling - resizeElement.style.cssText = 'position: absolute; left: 0; top: 0; right: 0; bottom: 0; overflow: scroll; z-index: -1; visibility: hidden;'; - resizeGrowElement.style.cssText = 'position: absolute; left: 0; top: 0; right: 0; bottom: 0; overflow: scroll; z-index: -1; visibility: hidden;'; - resizeShrinkElement.style.cssText = 'position: absolute; left: 0; top: 0; right: 0; bottom: 0; overflow: scroll; z-index: -1; visibility: hidden;'; - - resizeGrowChildElement.style.cssText = 'position: absolute; left: 0; top: 0;'; - resizeShrinkChildElement.style.cssText = 'position: absolute; left: 0; top: 0; width: 200%; height: 200%;'; - - // Create a function to programmatically update sizes - var updateSizes = function() { - - resizeGrowChildElement.style.width = resizeGrowElement.offsetWidth + 10 + 'px'; - resizeGrowChildElement.style.height = resizeGrowElement.offsetHeight + 10 + 'px'; - - resizeGrowElement.scrollLeft = resizeGrowElement.scrollWidth; - resizeGrowElement.scrollTop = resizeGrowElement.scrollHeight; - - resizeShrinkElement.scrollLeft = resizeShrinkElement.scrollWidth; - resizeShrinkElement.scrollTop = resizeShrinkElement.scrollHeight; - - resizeWidth = element.width(); - resizeHeight = element.height(); - }; - - // create functions to call when content grows - var onGrow = function() { - - // check to see if the content has change size - if (element.width() > resizeWidth || element.height() > resizeHeight) { - - // if size has changed then reinitialise - callback.apply(this, []); - } - // after reinitialising update sizes - updateSizes(); - }; - - // create functions to call when content shrinks - var onShrink = function() { - - // check to see if the content has change size - if (element.width() < resizeWidth || element.height() < resizeHeight) { - - // if size has changed then reinitialise - callback.apply(this, []); - } - // after reinitialising update sizes - updateSizes(); - }; - - // bind to scroll events - resizeGrowElement.addEventListener('scroll', onGrow.bind(this)); - resizeShrinkElement.addEventListener('scroll', onShrink.bind(this)); - - // nest elements before adding to pane - resizeGrowElement.appendChild(resizeGrowChildElement); - resizeShrinkElement.appendChild(resizeShrinkChildElement); - - resizeElement.appendChild(resizeGrowElement); - resizeElement.appendChild(resizeShrinkElement); - - element.append(resizeElement); - - // ensure parent element is not statically positioned - if(window.getComputedStyle(element[0], null).getPropertyValue('position') === 'static') { - element[0].style.position = 'relative'; - } - - // update sizes initially - updateSizes(); - } - - function initialiseVerticalScroll() - { - if (isScrollableV) { - - container.append( - $('').append( - $(''), - $('').append( - $('').append( - $(''), - $('') - ) - ), - $('') - ) - ); + initFocusHandler(); + initMousewheel(); + initTouch(); - verticalBar = container.find('>.jspVerticalBar'); - verticalTrack = verticalBar.find('>.jspTrack'); - verticalDrag = verticalTrack.find('>.jspDrag'); - - if (settings.showArrows) { - arrowUp = $('').on( - 'mousedown.jsp', getArrowScroll(0, -1) - ).on('click.jsp', nil); - arrowDown = $('').on( - 'mousedown.jsp', getArrowScroll(0, 1) - ).on('click.jsp', nil); - if (settings.arrowScrollOnHover) { - arrowUp.on('mouseover.jsp', getArrowScroll(0, -1, arrowUp)); - arrowDown.on('mouseover.jsp', getArrowScroll(0, 1, arrowDown)); - } + if (settings.enableKeyboardNavigation) { + initKeyboardNav(); + } + if (settings.clickOnTrack) { + initClickOnTrack(); + } - appendArrows(verticalTrack, settings.verticalArrowPositions, arrowUp, arrowDown); - } + observeHash(); + if (settings.hijackInternalLinks) { + hijackInternalLinks(); + } + } - verticalTrackHeight = paneHeight; - container.find('>.jspVerticalBar>.jspCap:visible,>.jspVerticalBar>.jspArrow').each( - function() - { - verticalTrackHeight -= $(this).outerHeight(); - } - ); + if (!settings.resizeSensor && settings.autoReinitialise && !reinitialiseInterval) { + reinitialiseInterval = setInterval(function () { + initialise(settings); + }, settings.autoReinitialiseDelay); + } else if (!settings.resizeSensor && !settings.autoReinitialise && reinitialiseInterval) { + clearInterval(reinitialiseInterval); + } + if (settings.resizeSensor && !resizeEventsAdded) { + // detect size change in content + detectSizeChanges(pane, reinitialiseFn); - verticalDrag.on( - "mouseenter", - function() - { - verticalDrag.addClass('jspHover'); - } - ).on( - "mouseleave", - function() - { - verticalDrag.removeClass('jspHover'); - } - ).on( - 'mousedown.jsp', - function(e) - { - // Stop IE from allowing text selection - $('html').on('dragstart.jsp selectstart.jsp', nil); + // detect size changes of scroll element + detectSizeChanges(elem, reinitialiseFn); - verticalDrag.addClass('jspActive'); + // detect size changes of container + detectSizeChanges(elem.parent(), reinitialiseFn); - var startY = e.pageY - verticalDrag.position().top; + // add a reinit on window resize also for safety + window.addEventListener('resize', reinitialiseFn); - $('html').on( - 'mousemove.jsp', - function(e) - { - positionDragY(e.pageY - startY, false); - } - ).on('mouseup.jsp mouseleave.jsp', cancelDrag); - return false; - } - ); - sizeVerticalScrollbar(); - } - } - - function sizeVerticalScrollbar() - { - verticalTrack.height(verticalTrackHeight + 'px'); - verticalDragPosition = 0; - scrollbarWidth = settings.verticalGutter + verticalTrack.outerWidth(); - - // Make the pane thinner to allow for the vertical scrollbar - pane.width(paneWidth - scrollbarWidth - originalPaddingTotalWidth); - - // Add margin to the left of the pane if scrollbars are on that side (to position - // the scrollbar on the left or right set it's left or right property in CSS) - try { - if (verticalBar.position().left === 0) { - pane.css('margin-left', scrollbarWidth + 'px'); - } - } catch (err) { - } - } - - function initialiseHorizontalScroll() - { - if (isScrollableH) { - - container.append( - $('').append( - $(''), - $('').append( - $('').append( - $(''), - $('') - ) - ), - $('') - ) - ); + resizeEventsAdded = true; + } - horizontalBar = container.find('>.jspHorizontalBar'); - horizontalTrack = horizontalBar.find('>.jspTrack'); - horizontalDrag = horizontalTrack.find('>.jspDrag'); - - if (settings.showArrows) { - arrowLeft = $('').on( - 'mousedown.jsp', getArrowScroll(-1, 0) - ).on('click.jsp', nil); - arrowRight = $('').on( - 'mousedown.jsp', getArrowScroll(1, 0) - ).on('click.jsp', nil); - if (settings.arrowScrollOnHover) { - arrowLeft.on('mouseover.jsp', getArrowScroll(-1, 0, arrowLeft)); - arrowRight.on('mouseover.jsp', getArrowScroll(1, 0, arrowRight)); - } - appendArrows(horizontalTrack, settings.horizontalArrowPositions, arrowLeft, arrowRight); - } + if (originalScrollTop && elem.scrollTop(0)) { + scrollToY(originalScrollTop, false); + } - horizontalDrag.on( - "mouseenter", - function() - { - horizontalDrag.addClass('jspHover'); - } - ).on( - "mouseleave", - function() - { - horizontalDrag.removeClass('jspHover'); - } - ).on( - 'mousedown.jsp', - function(e) - { - // Stop IE from allowing text selection - $('html').on('dragstart.jsp selectstart.jsp', nil); + if (originalScrollLeft && elem.scrollLeft(0)) { + scrollToX(originalScrollLeft, false); + } - horizontalDrag.addClass('jspActive'); + elem.trigger('jsp-initialised', [isScrollableH || isScrollableV]); + } - var startX = e.pageX - horizontalDrag.position().left; + function detectSizeChanges(element, callback) { + // create resize event elements - based on resize sensor: https://github.com/flowkey/resize-sensor/ + var resizeWidth, resizeHeight; + var resizeElement = document.createElement('div'); + var resizeGrowElement = document.createElement('div'); + var resizeGrowChildElement = document.createElement('div'); + var resizeShrinkElement = document.createElement('div'); + var resizeShrinkChildElement = document.createElement('div'); + + // add necessary styling + resizeElement.style.cssText = + 'position: absolute; left: 0; top: 0; right: 0; bottom: 0; overflow: scroll; z-index: -1; visibility: hidden;'; + resizeGrowElement.style.cssText = + 'position: absolute; left: 0; top: 0; right: 0; bottom: 0; overflow: scroll; z-index: -1; visibility: hidden;'; + resizeShrinkElement.style.cssText = + 'position: absolute; left: 0; top: 0; right: 0; bottom: 0; overflow: scroll; z-index: -1; visibility: hidden;'; + + resizeGrowChildElement.style.cssText = 'position: absolute; left: 0; top: 0;'; + resizeShrinkChildElement.style.cssText = 'position: absolute; left: 0; top: 0; width: 200%; height: 200%;'; + + // Create a function to programmatically update sizes + var updateSizes = function () { + resizeGrowChildElement.style.width = resizeGrowElement.offsetWidth + 10 + 'px'; + resizeGrowChildElement.style.height = resizeGrowElement.offsetHeight + 10 + 'px'; + + resizeGrowElement.scrollLeft = resizeGrowElement.scrollWidth; + resizeGrowElement.scrollTop = resizeGrowElement.scrollHeight; + + resizeShrinkElement.scrollLeft = resizeShrinkElement.scrollWidth; + resizeShrinkElement.scrollTop = resizeShrinkElement.scrollHeight; + + resizeWidth = element.width(); + resizeHeight = element.height(); + }; + + // create functions to call when content grows + var onGrow = function () { + // check to see if the content has change size + if (element.width() > resizeWidth || element.height() > resizeHeight) { + // if size has changed then reinitialise + callback.apply(this, []); + } + // after reinitialising update sizes + updateSizes(); + }; + + // create functions to call when content shrinks + var onShrink = function () { + // check to see if the content has change size + if (element.width() < resizeWidth || element.height() < resizeHeight) { + // if size has changed then reinitialise + callback.apply(this, []); + } + // after reinitialising update sizes + updateSizes(); + }; - $('html').on( - 'mousemove.jsp', - function(e) - { - positionDragX(e.pageX - startX, false); - } - ).on('mouseup.jsp mouseleave.jsp', cancelDrag); - return false; - } - ); - horizontalTrackWidth = container.innerWidth(); - sizeHorizontalScrollbar(); - } - } - - function sizeHorizontalScrollbar() - { - container.find('>.jspHorizontalBar>.jspCap:visible,>.jspHorizontalBar>.jspArrow').each( - function() - { - horizontalTrackWidth -= $(this).outerWidth(); - } - ); - - horizontalTrack.width(horizontalTrackWidth + 'px'); - horizontalDragPosition = 0; - } - - function resizeScrollbars() - { - if (isScrollableH && isScrollableV) { - var horizontalTrackHeight = horizontalTrack.outerHeight(), - verticalTrackWidth = verticalTrack.outerWidth(); - verticalTrackHeight -= horizontalTrackHeight; - $(horizontalBar).find('>.jspCap:visible,>.jspArrow').each( - function() - { - horizontalTrackWidth += $(this).outerWidth(); - } - ); - horizontalTrackWidth -= verticalTrackWidth; - paneHeight -= verticalTrackWidth; - paneWidth -= horizontalTrackHeight; - horizontalTrack.parent().append( - $('').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.css('width', horizontalDragWidth + 'px'); - dragMaxX = horizontalTrackWidth - horizontalDragWidth; - _positionDragX(horizontalDragPosition); // To update the state for the arrow buttons - } - if (isScrollableV) { - verticalDragHeight = Math.ceil(1 / percentInViewV * verticalTrackHeight); - if (verticalDragHeight > settings.verticalDragMaxHeight) { - verticalDragHeight = settings.verticalDragMaxHeight; - } else if (verticalDragHeight < settings.verticalDragMinHeight) { - verticalDragHeight = settings.verticalDragMinHeight; - } - verticalDrag.css('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.on( - eve, - function() - { - arrow.removeClass('jspActive'); - if(scrollTimeout) { - clearTimeout(scrollTimeout); + // bind to scroll events + resizeGrowElement.addEventListener('scroll', onGrow.bind(this)); + resizeShrinkElement.addEventListener('scroll', onShrink.bind(this)); + + // nest elements before adding to pane + resizeGrowElement.appendChild(resizeGrowChildElement); + resizeShrinkElement.appendChild(resizeShrinkChildElement); + + resizeElement.appendChild(resizeGrowElement); + resizeElement.appendChild(resizeShrinkElement); + + element.append(resizeElement); + + // ensure parent element is not statically positioned + if (window.getComputedStyle(element[0], null).getPropertyValue('position') === 'static') { + element[0].style.position = 'relative'; + } + + // update sizes initially + updateSizes(); } - scrollTimeout = null; - ele.off(eve); - } - ); - } - - function initClickOnTrack() - { - removeClickOnTrack(); - if (isScrollableV) { - verticalTrack.on( - '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() - { - if(scrollTimeout) { - clearTimeout(scrollTimeout); + + function initialiseVerticalScroll() { + if (isScrollableV) { + container.append( + $('').append( + $(''), + $('').append( + $('').append( + $(''), + $(''), + ), + ), + $(''), + ), + ); + + verticalBar = container.find('>.jspVerticalBar'); + verticalTrack = verticalBar.find('>.jspTrack'); + verticalDrag = verticalTrack.find('>.jspDrag'); + + if (settings.showArrows) { + arrowUp = $('').on('mousedown.jsp', getArrowScroll(0, -1)).on('click.jsp', nil); + arrowDown = $('') + .on('mousedown.jsp', getArrowScroll(0, 1)) + .on('click.jsp', nil); + if (settings.arrowScrollOnHover) { + arrowUp.on('mouseover.jsp', getArrowScroll(0, -1, arrowUp)); + arrowDown.on('mouseover.jsp', getArrowScroll(0, 1, arrowDown)); + } + + appendArrows(verticalTrack, settings.verticalArrowPositions, arrowUp, arrowDown); } - scrollTimeout = null; - $(document).off('mouseup.jsp', cancelClick); - }; - doScroll(); - $(document).on('mouseup.jsp', cancelClick); - return false; - } - } - ); - } - if (isScrollableH) { - horizontalTrack.on( - '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() - { - if(scrollTimeout) { - clearTimeout(scrollTimeout); + verticalTrackHeight = paneHeight; + container.find('>.jspVerticalBar>.jspCap:visible,>.jspVerticalBar>.jspArrow').each(function () { + verticalTrackHeight -= $(this).outerHeight(); + }); + + verticalDrag + .on('mouseenter', function () { + verticalDrag.addClass('jspHover'); + }) + .on('mouseleave', function () { + verticalDrag.removeClass('jspHover'); + }) + .on('mousedown.jsp', function (e) { + // Stop IE from allowing text selection + $('html').on('dragstart.jsp selectstart.jsp', nil); + + verticalDrag.addClass('jspActive'); + + var startY = e.pageY - verticalDrag.position().top; + + $('html') + .on('mousemove.jsp', function (e) { + positionDragY(e.pageY - startY, false); + }) + .on('mouseup.jsp mouseleave.jsp', cancelDrag); + return false; + }); + sizeVerticalScrollbar(); + } + } + + function sizeVerticalScrollbar() { + verticalTrack.height(verticalTrackHeight + 'px'); + verticalDragPosition = 0; + scrollbarWidth = settings.verticalGutter + verticalTrack.outerWidth(); + + // Make the pane thinner to allow for the vertical scrollbar + pane.width(paneWidth - scrollbarWidth - originalPaddingTotalWidth); + + // Add margin to the left of the pane if scrollbars are on that side (to position + // the scrollbar on the left or right set it's left or right property in CSS) + try { + if (verticalBar.position().left === 0) { + pane.css('margin-left', scrollbarWidth + 'px'); } - scrollTimeout = null; - $(document).off('mouseup.jsp', cancelClick); - }; - doScroll(); - $(document).on('mouseup.jsp', cancelClick); - return false; - } - } - ); - } - } - - function removeClickOnTrack() - { - if (horizontalTrack) { - horizontalTrack.off('mousedown.jsp'); - } - if (verticalTrack) { - verticalTrack.off('mousedown.jsp'); - } - } - - function cancelDrag() - { - $('html').off('dragstart.jsp selectstart.jsp mousemove.jsp mouseup.jsp mouseleave.jsp'); - - if (verticalDrag) { - verticalDrag.removeClass('jspActive'); - } - if (horizontalDrag) { - horizontalDrag.removeClass('jspActive'); - } - } - - function positionDragY(destY, animate) - { - if (!isScrollableV) { - return; - } - if (destY < 0) { - destY = 0; - } else if (destY > dragMaxY) { - destY = dragMaxY; - } - - // allow for devs to prevent the JSP from being scrolled - var willScrollYEvent = new $.Event("jsp-will-scroll-y"); - elem.trigger(willScrollYEvent, [destY]); - - if (willScrollYEvent.isDefaultPrevented()) { - return; - } - - var tmpVerticalDragPosition = destY || 0; - - var isAtTop = tmpVerticalDragPosition === 0, - isAtBottom = tmpVerticalDragPosition == dragMaxY, - percentScrolled = destY/ dragMaxY, - destTop = -percentScrolled * (contentHeight - paneHeight); - - // 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, function() { - elem.trigger('jsp-user-scroll-y', [-destTop, isAtTop, isAtBottom]); - }); - } else { - verticalDrag.css('top', destY + 'px'); - _positionDragY(destY); - elem.trigger('jsp-user-scroll-y', [-destTop, isAtTop, isAtBottom]); - } - - } - - function _positionDragY(destY) - { - if (destY === undefined) { - destY = verticalDrag.position().top; - } - - container.scrollTop(0); - verticalDragPosition = destY || 0; - - 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 + 'px'); - 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; - } - - - // allow for devs to prevent the JSP from being scrolled - var willScrollXEvent = new $.Event("jsp-will-scroll-x"); - elem.trigger(willScrollXEvent, [destX]); - - if (willScrollXEvent.isDefaultPrevented()) { - return; - } - - var tmpHorizontalDragPosition = destX ||0; - - var isAtLeft = tmpHorizontalDragPosition === 0, - isAtRight = tmpHorizontalDragPosition == dragMaxX, - percentScrolled = destX / dragMaxX, - destLeft = -percentScrolled * (contentWidth - paneWidth); - - if (animate === undefined) { - animate = settings.animateScroll; - } - if (animate) { - jsp.animate(horizontalDrag, 'left', destX, _positionDragX, function() { - elem.trigger('jsp-user-scroll-x', [-destLeft, isAtLeft, isAtRight]); - }); - } else { - horizontalDrag.css('left', destX + 'px'); - _positionDragX(destX); - elem.trigger('jsp-user-scroll-x', [-destLeft, isAtLeft, isAtRight]); - } - } - - function _positionDragX(destX) - { - if (destX === undefined) { - destX = horizontalDrag.position().left; - } - - container.scrollTop(0); - horizontalDragPosition = destX ||0; - - var isAtLeft = horizontalDragPosition === 0, - isAtRight = horizontalDragPosition == dragMaxX, - percentScrolled = destX / dragMaxX, - destLeft = -percentScrolled * (contentWidth - paneWidth); - - if (wasAtLeft != isAtLeft || wasAtRight != isAtRight) { - wasAtLeft = isAtLeft; - wasAtRight = isAtRight; - elem.trigger('jsp-arrow-change', [wasAtTop, wasAtBottom, wasAtLeft, wasAtRight]); - } - - updateHorizontalArrows(isAtLeft, isAtRight); - pane.css('left', destLeft + 'px'); - elem.trigger('jsp-scroll-x', [-destLeft, isAtLeft, isAtRight]).trigger('scroll'); - } - - function updateVerticalArrows(isAtTop, isAtBottom) - { - if (settings.showArrows) { - arrowUp[isAtTop ? 'addClass' : 'removeClass']('jspDisabled'); - arrowDown[isAtBottom ? 'addClass' : 'removeClass']('jspDisabled'); - } - } - - function updateHorizontalArrows(isAtLeft, isAtRight) - { - if (settings.showArrows) { - arrowLeft[isAtLeft ? 'addClass' : 'removeClass']('jspDisabled'); - arrowRight[isAtRight ? 'addClass' : 'removeClass']('jspDisabled'); - } - } - - function scrollToY(destY, animate) - { - var percentScrolled = destY / (contentHeight - paneHeight); - positionDragY(percentScrolled * dragMaxY, animate); - } - - function scrollToX(destX, animate) - { - var percentScrolled = destX / (contentWidth - paneWidth); - positionDragX(percentScrolled * dragMaxX, animate); - } - - function scrollToElement(ele, stickToTop, animate) - { - var e, eleHeight, eleWidth, eleTop = 0, eleLeft = 0, viewportTop, viewportLeft, maxVisibleEleTop, maxVisibleEleLeft, destY, destX; - - // Legal hash values aren't necessarily legal jQuery selectors so we need to catch any - // errors from the lookup... - try { - e = $(ele); - } catch (err) { - return; - } - eleHeight = e.outerHeight(); - eleWidth= e.outerWidth(); - - container.scrollTop(0); - container.scrollLeft(0); - - // loop through parents adding the offset top of any elements that are relatively positioned between - // the focused element and the jspPane so we can get the true distance from the top - // of the focused element to the top of the scrollpane... - while (!e.is('.jspPane')) { - eleTop += e.position().top; - eleLeft += e.position().left; - e = e.offsetParent(); - if (/^body|html$/i.test(e[0].nodeName)) { - // we ended up too high in the document structure. Quit! - return; - } - } - - viewportTop = contentPositionY(); - maxVisibleEleTop = viewportTop + paneHeight; - if (eleTop < viewportTop || stickToTop) { // element is above viewport - destY = eleTop - settings.horizontalGutter; - } else if (eleTop + eleHeight > maxVisibleEleTop) { // element is below viewport - destY = eleTop - paneHeight + eleHeight + settings.horizontalGutter; - } - if (!isNaN(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 (!isNaN(destX)) { - scrollToX(destX, animate); - } - - } - - function contentPositionX() - { - return -pane.position().left; - } - - function contentPositionY() - { - return -pane.position().top; - } - - function isCloseToBottom() - { - var scrollableHeight = contentHeight - paneHeight; - - if(settings.maintainPosition == true) - { - return (scrollableHeight >= 20) && (scrollableHeight - contentPositionY() < 10); - } - - return true; - } - - function isCloseToRight() - { - var scrollableWidth = contentWidth - paneWidth; - - if(settings.maintainPosition == true) - { - return (scrollableWidth >= 20) && (scrollableWidth - contentPositionX() < 10); - } - - return true; - } - - function initMousewheel() - { - container.off(mwEvent).on( - mwEvent, - function (event, delta, deltaX, deltaY) { - - if (!horizontalDragPosition) horizontalDragPosition = 0; - if (!verticalDragPosition) verticalDragPosition = 0; - - var dX = horizontalDragPosition, dY = verticalDragPosition, factor = event.deltaFactor || settings.mouseWheelSpeed; - jsp.scrollBy(deltaX * factor, -deltaY * factor, false); - // return true if there was no movement so rest of screen can scroll - return dX == horizontalDragPosition && dY == verticalDragPosition; - } - ); - } - - function removeMousewheel() - { - container.off(mwEvent); - } - - function nil() - { - return false; - } - - function initFocusHandler() - { - pane.find(':input,a').off('focus.jsp').on( - 'focus.jsp', - function(e) - { - scrollToElement(e.target, false); - } - ); - } - - function removeFocusHandler() - { - pane.find(':input,a').off('focus.jsp'); - } - - function initKeyboardNav() - { - var keyDown, elementHasScrolled, validParents = []; - if(isScrollableH) { - validParents.push(horizontalBar[0]); - } + } catch (err) {} + } - if(isScrollableV) { - validParents.push(verticalBar[0]); - } + function initialiseHorizontalScroll() { + if (isScrollableH) { + container.append( + $('').append( + $(''), + $('').append( + $('').append( + $(''), + $(''), + ), + ), + $(''), + ), + ); + + horizontalBar = container.find('>.jspHorizontalBar'); + horizontalTrack = horizontalBar.find('>.jspTrack'); + horizontalDrag = horizontalTrack.find('>.jspDrag'); + + if (settings.showArrows) { + arrowLeft = $('') + .on('mousedown.jsp', getArrowScroll(-1, 0)) + .on('click.jsp', nil); + arrowRight = $('') + .on('mousedown.jsp', getArrowScroll(1, 0)) + .on('click.jsp', nil); + if (settings.arrowScrollOnHover) { + arrowLeft.on('mouseover.jsp', getArrowScroll(-1, 0, arrowLeft)); + arrowRight.on('mouseover.jsp', getArrowScroll(1, 0, arrowRight)); + } + appendArrows(horizontalTrack, settings.horizontalArrowPositions, arrowLeft, arrowRight); + } - // IE also focuses elements that don't have tabindex set. - pane.on( - 'focus.jsp', - function() - { - elem.focus(); - } - ); - - elem.attr('tabindex', 0) - .off('keydown.jsp keypress.jsp') - .on( - 'keydown.jsp', - function(e) - { - if (e.target !== this && !(validParents.length && $(e.target).closest(validParents).length)){ - return; - } - var dX = horizontalDragPosition, dY = verticalDragPosition; - switch(e.keyCode) { - case 40: // down - case 38: // up - case 34: // page down - case 32: // space - case 33: // page up - case 39: // right - case 37: // left - keyDown = e.keyCode; - keyDownHandler(); - break; - case 35: // end - scrollToY(contentHeight - paneHeight); - keyDown = null; - break; - case 36: // home - scrollToY(0); - keyDown = null; - break; - } - - elementHasScrolled = e.keyCode == keyDown && dX != horizontalDragPosition || dY != verticalDragPosition; - return !elementHasScrolled; - } - ).on( - '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(); - } - // If the keypress is not related to the area, ignore it. Fixes problem with inputs inside scrolled area. Copied from line 955. - if (e.target !== this && !(validParents.length && $(e.target).closest(validParents).length)){ - return; - } - return !elementHasScrolled; - } - ); + horizontalDrag + .on('mouseenter', function () { + horizontalDrag.addClass('jspHover'); + }) + .on('mouseleave', function () { + horizontalDrag.removeClass('jspHover'); + }) + .on('mousedown.jsp', function (e) { + // Stop IE from allowing text selection + $('html').on('dragstart.jsp selectstart.jsp', nil); + + horizontalDrag.addClass('jspActive'); + + var startX = e.pageX - horizontalDrag.position().left; + + $('html') + .on('mousemove.jsp', function (e) { + positionDragX(e.pageX - startX, false); + }) + .on('mouseup.jsp mouseleave.jsp', cancelDrag); + return false; + }); + horizontalTrackWidth = container.innerWidth(); + sizeHorizontalScrollbar(); + } + } - if (settings.hideFocus) { - elem.css('outline', 'none'); - if ('hideFocus' in container[0]){ - elem.attr('hideFocus', true); - } - } else { - elem.css('outline', ''); - if ('hideFocus' in container[0]){ - elem.attr('hideFocus', false); - } - } - - function keyDownHandler() - { - var dX = horizontalDragPosition, dY = verticalDragPosition; - switch(keyDown) { - case 40: // down - jsp.scrollByY(settings.keyboardSpeed, false); - break; - case 38: // up - jsp.scrollByY(-settings.keyboardSpeed, false); - break; - case 34: // page down - case 32: // space - jsp.scrollByY(paneHeight * settings.scrollPagePercent, false); - break; - case 33: // page up - jsp.scrollByY(-paneHeight * settings.scrollPagePercent, false); - break; - case 39: // right - jsp.scrollByX(settings.keyboardSpeed, false); - break; - case 37: // left - jsp.scrollByX(-settings.keyboardSpeed, false); - break; - } - - elementHasScrolled = dX != horizontalDragPosition || dY != verticalDragPosition; - return elementHasScrolled; - } - } - - function removeKeyboardNav() - { - elem.attr('tabindex', '-1') - .removeAttr('tabindex') - .off('keydown.jsp keypress.jsp'); - - pane.off('.jsp'); - } - - function observeHash() - { - if (location.hash && location.hash.length > 1) { - var e, - retryInt, - hash = escape(location.hash.substr(1)) // hash must be escaped to prevent XSS - ; - try { - e = $('#' + hash + ', a[name="' + hash + '"]'); - } catch (err) { - return; - } - - if (e.length && pane.find(hash)) { - // nasty workaround but it appears to take a little while before the hash has done its thing - // to the rendered page so we just wait until the container's scrollTop has been messed up. - if (container.scrollTop() === 0) { - retryInt = setInterval( - function() - { - if (container.scrollTop() > 0) { - scrollToElement(e, true); - $(document).scrollTop(container.position().top); - clearInterval(retryInt); - } - }, - 50 - ); - } else { - scrollToElement(e, true); - $(document).scrollTop(container.position().top); - } - } - } - } - - function hijackInternalLinks() - { - // only register the link handler once - if ($(document.body).data('jspHijack')) { - return; - } - - // remember that the handler was bound - $(document.body).data('jspHijack', true); - - // use live handler to also capture newly created links - $(document.body).delegate('a[href*="#"]', 'click', function(event) { - // does the link point to the same page? - // this also takes care of cases with a