/**
 * (./) viewport.js, v0.2, 09/12/09 10:40
 * (by) cousot stephane @ http://www.ubaa.net/
 * (cc) some right reserved
 *
 * Create and manage Javascript object to display HTML content onto a smaller area,
 * using custom scrollbars.
 *
 *
 * This script is released under a Creative Commons Attribution 3.0 License
 * ‹ http://creativecommons.org/licenses/by/3.0/ ›
 */
 	 
 	 
 	 


/**
 * The viewport object.
 * @param elem	the viewport element or  viewport element ID
 */
function Viewport( elem ) {
	
	
	
	// private properties
	var _elem	= null;	// this element
	var _inner	= null;	// the inner container element
	var _bar	= null;	// the scollbar element
	
	
	// the scoll properties
	var _scroll = {
		bar : {
			lastpos : 0,	// the previous element position 
			diff : 0		// the difference between the scollbar position and the user click position
		},
		wheel : {
			ratio : 0,		// the scoll ratio between the inner content height and the viewport height
			constrain : 21,	// the scroll wheel maximum / minimum value
			velocity : 2	// the scroll wheel delta amplifier
		}
	}
	
	
	

	
	
	// initialize object first
	_init();
	
	
	// public properties / method
	this.elem   = _elem;
	this.bar    = _bar;
	this.inner  = _inner;
	this.update = update;
	this.scroll = scroll;
	this.up 	= function() { scroll(_inner.offsetTop); };
	this.down 	= function() { scroll(_inner.offsetHeight); };
	
	
	
	
	/**
	 * Setup viewport elements.
	 */
	function _init() {
		
		
		// display dependency
		if ( typeof(CSS)=='undefined' ) {
			alert(
				"warning : Viewport object require 'css.js' script to run properly. "+
				"Add a link to this script in your main HTML document"
			);
		}
		
		
		// modify elemnt attributs
		_elem = typeof(elem)=="string" ? document.getElementById(elem) : elem;
		_elem.className	= 'viewport';
		
		
		// check compatibility
		// -> exit if not
		if ( !isCompatibleBrowser() ) {
			_elem.style.overflow = "auto";
			return;
		}
		
		
		
		// add mousewheel listener
		if ( _elem.addEventListener ) {
			_elem.addEventListener( 'DOMMouseScroll', _wheel, false );
			_elem.addEventListener( 'mousewheel', _wheel, false );
		}
		else _elem.attachEvent( 'onmousewheel', _wheel );
		
		
		
		// add scollbar element if need
		// and attach scroll event
		_bar = document.createElement( 'div' );
		_bar.className = 'viewport_vscrollbar';
		_bar.onmousedown = dragBegin;
		
		
		// add inner container
		_inner = document.createElement( 'div' );
		_inner.className = 'viewport_inner';
		_inner.innerHTML = _elem.innerHTML;
		
		
		// clear and append elements
		_elem.innerHTML = '';
		_elem.appendChild( _bar );
		_elem.appendChild( _inner );
		
		
		// update elements
		update();
		
	}
	
	
	
	
	/** 
	 * Update properties and element.
	 * @return True while scrollbar if showing
	 */
	function update() {
		if ( !_inner || !_bar ) return;
		
		var hoffset = _inner.offsetHeight; // first to fix IE7 scrollHeigt value
		var hscroll = _inner.scrollHeight;
		
		
		// wheel ratio
		_scroll.wheel.ratio = hscroll / hoffset;
		
		// scollbar
		if ( _scroll.wheel.ratio>1 ) {
			_bar.style.display = 'block';
			_bar.style.height = ( _inner.offsetHeight/_scroll.wheel.ratio  ) + 'px';
		}
		else _bar.style.display = 'none';
		
		// return scrollbar visibility
		return  _bar.style.display != 'none';
		
	}
	
	
	
	/**
	 * Check whether this object is compatible with the current browser or not…
	 * i.e IE >=7 and all other sane browser
	 * @return boolean
	 */
	function isCompatibleBrowser() {
		var version = /MSIE\s(\d+\.\d+)/.exec( navigator.appVersion );
		return version ? parseFloat(version[1])>=7 : true;
	}
	
	
	/**
	 * Returns the vertical scrollbar height ratio according to the current viewport content
	 * @return float
	 */
	function ratio() {
		
		// calculate the ratio property
		var hoffset = _inner.offsetHeight; // first to fix IE7 scrollHeigt value
		var hscroll = _inner.scrollHeight;
		
		return hscroll / hoffset;
	}
	
	
	
	/** 
	 * Mouse wheel event handler.
	 * @param e	the mousewheel event
	 */
	function _wheel( e ) {
		if (!e) e = window.event;
		
		/* delta = window.opera ? -e.wheelDelta : e.wheelDelta */
  		var delta = e.detail ? e.detail*-1 : e.wheelDelta/40;
		
		// scroll
		_inner.scrollTop -= Math.max(Math.min(delta,_scroll.wheel.constrain),-_scroll.wheel.constrain) * _scroll.wheel.velocity;
		_bar.style.top = ( _inner.scrollTop / _scroll.wheel.ratio +_inner.offsetTop )+'px';
		
		// TODO : SET THE SCROLLBAR USING A PROPORTION BETWEEN THE _bar.offsetHeigt VALUE  AND 
		// THE _inner.scrollHeight VALUE TO FIX BAR CUSTOM STYLES
		
		// block other wheel events
		if ( e.stopPropagation ) e.stopPropagation();
		if ( e.preventDefault ) e.preventDefault();
		e.returnValue = false;
		
	}
	
	
	
	/**
	 * Begin drag process handler.
	 * @param evt	mouse event
	 */
	function dragBegin( evt ) {
		
		var ymouse  = evt ? evt.clientY : window.event.clientY;
		var yparent = CSS.offsetTop( _elem );
		var ypage	= CSS.pageOffsetTop();
		
		_scroll.bar.diff  = ymouse-( yparent + _inner.offsetTop - ypage );
		_scroll.bar.diff -= _bar.offsetTop;
		
		// register mouse drag events
		document.onmousemove = drag;
		document.onmouseup	 = dragEnd;
		
		// stop mouse down event process
		return false;
	}
	
	
	
	/**
	 * Drag (move) element.
	 * @param evt	mouse event
	 */
	function drag( evt ) {
		scroll( evt ? evt.clientY : window.event.clientY );
		return false;	// <-- stop mousemove event process
	}


	/**
	 * Stop drag process.
	 * forget handlers
	 */
	function dragEnd() {
		document.onmousemove = null;
		document.onmouseup = null;
	}
	
	
	/**
	 * Scroll viewport and contained elements to the given Y position.
	 * @param y	the new y scrollbar position
	 */
	function scroll( y ) {
	
		var yparent = CSS.offsetTop( _elem );
		var ypage	= CSS.pageOffsetTop();
		var ymin	= _inner.offsetTop;
		var ymax	= ymin + _inner.offsetHeight - _bar.offsetHeight;
		var ypos	= Math.max( ymin, Math.min( y+ypage-yparent-_scroll.bar.diff, ymax ));

		
		// move scollbar element
		_bar.style.top = ypos + 'px';
		
		// affect inner content position
		_inner.scrollTop  = ( (_inner.scrollHeight-_inner.offsetHeight) * (ypos-ymin) ) / ( ymax-ymin );
		
	}
	
}


