jquery.jscrollpane.js 74 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614
  1. /*!
  2. * jScrollPane - v2.2.1 - 2018-09-27
  3. * http://jscrollpane.kelvinluck.com/
  4. *
  5. * Copyright (c) 2014 Kelvin Luck
  6. * Copyright (c) 2017-2018 Tuukka Pasanen
  7. * Dual licensed under the MIT or GPL licenses.
  8. */
  9. // Script: jScrollPane - cross browser customisable scrollbars
  10. //
  11. // *Version: 2.2.1, Last updated: 2018-09-27*
  12. //
  13. // Project Home - http://jscrollpane.kelvinluck.com/
  14. // GitHub - http://github.com/vitch/jScrollPane
  15. // CND - https://cdnjs.com/libraries/jScrollPane
  16. // Source - https://cdnjs.cloudflare.com/ajax/libs/jScrollPane/2.2.1/script/jquery.jscrollpane.min.js
  17. // (Minified) - https://cdnjs.cloudflare.com/ajax/libs/jScrollPane/2.2.1/script/jquery.jscrollpane.js
  18. // CSS - https://cdnjs.cloudflare.com/ajax/libs/jScrollPane/2.2.1/style/jquery.jscrollpane.css
  19. // (Minified) - https://cdnjs.cloudflare.com/ajax/libs/jScrollPane/2.2.1/style/jquery.jscrollpane.min.css
  20. //
  21. // About: License
  22. //
  23. // Copyright (c) 2017 Kelvin Luck
  24. // Copyright (c) 2017-2018 Tuukka Pasanen
  25. // Dual licensed under the MIT or GPL Version 2 licenses.
  26. // http://jscrollpane.kelvinluck.com/MIT-LICENSE.txt
  27. // http://jscrollpane.kelvinluck.com/GPL-LICENSE.txt
  28. //
  29. // About: Examples
  30. //
  31. // All examples and demos are available through the jScrollPane example site at:
  32. // http://jscrollpane.kelvinluck.com/
  33. //
  34. // About: Support and Testing
  35. //
  36. // This plugin is tested on the browsers below and has been found to work reliably on them. If you run
  37. // into a problem on one of the supported browsers then please visit the support section on the jScrollPane
  38. // website (http://jscrollpane.kelvinluck.com/) for more information on getting support. You are also
  39. // welcome to fork the project on GitHub if you can contribute a fix for a given issue.
  40. //
  41. // jQuery Versions - jQuery 3.x. Although script should work from jQuery 1.1 and up but no promises are made.
  42. // Browsers Tested - See jQuery browser support page: https://jquery.com/browser-support/. Only modern
  43. // browsers are supported.
  44. //
  45. // About: Release History
  46. //
  47. // 2.2.1 - (2018-09-27) No changed applied to release so same as RC1/2
  48. // 2.2.1-rc.2 - (2018-06-14) Sucked NPM release have to make new Release.. this is 2018!
  49. // 2.2.1-rc.1 - (2018-06-14) Fixed CSSLint warnings which can lead CSS problems in
  50. // production! Please report a issue if this breaks something!
  51. // * Merged:
  52. // - #360 Register to globally available version of jQuery
  53. // 2.2.0 - (2018-05-16) No changes to RC1
  54. // 2.2.0-rc.1 - (2018-04-28) Merged resize sensor to find out size changes of screen and
  55. // again little bit tuned this to support more npm goodies.
  56. // * Merged:
  57. // - #361 Event based reinitialising - Resize Sensor
  58. // - #359 Use npm scripts and local dev dependencies to build the project
  59. // 2.1.3 - (2018-04-04) No changes from Release Candidate 2 so making release
  60. // 2.1.3-rc.2 - (2018-03-13) Now using 'script/jquery.jscrollpane.min.js' main
  61. // in package.json rather than 'Gruntfile.js'
  62. // 2.1.3-rc.1 - (2018-03-05) Moving Gruntfile.js to root and example HTML
  63. // to subdirectory examples
  64. // 2.1.2 - (2018-02-16) Just on console.log remove and Release!
  65. // This version should play nicely with NPM
  66. // 2.1.2-rc.2 - (2018-02-03) Update package.json main-tag
  67. // 2.1.2-rc.1 - (2018-01-18) Release on NPM.
  68. // 2.1.1 - (2018-01-12) As everyone stays silent then we just release! No changes from RC.1
  69. // 2.1.1-rc.1 - (2017-12-23) Started to slowly merge stuff (HO HO HO Merry Christmas!)
  70. // * Merged
  71. // - #349 - ScrollPane reinitialization should adapt to changed container size
  72. // - #335 Set drag bar width/height with .css instead of .width/.height
  73. // - #297 added two settings: always show HScroll and VScroll
  74. // * Bugs
  75. // - #8 Make it possible to tell a scrollbar to be "always on"
  76. // 2.1.0 - (2017-12-16) Update jQuery to version 3.x
  77. (function (factory) {
  78. if (typeof define === 'function' && define.amd) {
  79. // AMD. Register as an anonymous module.
  80. define(['jquery'], factory);
  81. } else if (typeof exports === 'object') {
  82. // Node/CommonJS style for Browserify
  83. module.exports = factory(jQuery || require('jquery'));
  84. } else {
  85. // Browser globals
  86. factory(jQuery);
  87. }
  88. }(function ($) {
  89. $.fn.jScrollPane = function (settings) {
  90. // JScrollPane "class" - public methods are available through $('selector').data('jsp')
  91. function JScrollPane(elem, s) {
  92. var settings, jsp = this, pane, paneWidth, paneHeight, container, contentWidth, contentHeight,
  93. percentInViewH, percentInViewV, isScrollableV, isScrollableH, verticalDrag, dragMaxY,
  94. verticalDragPosition, horizontalDrag, dragMaxX, horizontalDragPosition,
  95. verticalBar, verticalTrack, scrollbarWidth, verticalTrackHeight, verticalDragHeight, arrowUp, arrowDown,
  96. horizontalBar, horizontalTrack, horizontalTrackWidth, horizontalDragWidth, arrowLeft, arrowRight,
  97. reinitialiseInterval, originalPadding, originalPaddingTotalWidth, previousContentWidth,
  98. wasAtTop = true, wasAtLeft = true, wasAtBottom = false, wasAtRight = false,
  99. originalElement = elem.clone(false, false).empty(), resizeEventsAdded = false,
  100. mwEvent = $.fn.mwheelIntent ? 'mwheelIntent' : 'mousewheel';
  101. var reinitialiseFn = function () {
  102. // if size has changed then reinitialise
  103. if (settings.resizeSensorDelay > 0) {
  104. setTimeout(function () {
  105. initialise(settings);
  106. }, settings.resizeSensorDelay);
  107. } else {
  108. initialise(settings);
  109. }
  110. };
  111. if (elem.css('box-sizing') === 'border-box') {
  112. originalPadding = 0;
  113. originalPaddingTotalWidth = 0;
  114. } else {
  115. originalPadding = elem.css('paddingTop') + ' ' +
  116. elem.css('paddingRight') + ' ' +
  117. elem.css('paddingBottom') + ' ' +
  118. elem.css('paddingLeft');
  119. originalPaddingTotalWidth = (parseInt(elem.css('paddingLeft'), 10) || 0) +
  120. (parseInt(elem.css('paddingRight'), 10) || 0);
  121. }
  122. function initialise(s) {
  123. var /*firstChild, lastChild, */isMaintainingPositon, lastContentX, lastContentY,
  124. hasContainingSpaceChanged, originalScrollTop, originalScrollLeft,
  125. newPaneWidth, newPaneHeight, maintainAtBottom = false, maintainAtRight = false;
  126. settings = s;
  127. if (pane === undefined) {
  128. originalScrollTop = elem.scrollTop();
  129. originalScrollLeft = elem.scrollLeft();
  130. elem.css(
  131. {
  132. overflow: 'hidden',
  133. padding: 0
  134. }
  135. );
  136. // TODO: Deal with where width/ height is 0 as it probably means the element is hidden and we should
  137. // come back to it later and check once it is unhidden...
  138. paneWidth = elem.outerWidth() + originalPaddingTotalWidth;
  139. paneHeight = elem.outerHeight();
  140. elem.width(paneWidth);
  141. pane = $('<div class="jspPane" />').css('padding', originalPadding).append(elem.children());
  142. container = $('<div class="jspContainer" />')
  143. .css({
  144. 'width': paneWidth + 'px',
  145. 'height': paneHeight + 'px'
  146. }
  147. ).append(pane).appendTo(elem);
  148. /*
  149. // Move any margins from the first and last children up to the container so they can still
  150. // collapse with neighbouring elements as they would before jScrollPane
  151. firstChild = pane.find(':first-child');
  152. lastChild = pane.find(':last-child');
  153. elem.css(
  154. {
  155. 'margin-top': firstChild.css('margin-top'),
  156. 'margin-bottom': lastChild.css('margin-bottom')
  157. }
  158. );
  159. firstChild.css('margin-top', 0);
  160. lastChild.css('margin-bottom', 0);
  161. */
  162. } else {
  163. elem.css('width', '');
  164. // To measure the required dimensions accurately, temporarily override the CSS positioning
  165. // of the container and pane.
  166. container.css({width: 'auto', height: 'auto'});
  167. pane.css('position', 'static');
  168. newPaneWidth = elem.outerWidth() + originalPaddingTotalWidth;
  169. newPaneHeight = elem.outerHeight();
  170. pane.css('position', 'absolute');
  171. maintainAtBottom = settings.stickToBottom && isCloseToBottom();
  172. maintainAtRight = settings.stickToRight && isCloseToRight();
  173. hasContainingSpaceChanged = newPaneWidth !== paneWidth || newPaneHeight !== paneHeight;
  174. paneWidth = newPaneWidth;
  175. paneHeight = newPaneHeight;
  176. container.css({width: paneWidth, height: paneHeight});
  177. // If nothing changed since last check...
  178. if (!hasContainingSpaceChanged && previousContentWidth == contentWidth && pane.outerHeight() == contentHeight) {
  179. elem.width(paneWidth);
  180. return;
  181. }
  182. previousContentWidth = contentWidth;
  183. pane.css('width', '');
  184. elem.width(paneWidth);
  185. container.find('>.jspVerticalBar,>.jspHorizontalBar').remove().end();
  186. }
  187. pane.css('overflow', 'auto');
  188. if (s.contentWidth) {
  189. contentWidth = s.contentWidth;
  190. } else {
  191. contentWidth = pane[0].scrollWidth;
  192. }
  193. contentHeight = pane[0].scrollHeight;
  194. pane.css('overflow', '');
  195. /* Round up in case of rendering errors in Firefox */
  196. percentInViewH = Math.ceil(paneWidth) / Math.ceil(contentWidth);
  197. percentInViewV = Math.ceil(paneHeight) / Math.ceil(contentHeight);
  198. isScrollableV = percentInViewV >= 1 || settings.alwaysShowVScroll;
  199. isScrollableH = percentInViewH >= 1 || settings.alwaysShowHScroll;
  200. if (!(isScrollableH || isScrollableV)) {
  201. elem.removeClass('jspScrollable');
  202. pane.css({
  203. top: 0,
  204. left: 0,
  205. width: container.width() - originalPaddingTotalWidth
  206. });
  207. removeMousewheel();
  208. removeFocusHandler();
  209. removeKeyboardNav();
  210. removeClickOnTrack();
  211. } else {
  212. elem.addClass('jspScrollable');
  213. isMaintainingPositon = settings.maintainPosition && (verticalDragPosition || horizontalDragPosition);
  214. if (isMaintainingPositon) {
  215. lastContentX = contentPositionX();
  216. lastContentY = contentPositionY();
  217. }
  218. initialiseVerticalScroll();
  219. initialiseHorizontalScroll();
  220. resizeScrollbars();
  221. if (isMaintainingPositon) {
  222. scrollToX(maintainAtRight ? (contentWidth - paneWidth) : lastContentX, false);
  223. scrollToY(maintainAtBottom ? (contentHeight - paneHeight) : lastContentY, false);
  224. }
  225. initFocusHandler();
  226. initMousewheel();
  227. initTouch();
  228. if (settings.enableKeyboardNavigation) {
  229. initKeyboardNav();
  230. }
  231. if (settings.clickOnTrack) {
  232. initClickOnTrack();
  233. }
  234. observeHash();
  235. if (settings.hijackInternalLinks) {
  236. hijackInternalLinks();
  237. }
  238. }
  239. /* Remove navigation if we don't need to scroll */
  240. if (percentInViewV >= 1 ) {
  241. removeMousewheel();
  242. removeFocusHandler();
  243. removeKeyboardNav();
  244. removeVerticalClickOnTrack();
  245. }
  246. if (percentInViewH >= 1 ) {
  247. removeHorizontalClickOnTrack();
  248. }
  249. if (!settings.resizeSensor && settings.autoReinitialise && !reinitialiseInterval) {
  250. reinitialiseInterval = setInterval(
  251. function () {
  252. initialise(settings);
  253. },
  254. settings.autoReinitialiseDelay
  255. );
  256. } else if (!settings.resizeSensor && !settings.autoReinitialise && reinitialiseInterval) {
  257. clearInterval(reinitialiseInterval);
  258. }
  259. if (settings.resizeSensor && !resizeEventsAdded) {
  260. // detect size change in content
  261. detectSizeChanges(pane, reinitialiseFn);
  262. // detect size changes of scroll element
  263. detectSizeChanges(elem, reinitialiseFn);
  264. // detect size changes of container
  265. detectSizeChanges(elem.parent(), reinitialiseFn);
  266. // add a reinit on window resize also for safety
  267. window.addEventListener('resize', reinitialiseFn);
  268. resizeEventsAdded = true;
  269. }
  270. if (originalScrollTop && elem.scrollTop(0)) {
  271. scrollToY(originalScrollTop, false);
  272. }
  273. if (originalScrollLeft && elem.scrollLeft(0)) {
  274. scrollToX(originalScrollLeft, false);
  275. }
  276. elem.trigger('jsp-initialised', [isScrollableH || isScrollableV]);
  277. }
  278. function detectSizeChanges(element, callback) {
  279. // create resize event elements - based on resize sensor: https://github.com/flowkey/resize-sensor/
  280. var resizeWidth, resizeHeight;
  281. var resizeElement = document.createElement('div');
  282. var resizeGrowElement = document.createElement('div');
  283. var resizeGrowChildElement = document.createElement('div');
  284. var resizeShrinkElement = document.createElement('div');
  285. var resizeShrinkChildElement = document.createElement('div');
  286. // add necessary styling
  287. resizeElement.style.cssText = 'position: absolute; left: 0; top: 0; right: 0; bottom: 0; overflow: scroll; z-index: -1; visibility: hidden;';
  288. resizeGrowElement.style.cssText = 'position: absolute; left: 0; top: 0; right: 0; bottom: 0; overflow: scroll; z-index: -1; visibility: hidden;';
  289. resizeShrinkElement.style.cssText = 'position: absolute; left: 0; top: 0; right: 0; bottom: 0; overflow: scroll; z-index: -1; visibility: hidden;';
  290. resizeGrowChildElement.style.cssText = 'position: absolute; left: 0; top: 0;';
  291. resizeShrinkChildElement.style.cssText = 'position: absolute; left: 0; top: 0; width: 200%; height: 200%;';
  292. // Create a function to programmatically update sizes
  293. var updateSizes = function () {
  294. resizeGrowChildElement.style.width = resizeGrowElement.offsetWidth + 10 + 'px';
  295. resizeGrowChildElement.style.height = resizeGrowElement.offsetHeight + 10 + 'px';
  296. resizeGrowElement.scrollLeft = resizeGrowElement.scrollWidth;
  297. resizeGrowElement.scrollTop = resizeGrowElement.scrollHeight;
  298. resizeShrinkElement.scrollLeft = resizeShrinkElement.scrollWidth;
  299. resizeShrinkElement.scrollTop = resizeShrinkElement.scrollHeight;
  300. resizeWidth = element.width();
  301. resizeHeight = element.height();
  302. };
  303. // create functions to call when content grows
  304. var onGrow = function () {
  305. // check to see if the content has change size
  306. if (element.width() > resizeWidth || element.height() > resizeHeight) {
  307. // if size has changed then reinitialise
  308. callback.apply(this, []);
  309. }
  310. // after reinitialising update sizes
  311. updateSizes();
  312. };
  313. // create functions to call when content shrinks
  314. var onShrink = function () {
  315. // check to see if the content has change size
  316. if (element.width() < resizeWidth || element.height() < resizeHeight) {
  317. // if size has changed then reinitialise
  318. callback.apply(this, []);
  319. }
  320. // after reinitialising update sizes
  321. updateSizes();
  322. };
  323. // bind to scroll events
  324. resizeGrowElement.addEventListener('scroll', onGrow.bind(this));
  325. resizeShrinkElement.addEventListener('scroll', onShrink.bind(this));
  326. // nest elements before adding to pane
  327. resizeGrowElement.appendChild(resizeGrowChildElement);
  328. resizeShrinkElement.appendChild(resizeShrinkChildElement);
  329. resizeElement.appendChild(resizeGrowElement);
  330. resizeElement.appendChild(resizeShrinkElement);
  331. element.append(resizeElement);
  332. // ensure parent element is not statically positioned
  333. if (window.getComputedStyle(element[0], null).getPropertyValue('position') === 'static') {
  334. element[0].style.position = 'relative';
  335. }
  336. // update sizes initially
  337. updateSizes();
  338. }
  339. function initialiseVerticalScroll() {
  340. if (isScrollableV) {
  341. container.append(
  342. $('<div class="jspVerticalBar" />').append(
  343. $('<div class="jspCap jspCapTop" />'),
  344. $('<div class="jspTrack" />').append(
  345. $('<div class="jspDrag" />').append(
  346. $('<div class="jspDragTop" />'),
  347. $('<div class="jspDragBottom" />')
  348. )
  349. ),
  350. $('<div class="jspCap jspCapBottom" />')
  351. )
  352. );
  353. verticalBar = container.find('>.jspVerticalBar');
  354. verticalTrack = verticalBar.find('>.jspTrack');
  355. verticalDrag = verticalTrack.find('>.jspDrag');
  356. if (percentInViewV >= 1) {
  357. verticalBar.addClass("jspAllInView");
  358. }
  359. if (settings.showArrows) {
  360. arrowUp = $('<a class="jspArrow jspArrowUp" />').on(
  361. 'mousedown.jsp', getArrowScroll(0, -1)
  362. ).on('click.jsp', nil);
  363. arrowDown = $('<a class="jspArrow jspArrowDown" />').on(
  364. 'mousedown.jsp', getArrowScroll(0, 1)
  365. ).on('click.jsp', nil);
  366. if (settings.arrowScrollOnHover) {
  367. arrowUp.on('mouseover.jsp', getArrowScroll(0, -1, arrowUp));
  368. arrowDown.on('mouseover.jsp', getArrowScroll(0, 1, arrowDown));
  369. }
  370. appendArrows(verticalTrack, settings.verticalArrowPositions, arrowUp, arrowDown);
  371. }
  372. verticalTrackHeight = paneHeight;
  373. container.find('>.jspVerticalBar>.jspCap:visible,>.jspVerticalBar>.jspArrow').each(
  374. function () {
  375. verticalTrackHeight -= $(this).outerHeight();
  376. }
  377. );
  378. verticalDrag.on(
  379. "mouseenter",
  380. function () {
  381. verticalDrag.addClass('jspHover');
  382. }
  383. ).on(
  384. "mouseleave",
  385. function () {
  386. verticalDrag.removeClass('jspHover');
  387. }
  388. ).on(
  389. 'mousedown.jsp',
  390. function (e) {
  391. // Stop IE from allowing text selection
  392. $('html').on('dragstart.jsp selectstart.jsp', nil);
  393. verticalDrag.addClass('jspActive');
  394. var startY = e.pageY - verticalDrag.position().top;
  395. $('html').on(
  396. 'mousemove.jsp',
  397. function (e) {
  398. positionDragY(e.pageY - startY, false);
  399. }
  400. ).on('mouseup.jsp mouseleave.jsp', cancelDrag);
  401. return false;
  402. }
  403. );
  404. sizeVerticalScrollbar();
  405. }
  406. }
  407. function sizeVerticalScrollbar() {
  408. verticalTrack.height(verticalTrackHeight + "px");
  409. verticalDragPosition = 0;
  410. scrollbarWidth = settings.verticalGutter + verticalTrack.outerWidth();
  411. // Make the pane thinner to allow for the vertical scrollbar
  412. pane.width(paneWidth - scrollbarWidth - originalPaddingTotalWidth);
  413. // Add margin to the left of the pane if scrollbars are on that side (to position
  414. // the scrollbar on the left or right set it's left or right property in CSS)
  415. try {
  416. if (verticalBar.position().left === 0) {
  417. pane.css('margin-left', scrollbarWidth + 'px');
  418. }
  419. } catch (err) {
  420. }
  421. }
  422. function initialiseHorizontalScroll() {
  423. if (isScrollableH) {
  424. //container.css("height", contentHeight + verticalTrack.width() + 1 + "px");
  425. container.append(
  426. $('<div class="jspHorizontalBar" />').append(
  427. $('<div class="jspCap jspCapLeft" />'),
  428. $('<div class="jspTrack" />').append(
  429. $('<div class="jspDrag" />').append(
  430. $('<div class="jspDragLeft" />'),
  431. $('<div class="jspDragRight" />')
  432. )
  433. ),
  434. $('<div class="jspCap jspCapRight" />')
  435. )
  436. );
  437. horizontalBar = container.find('>.jspHorizontalBar');
  438. horizontalTrack = horizontalBar.find('>.jspTrack');
  439. horizontalDrag = horizontalTrack.find('>.jspDrag');
  440. if (percentInViewH >= 1) {
  441. horizontalBar.addClass("jspAllInView");
  442. }
  443. if (settings.showArrows) {
  444. arrowLeft = $('<a class="jspArrow jspArrowLeft" />').on(
  445. 'mousedown.jsp', getArrowScroll(-1, 0)
  446. ).on('click.jsp', nil);
  447. arrowRight = $('<a class="jspArrow jspArrowRight" />').on(
  448. 'mousedown.jsp', getArrowScroll(1, 0)
  449. ).on('click.jsp', nil);
  450. if (settings.arrowScrollOnHover) {
  451. arrowLeft.on('mouseover.jsp', getArrowScroll(-1, 0, arrowLeft));
  452. arrowRight.on('mouseover.jsp', getArrowScroll(1, 0, arrowRight));
  453. }
  454. appendArrows(horizontalTrack, settings.horizontalArrowPositions, arrowLeft, arrowRight);
  455. }
  456. horizontalDrag.on(
  457. "mouseenter",
  458. function () {
  459. horizontalDrag.addClass('jspHover');
  460. }
  461. ).on(
  462. "mouseleave",
  463. function () {
  464. horizontalDrag.removeClass('jspHover');
  465. }
  466. ).on(
  467. 'mousedown.jsp',
  468. function (e) {
  469. // Stop IE from allowing text selection
  470. $('html').on('dragstart.jsp selectstart.jsp', nil);
  471. horizontalDrag.addClass('jspActive');
  472. var startX = e.pageX - horizontalDrag.position().left;
  473. $('html').on(
  474. 'mousemove.jsp',
  475. function (e) {
  476. positionDragX(e.pageX - startX, false);
  477. }
  478. ).on('mouseup.jsp mouseleave.jsp', cancelDrag);
  479. return false;
  480. }
  481. );
  482. horizontalTrackWidth = container.innerWidth();
  483. sizeHorizontalScrollbar();
  484. }
  485. }
  486. function sizeHorizontalScrollbar() {
  487. container.find('>.jspHorizontalBar>.jspCap:visible,>.jspHorizontalBar>.jspArrow').each(
  488. function () {
  489. horizontalTrackWidth -= $(this).outerWidth();
  490. }
  491. );
  492. horizontalTrack.width(horizontalTrackWidth + 'px');
  493. horizontalDragPosition = 0;
  494. }
  495. function resizeScrollbars() {
  496. if (isScrollableH && isScrollableV) {
  497. var horizontalTrackHeight = horizontalTrack.outerHeight(),
  498. verticalTrackWidth = verticalTrack.outerWidth();
  499. verticalTrackHeight -= horizontalTrackHeight;
  500. $(horizontalBar).find('>.jspCap:visible,>.jspArrow').each(
  501. function () {
  502. horizontalTrackWidth += $(this).outerWidth();
  503. }
  504. );
  505. horizontalTrackWidth -= verticalTrackWidth;
  506. paneHeight -= verticalTrackWidth;
  507. paneWidth -= horizontalTrackHeight;
  508. horizontalTrack.parent().append(
  509. $('<div class="jspCorner" />').css('width', horizontalTrackHeight + 'px')
  510. );
  511. sizeVerticalScrollbar();
  512. sizeHorizontalScrollbar();
  513. }
  514. // reflow content
  515. if (isScrollableH) {
  516. pane.width((container.outerWidth() - originalPaddingTotalWidth) + 'px');
  517. }
  518. contentHeight = pane.outerHeight();
  519. percentInViewV = paneHeight / (contentHeight - horizontalTrackHeight);
  520. percentInViewH = container.outerWidth() / contentWidth;
  521. if (isScrollableH) {
  522. horizontalDragWidth = Math.ceil(percentInViewH * horizontalTrackWidth);
  523. if (horizontalDragWidth > settings.horizontalDragMaxWidth) {
  524. horizontalDragWidth = settings.horizontalDragMaxWidth;
  525. } else if (horizontalDragWidth < settings.horizontalDragMinWidth) {
  526. horizontalDragWidth = settings.horizontalDragMinWidth;
  527. }
  528. horizontalDrag.css('width', horizontalDragWidth + 'px');
  529. dragMaxX = horizontalTrackWidth - horizontalDragWidth;
  530. _positionDragX(horizontalDragPosition); // To update the state for the arrow buttons
  531. }
  532. if (isScrollableV) {
  533. verticalDragHeight = Math.ceil(percentInViewV * verticalTrackHeight);
  534. if (verticalDragHeight > settings.verticalDragMaxHeight) {
  535. verticalDragHeight = settings.verticalDragMaxHeight;
  536. } else if (verticalDragHeight < settings.verticalDragMinHeight) {
  537. verticalDragHeight = settings.verticalDragMinHeight;
  538. }
  539. verticalDrag.css('height', verticalDragHeight + 'px');
  540. dragMaxY = verticalTrackHeight - verticalDragHeight;
  541. _positionDragY(verticalDragPosition); // To update the state for the arrow buttons
  542. }
  543. }
  544. function appendArrows(ele, p, a1, a2) {
  545. var p1 = "before", p2 = "after", aTemp;
  546. // Sniff for mac... Is there a better way to determine whether the arrows would naturally appear
  547. // at the top or the bottom of the bar?
  548. if (p == "os") {
  549. p = /Mac/.test(navigator.platform) ? "after" : "split";
  550. }
  551. if (p == p1) {
  552. p2 = p;
  553. } else if (p == p2) {
  554. p1 = p;
  555. aTemp = a1;
  556. a1 = a2;
  557. a2 = aTemp;
  558. }
  559. ele[p1](a1)[p2](a2);
  560. }
  561. function getArrowScroll(dirX, dirY, ele) {
  562. return function () {
  563. arrowScroll(dirX, dirY, this, ele);
  564. this.blur();
  565. return false;
  566. };
  567. }
  568. function arrowScroll(dirX, dirY, arrow, ele) {
  569. if (dirX !== 0 && percentInViewH >= 1) return;
  570. if (dirY !== 0 && percentInViewV >= 1) return;
  571. arrow = $(arrow).addClass('jspActive');
  572. var eve,
  573. scrollTimeout,
  574. isFirst = true,
  575. doScroll = function () {
  576. if (dirX !== 0) {
  577. jsp.scrollByX(dirX * settings.arrowButtonSpeed);
  578. }
  579. if (dirY !== 0) {
  580. jsp.scrollByY(dirY * settings.arrowButtonSpeed);
  581. }
  582. scrollTimeout = setTimeout(doScroll, isFirst ? settings.initialDelay : settings.arrowRepeatFreq);
  583. isFirst = false;
  584. };
  585. doScroll();
  586. eve = ele ? 'mouseout.jsp' : 'mouseup.jsp';
  587. ele = ele || $('html');
  588. ele.on(
  589. eve,
  590. function () {
  591. arrow.removeClass('jspActive');
  592. if (scrollTimeout) {
  593. clearTimeout(scrollTimeout);
  594. }
  595. scrollTimeout = null;
  596. ele.off(eve);
  597. }
  598. );
  599. }
  600. function initClickOnTrack() {
  601. removeClickOnTrack();
  602. if (isScrollableV) {
  603. verticalTrack.on(
  604. 'mousedown.jsp',
  605. function (e) {
  606. if (e.originalTarget === undefined || e.originalTarget == e.currentTarget) {
  607. var clickedTrack = $(this),
  608. offset = clickedTrack.offset(),
  609. direction = e.pageY - offset.top - verticalDragPosition,
  610. scrollTimeout,
  611. isFirst = true,
  612. doScroll = function () {
  613. var offset = clickedTrack.offset(),
  614. pos = e.pageY - offset.top - verticalDragHeight / 2,
  615. contentDragY = paneHeight * settings.scrollPagePercent,
  616. dragY = dragMaxY * contentDragY / (contentHeight - paneHeight);
  617. if (clickedTrack.hasClass("disabled")) return;
  618. if (direction < 0) {
  619. if (verticalDragPosition - dragY > pos) {
  620. jsp.scrollByY(-contentDragY);
  621. } else {
  622. positionDragY(pos);
  623. }
  624. } else if (direction > 0) {
  625. if (verticalDragPosition + dragY < pos) {
  626. jsp.scrollByY(contentDragY);
  627. } else {
  628. positionDragY(pos);
  629. }
  630. } else {
  631. cancelClick();
  632. return;
  633. }
  634. scrollTimeout = setTimeout(doScroll, isFirst ? settings.initialDelay : settings.trackClickRepeatFreq);
  635. isFirst = false;
  636. },
  637. cancelClick = function () {
  638. if (scrollTimeout) {
  639. clearTimeout(scrollTimeout);
  640. }
  641. scrollTimeout = null;
  642. $(document).off('mouseup.jsp', cancelClick);
  643. };
  644. if (!clickedTrack.hasClass("disabled")) {
  645. doScroll();
  646. $(document).on('mouseup.jsp', cancelClick);
  647. return false;
  648. }
  649. }
  650. }
  651. );
  652. }
  653. if (isScrollableH) {
  654. horizontalTrack.on(
  655. 'mousedown.jsp',
  656. function (e) {
  657. if (e.originalTarget === undefined || e.originalTarget == e.currentTarget) {
  658. var clickedTrack = $(this),
  659. offset = clickedTrack.offset(),
  660. direction = e.pageX - offset.left - horizontalDragPosition,
  661. scrollTimeout,
  662. isFirst = true,
  663. doScroll = function () {
  664. var offset = clickedTrack.offset(),
  665. pos = e.pageX - offset.left - horizontalDragWidth / 2,
  666. contentDragX = paneWidth * settings.scrollPagePercent,
  667. dragX = dragMaxX * contentDragX / (contentWidth - paneWidth);
  668. if (direction < 0) {
  669. if (horizontalDragPosition - dragX > pos) {
  670. jsp.scrollByX(-contentDragX);
  671. } else {
  672. positionDragX(pos);
  673. }
  674. } else if (direction > 0) {
  675. if (horizontalDragPosition + dragX < pos) {
  676. jsp.scrollByX(contentDragX);
  677. } else {
  678. positionDragX(pos);
  679. }
  680. } else {
  681. cancelClick();
  682. return;
  683. }
  684. scrollTimeout = setTimeout(doScroll, isFirst ? settings.initialDelay : settings.trackClickRepeatFreq);
  685. isFirst = false;
  686. },
  687. cancelClick = function () {
  688. if (scrollTimeout) {
  689. clearTimeout(scrollTimeout);
  690. }
  691. scrollTimeout = null;
  692. $(document).off('mouseup.jsp', cancelClick);
  693. };
  694. if (!clickedTrack.hasClass("disabled")) {
  695. doScroll();
  696. $(document).on('mouseup.jsp', cancelClick);
  697. return false;
  698. }
  699. }
  700. }
  701. );
  702. }
  703. }
  704. function removeClickOnTrack() {
  705. removeHorizontalClickOnTrack();
  706. removeVerticalClickOnTrack();
  707. }
  708. function removeHorizontalClickOnTrack() {
  709. if (horizontalTrack) {
  710. horizontalTrack.off('mousedown.jsp');
  711. }
  712. }
  713. function removeVerticalClickOnTrack() {
  714. if (verticalTrack) {
  715. verticalTrack.off('mousedown.jsp');
  716. }
  717. }
  718. function cancelDrag() {
  719. $('html').off('dragstart.jsp selectstart.jsp mousemove.jsp mouseup.jsp mouseleave.jsp');
  720. if (verticalDrag) {
  721. verticalDrag.removeClass('jspActive');
  722. }
  723. if (horizontalDrag) {
  724. horizontalDrag.removeClass('jspActive');
  725. }
  726. }
  727. function positionDragY(destY, animate) {
  728. if (!isScrollableV) {
  729. return;
  730. }
  731. if (destY < 0) {
  732. destY = 0;
  733. } else if (destY > dragMaxY) {
  734. destY = dragMaxY;
  735. }
  736. // allow for devs to prevent the JSP from being scrolled
  737. var willScrollYEvent = new $.Event("jsp-will-scroll-y");
  738. elem.trigger(willScrollYEvent, [destY]);
  739. if (willScrollYEvent.isDefaultPrevented()) {
  740. return;
  741. }
  742. var tmpVerticalDragPosition = destY || 0;
  743. var isAtTop = tmpVerticalDragPosition === 0,
  744. isAtBottom = tmpVerticalDragPosition == dragMaxY,
  745. percentScrolled = destY / dragMaxY,
  746. destTop = -percentScrolled * (contentHeight - paneHeight);
  747. // can't just check if(animate) because false is a valid value that could be passed in...
  748. if (animate === undefined) {
  749. animate = settings.animateScroll;
  750. }
  751. if (animate) {
  752. jsp.animate(verticalDrag, 'top', destY, _positionDragY, function () {
  753. elem.trigger('jsp-user-scroll-y', [-destTop, isAtTop, isAtBottom]);
  754. });
  755. } else {
  756. verticalDrag.css('top', destY);
  757. _positionDragY(destY);
  758. elem.trigger('jsp-user-scroll-y', [-destTop, isAtTop, isAtBottom]);
  759. }
  760. }
  761. function _positionDragY(destY) {
  762. if (destY === undefined) {
  763. destY = verticalDrag.position().top;
  764. }
  765. container.scrollTop(0);
  766. verticalDragPosition = destY || 0;
  767. var isAtTop = verticalDragPosition === 0,
  768. isAtBottom = verticalDragPosition == dragMaxY,
  769. percentScrolled = destY / dragMaxY,
  770. destTop = -percentScrolled * (contentHeight - paneHeight);
  771. if (wasAtTop != isAtTop || wasAtBottom != isAtBottom) {
  772. wasAtTop = isAtTop;
  773. wasAtBottom = isAtBottom;
  774. elem.trigger('jsp-arrow-change', [wasAtTop, wasAtBottom, wasAtLeft, wasAtRight]);
  775. }
  776. updateVerticalArrows(isAtTop, isAtBottom);
  777. pane.css('top', destTop);
  778. elem.trigger('jsp-scroll-y', [-destTop, isAtTop, isAtBottom]).trigger('scroll');
  779. }
  780. function positionDragX(destX, animate) {
  781. if (!isScrollableH) {
  782. return;
  783. }
  784. if (destX < 0) {
  785. destX = 0;
  786. } else if (destX > dragMaxX) {
  787. destX = dragMaxX;
  788. }
  789. // allow for devs to prevent the JSP from being scrolled
  790. var willScrollXEvent = new $.Event("jsp-will-scroll-x");
  791. elem.trigger(willScrollXEvent, [destX]);
  792. if (willScrollXEvent.isDefaultPrevented()) {
  793. return;
  794. }
  795. var tmpHorizontalDragPosition = destX || 0;
  796. var isAtLeft = tmpHorizontalDragPosition === 0,
  797. isAtRight = tmpHorizontalDragPosition == dragMaxX,
  798. percentScrolled = destX / dragMaxX,
  799. destLeft = -percentScrolled * (contentWidth - paneWidth);
  800. if (animate === undefined) {
  801. animate = settings.animateScroll;
  802. }
  803. if (animate) {
  804. jsp.animate(horizontalDrag, 'left', destX, _positionDragX, function () {
  805. elem.trigger('jsp-user-scroll-x', [-destLeft, isAtLeft, isAtRight]);
  806. });
  807. } else {
  808. horizontalDrag.css('left', destX);
  809. _positionDragX(destX);
  810. elem.trigger('jsp-user-scroll-x', [-destLeft, isAtLeft, isAtRight]);
  811. }
  812. }
  813. function _positionDragX(destX) {
  814. if (destX === undefined) {
  815. destX = horizontalDrag.position().left;
  816. }
  817. container.scrollTop(0);
  818. horizontalDragPosition = destX || 0;
  819. var isAtLeft = horizontalDragPosition === 0,
  820. isAtRight = horizontalDragPosition == dragMaxX,
  821. percentScrolled = destX / dragMaxX,
  822. destLeft = -percentScrolled * (contentWidth - paneWidth);
  823. if (wasAtLeft != isAtLeft || wasAtRight != isAtRight) {
  824. wasAtLeft = isAtLeft;
  825. wasAtRight = isAtRight;
  826. elem.trigger('jsp-arrow-change', [wasAtTop, wasAtBottom, wasAtLeft, wasAtRight]);
  827. }
  828. updateHorizontalArrows(isAtLeft, isAtRight);
  829. pane.css('left', destLeft);
  830. elem.trigger('jsp-scroll-x', [-destLeft, isAtLeft, isAtRight]).trigger('scroll');
  831. }
  832. function updateVerticalArrows(isAtTop, isAtBottom) {
  833. if (settings.showArrows) {
  834. arrowUp[isAtTop ? 'addClass' : 'removeClass']('jspDisabled');
  835. arrowDown[isAtBottom ? 'addClass' : 'removeClass']('jspDisabled');
  836. }
  837. }
  838. function updateHorizontalArrows(isAtLeft, isAtRight) {
  839. if (settings.showArrows) {
  840. arrowLeft[isAtLeft ? 'addClass' : 'removeClass']('jspDisabled');
  841. arrowRight[isAtRight ? 'addClass' : 'removeClass']('jspDisabled');
  842. }
  843. }
  844. function scrollToY(destY, animate) {
  845. var percentScrolled = destY / (contentHeight - paneHeight);
  846. positionDragY(percentScrolled * dragMaxY, animate);
  847. }
  848. function scrollToX(destX, animate) {
  849. var percentScrolled = destX / (contentWidth - paneWidth);
  850. positionDragX(percentScrolled * dragMaxX, animate);
  851. }
  852. function scrollToElement(ele, stickToTop, animate) {
  853. var e, eleHeight, eleWidth, eleTop = 0, eleLeft = 0, viewportTop, viewportLeft, maxVisibleEleTop,
  854. maxVisibleEleLeft, destY, destX;
  855. // Legal hash values aren't necessarily legal jQuery selectors so we need to catch any
  856. // errors from the lookup...
  857. try {
  858. e = $(ele);
  859. } catch (err) {
  860. return;
  861. }
  862. eleHeight = e.outerHeight();
  863. eleWidth = e.outerWidth();
  864. container.scrollTop(0);
  865. container.scrollLeft(0);
  866. // loop through parents adding the offset top of any elements that are relatively positioned between
  867. // the focused element and the jspPane so we can get the true distance from the top
  868. // of the focused element to the top of the scrollpane...
  869. while (!e.is('.jspPane')) {
  870. eleTop += e.position().top;
  871. eleLeft += e.position().left;
  872. e = e.offsetParent();
  873. if (/^body|html$/i.test(e[0].nodeName)) {
  874. // we ended up too high in the document structure. Quit!
  875. return;
  876. }
  877. }
  878. viewportTop = contentPositionY();
  879. maxVisibleEleTop = viewportTop + paneHeight;
  880. if (eleTop < viewportTop || stickToTop) { // element is above viewport
  881. destY = eleTop - settings.horizontalGutter;
  882. } else if (eleTop + eleHeight > maxVisibleEleTop) { // element is below viewport
  883. destY = eleTop - paneHeight + eleHeight + settings.horizontalGutter;
  884. }
  885. if (!isNaN(destY)) {
  886. scrollToY(destY, animate);
  887. }
  888. viewportLeft = contentPositionX();
  889. maxVisibleEleLeft = viewportLeft + paneWidth;
  890. if (eleLeft < viewportLeft || stickToTop) { // element is to the left of viewport
  891. destX = eleLeft - settings.horizontalGutter;
  892. } else if (eleLeft + eleWidth > maxVisibleEleLeft) { // element is to the right viewport
  893. destX = eleLeft - paneWidth + eleWidth + settings.horizontalGutter;
  894. }
  895. if (!isNaN(destX)) {
  896. scrollToX(destX, animate);
  897. }
  898. }
  899. function contentPositionX() {
  900. return -pane.position().left;
  901. }
  902. function contentPositionY() {
  903. return -pane.position().top;
  904. }
  905. function isCloseToBottom() {
  906. var scrollableHeight = contentHeight - paneHeight;
  907. return (scrollableHeight > 20) && (scrollableHeight - contentPositionY() < 10);
  908. }
  909. function isCloseToRight() {
  910. var scrollableWidth = contentWidth - paneWidth;
  911. return (scrollableWidth > 20) && (scrollableWidth - contentPositionX() < 10);
  912. }
  913. function initMousewheel() {
  914. container.off(mwEvent).on(
  915. mwEvent,
  916. function (event, delta, deltaX, deltaY) {
  917. if (!horizontalDragPosition) horizontalDragPosition = 0;
  918. if (!verticalDragPosition) verticalDragPosition = 0;
  919. var dX = horizontalDragPosition, dY = verticalDragPosition,
  920. factor = event.deltaFactor || settings.mouseWheelSpeed;
  921. jsp.scrollBy(deltaX * factor, -deltaY * factor, false);
  922. // return true if there was no movement so rest of screen can scroll
  923. return dX == horizontalDragPosition && dY == verticalDragPosition;
  924. }
  925. );
  926. }
  927. function removeMousewheel() {
  928. container.off(mwEvent);
  929. }
  930. function nil() {
  931. return false;
  932. }
  933. function initFocusHandler() {
  934. pane.find(':input,a').off('focus.jsp').on(
  935. 'focus.jsp',
  936. function (e) {
  937. scrollToElement(e.target, false);
  938. }
  939. );
  940. }
  941. function removeFocusHandler() {
  942. pane.find(':input,a').off('focus.jsp');
  943. }
  944. function initKeyboardNav() {
  945. var keyDown, elementHasScrolled, validParents = [];
  946. if (isScrollableH) {
  947. validParents.push(horizontalBar[0]);
  948. }
  949. if (isScrollableV) {
  950. validParents.push(verticalBar[0]);
  951. }
  952. // IE also focuses elements that don't have tabindex set.
  953. pane.on(
  954. 'focus.jsp',
  955. function () {
  956. elem.focus();
  957. }
  958. );
  959. elem.attr('tabindex', 0)
  960. .off('keydown.jsp keypress.jsp')
  961. .on(
  962. 'keydown.jsp',
  963. function (e) {
  964. if (e.target !== this && !(validParents.length && $(e.target).closest(validParents).length)) {
  965. return;
  966. }
  967. var dX = horizontalDragPosition, dY = verticalDragPosition;
  968. switch (e.keyCode) {
  969. case 40: // down
  970. case 38: // up
  971. case 34: // page down
  972. case 32: // space
  973. case 33: // page up
  974. case 39: // right
  975. case 37: // left
  976. keyDown = e.keyCode;
  977. keyDownHandler();
  978. break;
  979. case 35: // end
  980. scrollToY(contentHeight - paneHeight);
  981. keyDown = null;
  982. break;
  983. case 36: // home
  984. scrollToY(0);
  985. keyDown = null;
  986. break;
  987. }
  988. elementHasScrolled = e.keyCode == keyDown && dX != horizontalDragPosition || dY != verticalDragPosition;
  989. return !elementHasScrolled;
  990. }
  991. ).on(
  992. 'keypress.jsp', // For FF/ OSX so that we can cancel the repeat key presses if the JSP scrolls...
  993. function (e) {
  994. if (e.keyCode == keyDown) {
  995. keyDownHandler();
  996. }
  997. // If the keypress is not related to the area, ignore it. Fixes problem with inputs inside scrolled area. Copied from line 955.
  998. if (e.target !== this && !(validParents.length && $(e.target).closest(validParents).length)) {
  999. return;
  1000. }
  1001. return !elementHasScrolled;
  1002. }
  1003. );
  1004. if (settings.hideFocus) {
  1005. elem.css('outline', 'none');
  1006. if ('hideFocus' in container[0]) {
  1007. elem.attr('hideFocus', true);
  1008. }
  1009. } else {
  1010. elem.css('outline', '');
  1011. if ('hideFocus' in container[0]) {
  1012. elem.attr('hideFocus', false);
  1013. }
  1014. }
  1015. function keyDownHandler() {
  1016. var dX = horizontalDragPosition, dY = verticalDragPosition;
  1017. switch (keyDown) {
  1018. case 40: // down
  1019. jsp.scrollByY(settings.keyboardSpeed, false);
  1020. break;
  1021. case 38: // up
  1022. jsp.scrollByY(-settings.keyboardSpeed, false);
  1023. break;
  1024. case 34: // page down
  1025. case 32: // space
  1026. jsp.scrollByY(paneHeight * settings.scrollPagePercent, false);
  1027. break;
  1028. case 33: // page up
  1029. jsp.scrollByY(-paneHeight * settings.scrollPagePercent, false);
  1030. break;
  1031. case 39: // right
  1032. jsp.scrollByX(settings.keyboardSpeed, false);
  1033. break;
  1034. case 37: // left
  1035. jsp.scrollByX(-settings.keyboardSpeed, false);
  1036. break;
  1037. }
  1038. elementHasScrolled = dX != horizontalDragPosition || dY != verticalDragPosition;
  1039. return elementHasScrolled;
  1040. }
  1041. }
  1042. function removeKeyboardNav() {
  1043. elem.attr('tabindex', '-1')
  1044. .removeAttr('tabindex')
  1045. .off('keydown.jsp keypress.jsp');
  1046. pane.off('.jsp');
  1047. }
  1048. function observeHash() {
  1049. if (location.hash && location.hash.length > 1) {
  1050. var e,
  1051. retryInt,
  1052. hash = escape(location.hash.substr(1)) // hash must be escaped to prevent XSS
  1053. ;
  1054. try {
  1055. e = $('#' + hash + ', a[name="' + hash + '"]');
  1056. } catch (err) {
  1057. return;
  1058. }
  1059. if (e.length && pane.find(hash)) {
  1060. // nasty workaround but it appears to take a little while before the hash has done its thing
  1061. // to the rendered page so we just wait until the container's scrollTop has been messed up.
  1062. if (container.scrollTop() === 0) {
  1063. retryInt = setInterval(
  1064. function () {
  1065. if (container.scrollTop() > 0) {
  1066. scrollToElement(e, true);
  1067. $(document).scrollTop(container.position().top);
  1068. clearInterval(retryInt);
  1069. }
  1070. },
  1071. 50
  1072. );
  1073. } else {
  1074. scrollToElement(e, true);
  1075. $(document).scrollTop(container.position().top);
  1076. }
  1077. }
  1078. }
  1079. }
  1080. function hijackInternalLinks() {
  1081. // only register the link handler once
  1082. if ($(document.body).data('jspHijack')) {
  1083. return;
  1084. }
  1085. // remember that the handler was bound
  1086. $(document.body).data('jspHijack', true);
  1087. // use live handler to also capture newly created links
  1088. $(document.body).delegate('a[href*="#"]', 'click', function (event) {
  1089. // does the link point to the same page?
  1090. // this also takes care of cases with a <base>-Tag or Links not starting with the hash #
  1091. // e.g. <a href="index.html#test"> when the current url already is index.html
  1092. var href = this.href.substr(0, this.href.indexOf('#')),
  1093. locationHref = location.href,
  1094. hash,
  1095. element,
  1096. container,
  1097. jsp,
  1098. scrollTop,
  1099. elementTop;
  1100. if (location.href.indexOf('#') !== -1) {
  1101. locationHref = location.href.substr(0, location.href.indexOf('#'));
  1102. }
  1103. if (href !== locationHref) {
  1104. // the link points to another page
  1105. return;
  1106. }
  1107. // check if jScrollPane should handle this click event
  1108. hash = escape(this.href.substr(this.href.indexOf('#') + 1));
  1109. // find the element on the page
  1110. try {
  1111. element = $('#' + hash + ', a[name="' + hash + '"]');
  1112. } catch (e) {
  1113. // hash is not a valid jQuery identifier
  1114. return;
  1115. }
  1116. if (!element.length) {
  1117. // this link does not point to an element on this page
  1118. return;
  1119. }
  1120. container = element.closest('.jspScrollable');
  1121. jsp = container.data('jsp');
  1122. // jsp might be another jsp instance than the one, that bound this event
  1123. // remember: this event is only bound once for all instances.
  1124. jsp.scrollToElement(element, true);
  1125. if (container[0].scrollIntoView) {
  1126. // also scroll to the top of the container (if it is not visible)
  1127. scrollTop = $(window).scrollTop();
  1128. elementTop = element.offset().top;
  1129. if (elementTop < scrollTop || elementTop > scrollTop + $(window).height()) {
  1130. container[0].scrollIntoView();
  1131. }
  1132. }
  1133. // jsp handled this event, prevent the browser default (scrolling :P)
  1134. event.preventDefault();
  1135. });
  1136. }
  1137. // Init touch on iPad, iPhone, iPod, Android
  1138. function initTouch() {
  1139. var startX,
  1140. startY,
  1141. touchStartX,
  1142. touchStartY,
  1143. moved,
  1144. moving = false;
  1145. container.off('touchstart.jsp touchmove.jsp touchend.jsp click.jsp-touchclick').on(
  1146. 'touchstart.jsp',
  1147. function (e) {
  1148. var touch = e.originalEvent.touches[0];
  1149. startX = contentPositionX();
  1150. startY = contentPositionY();
  1151. touchStartX = touch.pageX;
  1152. touchStartY = touch.pageY;
  1153. moved = false;
  1154. moving = true;
  1155. }
  1156. ).on(
  1157. 'touchmove.jsp',
  1158. function (ev) {
  1159. if (!moving) {
  1160. return;
  1161. }
  1162. var touchPos = ev.originalEvent.touches[0],
  1163. dX = horizontalDragPosition, dY = verticalDragPosition;
  1164. jsp.scrollTo(startX + touchStartX - touchPos.pageX, startY + touchStartY - touchPos.pageY);
  1165. moved = moved || Math.abs(touchStartX - touchPos.pageX) > 5 || Math.abs(touchStartY - touchPos.pageY) > 5;
  1166. // return true if there was no movement so rest of screen can scroll
  1167. return dX == horizontalDragPosition && dY == verticalDragPosition;
  1168. }
  1169. ).on(
  1170. 'touchend.jsp',
  1171. function (e) {
  1172. moving = false;
  1173. /*if(moved) {
  1174. return false;
  1175. }*/
  1176. }
  1177. ).on(
  1178. 'click.jsp-touchclick',
  1179. function (e) {
  1180. if (moved) {
  1181. moved = false;
  1182. return false;
  1183. }
  1184. }
  1185. );
  1186. }
  1187. function destroy() {
  1188. var currentY = contentPositionY(),
  1189. currentX = contentPositionX();
  1190. elem.removeClass('jspScrollable').off('.jsp');
  1191. pane.off('.jsp');
  1192. elem.replaceWith(originalElement.append(pane.children()));
  1193. originalElement.scrollTop(currentY);
  1194. originalElement.scrollLeft(currentX);
  1195. // clear reinitialize timer if active
  1196. if (reinitialiseInterval) {
  1197. clearInterval(reinitialiseInterval);
  1198. }
  1199. }
  1200. // Public API
  1201. $.extend(
  1202. jsp,
  1203. {
  1204. // Reinitialises the scroll pane (if it's internal dimensions have changed since the last time it
  1205. // was initialised). The settings object which is passed in will override any settings from the
  1206. // previous time it was initialised - if you don't pass any settings then the ones from the previous
  1207. // initialisation will be used.
  1208. reinitialise: function (s) {
  1209. s = $.extend({}, settings, s);
  1210. initialise(s);
  1211. },
  1212. // Scrolls the specified element (a jQuery object, DOM node or jQuery selector string) into view so
  1213. // that it can be seen within the viewport. If stickToTop is true then the element will appear at
  1214. // the top of the viewport, if it is false then the viewport will scroll as little as possible to
  1215. // show the element. You can also specify if you want animation to occur. If you don't provide this
  1216. // argument then the animateScroll value from the settings object is used instead.
  1217. scrollToElement: function (ele, stickToTop, animate) {
  1218. scrollToElement(ele, stickToTop, animate);
  1219. },
  1220. // Scrolls the pane so that the specified co-ordinates within the content are at the top left
  1221. // of the viewport. animate is optional and if not passed then the value of animateScroll from
  1222. // the settings object this jScrollPane was initialised with is used.
  1223. scrollTo: function (destX, destY, animate) {
  1224. scrollToX(destX, animate);
  1225. scrollToY(destY, animate);
  1226. },
  1227. // Scrolls the pane so that the specified co-ordinate within the content is at the left of the
  1228. // viewport. animate is optional and if not passed then the value of animateScroll from the settings
  1229. // object this jScrollPane was initialised with is used.
  1230. scrollToX: function (destX, animate) {
  1231. scrollToX(destX, animate);
  1232. },
  1233. // Scrolls the pane so that the specified co-ordinate within the content is at the top of the
  1234. // viewport. animate is optional and if not passed then the value of animateScroll from the settings
  1235. // object this jScrollPane was initialised with is used.
  1236. scrollToY: function (destY, animate) {
  1237. scrollToY(destY, animate);
  1238. },
  1239. // Scrolls the pane to the specified percentage of its maximum horizontal scroll position. animate
  1240. // is optional and if not passed then the value of animateScroll from the settings object this
  1241. // jScrollPane was initialised with is used.
  1242. scrollToPercentX: function (destPercentX, animate) {
  1243. scrollToX(destPercentX * (contentWidth - paneWidth), animate);
  1244. },
  1245. // Scrolls the pane to the specified percentage of its maximum vertical scroll position. animate
  1246. // is optional and if not passed then the value of animateScroll from the settings object this
  1247. // jScrollPane was initialised with is used.
  1248. scrollToPercentY: function (destPercentY, animate) {
  1249. scrollToY(destPercentY * (contentHeight - paneHeight), animate);
  1250. },
  1251. // Scrolls the pane by the specified amount of pixels. animate is optional and if not passed then
  1252. // the value of animateScroll from the settings object this jScrollPane was initialised with is used.
  1253. scrollBy: function (deltaX, deltaY, animate) {
  1254. jsp.scrollByX(deltaX, animate);
  1255. jsp.scrollByY(deltaY, animate);
  1256. },
  1257. // Scrolls the pane by the specified amount of pixels. animate is optional and if not passed then
  1258. // the value of animateScroll from the settings object this jScrollPane was initialised with is used.
  1259. scrollByX: function (deltaX, animate) {
  1260. var destX = contentPositionX() + Math[deltaX < 0 ? 'floor' : 'ceil'](deltaX),
  1261. percentScrolled = destX / (contentWidth - paneWidth);
  1262. positionDragX(percentScrolled * dragMaxX, animate);
  1263. },
  1264. // Scrolls the pane by the specified amount of pixels. animate is optional and if not passed then
  1265. // the value of animateScroll from the settings object this jScrollPane was initialised with is used.
  1266. scrollByY: function (deltaY, animate) {
  1267. var destY = contentPositionY() + Math[deltaY < 0 ? 'floor' : 'ceil'](deltaY),
  1268. percentScrolled = destY / (contentHeight - paneHeight);
  1269. positionDragY(percentScrolled * dragMaxY, animate);
  1270. },
  1271. // Positions the horizontal drag at the specified x position (and updates the viewport to reflect
  1272. // this). animate is optional and if not passed then the value of animateScroll from the settings
  1273. // object this jScrollPane was initialised with is used.
  1274. positionDragX: function (x, animate) {
  1275. positionDragX(x, animate);
  1276. },
  1277. // Positions the vertical drag at the specified y position (and updates the viewport to reflect
  1278. // this). animate is optional and if not passed then the value of animateScroll from the settings
  1279. // object this jScrollPane was initialised with is used.
  1280. positionDragY: function (y, animate) {
  1281. positionDragY(y, animate);
  1282. },
  1283. // This method is called when jScrollPane is trying to animate to a new position. You can override
  1284. // it if you want to provide advanced animation functionality. It is passed the following arguments:
  1285. // * ele - the element whose position is being animated
  1286. // * prop - the property that is being animated
  1287. // * value - the value it's being animated to
  1288. // * stepCallback - a function that you must execute each time you update the value of the property
  1289. // * completeCallback - a function that will be executed after the animation had finished
  1290. // You can use the default implementation (below) as a starting point for your own implementation.
  1291. animate: function (ele, prop, value, stepCallback, completeCallback) {
  1292. var params = {};
  1293. params[prop] = value;
  1294. ele.animate(
  1295. params,
  1296. {
  1297. 'duration': settings.animateDuration,
  1298. 'easing': settings.animateEase,
  1299. 'queue': false,
  1300. 'step': stepCallback,
  1301. 'complete': completeCallback
  1302. }
  1303. );
  1304. },
  1305. // Returns the current x position of the viewport with regards to the content pane.
  1306. getContentPositionX: function () {
  1307. return contentPositionX();
  1308. },
  1309. // Returns the current y position of the viewport with regards to the content pane.
  1310. getContentPositionY: function () {
  1311. return contentPositionY();
  1312. },
  1313. // Returns the width of the content within the scroll pane.
  1314. getContentWidth: function () {
  1315. return contentWidth;
  1316. },
  1317. // Returns the height of the content within the scroll pane.
  1318. getContentHeight: function () {
  1319. return contentHeight;
  1320. },
  1321. // Returns the horizontal position of the viewport within the pane content.
  1322. getPercentScrolledX: function () {
  1323. return contentPositionX() / (contentWidth - paneWidth);
  1324. },
  1325. // Returns the vertical position of the viewport within the pane content.
  1326. getPercentScrolledY: function () {
  1327. return contentPositionY() / (contentHeight - paneHeight);
  1328. },
  1329. // Returns whether or not this scrollpane has a horizontal scrollbar.
  1330. getIsScrollableH: function () {
  1331. return isScrollableH;
  1332. },
  1333. // Returns whether or not this scrollpane has a vertical scrollbar.
  1334. getIsScrollableV: function () {
  1335. return isScrollableV;
  1336. },
  1337. // Gets a reference to the content pane. It is important that you use this method if you want to
  1338. // edit the content of your jScrollPane as if you access the element directly then you may have some
  1339. // problems (as your original element has had additional elements for the scrollbars etc added into
  1340. // it).
  1341. getContentPane: function () {
  1342. return pane;
  1343. },
  1344. // Scrolls this jScrollPane down as far as it can currently scroll. If animate isn't passed then the
  1345. // animateScroll value from settings is used instead.
  1346. scrollToBottom: function (animate) {
  1347. positionDragY(dragMaxY, animate);
  1348. },
  1349. // Hijacks the links on the page which link to content inside the scrollpane. If you have changed
  1350. // the content of your page (e.g. via AJAX) and want to make sure any new anchor links to the
  1351. // contents of your scroll pane will work then call this function.
  1352. hijackInternalLinks: $.noop,
  1353. // Removes the jScrollPane and returns the page to the state it was in before jScrollPane was
  1354. // initialised.
  1355. destroy: function () {
  1356. destroy();
  1357. }
  1358. }
  1359. );
  1360. initialise(s);
  1361. }
  1362. // Pluginifying code...
  1363. settings = $.extend({}, $.fn.jScrollPane.defaults, settings);
  1364. // Apply default speed
  1365. $.each(['arrowButtonSpeed', 'trackClickSpeed', 'keyboardSpeed'], function () {
  1366. settings[this] = settings[this] || settings.speed;
  1367. });
  1368. return this.each(
  1369. function () {
  1370. var elem = $(this), jspApi = elem.data('jsp');
  1371. if (jspApi) {
  1372. jspApi.reinitialise(settings);
  1373. } else {
  1374. $("script", elem).filter('[type="text/javascript"],:not([type])').remove();
  1375. jspApi = new JScrollPane(elem, settings);
  1376. elem.data('jsp', jspApi);
  1377. }
  1378. }
  1379. );
  1380. };
  1381. $.fn.jScrollPane.defaults = {
  1382. showArrows: false,
  1383. maintainPosition: true,
  1384. stickToBottom: false,
  1385. stickToRight: false,
  1386. clickOnTrack: true,
  1387. autoReinitialise: false,
  1388. autoReinitialiseDelay: 500,
  1389. verticalDragMinHeight: 0,
  1390. verticalDragMaxHeight: 99999,
  1391. horizontalDragMinWidth: 0,
  1392. horizontalDragMaxWidth: 99999,
  1393. contentWidth: undefined,
  1394. animateScroll: false,
  1395. animateDuration: 300,
  1396. animateEase: 'linear',
  1397. hijackInternalLinks: false,
  1398. verticalGutter: 4,
  1399. horizontalGutter: 4,
  1400. mouseWheelSpeed: 3,
  1401. arrowButtonSpeed: 0,
  1402. arrowRepeatFreq: 50,
  1403. arrowScrollOnHover: false,
  1404. trackClickSpeed: 0,
  1405. trackClickRepeatFreq: 70,
  1406. verticalArrowPositions: 'split',
  1407. horizontalArrowPositions: 'split',
  1408. enableKeyboardNavigation: true,
  1409. hideFocus: false,
  1410. keyboardSpeed: 0,
  1411. initialDelay: 300, // Delay before starting repeating
  1412. speed: 30, // Default speed when others falsey
  1413. scrollPagePercent: 0.8, // Percent of visible area scrolled when pageUp/Down or track area pressed
  1414. alwaysShowVScroll: false,
  1415. alwaysShowHScroll: false,
  1416. resizeSensor: false,
  1417. resizeSensorDelay: 0,
  1418. };
  1419. }));