/*! * jScrollPane - v2.2.3-rc.1 - 2020-06-26 * http://jscrollpane.kelvinluck.com/ * * Copyright (c) 2014 Kelvin Luck * Copyright (c) 2017-2020 Tuukka Pasanen * Dual licensed under the MIT or GPL licenses. * * SPDX-License-Identifier: MIT * SPDX-License-Identifier: GPL-2.0-or-later */ // Script: jScrollPane - cross browser customisable scrollbars // // Project Home - http://jscrollpane.kelvinluck.com/ // GitHub - http://github.com/vitch/jScrollPane // CND - https://cdnjs.com/libraries/jScrollPane // Source - https://cdnjs.cloudflare.com/ajax/libs/jScrollPane/2.2.1/script/jquery.jscrollpane.min.js // (Minified) - https://cdnjs.cloudflare.com/ajax/libs/jScrollPane/2.2.1/script/jquery.jscrollpane.js // CSS - https://cdnjs.cloudflare.com/ajax/libs/jScrollPane/2.2.1/style/jquery.jscrollpane.css // (Minified) - https://cdnjs.cloudflare.com/ajax/libs/jScrollPane/2.2.1/style/jquery.jscrollpane.min.css // // About: License // // Copyright (c) 2017 Kelvin Luck // Copyright (c) 2017-2020 Tuukka Pasanen // Dual licensed under the MIT or GPL Version 2 licenses. // https://github.com/vitch/jScrollPane/blob/master/MIT-LICENSE.txt // https://github.com/vitch/jScrollPane/blob/master/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 - jQuery 3.x. Although script should work from jQuery 1.1 and up but no promises are made. // Browsers Tested - See jQuery browser support page: https://jquery.com/browser-support/. Only modern // browsers are supported. // // About: Release History // // 2.2.3-rc.1 - (2020-06-26) Fix Github Issue #376 and #377 with jQuery 3.5 and upcoming jQuery 4.0 // Small change with setting stickToBottom. Updated other scripts to // jQuery 3.x. // 2.2.2 - (2020-05-06) Just update NPM dependecies to remove vunerbilities // 2.2.1 - (2018-09-27) No changed applied to release so same as RC1/2 // 2.2.1-rc.2 - (2018-06-14) Sucked NPM release have to make new Release.. this is 2018! // 2.2.1-rc.1 - (2018-06-14) Fixed CSSLint warnings which can lead CSS problems in // production! Please report a issue if this breaks something! // * Merged: // - #360 Register to globally available version of jQuery // 2.2.0 - (2018-05-16) No changes to RC1 // 2.2.0-rc.1 - (2018-04-28) Merged resize sensor to find out size changes of screen and // again little bit tuned this to support more npm goodies. // * Merged: // - #361 Event based reinitialising - Resize Sensor // - #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; 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); /* // Move any margins from the first and last children up to the container so they can still // collapse with neighbouring elements as they would before jScrollPane firstChild = pane.find(':first-child'); lastChild = pane.find(':last-child'); elem.css( { 'margin-top': firstChild.css('margin-top'), 'margin-bottom': lastChild.css('margin-bottom') } ); firstChild.css('margin-top', 0); lastChild.css('margin-bottom', 0); */ } else { elem.css('width', ''); // 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); } if (settings.resizeSensor && !resizeEventsAdded) { // detect size change in content detectSizeChanges(pane, reinitialiseFn); // detect size changes of scroll element detectSizeChanges(elem, reinitialiseFn); // detect size changes of container detectSizeChanges(elem.parent(), reinitialiseFn); // add a reinit on window resize also for safety window.addEventListener('resize', reinitialiseFn); resizeEventsAdded = true; } if (originalScrollTop && elem.scrollTop(0)) { scrollToY(originalScrollTop, false); } if (originalScrollLeft && elem.scrollLeft(0)) { scrollToX(originalScrollLeft, 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( $(''), $(''), ), ), $(''), ), ); 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); } 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'); } } catch (err) {} } 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); } 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(); } } 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); } 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); } 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); } 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]); } if (isScrollableV) { validParents.push(verticalBar[0]); } // 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; }, ); 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