jscrollpane-2b3.js 33 KB

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