| 449 |
rajveer |
1 |
/* Copyright (c) 2009 Kelvin Luck (kelvin AT kelvinluck DOT com || http://www.kelvinluck.com)
|
|
|
2 |
* Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
|
|
|
3 |
* and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
|
|
|
4 |
*
|
|
|
5 |
* See http://kelvinluck.com/assets/jquery/jScrollPane/
|
|
|
6 |
* $Id: jScrollPane.js 93 2010-06-01 08:17:28Z kelvin.luck $
|
|
|
7 |
*/
|
|
|
8 |
|
|
|
9 |
/**
|
|
|
10 |
* Replace the vertical scroll bars on any matched elements with a fancy
|
|
|
11 |
* styleable (via CSS) version. With JS disabled the elements will
|
|
|
12 |
* gracefully degrade to the browsers own implementation of overflow:auto.
|
|
|
13 |
* If the mousewheel plugin has been included on the page then the scrollable areas will also
|
|
|
14 |
* respond to the mouse wheel.
|
|
|
15 |
*
|
|
|
16 |
* @example jQuery(".scroll-pane").jScrollPane();
|
|
|
17 |
*
|
|
|
18 |
* @name jScrollPane
|
|
|
19 |
* @type jQuery
|
|
|
20 |
* @param Object settings hash with options, described below.
|
|
|
21 |
* scrollbarWidth - The width of the generated scrollbar in pixels
|
|
|
22 |
* scrollbarMargin - The amount of space to leave on the side of the scrollbar in pixels
|
|
|
23 |
* wheelSpeed - The speed the pane will scroll in response to the mouse wheel in pixels
|
|
|
24 |
* showArrows - Whether to display arrows for the user to scroll with
|
|
|
25 |
* arrowSize - The height of the arrow buttons if showArrows=true
|
|
|
26 |
* animateTo - Whether to animate when calling scrollTo and scrollBy
|
|
|
27 |
* dragMinHeight - The minimum height to allow the drag bar to be
|
|
|
28 |
* dragMaxHeight - The maximum height to allow the drag bar to be
|
|
|
29 |
* animateInterval - The interval in milliseconds to update an animating scrollPane (default 100)
|
|
|
30 |
* animateStep - The amount to divide the remaining scroll distance by when animating (default 3)
|
|
|
31 |
* maintainPosition- Whether you want the contents of the scroll pane to maintain it's position when you re-initialise it - so it doesn't scroll as you add more content (default true)
|
|
|
32 |
* tabIndex - The tabindex for this jScrollPane to control when it is tabbed to when navigating via keyboard (default 0)
|
|
|
33 |
* enableKeyboardNavigation - Whether to allow keyboard scrolling of this jScrollPane when it is focused (default true)
|
|
|
34 |
* animateToInternalLinks - Whether the move to an internal link (e.g. when it's focused by tabbing or by a hash change in the URL) should be animated or instant (default false)
|
|
|
35 |
* scrollbarOnLeft - Display the scrollbar on the left side? (needs stylesheet changes, see examples.html)
|
|
|
36 |
* reinitialiseOnImageLoad - Whether the jScrollPane should automatically re-initialise itself when any contained images are loaded (default false)
|
|
|
37 |
* topCapHeight - The height of the "cap" area between the top of the jScrollPane and the top of the track/ buttons
|
|
|
38 |
* bottomCapHeight - The height of the "cap" area between the bottom of the jScrollPane and the bottom of the track/ buttons
|
|
|
39 |
* observeHash - Whether jScrollPane should attempt to automagically scroll to the correct place when an anchor inside the scrollpane is linked to (default true)
|
|
|
40 |
* @return jQuery
|
|
|
41 |
* @cat Plugins/jScrollPane
|
|
|
42 |
* @author Kelvin Luck (kelvin AT kelvinluck DOT com || http://www.kelvinluck.com)
|
|
|
43 |
*/
|
|
|
44 |
|
|
|
45 |
(function($) {
|
|
|
46 |
|
|
|
47 |
$.jScrollPane = {
|
|
|
48 |
active : []
|
|
|
49 |
};
|
|
|
50 |
$.fn.jScrollPane = function(settings)
|
|
|
51 |
{
|
|
|
52 |
settings = $.extend({}, $.fn.jScrollPane.defaults, settings);
|
|
|
53 |
|
|
|
54 |
var rf = function() { return false; };
|
|
|
55 |
|
|
|
56 |
return this.each(
|
|
|
57 |
function()
|
|
|
58 |
{
|
|
|
59 |
var $this = $(this);
|
|
|
60 |
var paneEle = this;
|
|
|
61 |
var currentScrollPosition = 0;
|
|
|
62 |
var paneWidth;
|
|
|
63 |
var paneHeight;
|
|
|
64 |
var trackHeight;
|
|
|
65 |
var trackOffset = settings.topCapHeight;
|
|
|
66 |
var $container;
|
|
|
67 |
|
|
|
68 |
if ($(this).parent().is('.jScrollPaneContainer')) {
|
|
|
69 |
$container = $(this).parent();
|
|
|
70 |
currentScrollPosition = settings.maintainPosition ? $this.position().top : 0;
|
|
|
71 |
var $c = $(this).parent();
|
|
|
72 |
paneWidth = $c.innerWidth();
|
|
|
73 |
paneHeight = $c.outerHeight();
|
|
|
74 |
$('>.jScrollPaneTrack, >.jScrollArrowUp, >.jScrollArrowDown, >.jScrollCap', $c).remove();
|
|
|
75 |
$this.css({'top':0});
|
|
|
76 |
} else {
|
|
|
77 |
$this.data('originalStyleTag', $this.attr('style'));
|
|
|
78 |
// Switch the element's overflow to hidden to ensure we get the size of the element without the scrollbars [http://plugins.jquery.com/node/1208]
|
|
|
79 |
$this.css('overflow', 'hidden');
|
|
|
80 |
this.originalPadding = $this.css('paddingTop') + ' ' + $this.css('paddingRight') + ' ' + $this.css('paddingBottom') + ' ' + $this.css('paddingLeft');
|
|
|
81 |
this.originalSidePaddingTotal = (parseInt($this.css('paddingLeft')) || 0) + (parseInt($this.css('paddingRight')) || 0);
|
|
|
82 |
paneWidth = $this.innerWidth();
|
|
|
83 |
paneHeight = $this.innerHeight();
|
|
|
84 |
$container = $('<div></div>')
|
|
|
85 |
.attr({'className':'jScrollPaneContainer'})
|
|
|
86 |
.css(
|
|
|
87 |
{
|
|
|
88 |
'height':paneHeight+'px',
|
|
|
89 |
'width':100+'%'
|
|
|
90 |
}
|
|
|
91 |
);
|
|
|
92 |
if (settings.enableKeyboardNavigation) {
|
|
|
93 |
$container.attr(
|
|
|
94 |
'tabindex',
|
|
|
95 |
settings.tabIndex
|
|
|
96 |
);
|
|
|
97 |
}
|
|
|
98 |
$this.wrap($container);
|
|
|
99 |
$container = $this.parent();
|
|
|
100 |
// deal with text size changes (if the jquery.em plugin is included)
|
|
|
101 |
// and re-initialise the scrollPane so the track maintains the
|
|
|
102 |
// correct size
|
|
|
103 |
$(document).bind(
|
|
|
104 |
'emchange',
|
|
|
105 |
function(e, cur, prev)
|
|
|
106 |
{
|
|
|
107 |
$this.jScrollPane(settings);
|
|
|
108 |
}
|
|
|
109 |
);
|
|
|
110 |
|
|
|
111 |
}
|
|
|
112 |
trackHeight = paneHeight;
|
|
|
113 |
|
|
|
114 |
if (settings.reinitialiseOnImageLoad) {
|
|
|
115 |
// code inspired by jquery.onImagesLoad: http://plugins.jquery.com/project/onImagesLoad
|
|
|
116 |
// except we re-initialise the scroll pane when each image loads so that the scroll pane is always up to size...
|
|
|
117 |
// TODO: Do I even need to store it in $.data? Is a local variable here the same since I don't pass the reinitialiseOnImageLoad when I re-initialise?
|
|
|
118 |
var $imagesToLoad = $.data(paneEle, 'jScrollPaneImagesToLoad') || $('img', $this);
|
|
|
119 |
var loadedImages = [];
|
|
|
120 |
|
|
|
121 |
if ($imagesToLoad.length) {
|
|
|
122 |
$imagesToLoad.each(function(i, val) {
|
|
|
123 |
$(this).bind('load readystatechange', function() {
|
|
|
124 |
if($.inArray(i, loadedImages) == -1){ //don't double count images
|
|
|
125 |
loadedImages.push(val); //keep a record of images we've seen
|
|
|
126 |
$imagesToLoad = $.grep($imagesToLoad, function(n, i) {
|
|
|
127 |
return n != val;
|
|
|
128 |
});
|
|
|
129 |
$.data(paneEle, 'jScrollPaneImagesToLoad', $imagesToLoad);
|
|
|
130 |
var s2 = $.extend(settings, {reinitialiseOnImageLoad:false});
|
|
|
131 |
$this.jScrollPane(s2); // re-initialise
|
|
|
132 |
}
|
|
|
133 |
}).each(function(i, val) {
|
|
|
134 |
if(this.complete || this.complete===undefined) {
|
|
|
135 |
//needed for potential cached images
|
|
|
136 |
this.src = this.src;
|
|
|
137 |
}
|
|
|
138 |
});
|
|
|
139 |
});
|
|
|
140 |
};
|
|
|
141 |
}
|
|
|
142 |
|
|
|
143 |
var p = this.originalSidePaddingTotal;
|
|
|
144 |
var realPaneWidth = paneWidth - settings.scrollbarWidth - settings.scrollbarMargin - p;
|
|
|
145 |
|
|
|
146 |
var cssToApply = {
|
|
|
147 |
'height':'auto',
|
|
|
148 |
'width': realPaneWidth + 'px'
|
|
|
149 |
}
|
|
|
150 |
|
|
|
151 |
if(settings.scrollbarOnLeft) {
|
|
|
152 |
cssToApply.paddingLeft = settings.scrollbarMargin + settings.scrollbarWidth + 'px';
|
|
|
153 |
} else {
|
|
|
154 |
cssToApply.paddingRight = settings.scrollbarMargin + 'px';
|
|
|
155 |
}
|
|
|
156 |
|
|
|
157 |
$this.css(cssToApply);
|
|
|
158 |
|
|
|
159 |
var contentHeight = $this.outerHeight();
|
|
|
160 |
var percentInView = paneHeight / contentHeight;
|
|
|
161 |
|
|
|
162 |
var isScrollable = percentInView < .99;
|
|
|
163 |
$container[isScrollable ? 'addClass' : 'removeClass']('jScrollPaneScrollable');
|
|
|
164 |
|
|
|
165 |
if (isScrollable) {
|
|
|
166 |
$container.append(
|
|
|
167 |
$('<div></div>').addClass('jScrollCap jScrollCapTop').css({height:settings.topCapHeight}),
|
|
|
168 |
$('<div></div>').attr({'className':'jScrollPaneTrack'}).css({'width':settings.scrollbarWidth+'px'}).append(
|
|
|
169 |
$('<div></div>').attr({'className':'jScrollPaneDrag'}).css({'width':settings.scrollbarWidth+'px'}).append(
|
|
|
170 |
$('<div></div>').attr({'className':'jScrollPaneDragTop'}).css({'width':settings.scrollbarWidth+'px'}),
|
|
|
171 |
$('<div></div>').attr({'className':'jScrollPaneDragBottom'}).css({'width':settings.scrollbarWidth+'px'})
|
|
|
172 |
)
|
|
|
173 |
),
|
|
|
174 |
$('<div></div>').addClass('jScrollCap jScrollCapBottom').css({height:settings.bottomCapHeight})
|
|
|
175 |
);
|
|
|
176 |
|
|
|
177 |
var $track = $('>.jScrollPaneTrack', $container);
|
|
|
178 |
var $drag = $('>.jScrollPaneTrack .jScrollPaneDrag', $container);
|
|
|
179 |
|
|
|
180 |
|
|
|
181 |
var currentArrowDirection;
|
|
|
182 |
var currentArrowTimerArr = [];// Array is used to store timers since they can stack up when dealing with keyboard events. This ensures all timers are cleaned up in the end, preventing an acceleration bug.
|
|
|
183 |
var currentArrowInc;
|
|
|
184 |
var whileArrowButtonDown = function()
|
|
|
185 |
{
|
|
|
186 |
if (currentArrowInc > 4 || currentArrowInc % 4 == 0) {
|
|
|
187 |
positionDrag(dragPosition + currentArrowDirection * mouseWheelMultiplier);
|
|
|
188 |
}
|
|
|
189 |
currentArrowInc++;
|
|
|
190 |
};
|
|
|
191 |
|
|
|
192 |
if (settings.enableKeyboardNavigation) {
|
|
|
193 |
$container.bind(
|
|
|
194 |
'keydown.jscrollpane',
|
|
|
195 |
function(e)
|
|
|
196 |
{
|
|
|
197 |
switch (e.keyCode) {
|
|
|
198 |
case 38: //up
|
|
|
199 |
currentArrowDirection = -1;
|
|
|
200 |
currentArrowInc = 0;
|
|
|
201 |
whileArrowButtonDown();
|
|
|
202 |
currentArrowTimerArr[currentArrowTimerArr.length] = setInterval(whileArrowButtonDown, 100);
|
|
|
203 |
return false;
|
|
|
204 |
case 40: //down
|
|
|
205 |
currentArrowDirection = 1;
|
|
|
206 |
currentArrowInc = 0;
|
|
|
207 |
whileArrowButtonDown();
|
|
|
208 |
currentArrowTimerArr[currentArrowTimerArr.length] = setInterval(whileArrowButtonDown, 100);
|
|
|
209 |
return false;
|
|
|
210 |
case 33: // page up
|
|
|
211 |
case 34: // page down
|
|
|
212 |
// TODO
|
|
|
213 |
return false;
|
|
|
214 |
default:
|
|
|
215 |
}
|
|
|
216 |
}
|
|
|
217 |
).bind(
|
|
|
218 |
'keyup.jscrollpane',
|
|
|
219 |
function(e)
|
|
|
220 |
{
|
|
|
221 |
if (e.keyCode == 38 || e.keyCode == 40) {
|
|
|
222 |
for (var i = 0; i < currentArrowTimerArr.length; i++) {
|
|
|
223 |
clearInterval(currentArrowTimerArr[i]);
|
|
|
224 |
}
|
|
|
225 |
return false;
|
|
|
226 |
}
|
|
|
227 |
}
|
|
|
228 |
);
|
|
|
229 |
}
|
|
|
230 |
|
|
|
231 |
if (settings.showArrows) {
|
|
|
232 |
|
|
|
233 |
var currentArrowButton;
|
|
|
234 |
var currentArrowInterval;
|
|
|
235 |
|
|
|
236 |
var onArrowMouseUp = function(event)
|
|
|
237 |
{
|
|
|
238 |
$('html').unbind('mouseup', onArrowMouseUp);
|
|
|
239 |
currentArrowButton.removeClass('jScrollActiveArrowButton');
|
|
|
240 |
clearInterval(currentArrowInterval);
|
|
|
241 |
};
|
|
|
242 |
var onArrowMouseDown = function() {
|
|
|
243 |
$('html').bind('mouseup', onArrowMouseUp);
|
|
|
244 |
currentArrowButton.addClass('jScrollActiveArrowButton');
|
|
|
245 |
currentArrowInc = 0;
|
|
|
246 |
whileArrowButtonDown();
|
|
|
247 |
currentArrowInterval = setInterval(whileArrowButtonDown, 100);
|
|
|
248 |
};
|
|
|
249 |
$container
|
|
|
250 |
.append(
|
|
|
251 |
$('<a></a>')
|
|
|
252 |
.attr(
|
|
|
253 |
{
|
|
|
254 |
'href':'javascript:;',
|
|
|
255 |
'className':'jScrollArrowUp',
|
|
|
256 |
'tabindex':-1
|
|
|
257 |
}
|
|
|
258 |
)
|
|
|
259 |
.css(
|
|
|
260 |
{
|
|
|
261 |
'width':settings.scrollbarWidth+'px',
|
|
|
262 |
'top':settings.topCapHeight + 'px'
|
|
|
263 |
}
|
|
|
264 |
)
|
|
|
265 |
.html('Scroll up')
|
|
|
266 |
.bind('mousedown', function()
|
|
|
267 |
{
|
|
|
268 |
currentArrowButton = $(this);
|
|
|
269 |
currentArrowDirection = -1;
|
|
|
270 |
onArrowMouseDown();
|
|
|
271 |
this.blur();
|
|
|
272 |
return false;
|
|
|
273 |
})
|
|
|
274 |
.bind('click', rf),
|
|
|
275 |
$('<a></a>')
|
|
|
276 |
.attr(
|
|
|
277 |
{
|
|
|
278 |
'href':'javascript:;',
|
|
|
279 |
'className':'jScrollArrowDown',
|
|
|
280 |
'tabindex':-1
|
|
|
281 |
}
|
|
|
282 |
)
|
|
|
283 |
.css(
|
|
|
284 |
{
|
|
|
285 |
'width':settings.scrollbarWidth+'px',
|
|
|
286 |
'bottom':settings.bottomCapHeight + 'px'
|
|
|
287 |
}
|
|
|
288 |
)
|
|
|
289 |
.html('Scroll down')
|
|
|
290 |
.bind('mousedown', function()
|
|
|
291 |
{
|
|
|
292 |
currentArrowButton = $(this);
|
|
|
293 |
currentArrowDirection = 1;
|
|
|
294 |
onArrowMouseDown();
|
|
|
295 |
this.blur();
|
|
|
296 |
return false;
|
|
|
297 |
})
|
|
|
298 |
.bind('click', rf)
|
|
|
299 |
);
|
|
|
300 |
var $upArrow = $('>.jScrollArrowUp', $container);
|
|
|
301 |
var $downArrow = $('>.jScrollArrowDown', $container);
|
|
|
302 |
}
|
|
|
303 |
|
|
|
304 |
if (settings.arrowSize) {
|
|
|
305 |
trackHeight = paneHeight - settings.arrowSize - settings.arrowSize;
|
|
|
306 |
trackOffset += settings.arrowSize;
|
|
|
307 |
} else if ($upArrow) {
|
|
|
308 |
var topArrowHeight = $upArrow.height();
|
|
|
309 |
settings.arrowSize = topArrowHeight;
|
|
|
310 |
trackHeight = paneHeight - topArrowHeight - $downArrow.height();
|
|
|
311 |
trackOffset += topArrowHeight;
|
|
|
312 |
}
|
|
|
313 |
trackHeight -= settings.topCapHeight + settings.bottomCapHeight;
|
|
|
314 |
$track.css({'height': trackHeight+'px', top:trackOffset+'px'})
|
|
|
315 |
|
|
|
316 |
var $pane = $(this).css({'position':'absolute', 'overflow':'visible'});
|
|
|
317 |
|
|
|
318 |
var currentOffset;
|
|
|
319 |
var maxY;
|
|
|
320 |
var mouseWheelMultiplier;
|
|
|
321 |
// store this in a seperate variable so we can keep track more accurately than just updating the css property..
|
|
|
322 |
var dragPosition = 0;
|
|
|
323 |
var dragMiddle = percentInView*paneHeight/2;
|
|
|
324 |
|
|
|
325 |
// pos function borrowed from tooltip plugin and adapted...
|
|
|
326 |
var getPos = function (event, c) {
|
|
|
327 |
var p = c == 'X' ? 'Left' : 'Top';
|
|
|
328 |
return event['page' + c] || (event['client' + c] + (document.documentElement['scroll' + p] || document.body['scroll' + p])) || 0;
|
|
|
329 |
};
|
|
|
330 |
|
|
|
331 |
var ignoreNativeDrag = function() { return false; };
|
|
|
332 |
|
|
|
333 |
var initDrag = function()
|
|
|
334 |
{
|
|
|
335 |
ceaseAnimation();
|
|
|
336 |
currentOffset = $drag.offset(false);
|
|
|
337 |
currentOffset.top -= dragPosition;
|
|
|
338 |
maxY = trackHeight - $drag[0].offsetHeight;
|
|
|
339 |
mouseWheelMultiplier = 2 * settings.wheelSpeed * maxY / contentHeight;
|
|
|
340 |
};
|
|
|
341 |
|
|
|
342 |
var onStartDrag = function(event)
|
|
|
343 |
{
|
|
|
344 |
initDrag();
|
|
|
345 |
dragMiddle = getPos(event, 'Y') - dragPosition - currentOffset.top;
|
|
|
346 |
$('html').bind('mouseup', onStopDrag).bind('mousemove', updateScroll).bind('mouseleave', onStopDrag)
|
|
|
347 |
if ($.browser.msie) {
|
|
|
348 |
$('html').bind('dragstart', ignoreNativeDrag).bind('selectstart', ignoreNativeDrag);
|
|
|
349 |
}
|
|
|
350 |
return false;
|
|
|
351 |
};
|
|
|
352 |
var onStopDrag = function()
|
|
|
353 |
{
|
|
|
354 |
$('html').unbind('mouseup', onStopDrag).unbind('mousemove', updateScroll);
|
|
|
355 |
dragMiddle = percentInView*paneHeight/2;
|
|
|
356 |
if ($.browser.msie) {
|
|
|
357 |
$('html').unbind('dragstart', ignoreNativeDrag).unbind('selectstart', ignoreNativeDrag);
|
|
|
358 |
}
|
|
|
359 |
};
|
|
|
360 |
var positionDrag = function(destY)
|
|
|
361 |
{
|
|
|
362 |
$container.scrollTop(0);
|
|
|
363 |
destY = destY < 0 ? 0 : (destY > maxY ? maxY : destY);
|
|
|
364 |
dragPosition = destY;
|
|
|
365 |
$drag.css({'top':destY+'px'});
|
|
|
366 |
var p = destY / maxY;
|
|
|
367 |
$this.data('jScrollPanePosition', (paneHeight-contentHeight)*-p);
|
|
|
368 |
$pane.css({'top':((paneHeight-contentHeight)*p) + 'px'});
|
|
|
369 |
$this.trigger('scroll');
|
|
|
370 |
if (settings.showArrows) {
|
|
|
371 |
$upArrow[destY == 0 ? 'addClass' : 'removeClass']('disabled');
|
|
|
372 |
$downArrow[destY == maxY ? 'addClass' : 'removeClass']('disabled');
|
|
|
373 |
}
|
|
|
374 |
};
|
|
|
375 |
var updateScroll = function(e)
|
|
|
376 |
{
|
|
|
377 |
positionDrag(getPos(e, 'Y') - currentOffset.top - dragMiddle);
|
|
|
378 |
};
|
|
|
379 |
|
|
|
380 |
var dragH = Math.max(Math.min(percentInView*(paneHeight-settings.arrowSize*2), settings.dragMaxHeight), settings.dragMinHeight);
|
|
|
381 |
|
|
|
382 |
$drag.css(
|
|
|
383 |
{'height':dragH+'px'}
|
|
|
384 |
).bind('mousedown', onStartDrag);
|
|
|
385 |
|
|
|
386 |
var trackScrollInterval;
|
|
|
387 |
var trackScrollInc;
|
|
|
388 |
var trackScrollMousePos;
|
|
|
389 |
var doTrackScroll = function()
|
|
|
390 |
{
|
|
|
391 |
if (trackScrollInc > 8 || trackScrollInc%4==0) {
|
|
|
392 |
positionDrag((dragPosition - ((dragPosition - trackScrollMousePos) / 2)));
|
|
|
393 |
}
|
|
|
394 |
trackScrollInc ++;
|
|
|
395 |
};
|
|
|
396 |
var onStopTrackClick = function()
|
|
|
397 |
{
|
|
|
398 |
clearInterval(trackScrollInterval);
|
|
|
399 |
$('html').unbind('mouseup', onStopTrackClick).unbind('mousemove', onTrackMouseMove);
|
|
|
400 |
};
|
|
|
401 |
var onTrackMouseMove = function(event)
|
|
|
402 |
{
|
|
|
403 |
trackScrollMousePos = getPos(event, 'Y') - currentOffset.top - dragMiddle;
|
|
|
404 |
};
|
|
|
405 |
var onTrackClick = function(event)
|
|
|
406 |
{
|
|
|
407 |
initDrag();
|
|
|
408 |
onTrackMouseMove(event);
|
|
|
409 |
trackScrollInc = 0;
|
|
|
410 |
$('html').bind('mouseup', onStopTrackClick).bind('mousemove', onTrackMouseMove);
|
|
|
411 |
trackScrollInterval = setInterval(doTrackScroll, 100);
|
|
|
412 |
doTrackScroll();
|
|
|
413 |
return false;
|
|
|
414 |
};
|
|
|
415 |
|
|
|
416 |
$track.bind('mousedown', onTrackClick);
|
|
|
417 |
|
|
|
418 |
$container.bind(
|
|
|
419 |
'mousewheel',
|
|
|
420 |
function (event, delta) {
|
|
|
421 |
delta = delta || (event.wheelDelta ? event.wheelDelta / 120 : (event.detail) ?
|
|
|
422 |
-event.detail/3 : 0);
|
|
|
423 |
initDrag();
|
|
|
424 |
ceaseAnimation();
|
|
|
425 |
var d = dragPosition;
|
|
|
426 |
positionDrag(dragPosition - delta * mouseWheelMultiplier);
|
|
|
427 |
var dragOccured = d != dragPosition;
|
|
|
428 |
return !dragOccured;
|
|
|
429 |
}
|
|
|
430 |
);
|
|
|
431 |
|
|
|
432 |
var _animateToPosition;
|
|
|
433 |
var _animateToInterval;
|
|
|
434 |
function animateToPosition()
|
|
|
435 |
{
|
|
|
436 |
var diff = (_animateToPosition - dragPosition) / settings.animateStep;
|
|
|
437 |
if (diff > 1 || diff < -1) {
|
|
|
438 |
positionDrag(dragPosition + diff);
|
|
|
439 |
} else {
|
|
|
440 |
positionDrag(_animateToPosition);
|
|
|
441 |
ceaseAnimation();
|
|
|
442 |
}
|
|
|
443 |
}
|
|
|
444 |
var ceaseAnimation = function()
|
|
|
445 |
{
|
|
|
446 |
if (_animateToInterval) {
|
|
|
447 |
clearInterval(_animateToInterval);
|
|
|
448 |
delete _animateToPosition;
|
|
|
449 |
}
|
|
|
450 |
};
|
|
|
451 |
var scrollTo = function(pos, preventAni)
|
|
|
452 |
{
|
|
|
453 |
if (typeof pos == "string") {
|
|
|
454 |
// Legal hash values aren't necessarily legal jQuery selectors so we need to catch any
|
|
|
455 |
// errors from the lookup...
|
|
|
456 |
try {
|
|
|
457 |
$e = $(pos, $this);
|
|
|
458 |
} catch (err) {
|
|
|
459 |
return;
|
|
|
460 |
}
|
|
|
461 |
if (!$e.length) return;
|
|
|
462 |
pos = $e.offset().top - $this.offset().top;
|
|
|
463 |
}
|
|
|
464 |
ceaseAnimation();
|
|
|
465 |
var maxScroll = contentHeight - paneHeight;
|
|
|
466 |
pos = pos > maxScroll ? maxScroll : pos;
|
|
|
467 |
$this.data('jScrollPaneMaxScroll', maxScroll);
|
|
|
468 |
var destDragPosition = pos/maxScroll * maxY;
|
|
|
469 |
if (preventAni || !settings.animateTo) {
|
|
|
470 |
positionDrag(destDragPosition);
|
|
|
471 |
} else {
|
|
|
472 |
$container.scrollTop(0);
|
|
|
473 |
_animateToPosition = destDragPosition;
|
|
|
474 |
_animateToInterval = setInterval(animateToPosition, settings.animateInterval);
|
|
|
475 |
}
|
|
|
476 |
};
|
|
|
477 |
$this[0].scrollTo = scrollTo;
|
|
|
478 |
|
|
|
479 |
$this[0].scrollBy = function(delta)
|
|
|
480 |
{
|
|
|
481 |
var currentPos = -parseInt($pane.css('top')) || 0;
|
|
|
482 |
scrollTo(currentPos + delta);
|
|
|
483 |
};
|
|
|
484 |
|
|
|
485 |
initDrag();
|
|
|
486 |
|
|
|
487 |
scrollTo(-currentScrollPosition, true);
|
|
|
488 |
|
|
|
489 |
// Deal with it when the user tabs to a link or form element within this scrollpane
|
|
|
490 |
$('*', this).bind(
|
|
|
491 |
'focus',
|
|
|
492 |
function(event)
|
|
|
493 |
{
|
|
|
494 |
var $e = $(this);
|
|
|
495 |
|
|
|
496 |
// loop through parents adding the offset top of any elements that are relatively positioned between
|
|
|
497 |
// the focused element and the jScrollPaneContainer so we can get the true distance from the top
|
|
|
498 |
// of the focused element to the top of the scrollpane...
|
|
|
499 |
var eleTop = 0;
|
|
|
500 |
|
|
|
501 |
var preventInfiniteLoop = 100;
|
|
|
502 |
|
|
|
503 |
while ($e[0] != $this[0]) {
|
|
|
504 |
eleTop += $e.position().top;
|
|
|
505 |
$e = $e.offsetParent();
|
|
|
506 |
if (!preventInfiniteLoop--) {
|
|
|
507 |
return;
|
|
|
508 |
}
|
|
|
509 |
}
|
|
|
510 |
|
|
|
511 |
var viewportTop = -parseInt($pane.css('top')) || 0;
|
|
|
512 |
var maxVisibleEleTop = viewportTop + paneHeight;
|
|
|
513 |
var eleInView = eleTop > viewportTop && eleTop < maxVisibleEleTop;
|
|
|
514 |
if (!eleInView) {
|
|
|
515 |
var destPos = eleTop - settings.scrollbarMargin;
|
|
|
516 |
if (eleTop > viewportTop) { // element is below viewport - scroll so it is at bottom.
|
|
|
517 |
destPos += $(this).height() + 15 + settings.scrollbarMargin - paneHeight;
|
|
|
518 |
}
|
|
|
519 |
scrollTo(destPos);
|
|
|
520 |
}
|
|
|
521 |
}
|
|
|
522 |
)
|
|
|
523 |
|
|
|
524 |
|
|
|
525 |
if (settings.observeHash) {
|
|
|
526 |
if (location.hash && location.hash.length > 1) {
|
|
|
527 |
setTimeout(function(){
|
|
|
528 |
scrollTo(location.hash);
|
|
|
529 |
}, $.browser.safari ? 100 : 0);
|
|
|
530 |
}
|
|
|
531 |
|
|
|
532 |
// use event delegation to listen for all clicks on links and hijack them if they are links to
|
|
|
533 |
// anchors within our content...
|
|
|
534 |
$(document).bind('click', function(e){
|
|
|
535 |
$target = $(e.target);
|
|
|
536 |
if ($target.is('a')) {
|
|
|
537 |
var h = $target.attr('href');
|
|
|
538 |
if (h && h.substr(0, 1) == '#' && h.length > 1) {
|
|
|
539 |
setTimeout(function(){
|
|
|
540 |
scrollTo(h, !settings.animateToInternalLinks);
|
|
|
541 |
}, $.browser.safari ? 100 : 0);
|
|
|
542 |
}
|
|
|
543 |
}
|
|
|
544 |
});
|
|
|
545 |
}
|
|
|
546 |
|
|
|
547 |
// Deal with dragging and selecting text to make the scrollpane scroll...
|
|
|
548 |
function onSelectScrollMouseDown(e)
|
|
|
549 |
{
|
|
|
550 |
$(document).bind('mousemove.jScrollPaneDragging', onTextSelectionScrollMouseMove);
|
|
|
551 |
$(document).bind('mouseup.jScrollPaneDragging', onSelectScrollMouseUp);
|
|
|
552 |
|
|
|
553 |
}
|
|
|
554 |
|
|
|
555 |
var textDragDistanceAway;
|
|
|
556 |
var textSelectionInterval;
|
|
|
557 |
|
|
|
558 |
function onTextSelectionInterval()
|
|
|
559 |
{
|
|
|
560 |
direction = textDragDistanceAway < 0 ? -1 : 1;
|
|
|
561 |
$this[0].scrollBy(textDragDistanceAway / 2);
|
|
|
562 |
}
|
|
|
563 |
|
|
|
564 |
function clearTextSelectionInterval()
|
|
|
565 |
{
|
|
|
566 |
if (textSelectionInterval) {
|
|
|
567 |
clearInterval(textSelectionInterval);
|
|
|
568 |
textSelectionInterval = undefined;
|
|
|
569 |
}
|
|
|
570 |
}
|
|
|
571 |
|
|
|
572 |
function onTextSelectionScrollMouseMove(e)
|
|
|
573 |
{
|
|
|
574 |
var offset = $this.parent().offset().top;
|
|
|
575 |
var maxOffset = offset + paneHeight;
|
|
|
576 |
var mouseOffset = getPos(e, 'Y');
|
|
|
577 |
textDragDistanceAway = mouseOffset < offset ? mouseOffset - offset : (mouseOffset > maxOffset ? mouseOffset - maxOffset : 0);
|
|
|
578 |
if (textDragDistanceAway == 0) {
|
|
|
579 |
clearTextSelectionInterval();
|
|
|
580 |
} else {
|
|
|
581 |
if (!textSelectionInterval) {
|
|
|
582 |
textSelectionInterval = setInterval(onTextSelectionInterval, 100);
|
|
|
583 |
}
|
|
|
584 |
}
|
|
|
585 |
}
|
|
|
586 |
|
|
|
587 |
function onSelectScrollMouseUp(e)
|
|
|
588 |
{
|
|
|
589 |
$(document)
|
|
|
590 |
.unbind('mousemove.jScrollPaneDragging')
|
|
|
591 |
.unbind('mouseup.jScrollPaneDragging');
|
|
|
592 |
clearTextSelectionInterval();
|
|
|
593 |
}
|
|
|
594 |
|
|
|
595 |
$container.bind('mousedown.jScrollPane', onSelectScrollMouseDown);
|
|
|
596 |
|
|
|
597 |
|
|
|
598 |
$.jScrollPane.active.push($this[0]);
|
|
|
599 |
|
|
|
600 |
} else {
|
|
|
601 |
$this.css(
|
|
|
602 |
{
|
|
|
603 |
'height':paneHeight+'px',
|
|
|
604 |
'width':paneWidth-this.originalSidePaddingTotal+'px',
|
|
|
605 |
'padding':this.originalPadding
|
|
|
606 |
}
|
|
|
607 |
);
|
|
|
608 |
$this[0].scrollTo = $this[0].scrollBy = function() {};
|
|
|
609 |
// clean up listeners
|
|
|
610 |
$this.parent().unbind('mousewheel').unbind('mousedown.jScrollPane').unbind('keydown.jscrollpane').unbind('keyup.jscrollpane');
|
|
|
611 |
}
|
|
|
612 |
|
|
|
613 |
}
|
|
|
614 |
)
|
|
|
615 |
};
|
|
|
616 |
|
|
|
617 |
$.fn.jScrollPaneRemove = function()
|
|
|
618 |
{
|
|
|
619 |
$(this).each(function()
|
|
|
620 |
{
|
|
|
621 |
$this = $(this);
|
|
|
622 |
var $c = $this.parent();
|
|
|
623 |
if ($c.is('.jScrollPaneContainer')) {
|
|
|
624 |
$this.css(
|
|
|
625 |
{
|
|
|
626 |
'top':'',
|
|
|
627 |
'height':'',
|
|
|
628 |
'width':'',
|
|
|
629 |
'padding':'',
|
|
|
630 |
'overflow':'',
|
|
|
631 |
'position':''
|
|
|
632 |
}
|
|
|
633 |
);
|
|
|
634 |
$this.attr('style', $this.data('originalStyleTag'));
|
|
|
635 |
$c.after($this).remove();
|
|
|
636 |
}
|
|
|
637 |
});
|
|
|
638 |
}
|
|
|
639 |
|
|
|
640 |
$.fn.jScrollPane.defaults = {
|
|
|
641 |
scrollbarWidth : 10,
|
|
|
642 |
scrollbarMargin : 5,
|
|
|
643 |
wheelSpeed : 18,
|
|
|
644 |
showArrows : false,
|
|
|
645 |
arrowSize : 0,
|
|
|
646 |
animateTo : false,
|
|
|
647 |
dragMinHeight : 1,
|
|
|
648 |
dragMaxHeight : 99999,
|
|
|
649 |
animateInterval : 100,
|
|
|
650 |
animateStep: 3,
|
|
|
651 |
maintainPosition: true,
|
|
|
652 |
scrollbarOnLeft: false,
|
|
|
653 |
reinitialiseOnImageLoad: false,
|
|
|
654 |
tabIndex : 0,
|
|
|
655 |
enableKeyboardNavigation: true,
|
|
|
656 |
animateToInternalLinks: false,
|
|
|
657 |
topCapHeight: 0,
|
|
|
658 |
bottomCapHeight: 0,
|
|
|
659 |
observeHash: true
|
|
|
660 |
};
|
|
|
661 |
|
|
|
662 |
// clean up the scrollTo expandos
|
|
|
663 |
$(window)
|
|
|
664 |
.bind('unload', function() {
|
|
|
665 |
var els = $.jScrollPane.active;
|
|
|
666 |
for (var i=0; i<els.length; i++) {
|
|
|
667 |
els[i].scrollTo = els[i].scrollBy = null;
|
|
|
668 |
}
|
|
|
669 |
}
|
|
|
670 |
);
|
|
|
671 |
|
|
|
672 |
})(jQuery);
|