PageRenderTime 39ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/ajax/libs/jScrollPane/2.0.0beta3/script/jquery.jscrollpane.js

https://gitlab.com/Mirros/cdnjs
JavaScript | 1061 lines | 805 code | 117 blank | 139 comment | 133 complexity | 6914d006be814b2e00c14339dd375ebe MD5 | raw file
  1. /*!
  2. * jScrollPane - v2.0.0beta3 - 2010-08-27
  3. * http://jscrollpane.kelvinluck.com/
  4. *
  5. * Copyright (c) 2010 Kelvin Luck
  6. * Dual licensed under the MIT and GPL licenses.
  7. */
  8. // Script: jScrollPane - cross browser customisable scrollbars
  9. //
  10. // *Version: 2.0.0beta3, Last updated: 2010-08-27*
  11. //
  12. // Project Home - http://jscrollpane.kelvinluck.com/
  13. // GitHub - http://github.com/vitch/jScrollPane
  14. // Source - http://github.com/vitch/jScrollPane/raw/master/script/jquery.jscrollpane.js
  15. // (Minified) - http://github.com/vitch/jScrollPane/raw/master/script/jquery.jscrollpane.min.js
  16. //
  17. // About: License
  18. //
  19. // Copyright (c) 2010 Kelvin Luck
  20. // Dual licensed under the MIT or GPL Version 2 licenses.
  21. // http://jscrollpane.kelvinluck.com/MIT-LICENSE.txt
  22. // http://jscrollpane.kelvinluck.com/GPL-LICENSE.txt
  23. //
  24. // About: Examples
  25. //
  26. // All examples and demos are available through the jScrollPane example site at:
  27. // http://jscrollpane.kelvinluck.com/
  28. //
  29. // About: Support and Testing
  30. //
  31. // This plugin is tested on the browsers below and has been found to work reliably on them. If you run
  32. // into a problem on one of the supported browsers then please visit the support section on the jScrollPane
  33. // website (http://jscrollpane.kelvinluck.com/) for more information on getting support. You are also
  34. // welcome to fork the project on GitHub if you can contribute a fix for a given issue.
  35. //
  36. // jQuery Versions - 1.4.2
  37. // Browsers Tested - Firefox 3.6.8, Safari 5, Opera 10.6, Chrome 5.0, IE 6, 7, 8
  38. //
  39. // About: Release History
  40. //
  41. // 2.0.0beta3 - (2010-08-27) Horizontal mousewheel, mwheelIntent, keyboard support, bug fixes
  42. // 2.0.0beta2 - (2010-08-21) Bug fixes
  43. // 2.0.0beta1 - (2010-08-17) Rewrite to follow modern best practices and enable horizontal scrolling, initially hidden
  44. // elements and dynamically sized elements.
  45. // 1.x - (2006-12-31 - 2010-07-31) Initial version, hosted at googlecode, deprecated
  46. (function($,window,undefined){
  47. $.fn.jScrollPane = function(settings)
  48. {
  49. // JScrollPane "class" - public methods are available through $('selector').data('jsp')
  50. function JScrollPane(elem, s)
  51. {
  52. var settings, jsp = this, pane, paneWidth, paneHeight, container, contentWidth, contentHeight,
  53. percentInViewH, percentInViewV, isScrollableV, isScrollableH, verticalDrag, dragMaxY,
  54. verticalDragPosition, horizontalDrag, dragMaxX, horizontalDragPosition,
  55. verticalBar, verticalTrack, scrollbarWidth, verticalTrackHeight, verticalDragHeight, arrowUp, arrowDown,
  56. horizontalBar, horizontalTrack, horizontalTrackWidth, horizontalDragWidth, arrowLeft, arrowRight,
  57. reinitialiseInterval, originalPadding, originalPaddingTotalWidth, previousPaneWidth,
  58. wasAtTop = true, wasAtLeft = true, wasAtBottom = false, wasAtRight = false,
  59. mwEvent = $.fn.mwheelIntent ? 'mwheelIntent.jsp' : 'mousewheel.jsp';
  60. originalPadding = elem.css('paddingTop') + ' ' +
  61. elem.css('paddingRight') + ' ' +
  62. elem.css('paddingBottom') + ' ' +
  63. elem.css('paddingLeft');
  64. originalPaddingTotalWidth = (parseInt(elem.css('paddingLeft')) || 0) +
  65. (parseInt(elem.css('paddingRight')) || 0);
  66. initialise(s);
  67. function initialise(s)
  68. {
  69. var clonedElem, tempWrapper, /*firstChild, lastChild, */isMaintainingPositon, lastContentX, lastContentY,
  70. hasContainingSpaceChanged;
  71. settings = s;
  72. if (pane == undefined) {
  73. elem.css(
  74. {
  75. 'overflow': 'hidden',
  76. 'padding': 0
  77. }
  78. );
  79. // TODO: Deal with where width/ height is 0 as it probably means the element is hidden and we should
  80. // come back to it later and check once it is unhidden...
  81. paneWidth = elem.innerWidth() + originalPaddingTotalWidth;
  82. paneHeight = elem.innerHeight();
  83. elem.width(paneWidth);
  84. pane = $('<div class="jspPane" />').wrap(
  85. $('<div class="jspContainer" />')
  86. .css({
  87. 'width': paneWidth + 'px',
  88. 'height': paneHeight + 'px'
  89. }
  90. )
  91. );
  92. elem.wrapInner(pane.parent());
  93. // Need to get the vars after being added to the document, otherwise they reference weird
  94. // disconnected orphan elements...
  95. container = elem.find('>.jspContainer');
  96. pane = container.find('>.jspPane');
  97. pane.css('padding', originalPadding);
  98. /*
  99. // Move any margins from the first and last children up to the container so they can still
  100. // collapse with neighbouring elements as they would before jScrollPane
  101. firstChild = pane.find(':first-child');
  102. lastChild = pane.find(':last-child');
  103. elem.css(
  104. {
  105. 'margin-top': firstChild.css('margin-top'),
  106. 'margin-bottom': lastChild.css('margin-bottom')
  107. }
  108. );
  109. firstChild.css('margin-top', 0);
  110. lastChild.css('margin-bottom', 0);
  111. */
  112. } else {
  113. elem.css('width', null);
  114. hasContainingSpaceChanged = elem.outerWidth() + originalPaddingTotalWidth != paneWidth || elem.outerHeight() != paneHeight;
  115. if (hasContainingSpaceChanged) {
  116. paneWidth = elem.innerWidth() + originalPaddingTotalWidth;
  117. paneHeight = elem.innerHeight();
  118. container.css({
  119. 'width': paneWidth + 'px',
  120. 'height': paneHeight + 'px'
  121. });
  122. }
  123. previousPaneWidth = pane.innerWidth();
  124. if (!hasContainingSpaceChanged && pane.outerWidth() == contentWidth && pane.outerHeight() == contentHeight) {
  125. // Nothing has changed since we last initialised
  126. if (isScrollableH || isScrollableV) { // If we had already set a width then re-set it
  127. pane.css('width', previousPaneWidth + 'px');
  128. elem.css('width', (previousPaneWidth + originalPaddingTotalWidth) + 'px');
  129. }
  130. // Then abort...
  131. return;
  132. }
  133. pane.css('width', null);
  134. elem.css('width', (paneWidth + originalPaddingTotalWidth) + 'px');
  135. container.find('>.jspVerticalBar,>.jspHorizontalBar').remove().end();
  136. }
  137. // Unfortunately it isn't that easy to find out the width of the element as it will always report the
  138. // width as allowed by its container, regardless of overflow settings.
  139. // A cunning workaround is to clone the element, set its position to absolute and place it in a narrow
  140. // container. Now it will push outwards to its maxium real width...
  141. clonedElem = pane.clone().css('position', 'absolute');
  142. tempWrapper = $('<div style="width:1px; position: relative;" />').append(clonedElem);
  143. $('body').append(tempWrapper);
  144. contentWidth = Math.max(pane.outerWidth(), clonedElem.outerWidth());
  145. tempWrapper.remove();
  146. contentHeight = pane.outerHeight();
  147. percentInViewH = contentWidth / paneWidth;
  148. percentInViewV = contentHeight / paneHeight;
  149. isScrollableV = percentInViewV > 1;
  150. isScrollableH = percentInViewH > 1;
  151. //console.log(paneWidth, paneHeight, contentWidth, contentHeight, percentInViewH, percentInViewV, isScrollableH, isScrollableV);
  152. if (!(isScrollableH || isScrollableV)) {
  153. elem.removeClass('jspScrollable');
  154. pane.css({
  155. 'top': 0,
  156. 'width': container.width() + 'px'
  157. });
  158. removeMousewheel();
  159. removeFocusHandler();
  160. removeKeyboardNav();
  161. unhijackInternalLinks();
  162. } else {
  163. elem.addClass('jspScrollable');
  164. isMaintainingPositon = settings.maintainPosition && (verticalDragPosition || horizontalDragPosition);
  165. if (isMaintainingPositon) {
  166. lastContentX = contentPositionX();
  167. lastContentY = contentPositionY();
  168. }
  169. initialiseVerticalScroll();
  170. initialiseHorizontalScroll();
  171. resizeScrollbars();
  172. if (isMaintainingPositon) {
  173. scrollToX(lastContentX);
  174. scrollToY(lastContentY);
  175. }
  176. initFocusHandler();
  177. initMousewheel();
  178. if (settings.enableKeyboardNavigation) {
  179. initKeyboardNav();
  180. }
  181. observeHash();
  182. if (settings.hijackInternalLinks) {
  183. hijackInternalLinks();
  184. }
  185. }
  186. if (settings.autoReinitialise && !reinitialiseInterval) {
  187. reinitialiseInterval = setInterval(
  188. function()
  189. {
  190. initialise(settings);
  191. },
  192. settings.autoReinitialiseDelay
  193. );
  194. } else if (!settings.autoReinitialise && reinitialiseInterval) {
  195. clearInterval(reinitialiseInterval)
  196. }
  197. elem.trigger('jsp-initialised', [isScrollableH || isScrollableV]);
  198. }
  199. function initialiseVerticalScroll()
  200. {
  201. if (isScrollableV) {
  202. container.append(
  203. $('<div class="jspVerticalBar" />').append(
  204. $('<div class="jspCap jspCapTop" />'),
  205. $('<div class="jspTrack" />').append(
  206. $('<div class="jspDrag" />').append(
  207. $('<div class="jspDragTop" />'),
  208. $('<div class="jspDragBottom" />')
  209. )
  210. ),
  211. $('<div class="jspCap jspCapBottom" />')
  212. )
  213. );
  214. verticalBar = container.find('>.jspVerticalBar');
  215. verticalTrack = verticalBar.find('>.jspTrack');
  216. verticalDrag = verticalTrack.find('>.jspDrag');
  217. if (settings.showArrows) {
  218. arrowUp = $('<a class="jspArrow jspArrowUp" />').bind(
  219. 'mousedown.jsp', getArrowScroll(0, -1)
  220. ).bind('click.jsp', nil);
  221. arrowDown = $('<a class="jspArrow jspArrowDown" />').bind(
  222. 'mousedown.jsp', getArrowScroll(0, 1)
  223. ).bind('click.jsp', nil);
  224. if (settings.arrowScrollOnHover) {
  225. arrowUp.bind('mouseover.jsp', getArrowScroll(0, -1, arrowUp));
  226. arrowDown.bind('mouseover.jsp', getArrowScroll(0, 1, arrowDown));
  227. }
  228. appendArrows(verticalTrack, settings.verticalArrowPositions, arrowUp, arrowDown);
  229. }
  230. verticalTrackHeight = paneHeight;
  231. container.find('>.jspVerticalBar>.jspCap:visible,>.jspVerticalBar>.jspArrow').each(
  232. function()
  233. {
  234. verticalTrackHeight -= $(this).outerHeight();
  235. }
  236. );
  237. verticalDrag.hover(
  238. function()
  239. {
  240. verticalDrag.addClass('jspHover');
  241. },
  242. function()
  243. {
  244. verticalDrag.removeClass('jspHover');
  245. }
  246. ).bind(
  247. 'mousedown.jsp',
  248. function(e)
  249. {
  250. // Stop IE from allowing text selection
  251. $('html').bind('dragstart.jsp selectstart.jsp', function() { return false; });
  252. verticalDrag.addClass('jspActive');
  253. var startY = e.pageY - verticalDrag.position().top;
  254. $('html').bind(
  255. 'mousemove.jsp',
  256. function(e)
  257. {
  258. positionDragY(e.pageY - startY, false);
  259. }
  260. ).bind('mouseup.jsp mouseleave.jsp', cancelDrag);
  261. return false;
  262. }
  263. );
  264. sizeVerticalScrollbar();
  265. updateVerticalArrows();
  266. }
  267. }
  268. function sizeVerticalScrollbar()
  269. {
  270. verticalTrack.height(verticalTrackHeight + 'px');
  271. verticalDragPosition = 0;
  272. scrollbarWidth = settings.verticalGutter + verticalTrack.outerWidth();
  273. // Make the pane thinner to allow for the vertical scrollbar
  274. pane.width(paneWidth - scrollbarWidth - originalPaddingTotalWidth);
  275. // Add margin to the left of the pane if scrollbars are on that side (to position
  276. // the scrollbar on the left or right set it's left or right property in CSS)
  277. if (verticalBar.position().left == 0) {
  278. pane.css('margin-left', scrollbarWidth + 'px');
  279. }
  280. }
  281. function initialiseHorizontalScroll()
  282. {
  283. if (isScrollableH) {
  284. container.append(
  285. $('<div class="jspHorizontalBar" />').append(
  286. $('<div class="jspCap jspCapLeft" />'),
  287. $('<div class="jspTrack" />').append(
  288. $('<div class="jspDrag" />').append(
  289. $('<div class="jspDragLeft" />'),
  290. $('<div class="jspDragRight" />')
  291. )
  292. ),
  293. $('<div class="jspCap jspCapRight" />')
  294. )
  295. );
  296. horizontalBar = container.find('>.jspHorizontalBar');
  297. horizontalTrack = horizontalBar.find('>.jspTrack');
  298. horizontalDrag = horizontalTrack.find('>.jspDrag');
  299. if (settings.showArrows) {
  300. arrowLeft = $('<a class="jspArrow jspArrowLeft" />').bind(
  301. 'mousedown.jsp', getArrowScroll(-1, 0)
  302. ).bind('click.jsp', nil);
  303. arrowRight = $('<a class="jspArrow jspArrowRight" />').bind(
  304. 'mousedown.jsp', getArrowScroll(1, 0)
  305. ).bind('click.jsp', nil);
  306. if (settings.arrowScrollOnHover) {
  307. arrowLeft.bind('mouseover.jsp', getArrowScroll(-1, 0, arrowLeft));
  308. arrowRight.bind('mouseover.jsp', getArrowScroll(1, 0, arrowRight));
  309. }
  310. appendArrows(horizontalTrack, settings.horizontalArrowPositions, arrowLeft, arrowRight);
  311. }
  312. horizontalDrag.hover(
  313. function()
  314. {
  315. horizontalDrag.addClass('jspHover');
  316. },
  317. function()
  318. {
  319. horizontalDrag.removeClass('jspHover');
  320. }
  321. ).bind(
  322. 'mousedown.jsp',
  323. function(e)
  324. {
  325. // Stop IE from allowing text selection
  326. $('html').bind('dragstart.jsp selectstart.jsp', function() { return false; });
  327. horizontalDrag.addClass('jspActive');
  328. var startX = e.pageX - horizontalDrag.position().left;
  329. $('html').bind(
  330. 'mousemove.jsp',
  331. function(e)
  332. {
  333. positionDragX(e.pageX - startX, false);
  334. }
  335. ).bind('mouseup.jsp mouseleave.jsp', cancelDrag);
  336. return false;
  337. }
  338. );
  339. horizontalTrackWidth = container.innerWidth();
  340. sizeHorizontalScrollbar();
  341. updateHorizontalArrows();
  342. } else {
  343. // no horizontal scroll
  344. }
  345. }
  346. function sizeHorizontalScrollbar()
  347. {
  348. container.find('>.jspHorizontalBar>.jspCap:visible,>.jspHorizontalBar>.jspArrow').each(
  349. function()
  350. {
  351. horizontalTrackWidth -= $(this).outerWidth();
  352. }
  353. );
  354. horizontalTrack.width(horizontalTrackWidth + 'px');
  355. horizontalDragPosition = 0;
  356. }
  357. function resizeScrollbars()
  358. {
  359. if (isScrollableH && isScrollableV) {
  360. var horizontalTrackHeight = horizontalTrack.outerHeight(),
  361. verticalTrackWidth = verticalTrack.outerWidth();
  362. verticalTrackHeight -= horizontalTrackHeight;
  363. $(horizontalBar).find('>.jspCap:visible,>.jspArrow').each(
  364. function()
  365. {
  366. horizontalTrackWidth += $(this).outerWidth();
  367. }
  368. );
  369. horizontalTrackWidth -= verticalTrackWidth;
  370. paneHeight -= verticalTrackWidth;
  371. paneWidth -= horizontalTrackHeight;
  372. horizontalTrack.parent().append(
  373. $('<div class="jspCorner" />').css('width', horizontalTrackHeight + 'px')
  374. );
  375. sizeVerticalScrollbar();
  376. sizeHorizontalScrollbar();
  377. }
  378. // reflow content
  379. if (isScrollableH) {
  380. pane.width((container.outerWidth() - originalPaddingTotalWidth) + 'px');
  381. }
  382. contentHeight = pane.outerHeight();
  383. percentInViewV = contentHeight / paneHeight;
  384. if (isScrollableH) {
  385. horizontalDragWidth = 1 / percentInViewH * horizontalTrackWidth;
  386. if (horizontalDragWidth > settings.horizontalDragMaxWidth) {
  387. horizontalDragWidth = settings.horizontalDragMaxWidth;
  388. } else if (horizontalDragWidth < settings.horizontalDragMinWidth) {
  389. horizontalDragWidth = settings.horizontalDragMinWidth;
  390. }
  391. horizontalDrag.width(horizontalDragWidth + 'px');
  392. dragMaxX = horizontalTrackWidth - horizontalDragWidth;
  393. }
  394. if (isScrollableV) {
  395. verticalDragHeight = 1 / percentInViewV * verticalTrackHeight;
  396. if (verticalDragHeight > settings.verticalDragMaxHeight) {
  397. verticalDragHeight = settings.verticalDragMaxHeight;
  398. } else if (verticalDragHeight < settings.verticalDragMinHeight) {
  399. verticalDragHeight = settings.verticalDragMinHeight;
  400. }
  401. verticalDrag.height(verticalDragHeight + 'px');
  402. dragMaxY = verticalTrackHeight - verticalDragHeight;
  403. }
  404. }
  405. function appendArrows(ele, p, a1, a2)
  406. {
  407. var p1 = "before", p2 = "after", aTemp;
  408. // Sniff for mac... Is there a better way to determine whether the arrows would naturally appear
  409. // at the top or the bottom of the bar?
  410. if (p == "os") {
  411. p = /Mac/.test(navigator.platform) ? "after" : "split";
  412. }
  413. if (p == p1) {
  414. p2 = p;
  415. } else if (p == p2) {
  416. p1 = p;
  417. aTemp = a1;
  418. a1 = a2;
  419. a2 = aTemp;
  420. }
  421. ele[p1](a1)[p2](a2);
  422. }
  423. function getArrowScroll(dirX, dirY, ele) {
  424. return function()
  425. {
  426. arrowScroll(dirX, dirY, this, ele);
  427. this.blur();
  428. return false;
  429. }
  430. }
  431. function arrowScroll(dirX, dirY, arrow, ele)
  432. {
  433. arrow = $(arrow).addClass('jspActive');
  434. var eve, doScroll = function()
  435. {
  436. if (dirX != 0) {
  437. positionDragX(horizontalDragPosition + dirX * settings.arrowButtonSpeed, false);
  438. }
  439. if (dirY != 0) {
  440. positionDragY(verticalDragPosition + dirY * settings.arrowButtonSpeed, false);
  441. }
  442. },
  443. scrollInt = setInterval(doScroll, settings.arrowRepeatFreq);
  444. doScroll();
  445. eve = ele == undefined ? 'mouseup.jsp' : 'mouseout.jsp';
  446. ele = ele || $('html');
  447. ele.bind(
  448. eve,
  449. function()
  450. {
  451. arrow.removeClass('jspActive');
  452. clearInterval(scrollInt);
  453. ele.unbind(eve);
  454. }
  455. );
  456. }
  457. function cancelDrag()
  458. {
  459. $('html').unbind('dragstart.jsp selectstart.jsp mousemove.jsp mouseup.jsp mouseleave.jsp');
  460. verticalDrag && verticalDrag.removeClass('jspActive');
  461. horizontalDrag && horizontalDrag.removeClass('jspActive');
  462. }
  463. function positionDragY(destY, animate)
  464. {
  465. if (!isScrollableV) {
  466. return;
  467. }
  468. if (destY < 0) {
  469. destY = 0;
  470. } else if (destY > dragMaxY) {
  471. destY = dragMaxY;
  472. }
  473. // can't just check if(animate) because false is a valid value that could be passed in...
  474. if (animate == undefined) {
  475. animate = settings.animateScroll;
  476. }
  477. if (animate) {
  478. jsp.animate(verticalDrag, 'top', destY, _positionDragY);
  479. } else {
  480. verticalDrag.css('top', destY);
  481. _positionDragY(destY);
  482. }
  483. }
  484. function _positionDragY(destY)
  485. {
  486. if (destY == undefined) {
  487. destY = verticalDrag.position().top;
  488. }
  489. container.scrollTop(0);
  490. verticalDragPosition = destY;
  491. var isAtTop = verticalDragPosition == 0,
  492. isAtBottom = verticalDragPosition == dragMaxY,
  493. percentScrolled = destY/ dragMaxY,
  494. destTop = -percentScrolled * (contentHeight - paneHeight);
  495. if (wasAtTop != isAtTop || wasAtBottom != isAtBottom) {
  496. wasAtTop = isAtTop;
  497. wasAtBottom = isAtBottom;
  498. elem.trigger('jsp-arrow-change', [wasAtTop, wasAtBottom, wasAtLeft, wasAtRight]);
  499. }
  500. updateVerticalArrows(isAtTop, isAtBottom);
  501. pane.css('top', destTop);
  502. elem.trigger('jsp-scroll-y', [-destTop, isAtTop, isAtBottom]);
  503. }
  504. function positionDragX(destX, animate)
  505. {
  506. if (!isScrollableH) {
  507. return;
  508. }
  509. if (destX < 0) {
  510. destX = 0;
  511. } else if (destX > dragMaxX) {
  512. destX = dragMaxX;
  513. }
  514. if (animate == undefined) {
  515. animate = settings.animateScroll;
  516. }
  517. if (animate) {
  518. jsp.animate(horizontalDrag, 'left', destX, _positionDragX);
  519. } else {
  520. horizontalDrag.css('left', destX);
  521. _positionDragX(destX);
  522. }
  523. }
  524. function _positionDragX(destX)
  525. {
  526. if (destX == undefined) {
  527. destX = horizontalDrag.position().left;
  528. }
  529. container.scrollTop(0);
  530. horizontalDragPosition = destX;
  531. var isAtLeft = horizontalDragPosition == 0,
  532. isAtRight = horizontalDragPosition == dragMaxY,
  533. percentScrolled = destX / dragMaxX,
  534. destLeft = -percentScrolled * (contentWidth - paneWidth);
  535. if (wasAtLeft != isAtLeft || wasAtRight != isAtRight) {
  536. wasAtLeft = isAtLeft;
  537. wasAtRight = isAtRight;
  538. elem.trigger('jsp-arrow-change', [wasAtTop, wasAtBottom, wasAtLeft, wasAtRight]);
  539. }
  540. updateHorizontalArrows(isAtLeft, isAtRight);
  541. pane.css('left', destLeft);
  542. elem.trigger('jsp-scroll-x', [-destLeft, isAtLeft, isAtRight]);
  543. }
  544. function updateVerticalArrows(isAtTop, isAtBottom)
  545. {
  546. if (settings.showArrows) {
  547. arrowUp[isAtTop ? 'addClass' : 'removeClass']('jspDisabled');
  548. arrowDown[isAtBottom ? 'addClass' : 'removeClass']('jspDisabled');
  549. }
  550. }
  551. function updateHorizontalArrows(isAtLeft, isAtRight)
  552. {
  553. if (settings.showArrows) {
  554. arrowLeft[isAtLeft ? 'addClass' : 'removeClass']('jspDisabled');
  555. arrowRight[isAtRight ? 'addClass' : 'removeClass']('jspDisabled');
  556. }
  557. }
  558. function scrollToY(destY, animate)
  559. {
  560. var percentScrolled = destY / (contentHeight - paneHeight);
  561. positionDragY(percentScrolled * dragMaxY, animate);
  562. }
  563. function scrollToX(destX, animate)
  564. {
  565. var percentScrolled = destX / (contentWidth - paneWidth);
  566. positionDragX(percentScrolled * dragMaxX, animate);
  567. }
  568. function scrollToElement(ele, stickToTop, animate)
  569. {
  570. var e, eleHeight, eleTop = 0, viewportTop, maxVisibleEleTop, destY;
  571. // Legal hash values aren't necessarily legal jQuery selectors so we need to catch any
  572. // errors from the lookup...
  573. try {
  574. e = $(ele);
  575. } catch (err) {
  576. return;
  577. }
  578. eleHeight = e.outerHeight();
  579. container.scrollTop(0);
  580. // loop through parents adding the offset top of any elements that are relatively positioned between
  581. // the focused element and the jspPane so we can get the true distance from the top
  582. // of the focused element to the top of the scrollpane...
  583. while (!e.is('.jspPane')) {
  584. eleTop += e.position().top;
  585. e = e.offsetParent();
  586. if (/^body|html$/i.test(e[0].nodeName)) {
  587. // we ended up too high in the document structure. Quit!
  588. return;
  589. }
  590. }
  591. viewportTop = contentPositionY();
  592. maxVisibleEleTop = viewportTop + paneHeight;
  593. if (eleTop < viewportTop || stickToTop) { // element is above viewport
  594. destY = eleTop - settings.verticalGutter;
  595. } else if (eleTop + eleHeight > maxVisibleEleTop) { // element is below viewport
  596. destY = eleTop - paneHeight + eleHeight + settings.verticalGutter;
  597. }
  598. if (destY) {
  599. scrollToY(destY, animate);
  600. }
  601. // TODO: Implement automatic horizontal scrolling?
  602. }
  603. function contentPositionX()
  604. {
  605. return -pane.position().left;
  606. }
  607. function contentPositionY()
  608. {
  609. return -pane.position().top;
  610. }
  611. function initMousewheel()
  612. {
  613. container.unbind(mwEvent).bind(
  614. mwEvent,
  615. function (event, delta, deltaX, deltaY) {
  616. var dX = horizontalDragPosition, dY = verticalDragPosition;
  617. positionDragX(horizontalDragPosition + deltaX * settings.mouseWheelSpeed, false)
  618. positionDragY(verticalDragPosition - deltaY * settings.mouseWheelSpeed, false);
  619. // return true if there was no movement so rest of screen can scroll
  620. return dX == horizontalDragPosition && dY == verticalDragPosition;
  621. }
  622. );
  623. }
  624. function removeMousewheel()
  625. {
  626. container.unbind(mwEvent);
  627. }
  628. function nil()
  629. {
  630. return false;
  631. }
  632. function initFocusHandler()
  633. {
  634. pane.unbind('focusin.jsp').bind(
  635. 'focusin.jsp',
  636. function(e)
  637. {
  638. if(e.target === pane[0]){return;}
  639. scrollToElement(e.target, false);
  640. }
  641. );
  642. }
  643. function removeFocusHandler()
  644. {
  645. pane.unbind('focusin.jsp');
  646. }
  647. function initKeyboardNav()
  648. {
  649. var pressed, pressedTimer;
  650. elem.attr('tabindex', 0)
  651. .unbind('keydown.jsp')
  652. .bind(
  653. 'keydown.jsp',
  654. function(e)
  655. {
  656. if(e.target !== elem[0]){
  657. return;
  658. }
  659. var dX = horizontalDragPosition, dY = verticalDragPosition, step = pressed ? 2 : 16;
  660. switch(e.keyCode) {
  661. case 40: // down
  662. positionDragY(verticalDragPosition + step, false);
  663. break;
  664. case 38: // up
  665. positionDragY(verticalDragPosition - step, false);
  666. break;
  667. case 34: // page down
  668. case 32: // space
  669. scrollToY(contentPositionY() + Math.max(32, paneHeight) - 16);
  670. break;
  671. case 33: // page up
  672. scrollToY(contentPositionY() - paneHeight + 16);
  673. break;
  674. case 35: // end
  675. scrollToY(contentHeight - paneHeight);
  676. break;
  677. case 36: // home
  678. scrollToY(0);
  679. break;
  680. case 39: // right
  681. positionDragX(horizontalDragPosition + step, false);
  682. break;
  683. case 37: // left
  684. positionDragX(horizontalDragPosition - step, false);
  685. break;
  686. }
  687. if( !(dX == horizontalDragPosition && dY == verticalDragPosition) ){
  688. pressed = true;
  689. clearTimeout(pressedTimer);
  690. pressedTimer = setTimeout(function(){
  691. pressed = false;
  692. }, 260);
  693. return false;
  694. }
  695. }
  696. );
  697. if(settings.hideFocus) {
  698. elem.css('outline', 'none');
  699. if('hideFocus' in container[0]){
  700. elem.attr('hideFocus', true);
  701. }
  702. } else {
  703. elem.css('outline', '');
  704. if('hideFocus' in container[0]){
  705. elem.attr('hideFocus', false);
  706. }
  707. }
  708. }
  709. function removeKeyboardNav()
  710. {
  711. elem.attr('tabindex', '-1')
  712. .removeAttr('tabindex')
  713. .unbind('keydown.jsp');
  714. }
  715. function observeHash()
  716. {
  717. if (location.hash && location.hash.length > 1) {
  718. var e, retryInt;
  719. try {
  720. e = $(location.hash);
  721. } catch (err) {
  722. return;
  723. }
  724. if (e.length && pane.find(e)) {
  725. // nasty workaround but it appears to take a little while before the hash has done its thing
  726. // to the rendered page so we just wait until the container's scrollTop has been messed up.
  727. if (container.scrollTop() == 0) {
  728. retryInt = setInterval(
  729. function()
  730. {
  731. if (container.scrollTop() > 0) {
  732. scrollToElement(location.hash, true);
  733. $(document).scrollTop(container.position().top);
  734. clearInterval(retryInt);
  735. }
  736. },
  737. 50
  738. )
  739. } else {
  740. scrollToElement(location.hash, true);
  741. $(document).scrollTop(container.position().top);
  742. }
  743. }
  744. }
  745. }
  746. function unhijackInternalLinks()
  747. {
  748. $('a.jspHijack').unbind('click.jsp-hijack').removeClass('jspHijack');
  749. }
  750. function hijackInternalLinks()
  751. {
  752. unhijackInternalLinks();
  753. $('a[href^=#]').addClass('jspHijack').bind(
  754. 'click.jsp-hijack',
  755. function()
  756. {
  757. var uriParts = this.href.split('#'), hash;
  758. if (uriParts.length > 1) {
  759. hash = uriParts[1];
  760. if (hash.length > 0 && pane.find('#' + hash).length > 0) {
  761. scrollToElement('#' + hash, true);
  762. // Need to return false otherwise things mess up... Would be nice to maybe also scroll
  763. // the window to the top of the scrollpane?
  764. return false;
  765. }
  766. }
  767. }
  768. )
  769. }
  770. // Public API
  771. $.extend(
  772. jsp,
  773. {
  774. // Reinitialises the scroll pane (if it's internal dimensions have changed since the last time it
  775. // was initialised). The settings object which is passed in will override any settings from the
  776. // previous time it was initialised - if you don't pass any settings then the ones from the previous
  777. // initialisation will be used.
  778. reinitialise: function(s)
  779. {
  780. s = $.extend({}, s, settings);
  781. initialise(s);
  782. },
  783. // Scrolls the specified element (a jQuery object, DOM node or jQuery selector string) into view so
  784. // that it can be seen within the viewport. If stickToTop is true then the element will appear at
  785. // the top of the viewport, if it is false then the viewport will scroll as little as possible to
  786. // show the element. You can also specify if you want animation to occur. If you don't provide this
  787. // argument then the animateScroll value from the settings object is used instead.
  788. scrollToElement: function(ele, stickToTop, animate)
  789. {
  790. scrollToElement(ele, stickToTop, animate);
  791. },
  792. // Scrolls the pane so that the specified co-ordinates within the content are at the top left
  793. // of the viewport. animate is optional and if not passed then the value of animateScroll from
  794. // the settings object this jScrollPane was initialised with is used.
  795. scrollTo: function(destX, destY, animate)
  796. {
  797. scrollToX(destX, animate);
  798. scrollToY(destY, animate);
  799. },
  800. // Scrolls the pane so that the specified co-ordinate within the content is at the left of the
  801. // viewport. animate is optional and if not passed then the value of animateScroll from the settings
  802. // object this jScrollPane was initialised with is used.
  803. scrollToX: function(destX, animate)
  804. {
  805. scrollToX(destX, animate);
  806. },
  807. // Scrolls the pane so that the specified co-ordinate within the content is at the top of the
  808. // viewport. animate is optional and if not passed then the value of animateScroll from the settings
  809. // object this jScrollPane was initialised with is used.
  810. scrollToY: function(destY, animate)
  811. {
  812. scrollToY(destY, animate);
  813. },
  814. // Scrolls the pane by the specified amount of pixels. animate is optional and if not passed then
  815. // the value of animateScroll from the settings object this jScrollPane was initialised with is used.
  816. scrollBy: function(deltaX, deltaY, animate)
  817. {
  818. jsp.scrollByX(deltaX, animate);
  819. jsp.scrollByY(deltaY, animate);
  820. },
  821. // Scrolls the pane by the specified amount of pixels. animate is optional and if not passed then
  822. // the value of animateScroll from the settings object this jScrollPane was initialised with is used.
  823. scrollByX: function(deltaX, animate)
  824. {
  825. var destX = contentPositionX() + deltaX,
  826. percentScrolled = destX / (contentWidth - paneWidth);
  827. positionDragX(percentScrolled * dragMaxX, animate);
  828. },
  829. // Scrolls the pane by the specified amount of pixels. animate is optional and if not passed then
  830. // the value of animateScroll from the settings object this jScrollPane was initialised with is used.
  831. scrollByY: function(deltaY, animate)
  832. {
  833. var destY = contentPositionY() + deltaY,
  834. percentScrolled = destY / (contentHeight - paneHeight);
  835. positionDragY(percentScrolled * dragMaxY, animate);
  836. },
  837. // This method is called when jScrollPane is trying to animate to a new position. You can override
  838. // it if you want to provide advanced animation functionality. It is passed the following arguments:
  839. // * ele - the element whose position is being animated
  840. // * prop - the property that is being animated
  841. // * value - the value it's being animated to
  842. // * stepCallback - a function that you must execute each time you update the value of the property
  843. // You can use the default implementation (below) as a starting point for your own implementation.
  844. animate: function(ele, prop, value, stepCallback)
  845. {
  846. var params = {};
  847. params[prop] = value;
  848. ele.animate(
  849. params,
  850. {
  851. 'duration' : settings.animateDuration,
  852. 'ease' : settings.animateEase,
  853. 'queue' : false,
  854. 'step' : stepCallback
  855. }
  856. );
  857. },
  858. // Returns the current x position of the viewport with regards to the content pane.
  859. getContentPositionX: function()
  860. {
  861. return contentPositionX();
  862. },
  863. // Returns the current y position of the viewport with regards to the content pane.
  864. getContentPositionY: function()
  865. {
  866. return contentPositionY();
  867. },
  868. // Returns whether or not this scrollpane has a horizontal scrollbar.
  869. getIsScrollableH: function()
  870. {
  871. return isScrollableH;
  872. },
  873. // Returns whether or not this scrollpane has a vertical scrollbar.
  874. getIsScrollableV: function()
  875. {
  876. return isScrollableV;
  877. },
  878. // Gets a reference to the content pane. It is important that you use this method if you want to
  879. // edit the content of your jScrollPane as if you access the element directly then you may have some
  880. // problems (as your original element has had additional elements for the scrollbars etc added into
  881. // it).
  882. getContentPane: function()
  883. {
  884. return pane;
  885. },
  886. // Scrolls this jScrollPane down as far as it can currently scroll. If animate isn't passed then the
  887. // animateScroll value from settings is used instead.
  888. scrollToBottom: function(animate)
  889. {
  890. positionDragY(dragMaxY, animate);
  891. },
  892. // Hijacks the links on the page which link to content inside the scrollpane. If you have changed
  893. // the content of your page (e.g. via AJAX) and want to make sure any new anchor links to the
  894. // contents of your scroll pane will work then call this function.
  895. hijackInternalLinks: function()
  896. {
  897. hijackInternalLinks();
  898. }
  899. }
  900. );
  901. }
  902. // Pluginifying code...
  903. settings = $.extend({}, $.fn.jScrollPane.defaults, settings);
  904. var ret;
  905. this.each(
  906. function()
  907. {
  908. var elem = $(this), jspApi = elem.data('jsp');
  909. if (jspApi) {
  910. jspApi.reinitialise(settings);
  911. } else {
  912. jspApi = new JScrollPane(elem, settings);
  913. elem.data('jsp', jspApi);
  914. }
  915. ret = ret ? ret.add(elem) : elem;
  916. }
  917. )
  918. return ret;
  919. };
  920. $.fn.jScrollPane.defaults = {
  921. 'showArrows' : false,
  922. 'maintainPosition' : true,
  923. 'autoReinitialise' : false,
  924. 'autoReinitialiseDelay' : 500,
  925. 'verticalDragMinHeight' : 0,
  926. 'verticalDragMaxHeight' : 99999,
  927. 'horizontalDragMinWidth' : 0,
  928. 'horizontalDragMaxWidth' : 99999,
  929. 'animateScroll' : false,
  930. 'animateDuration' : 300,
  931. 'animateEase' : 'linear',
  932. 'hijackInternalLinks' : false,
  933. 'verticalGutter' : 4,
  934. 'horizontalGutter' : 4,
  935. 'mouseWheelSpeed' : 10,
  936. 'arrowButtonSpeed' : 10,
  937. 'arrowRepeatFreq' : 100,
  938. 'arrowScrollOnHover' : false,
  939. 'verticalArrowPositions' : 'split',
  940. 'horizontalArrowPositions' : 'split',
  941. 'enableKeyboardNavigation' : true,
  942. 'hideFocus' : false
  943. };
  944. })(jQuery,this);