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