Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

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