/* Copyright (c) 2006 Kelvin Luck (kelvin AT kelvinluck DOT com || http://www.kelvinluck.com)  * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)   * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.  *   * See http://kelvinluck.com/assets/jquery/jScrollPane/  * $Id: jScrollPane.js 3125 2007-09-06 20:39:42Z kelvin.luck $  */  /**  * Replace the vertical scroll bars on any matched elements with a fancy  * styleable (via CSS) version. With JS disabled the elements will  * gracefully degrade to the browsers own implementation of overflow:auto.  * If the mousewheel plugin has been included on the page then the scrollable areas will also  * respond to the mouse wheel.  *  * @example jQuery(".scroll-pane").jScrollPane();  *  * @name jScrollPane  * @type jQuery  * @param Object	settings	hash with options, described below.  *								scrollbarWidth	-	The width of the generated scrollbar in pixels  *								scrollbarMargin	-	The amount of space to leave on the side of the scrollbar in pixels  *								wheelSpeed		-	The speed the pane will scroll in response to the mouse wheel in pixels  *								showArrows		-	Whether to display arrows for the user to scroll with  *								arrowSize		-	The height of the arrow buttons if showArrows=true  *								animateTo		-	Whether to animate when calling scrollTo and scrollBy  *								dragMinHeight	-	The minimum height to allow the drag bar to be  *								dragMaxHeight	-	The maximum height to allow the drag bar to be  *								animateInterval	-	The interval in milliseconds to update an animating scrollPane (default 100)  *								animateStep		-	The amount to divide the remaining scroll distance by when animating (default 3)  *								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)  * @return jQuery  * @cat Plugins/jScrollPane  * @author Kelvin Luck (kelvin AT kelvinluck DOT com || http://www.kelvinluck.com)  */ jQuery.jScrollPane = { 	active : [] }; jQuery.fn.jScrollPane = function(settings) { 	settings = jQuery.extend( 		{ 			scrollbarWidth : 10, 			scrollbarMargin : 5, 			wheelSpeed : 18, 			showArrows : false, 			arrowSize : 0, 			animateTo : false, 			dragMinHeight : 1, 			dragMaxHeight : 99999, 			animateInterval : 100, 			animateStep: 3, 			maintainPosition: true 		}, settings 	); 	return this.each( 		function() 		{ 			var $this = jQuery(this); 			 			if (jQuery(this).parent().is('.jScrollPaneContainer')) { 				var currentScrollPosition = settings.maintainPosition ? $this.offset({relativeTo:jQuery(this).parent()[0]}).top : 0; 				var $c = jQuery(this).parent(); 				var paneWidth = $c.innerWidth(); 				var paneHeight = $c.outerHeight(); 				var trackHeight = paneHeight; 				if ($c.unmousewheel) { 					$c.unmousewheel(); 				} 				jQuery('>.jScrollPaneTrack, >.jScrollArrowUp, >.jScrollArrowDown', $c).remove(); 				$this.css({'top':0}); 			} else { 				var currentScrollPosition = 0; 				this.originalPadding = $this.css('paddingTop') + ' ' + $this.css('paddingRight') + ' ' + $this.css('paddingBottom') + ' ' + $this.css('paddingLeft'); 				this.originalSidePaddingTotal = (parseInt($this.css('paddingLeft')) || 0) + (parseInt($this.css('paddingRight')) || 0); 				var paneWidth = $this.innerWidth(); 				var paneHeight = $this.innerHeight(); 				var trackHeight = paneHeight; 				$this.wrap( 					jQuery('<div></div>').attr( 						{'className':'jScrollPaneContainer'} 					).css( 						{ 							'height':paneHeight+'px',  							'width':paneWidth+'px' 						} 					) 				); 				// deal with text size changes (if the jquery.em plugin is included) 				// and re-initialise the scrollPane so the track maintains the 				// correct size 				jQuery(document).bind( 					'emchange',  					function(e, cur, prev) 					{ 						$this.jScrollPane(settings); 					} 				); 			} 			var p = this.originalSidePaddingTotal; 			$this.css( 				{ 					'height':'auto', 					'width':paneWidth - settings.scrollbarWidth - settings.scrollbarMargin - p + 'px', 					'paddingRight':settings.scrollbarMargin + 'px' 				} 			); 			var contentHeight = $this.outerHeight(); 			var percentInView = paneHeight / contentHeight;  			if (percentInView < .99) { 				var $container = $this.parent(); 				$container.append( 					jQuery('<div></div>').attr({'className':'jScrollPaneTrack'}).css({'width':settings.scrollbarWidth+'px'}).append( 						jQuery('<div></div>').attr({'className':'jScrollPaneDrag'}).css({'width':settings.scrollbarWidth+'px'}).append( 							jQuery('<div></div>').attr({'className':'jScrollPaneDragTop'}).css({'width':settings.scrollbarWidth+'px'}), 							jQuery('<div></div>').attr({'className':'jScrollPaneDragBottom'}).css({'width':settings.scrollbarWidth+'px'}) 						) 					) 				); 				 				var $track = jQuery('>.jScrollPaneTrack', $container); 				var $drag = jQuery('>.jScrollPaneTrack .jScrollPaneDrag', $container); 				 				if (settings.showArrows) { 					 					var currentArrowButton; 					var currentArrowDirection; 					var currentArrowInterval; 					var currentArrowInc; 					var whileArrowButtonDown = function() 					{ 						if (currentArrowInc > 4 || currentArrowInc%4==0) { 							positionDrag(dragPosition + currentArrowDirection * mouseWheelMultiplier); 						} 						currentArrowInc ++; 					}; 					var onArrowMouseUp = function(event) 					{ 						jQuery('body').unbind('mouseup', onArrowMouseUp); 						currentArrowButton.removeClass('jScrollActiveArrowButton'); 						clearInterval(currentArrowInterval); 						//console.log($(event.target)); 						//currentArrowButton.parent().removeClass('jScrollArrowUpClicked jScrollArrowDownClicked'); 					}; 					var onArrowMouseDown = function() { 						//console.log(direction); 						//currentArrowButton = $(this); 						jQuery('body').bind('mouseup', onArrowMouseUp); 						currentArrowButton.addClass('jScrollActiveArrowButton'); 						currentArrowInc = 0; 						whileArrowButtonDown(); 						currentArrowInterval = setInterval(whileArrowButtonDown, 100); 					}; 					$container 						.append( 							jQuery('<a></a>') 								.attr({'href':'javascript:;', 'className':'jScrollArrowUp'}) 								.css({'width':settings.scrollbarWidth+'px'}) 								.html('Scroll up') 								.bind('mousedown', function() 								{ 									currentArrowButton = jQuery(this); 									currentArrowDirection = -1; 									onArrowMouseDown(); 									this.blur(); 									return false; 								}), 							jQuery('<a></a>') 								.attr({'href':'javascript:;', 'className':'jScrollArrowDown'}) 								.css({'width':settings.scrollbarWidth+'px'}) 								.html('Scroll down') 								.bind('mousedown', function() 								{ 									currentArrowButton = jQuery(this); 									currentArrowDirection = 1; 									onArrowMouseDown(); 									this.blur(); 									return false; 								}) 						); 					if (settings.arrowSize) { 						trackHeight = paneHeight - settings.arrowSize - settings.arrowSize; 						$track 							.css({'height': trackHeight+'px', top:settings.arrowSize+'px'}) 					} else { 						var topArrowHeight = jQuery('>.jScrollArrowUp', $container).height(); 						settings.arrowSize = topArrowHeight; 						trackHeight = paneHeight - topArrowHeight - jQuery('>.jScrollArrowDown', $container).height(); 						$track 							.css({'height': trackHeight+'px', top:topArrowHeight+'px'}) 					} 				} 				 				var $pane = jQuery(this).css({'position':'absolute', 'overflow':'visible'}); 				 				var currentOffset; 				var maxY; 				var mouseWheelMultiplier; 				// store this in a seperate variable so we can keep track more accurately than just updating the css property.. 				var dragPosition = 0; 				var dragMiddle = percentInView*paneHeight/2; 				 				// pos function borrowed from tooltip plugin and adapted... 				var getPos = function (event, c) { 					var p = c == 'X' ? 'Left' : 'Top'; 					return event['page' + c] || (event['client' + c] + (document.documentElement['scroll' + p] || document.body['scroll' + p])) || 0; 				}; 				 				var ignoreNativeDrag = function() {	return false; }; 				 				var initDrag = function() 				{ 					ceaseAnimation(); 					currentOffset = $drag.offset(false); 					currentOffset.top -= dragPosition; 					maxY = trackHeight - $drag[0].offsetHeight; 					mouseWheelMultiplier = 2 * settings.wheelSpeed * maxY / contentHeight; 				}; 				 				var onStartDrag = function(event) 				{ 					initDrag(); 					dragMiddle = getPos(event, 'Y') - dragPosition - currentOffset.top; 					jQuery('body').bind('mouseup', onStopDrag).bind('mousemove', updateScroll); 					if (jQuery.browser.msie) { 						jQuery('body').bind('dragstart', ignoreNativeDrag).bind('selectstart', ignoreNativeDrag); 					} 					return false; 				}; 				var onStopDrag = function() 				{ 					jQuery('body').unbind('mouseup', onStopDrag).unbind('mousemove', updateScroll); 					dragMiddle = percentInView*paneHeight/2; 					if (jQuery.browser.msie) { 						jQuery('body').unbind('dragstart', ignoreNativeDrag).unbind('selectstart', ignoreNativeDrag); 					} 				}; 				var positionDrag = function(destY) 				{ 					destY = destY < 0 ? 0 : (destY > maxY ? maxY : destY); 					dragPosition = destY; 					$drag.css({'top':destY+'px'}); 					var p = destY / maxY; 					$pane.css({'top':((paneHeight-contentHeight)*p) + 'px'}); 					$this.trigger('scroll'); 				}; 				var updateScroll = function(e) 				{ 					positionDrag(getPos(e, 'Y') - currentOffset.top - dragMiddle); 				}; 				 				var dragH = Math.max(Math.min(percentInView*(paneHeight-settings.arrowSize*2), settings.dragMaxHeight), settings.dragMinHeight); 				 				$drag.css( 					{'height':dragH+'px'} 				).bind('mousedown', onStartDrag); 				 				var trackScrollInterval; 				var trackScrollInc; 				var trackScrollMousePos; 				var doTrackScroll = function() 				{ 					if (trackScrollInc > 8 || trackScrollInc%4==0) { 						positionDrag((dragPosition - ((dragPosition - trackScrollMousePos) / 2))); 					} 					trackScrollInc ++; 				}; 				var onStopTrackClick = function() 				{ 					clearInterval(trackScrollInterval); 					jQuery('body').unbind('mouseup', onStopTrackClick).unbind('mousemove', onTrackMouseMove); 				}; 				var onTrackMouseMove = function(event) 				{ 					trackScrollMousePos = getPos(event, 'Y') - currentOffset.top - dragMiddle; 				}; 				var onTrackClick = function(event) 				{ 					initDrag(); 					onTrackMouseMove(event); 					trackScrollInc = 0; 					jQuery('body').bind('mouseup', onStopTrackClick).bind('mousemove', onTrackMouseMove); 					trackScrollInterval = setInterval(doTrackScroll, 100); 					doTrackScroll(); 				}; 				 				$track.bind('mousedown', onTrackClick); 				 				// if the mousewheel plugin has been included then also react to the mousewheel 				if ($container.mousewheel) { 					$container.mousewheel( 						function (event, delta) { 							initDrag(); 							ceaseAnimation(); 							var d = dragPosition; 							positionDrag(dragPosition - delta * mouseWheelMultiplier); 							var dragOccured = d != dragPosition; 							return !dragOccured; 						}, 						false 					);					 				} 				var _animateToPosition; 				var _animateToInterval; 				function animateToPosition() 				{ 					var diff = (_animateToPosition - dragPosition) / settings.animateStep; 					if (diff > 1 || diff < -1) { 						positionDrag(dragPosition + diff); 					} else { 						positionDrag(_animateToPosition); 						ceaseAnimation(); 					} 				} 				var ceaseAnimation = function() 				{ 					if (_animateToInterval) { 						clearInterval(_animateToInterval); 						delete _animateToPosition; 					} 				}; 				var scrollTo = function(pos, preventAni) 				{ 					if (typeof pos == "string") { 						$e = jQuery(pos, this); 						if (!$e.length) return; 						pos = $e.offset({relativeTo:this}).top; 					} 					ceaseAnimation(); 					var destDragPosition = -pos/(paneHeight-contentHeight) * maxY; 					if (!preventAni || settings.animateTo) { 						_animateToPosition = destDragPosition; 						_animateToInterval = setInterval(animateToPosition, settings.animateInterval); 					} else { 						positionDrag(destDragPosition); 					} 				}; 				$this[0].scrollTo = scrollTo; 				 				$this[0].scrollBy = function(delta) 				{ 					var currentPos = -parseInt($pane.css('top')) || 0; 					scrollTo(currentPos + delta); 				}; 				 				initDrag(); 				 				scrollTo(-currentScrollPosition, true); 				 				jQuery.jScrollPane.active.push($this[0]);  			} else { 				$this.css( 					{ 						'height':paneHeight+'px', 						'width':paneWidth-this.originalSidePaddingTotal+'px', 						'padding':this.originalPadding 					} 				); 				// remove from active list? 			} 			 		} 	) };  // clean up the scrollTo expandos jQuery(window) 	.bind('unload', function() { 		var els = jQuery.jScrollPane.active;  		for (var i=0; i<els.length; i++) { 			els[i].scrollTo = els[i].scrollBy = null; 		} 	} );

