jscrollpane-2b2.js 29 KB

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