/* Copyright (c) 2007 Paul Bakaus (paul.bakaus@googlemail.com) and Brandon Aaron (brandon.aaron@gmail.com || http://brandonaaron.net)
 * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
 * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
 *
 * $LastChangedDate$
 * $Rev$
 *
 * Version: @VERSION
 *
 * Requires: jQuery 1.2+
 */

(function($){
	
$.dimensions = {
	version: '@VERSION'
};

// Create innerHeight, innerWidth, outerHeight and outerWidth methods
$.each( [ 'Height', 'Width' ], function(i, name){
	
	// innerHeight and innerWidth
	$.fn[ 'inner' + name ] = function() {
		if (!this[0]) return;
		
		var torl = name == 'Height' ? 'Top'    : 'Left',  // top or left
		    borr = name == 'Height' ? 'Bottom' : 'Right'; // bottom or right
		
		return this.is(':visible') ? this[0]['client' + name] : num( this, name.toLowerCase() ) + num(this, 'padding' + torl) + num(this, 'padding' + borr);
	};
	
	// outerHeight and outerWidth
	$.fn[ 'outer' + name ] = function(options) {
		if (!this[0]) return;
		
		var torl = name == 'Height' ? 'Top'    : 'Left',  // top or left
		    borr = name == 'Height' ? 'Bottom' : 'Right'; // bottom or right
		
		options = $.extend({ margin: false }, options || {});
		
		var val = this.is(':visible') ? 
				this[0]['offset' + name] : 
				num( this, name.toLowerCase() )
					+ num(this, 'border' + torl + 'Width') + num(this, 'border' + borr + 'Width')
					+ num(this, 'padding' + torl) + num(this, 'padding' + borr);
		
		return val + (options.margin ? (num(this, 'margin' + torl) + num(this, 'margin' + borr)) : 0);
	};
});

// Create scrollLeft and scrollTop methods
$.each( ['Left', 'Top'], function(i, name) {
	$.fn[ 'scroll' + name ] = function(val) {
		if (!this[0]) return;
		
		return val != undefined ?
		
			// Set the scroll offset
			this.each(function() {
				this == window || this == document ?
					window.scrollTo( 
						name == 'Left' ? val : $(window)[ 'scrollLeft' ](),
						name == 'Top'  ? val : $(window)[ 'scrollTop'  ]()
					) :
					this[ 'scroll' + name ] = val;
			}) :
			
			// Return the scroll offset
			this[0] == window || this[0] == document ?
				self[ (name == 'Left' ? 'pageXOffset' : 'pageYOffset') ] ||
					$.boxModel && document.documentElement[ 'scroll' + name ] ||
					document.body[ 'scroll' + name ] :
				this[0][ 'scroll' + name ];
	};
});

$.fn.extend({
	position: function() {
		var left = 0, top = 0, elem = this[0], offset, parentOffset, offsetParent, results;
		
		if (elem) {
			// Get *real* offsetParent
			offsetParent = this.offsetParent();
			
			// Get correct offsets
			offset       = this.offset();
			parentOffset = offsetParent.offset();
			
			// Subtract element margins
			offset.top  -= num(elem, 'marginTop');
			offset.left -= num(elem, 'marginLeft');
			
			// Add offsetParent borders
			parentOffset.top  += num(offsetParent, 'borderTopWidth');
			parentOffset.left += num(offsetParent, 'borderLeftWidth');
			
			// Subtract the two offsets
			results = {
				top:  offset.top  - parentOffset.top,
				left: offset.left - parentOffset.left
			};
		}
		
		return results;
	},
	
	offsetParent: function() {
		var offsetParent = this[0].offsetParent;
		while ( offsetParent && (!/^body|html$/i.test(offsetParent.tagName) && $.css(offsetParent, 'position') == 'static') )
			offsetParent = offsetParent.offsetParent;
		return $(offsetParent);
	}
});

function num(el, prop) {
	return parseInt($.curCSS(el.jquery?el[0]:el,prop,true))||0;
};

})(jQuery);;
/*
 * positionBy 1.0.7 (2008-01-29)
 *
 * Copyright (c) 2006,2007 Jonathan Sharp (http://jdsharp.us)
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 * http://jdsharp.us/
 *
 * Built upon jQuery 1.2.2 (http://jquery.com)
 * This also requires the jQuery dimensions plugin
 */
(function($){var A=function(a,b,c,d){this.x1=a;this.x2=c;this.y1=b;this.y2=d};A.prototype.contains=function(a){return(this.x1<=a.x1&&a.x2<=this.x2)&&(this.y1<=a.y1&&a.y2<=this.y2)};A.prototype.transform=function(x,y){return new A(this.x1+x,this.y1+y,this.x2+x,this.y2+y)};$.fn.positionBy=function(q){var r=new Date();if(this.length==0){return this}var q=$.extend({target:null,targetPos:null,elementPos:null,x:null,y:null,positions:null,addClass:false,force:false,container:window},q);if(q.x!=null){var s=q.x;var t=q.y;var u=0;var v=0}else{var w=$($(q.target)[0]);var u=w.outerWidth();var v=w.outerHeight();var x=w.offset();var s=x.left;var t=x.top}var y=s+u;var z=t+v;return this.each(function(){var c=$(this);if(!c.is(':visible')){c.css({left:-3000,top:-3000}).show()}var d=c.outerWidth();var e=c.outerHeight();var f=[];var g=[];f[0]=new A(y,t,y+d,t+e);g[0]=[1,7,4];f[1]=new A(y,z-e,y+d,z);g[1]=[0,6,4];f[2]=new A(y,z,y+d,z+e);g[2]=[1,3,10];f[3]=new A(y-d,z,y,z+e);g[3]=[1,6,10];f[4]=new A(s,z,s+d,z+e);g[4]=[1,6,9];f[5]=new A(s-d,z,s,z+e);g[5]=[6,4,9];f[6]=new A(s-d,z-e,s,z);g[6]=[7,1,4];f[7]=new A(s-d,t,s,t+e);g[7]=[6,0,4];f[8]=new A(s-d,t-e,s,t);g[8]=[7,9,4];f[9]=new A(s,t-e,s+d,t);g[9]=[0,7,4];f[10]=new A(y-d,t-e,y,t);g[10]=[0,7,3];f[11]=new A(y,t-e,y+d,t);g[11]=[0,10,3];f[12]=new A(y-d,t,y,t+e);g[12]=[13,7,10];f[13]=new A(y-d,z-e,y,z);g[13]=[12,6,3];f[14]=new A(s,z-e,s+d,z);g[14]=[15,1,4];f[15]=new A(s,t,s+d,t+e);g[15]=[14,0,9];if(q.positions!==null){var h=q.positions[0]}else if(q.targetPos!=null&&q.elementPos!=null){var h=[];h[0]=[];h[0][0]=15;h[0][1]=7;h[0][2]=8;h[0][3]=9;h[1]=[];h[1][0]=0;h[1][1]=12;h[1][2]=10;h[1][3]=11;h[2]=[];h[2][0]=2;h[2][1]=3;h[2][2]=13;h[2][3]=1;h[3]=[];h[3][0]=4;h[3][1]=5;h[3][2]=6;h[3][3]=14;var h=h[q.targetPos][q.elementPos]}var i=f[h];var j=h;if(!q.force){$window=$(window);var k=$window.scrollLeft();var l=$window.scrollTop();var m=new A(k,l,k+$window.width(),l+$window.height());var n;if(q.positions){n=q.positions}else{n=[h]}var o=[];while(n.length>0){var p=n.shift();if(o[p]){continue}o[p]=true;if(!m.contains(f[p])){if(q.positions===null){n=jQuery.merge(n,g[p])}}else{i=f[p];break}}}c.parents().each(function(){var a=$(this);if(a.css('position')!='static'){var b=a.offset();i=i.transform(-b.left,-b.top);return false}});c.css({left:i.x1,top:i.y1});if(q.addClass){c.removeClass('positionBy0 positionBy1 positionBy2 positionBy3 positionBy4 positionBy5 '+'positionBy6 positionBy7 positionBy8 positionBy9 positionBy10 positionBy11 '+'positionBy12 positionBy13 positionBy14 positionBy15').addClass('positionBy'+p)}})}})(jQuery);
;
/**
 * jQuery.ScrollTo
 * Copyright (c) 2007-2009 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
 * Dual licensed under MIT and GPL.
 * Date: 5/25/2009
 *
 * @projectDescription Easy element scrolling using jQuery.
 * http://flesler.blogspot.com/2007/10/jqueryscrollto.html
 * Works with jQuery +1.2.6. Tested on FF 2/3, IE 6/7/8, Opera 9.5/6, Safari 3, Chrome 1 on WinXP.
 *
 * @author Ariel Flesler
 * @version 1.4.2
 *
 * @id jQuery.scrollTo
 * @id jQuery.fn.scrollTo
 * @param {String, Number, DOMElement, jQuery, Object} target Where to scroll the matched elements.
 *	  The different options for target are:
 *		- A number position (will be applied to all axes).
 *		- A string position ('44', '100px', '+=90', etc ) will be applied to all axes
 *		- A jQuery/DOM element ( logically, child of the element to scroll )
 *		- A string selector, that will be relative to the element to scroll ( 'li:eq(2)', etc )
 *		- A hash { top:x, left:y }, x and y can be any kind of number/string like above.
*		- A percentage of the container's dimension/s, for example: 50% to go to the middle.
 *		- The string 'max' for go-to-end. 
 * @param {Number} duration The OVERALL length of the animation, this argument can be the settings object instead.
 * @param {Object,Function} settings Optional set of settings or the onAfter callback.
 *	 @option {String} axis Which axis must be scrolled, use 'x', 'y', 'xy' or 'yx'.
 *	 @option {Number} duration The OVERALL length of the animation.
 *	 @option {String} easing The easing method for the animation.
 *	 @option {Boolean} margin If true, the margin of the target element will be deducted from the final position.
 *	 @option {Object, Number} offset Add/deduct from the end position. One number for both axes or { top:x, left:y }.
 *	 @option {Object, Number} over Add/deduct the height/width multiplied by 'over', can be { top:x, left:y } when using both axes.
 *	 @option {Boolean} queue If true, and both axis are given, the 2nd axis will only be animated after the first one ends.
 *	 @option {Function} onAfter Function to be called after the scrolling ends. 
 *	 @option {Function} onAfterFirst If queuing is activated, this function will be called after the first scrolling ends.
 * @return {jQuery} Returns the same jQuery object, for chaining.
 *
 * @desc Scroll to a fixed position
 * @example $('div').scrollTo( 340 );
 *
 * @desc Scroll relatively to the actual position
 * @example $('div').scrollTo( '+=340px', { axis:'y' } );
 *
 * @dec Scroll using a selector (relative to the scrolled element)
 * @example $('div').scrollTo( 'p.paragraph:eq(2)', 500, { easing:'swing', queue:true, axis:'xy' } );
 *
 * @ Scroll to a DOM element (same for jQuery object)
 * @example var second_child = document.getElementById('container').firstChild.nextSibling;
 *			$('#container').scrollTo( second_child, { duration:500, axis:'x', onAfter:function(){
 *				alert('scrolled!!');																   
 *			}});
 *
 * @desc Scroll on both axes, to different values
 * @example $('div').scrollTo( { top: 300, left:'+=200' }, { axis:'xy', offset:-20 } );
 */
;(function( $ ){
	
	var $scrollTo = $.scrollTo = function( target, duration, settings ){
		$(window).scrollTo( target, duration, settings );
	};

	$scrollTo.defaults = {
		axis:'xy',
		duration: parseFloat($.fn.jquery) >= 1.3 ? 0 : 1
	};

	// Returns the element that needs to be animated to scroll the window.
	// Kept for backwards compatibility (specially for localScroll & serialScroll)
	$scrollTo.window = function( scope ){
		return $(window)._scrollable();
	};

	// Hack, hack, hack :)
	// Returns the real elements to scroll (supports window/iframes, documents and regular nodes)
	$.fn._scrollable = function(){
		return this.map(function(){
			var elem = this,
				isWin = !elem.nodeName || $.inArray( elem.nodeName.toLowerCase(), ['iframe','#document','html','body'] ) != -1;

				if( !isWin )
					return elem;

			var doc = (elem.contentWindow || elem).document || elem.ownerDocument || elem;
			
			return $.browser.safari || doc.compatMode == 'BackCompat' ?
				doc.body : 
				doc.documentElement;
		});
	};

	$.fn.scrollTo = function( target, duration, settings ){
		if( typeof duration == 'object' ){
			settings = duration;
			duration = 0;
		}
		if( typeof settings == 'function' )
			settings = { onAfter:settings };
			
		if( target == 'max' )
			target = 9e9;
			
		settings = $.extend( {}, $scrollTo.defaults, settings );
		// Speed is still recognized for backwards compatibility
		duration = duration || settings.speed || settings.duration;
		// Make sure the settings are given right
		settings.queue = settings.queue && settings.axis.length > 1;
		
		if( settings.queue )
			// Let's keep the overall duration
			duration /= 2;
		settings.offset = both( settings.offset );
		settings.over = both( settings.over );

		return this._scrollable().each(function(){
			var elem = this,
				$elem = $(elem),
				targ = target, toff, attr = {},
				win = $elem.is('html,body');

			switch( typeof targ ){
				// A number will pass the regex
				case 'number':
				case 'string':
					if( /^([+-]=)?\d+(\.\d+)?(px|%)?$/.test(targ) ){
						targ = both( targ );
						// We are done
						break;
					}
					// Relative selector, no break!
					targ = $(targ,this);
				case 'object':
					// DOMElement / jQuery
					if( targ.is || targ.style )
						// Get the real position of the target 
						toff = (targ = $(targ)).offset();
			}
			$.each( settings.axis.split(''), function( i, axis ){
				var Pos	= axis == 'x' ? 'Left' : 'Top',
					pos = Pos.toLowerCase(),
					key = 'scroll' + Pos,
					old = elem[key],
					max = $scrollTo.max(elem, axis);

				if( toff ){// jQuery / DOMElement
					attr[key] = toff[pos] + ( win ? 0 : old - $elem.offset()[pos] );

					// If it's a dom element, reduce the margin
					if( settings.margin ){
						attr[key] -= parseInt(targ.css('margin'+Pos)) || 0;
						attr[key] -= parseInt(targ.css('border'+Pos+'Width')) || 0;
					}
					
					attr[key] += settings.offset[pos] || 0;
					
					if( settings.over[pos] )
						// Scroll to a fraction of its width/height
						attr[key] += targ[axis=='x'?'width':'height']() * settings.over[pos];
				}else{ 
					var val = targ[pos];
					// Handle percentage values
					attr[key] = val.slice && val.slice(-1) == '%' ? 
						parseFloat(val) / 100 * max
						: val;
				}

				// Number or 'number'
				if( /^\d+$/.test(attr[key]) )
					// Check the limits
					attr[key] = attr[key] <= 0 ? 0 : Math.min( attr[key], max );

				// Queueing axes
				if( !i && settings.queue ){
					// Don't waste time animating, if there's no need.
					if( old != attr[key] )
						// Intermediate animation
						animate( settings.onAfterFirst );
					// Don't animate this axis again in the next iteration.
					delete attr[key];
				}
			});

			animate( settings.onAfter );			

			function animate( callback ){
				$elem.animate( attr, duration, settings.easing, callback && function(){
					callback.call(this, target, settings);
				});
			};

		}).end();
	};
	
	// Max scrolling position, works on quirks mode
	// It only fails (not too badly) on IE, quirks mode.
	$scrollTo.max = function( elem, axis ){
		var Dim = axis == 'x' ? 'Width' : 'Height',
			scroll = 'scroll'+Dim;
		
		if( !$(elem).is('html,body') )
			return elem[scroll] - $(elem)[Dim.toLowerCase()]();
		
		var size = 'client' + Dim,
			html = elem.ownerDocument.documentElement,
			body = elem.ownerDocument.body;

		return Math.max( html[scroll], body[scroll] ) 
			 - Math.min( html[size]  , body[size]   );
			
	};

	function both( val ){
		return typeof val == 'object' ? val : { top:val, left:val };
	};

})( jQuery );;
/*
 * jdMenu 1.4.0 (2008-01-25)
 *
 * Copyright (c) 2006,2007 Jonathan Sharp (http://jdsharp.us)
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 * http://jdsharp.us/
 *
 * Built upon jQuery 1.2.1 (http://jquery.com)
 * This also requires the jQuery dimensions >= 1.2 plugin
 */
$(function() {
	$('ul.jd_menu').jdMenu();
});

(function($){
	$.jdMenu = {
		settings: 	[],
		getSettings: 	function( element ) {
							var t = $(element).parents('ul.jd_menu:eq(0)')[0];
							return this.settings[ t && t.$jdSettings ? t.$jdSettings : 0 ];	
						}
	};
	
	function activateMenu(ul) {
		var ul = $(ul);
		var li = ul.parent();
		ul	.trigger('jdMenuShow')
			.positionBy({ 	target: 	li[0], 
							targetPos: 	( li.parent().is('.jd_menu') ? 3 : 1 ), 
							elementPos: 0 
							});
		li	.addClass('jdm_active')
			// Hide any adjacent menus
			.siblings('li').find('ul:eq(0):visible')
				.each(function(){
					hideMenu( this ); 
				});
	}
	
	function hideMenu(ul) {
		$(ul)
			.filter(':not(.jd_menu)')
			.find('> li ul:eq(0):visible')
				.each(function() {
					hideMenu( this );
				})
			.end()
			.hide()
			.trigger('jdMenuHide')
			.parents('li:eq(0)')
				.removeClass('jdm_active jdm_hover')
			.end()
				.find('> li')
				.removeClass('jdm_active jdm_hover');
	}
	
	function getSettings(element) {
		return $.data( $(element).is('.jd_menu') ? element : $(element).parents('ul.jd_menu')[0], 'jdMenuSettings');;
	}
	
	// Public methods
	$.fn.jdMenu = function(settings) {
		var settings = $.extend({	activateDelay: 	500,
					showDelay: 		450, 
					hideDelay: 		1000
					}, settings);
		return this.filter('ul.jd_menu').each(function() {
			$.data(this, 'jdMenuSettings', settings);
			$('li', this)
				.bind('mouseenter.jdmenu', function() {
					$(this).addClass('jdm_hover');
					var ul = $('ul:eq(0)', this);
					if ( ul.length == 1 ) {
						var me = this;
						clearTimeout( this.$jdTimer );
						this.$jdTimer = setTimeout(function() {
							activateMenu( ul );
						}, getSettings(this).showDelay );
					}
				})
				.bind('mouseleave.jdmenu', function(){
					$(this).removeClass('jdm_hover');
					var ul = $('ul:eq(0)', this);
					if ( ul.length == 1 ) {
						var settings = $.jdMenu.getSettings( this );
						var me = this;
						clearTimeout( this.$jdTimer );
						this.$jdTimer = setTimeout(function() {
							hideMenu( ul );
						}, getSettings(this).hideDelay );
					}
				})
				.bind('click.jdmenu', function(evt) {
					var ul = $('> ul', this);
					if ( ul.length == 1 ) {
						activateMenu( ul );
						//return false;
					}
					
					// The user clicked the li and we need to trigger a click for the a
					if ( evt.target == this ) {
						var link = $('> a', evt.target).not('.accessible');
						if ( link.length > 0 ) {
							var a = link[0];
							if ( !a.onclick ) {
								window.open( a.href, a.target || '_self' );
							} else {
								$(a).trigger('click');
							}
						}
					}
					$(this).parent().jdMenuHide();
					evt.stopPropagation();
				})
				.bind('keydown.jdmenu', function(e) {
					if ( e.which == 27 ) {
						if ( !$(this).parent().is('.jd_menu') ) {
							hideMenu( $(this).parent()[0] );
						}
						$(this).parents('li:eq(0)').find('a:eq(0)').trigger('focus');
						return false;
					}
				})
				.find('> a')
					.bind('focus.jdmenu', function() {
						$(this).parents('li:eq(0)').addClass('jdm_hover');
					})
					.bind('blur.jdmenu', function() {
						$(this).parents('li:eq(0)').removeClass('jdm_hover');
					})
					.filter('.accessible')
						.bind('click.jdmenu', function(evt) {
							evt.preventDefault();
						});
		});
	};
	
	$.fn.jdMenuUnbind = function() {
		$('li', this)
			.unbind('mouseenter.jdmenu mouseleave.jdmenu click.jdmenu keydown.jdmenu')
			.find('> a').unbind('focus.jdmenu blur.jdmenu click.jdmenu');
		return this;
	};
	
	$.fn.jdMenuHide = function() {
		return this.filter('ul').each(function(){ hideMenu( this ); });
	};

	// Private methods and logic
	$(window)
		// Bind a click event to hide all visible menus when the document is clicked
		.bind('click.jdmenu', function(){
			$('ul.jd_menu ul:visible').jdMenuHide();
		});
})(jQuery);
;
﻿/**
* hoverIntent is similar to jQuery's built-in "hover" function except that
* instead of firing the onMouseOver event immediately, hoverIntent checks
* to see if the user's mouse has slowed down (beneath the sensitivity
* threshold) before firing the onMouseOver event.
* 
* hoverIntent r5 // 2007.03.27 // jQuery 1.1.2+
* <http://cherne.net/brian/resources/jquery.hoverIntent.html>
* 
* hoverIntent is currently available for use in all personal or commercial 
* projects under both MIT and GPL licenses. This means that you can choose 
* the license that best suits your project, and use it accordingly.
* 
* // basic usage (just like .hover) receives onMouseOver and onMouseOut functions
* $("ul li").hoverIntent( showNav , hideNav );
* 
* // advanced usage receives configuration object only
* $("ul li").hoverIntent({
*	sensitivity: 7, // number = sensitivity threshold (must be 1 or higher)
*	interval: 100,   // number = milliseconds of polling interval
*	over: showNav,  // function = onMouseOver callback (required)
*	timeout: 0,   // number = milliseconds delay before onMouseOut function call
*	out: hideNav    // function = onMouseOut callback (required)
* });
* 
* @param  f  onMouseOver function || An object with configuration options
* @param  g  onMouseOut function  || Nothing (use configuration options object)
* @author    Brian Cherne <brian@cherne.net>
*/
(function($) {
	$.fn.hoverIntent = function(f,g) {
		// default configuration options
		var cfg = {
			sensitivity: 7,
			interval: 100,
			timeout: 0
		};
		// override configuration options with user supplied object
		cfg = $.extend(cfg, g ? { over: f, out: g } : f );

		// instantiate variables
		// cX, cY = current X and Y position of mouse, updated by mousemove event
		// pX, pY = previous X and Y position of mouse, set by mouseover and polling interval
		var cX, cY, pX, pY;

		// A private function for getting mouse position
		var track = function(ev) {
			cX = ev.pageX;
			cY = ev.pageY;
		};

		// A private function for comparing current and previous mouse position
		var compare = function(ev,ob) {
			ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
			// compare mouse positions to see if they've crossed the threshold
			if ( ( Math.abs(pX-cX) + Math.abs(pY-cY) ) < cfg.sensitivity ) {
				$(ob).unbind("mousemove",track);
				// set hoverIntent state to true (so mouseOut can be called)
				ob.hoverIntent_s = 1;
				return cfg.over.apply(ob,[ev]);
			} else {
				// set previous coordinates for next time
				pX = cX; pY = cY;
				// use self-calling timeout, guarantees intervals are spaced out properly (avoids JavaScript timer bugs)
				ob.hoverIntent_t = setTimeout( function(){compare(ev, ob);} , cfg.interval );
			}
		};

		// A private function for delaying the mouseOut function
		var delay = function(ev,ob) {
			ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
			ob.hoverIntent_s = 0;
			return cfg.out.apply(ob,[ev]);
		};

		// A private function for handling mouse 'hovering'
		var handleHover = function(e) {
			// next three lines copied from jQuery.hover, ignore children onMouseOver/onMouseOut
			var p = (e.type == "mouseover" ? e.fromElement : e.toElement) || e.relatedTarget;
			while ( p && p != this ) { try { p = p.parentNode; } catch(e) { p = this; } }
			if ( p == this ) { return false; }

			// copy objects to be passed into t (required for event object to be passed in IE)
			var ev = jQuery.extend({},e);
			var ob = this;

			// cancel hoverIntent timer if it exists
			if (ob.hoverIntent_t) { ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t); }

			// else e.type == "onmouseover"
			if (e.type == "mouseover") {
				// set "previous" X and Y position based on initial entry point
				pX = ev.pageX; pY = ev.pageY;
				// update "current" X and Y position based on mousemove
				$(ob).bind("mousemove",track);
				// start polling interval (self-calling timeout) to compare mouse coordinates over time
				if (ob.hoverIntent_s != 1) { ob.hoverIntent_t = setTimeout( function(){compare(ev,ob);} , cfg.interval );}

			// else e.type == "onmouseout"
			} else {
				// unbind expensive mousemove event
				$(ob).unbind("mousemove",track);
				// if hoverIntent state is true, then call the mouseOut function after the specified delay
				if (ob.hoverIntent_s == 1) { ob.hoverIntent_t = setTimeout( function(){delay(ev,ob);} , cfg.timeout );}
			}
		};

		// bind the function to the two event listeners
		return this.mouseover(handleHover).mouseout(handleHover);
	};
})(jQuery);;
/**
 * @license 
 * jQuery Tools 1.2.5 Tooltip - UI essentials
 * 
 * NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE.
 * 
 * http://flowplayer.org/tools/tooltip/
 *
 * Since: November 2008
 * Date:    Wed Sep 22 06:02:10 2010 +0000 
 */
(function($) { 	
	// static constructs
	$.tools = $.tools || {version: '1.2.5'};
	
	$.tools.tooltip = {
		
		conf: { 
			
			// default effect variables
			effect: 'toggle',			
			fadeOutSpeed: "fast",
			predelay: 0,
			delay: 30,
			opacity: 1,			
			tip: 0,
			
			// 'top', 'bottom', 'right', 'left', 'center'
			position: ['top', 'center'], 
			offset: [0, 0],
			relative: false,
			cancelDefault: true,
			
			// type to event mapping 
			events: {
				def: 			"mouseenter,mouseleave",
				input: 		"focus,blur",
				widget:		"focus mouseenter,blur mouseleave",
				tooltip:		"mouseenter,mouseleave"
			},
			
			// 1.2
			layout: '<div/>',
			tipClass: 'tooltip'
		},
		
		addEffect: function(name, loadFn, hideFn) {
			effects[name] = [loadFn, hideFn];	
		} 
	};
	
	
	var effects = { 
		toggle: [ 
			function(done) { 
				var conf = this.getConf(), tip = this.getTip(), o = conf.opacity;
				if (o < 1) { tip.css({opacity: o}); }
				tip.show();
				done.call();
			},
			
			function(done) { 
				this.getTip().hide();
				done.call();
			} 
		],
		
		fade: [
			function(done) { 
				var conf = this.getConf();
				this.getTip().fadeTo(conf.fadeInSpeed, conf.opacity, done); 
			},  
			function(done) { 
				this.getTip().fadeOut(this.getConf().fadeOutSpeed, done); 
			} 
		]		
	};   

		
	/* calculate tip position relative to the trigger */  	
	function getPosition(trigger, tip, conf) {	

		
		// get origin top/left position 
		var top = conf.relative ? trigger.position().top : trigger.offset().top, 
			 left = conf.relative ? trigger.position().left : trigger.offset().left,
			 pos = conf.position[0];

		top  -= tip.outerHeight() - conf.offset[0];
		left += trigger.outerWidth() + conf.offset[1];
		
		// iPad position fix
		if (/iPad/i.test(navigator.userAgent)) {
			top -= $(window).scrollTop();
		}
		
		// adjust Y		
		var height = tip.outerHeight() + trigger.outerHeight();
		if (pos == 'center') 	{ top += height / 2; }
		if (pos == 'bottom') 	{ top += height; }
		
		
		// adjust X
		pos = conf.position[1]; 	
		var width = tip.outerWidth() + trigger.outerWidth();
		if (pos == 'center') 	{ left -= width / 2; }
		if (pos == 'left')   	{ left -= width; }	 
		
		return {top: top, left: left};
	}		

	
	
	function Tooltip(trigger, conf) {

		var self = this, 
			 fire = trigger.add(self),
			 tip,
			 timer = 0,
			 pretimer = 0, 
			 title = trigger.attr("title"),
			 tipAttr = trigger.attr("data-tooltip"),
			 effect = effects[conf.effect],
			 shown,
				 
			 // get show/hide configuration
			 isInput = trigger.is(":input"), 
			 isWidget = isInput && trigger.is(":checkbox, :radio, select, :button, :submit"),			
			 type = trigger.attr("type"),
			 evt = conf.events[type] || conf.events[isInput ? (isWidget ? 'widget' : 'input') : 'def']; 
		
		
		// check that configuration is sane
		if (!effect) { throw "Nonexistent effect \"" + conf.effect + "\""; }					
		
		evt = evt.split(/,\s*/); 
		if (evt.length != 2) { throw "Tooltip: bad events configuration for " + type; } 
		
		
		// trigger --> show  
		trigger.bind(evt[0], function(e) {

			clearTimeout(timer);
			if (conf.predelay) {
				pretimer = setTimeout(function() { self.show(e); }, conf.predelay);	
				
			} else {
				self.show(e);	
			}
			
		// trigger --> hide
		}).bind(evt[1], function(e)  {
			clearTimeout(pretimer);
			if (conf.delay)  {
				timer = setTimeout(function() { self.hide(e); }, conf.delay);	
				
			} else {
				self.hide(e);		
			}
			
		}); 
		
		
		// remove default title
		if (title && conf.cancelDefault) { 
			trigger.removeAttr("title");
			trigger.data("title", title);			
		}		
		
		$.extend(self, {
				
			show: function(e) {  

				// tip not initialized yet
				if (!tip) {
					
					// data-tooltip 
					if (tipAttr) {
						tip = $(tipAttr);

					// single tip element for all
					} else if (conf.tip) { 
						tip = $(conf.tip).eq(0);
						
					// autogenerated tooltip
					} else if (title) { 
						tip = $(conf.layout).addClass(conf.tipClass).appendTo(document.body)
							.hide().append(title);

					// manual tooltip
					} else {	
						tip = trigger.next();  
						if (!tip.length) { tip = trigger.parent().next(); } 	 
					}
					
					if (!tip.length) { throw "Cannot find tooltip for " + trigger;	}
				} 
			 	
			 	if (self.isShown()) { return self; }  
				
			 	// stop previous animation
			 	tip.stop(true, true); 			 	
			 	
				// get position
				var pos = getPosition(trigger, tip, conf);			
		
				// restore title for single tooltip element
				if (conf.tip) {
					tip.html(trigger.data("title"));
				}

				// onBeforeShow
				e = e || $.Event();
				e.type = "onBeforeShow";
				fire.trigger(e, [pos]);				
				if (e.isDefaultPrevented()) { return self; }
		
				
				// onBeforeShow may have altered the configuration
				pos = getPosition(trigger, tip, conf);
				
				// set position
				tip.css({position:'absolute', top: pos.top, left: pos.left});					
				
				shown = true;
				
				// invoke effect 
				effect[0].call(self, function() {
					e.type = "onShow";
					shown = 'full';
					fire.trigger(e);		 
				});					

	 	
				// tooltip events       
				var event = conf.events.tooltip.split(/,\s*/);

				if (!tip.data("__set")) {
					
					tip.bind(event[0], function() { 
						clearTimeout(timer);
						clearTimeout(pretimer);
					});
					
					if (event[1] && !trigger.is("input:not(:checkbox, :radio), textarea")) { 					
						tip.bind(event[1], function(e) {
	
							// being moved to the trigger element
							if (e.relatedTarget != trigger[0]) {
								trigger.trigger(evt[1].split(" ")[0]);
							}
						}); 
					} 
					
					tip.data("__set", true);
				}
				
				return self;
			},
			
			hide: function(e) {

				if (!tip || !self.isShown()) { return self; }
			
				// onBeforeHide
				e = e || $.Event();
				e.type = "onBeforeHide";
				fire.trigger(e);				
				if (e.isDefaultPrevented()) { return; }
	
				shown = false;
				
				effects[conf.effect][1].call(self, function() {
					e.type = "onHide";
					fire.trigger(e);		 
				});
				
				return self;
			},
			
			isShown: function(fully) {
				return fully ? shown == 'full' : shown;	
			},
				
			getConf: function() {
				return conf;	
			},
				
			getTip: function() {
				return tip;	
			},
			
			getTrigger: function() {
				return trigger;	
			}		

		});		

		// callbacks	
		$.each("onHide,onBeforeShow,onShow,onBeforeHide".split(","), function(i, name) {
				
			// configuration
			if ($.isFunction(conf[name])) { 
				$(self).bind(name, conf[name]); 
			}

			// API
			self[name] = function(fn) {
				if (fn) { $(self).bind(name, fn); }
				return self;
			};
		});
		
	}
		
	
	// jQuery plugin implementation
	$.fn.tooltip = function(conf) {
		
		// return existing instance
		var api = this.data("tooltip");
		if (api) { return api; }

		conf = $.extend(true, {}, $.tools.tooltip.conf, conf);
		
		// position can also be given as string
		if (typeof conf.position == 'string') {
			conf.position = conf.position.split(/,?\s/);	
		}
		
		// install tooltip for each entry in jQuery object
		this.each(function() {
			api = new Tooltip($(this), conf); 
			$(this).data("tooltip", api); 
		});
		
		return conf.api ? api: this;		 
	};
		
}) (jQuery);

		

;
/**
 * @license 
 * jQuery Tools 1.2.5 / Tooltip Slide Effect
 * 
 * NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE.
 * 
 * http://flowplayer.org/tools/tooltip/slide.html
 *
 * Since: September 2009
 * Date:    Wed Sep 22 06:02:10 2010 +0000 
 */
(function($) { 

	// version number
	var t = $.tools.tooltip;
		
	// extend global configuragion with effect specific defaults
	$.extend(t.conf, { 
		direction: 'up', // down, left, right 
		bounce: false,
		slideOffset: 10,
		slideInSpeed: 200,
		slideOutSpeed: 200, 
		slideFade: !$.browser.msie
	});			
	
	// directions for slide effect
	var dirs = {
		up: ['-', 'top'],
		down: ['+', 'top'],
		left: ['-', 'left'],
		right: ['+', 'left']
	};
	
	/* default effect: "slide"  */
	t.addEffect("slide", 
		
		// show effect
		function(done) { 

			// variables
			var conf = this.getConf(), 
				 tip = this.getTip(),
				 params = conf.slideFade ? {opacity: conf.opacity} : {}, 
				 dir = dirs[conf.direction] || dirs.up;

			// direction			
			params[dir[1]] = dir[0] +'='+ conf.slideOffset;
			
			// perform animation
			if (conf.slideFade) { tip.css({opacity:0}); }
			tip.show().animate(params, conf.slideInSpeed, done); 
		}, 
		
		// hide effect
		function(done) {
			
			// variables
			var conf = this.getConf(), 
				 offset = conf.slideOffset,
				 params = conf.slideFade ? {opacity: 0} : {}, 
				 dir = dirs[conf.direction] || dirs.up;
			
			// direction
			var sign = "" + dir[0];
			if (conf.bounce) { sign = sign == '+' ? '-' : '+'; }			
			params[dir[1]] = sign +'='+ offset;			
			
			// perform animation
			this.getTip().animate(params, conf.slideOutSpeed, function()  {
				$(this).hide();
				done.call();		
			});
		}
	);  
	
})(jQuery);	
		
;
/**
 * @license 
 * jQuery Tools 1.2.5 / Tooltip Dynamic Positioning
 * 
 * NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE.
 * 
 * http://flowplayer.org/tools/tooltip/dynamic.html
 *
 * Since: July 2009
 * Date:    Wed Sep 22 06:02:10 2010 +0000 
 */
(function($) { 

	// version number
	var t = $.tools.tooltip;
	
	t.dynamic = {
		conf: {
			classNames: "top right bottom left"
		}
	};
		
	/* 
	 * See if element is on the viewport. Returns an boolean array specifying which
	 * edges are hidden. Edges are in following order:
	 * 
	 * [top, right, bottom, left]
	 * 
	 * For example following return value means that top and right edges are hidden
	 * 
	 * [true, true, false, false]
	 * 
	 */
	function getCropping(el) {
		
		var w = $(window); 
		var right = w.width() + w.scrollLeft();
		var bottom = w.height() + w.scrollTop();		
		
		return [
			el.offset().top <= w.scrollTop(), 						// top
			right <= el.offset().left + el.width(),				// right
			bottom <= el.offset().top + el.height(),			// bottom
			w.scrollLeft() >= el.offset().left 					// left
		]; 
	}
	
	/*
		Returns true if all edges of an element are on viewport. false if not
		
		@param crop the cropping array returned by getCropping function
	 */
	function isVisible(crop) {
		var i = crop.length;
		while (i--) {
			if (crop[i]) { return false; }	
		}
		return true;
	}
	
	// dynamic plugin
	$.fn.dynamic = function(conf) {
		
		if (typeof conf == 'number') { conf = {speed: conf}; }
		
		conf = $.extend({}, t.dynamic.conf, conf);
		
		var cls = conf.classNames.split(/\s/), orig;	
			
		this.each(function() {		
				
			var api = $(this).tooltip().onBeforeShow(function(e, pos) {				

				// get nessessary variables
				var tip = this.getTip(), tipConf = this.getConf();  

				/*
					We store the original configuration and use it to restore back to the original state.
				*/					
				if (!orig) {
					orig = [
						tipConf.position[0], 
						tipConf.position[1], 
						tipConf.offset[0], 
						tipConf.offset[1], 
						$.extend({}, tipConf)
					];
				}
				
				/*
					display tip in it's default position and by setting visibility to hidden.
					this way we can check whether it will be on the viewport
				*/
				$.extend(tipConf, orig[4]);
				tipConf.position = [orig[0], orig[1]];
				tipConf.offset = [orig[2], orig[3]];

				tip.css({
					visibility: 'hidden',
					position: 'absolute',
					top: pos.top,
					left: pos.left 
				}).show(); 
				
				// now let's see for hidden edges
				var crop = getCropping(tip);		
								
				// possibly alter the configuration
				if (!isVisible(crop)) {
					
					// change the position and add class
					if (crop[2]) { $.extend(tipConf, conf.top);		tipConf.position[0] = 'top'; 		tip.addClass(cls[0]); }
					if (crop[3]) { $.extend(tipConf, conf.right);	tipConf.position[1] = 'right'; 	tip.addClass(cls[1]); }					
					if (crop[0]) { $.extend(tipConf, conf.bottom); 	tipConf.position[0] = 'bottom';	tip.addClass(cls[2]); } 
					if (crop[1]) { $.extend(tipConf, conf.left);		tipConf.position[1] = 'left'; 	tip.addClass(cls[3]); }					
					
					// vertical offset
					if (crop[0] || crop[2]) { tipConf.offset[0] *= -1; }
					
					// horizontal offset
					if (crop[1] || crop[3]) { tipConf.offset[1] *= -1; }
				}  
				
				tip.css({visibility: 'visible'}).hide();
		
			});
			
			// restore positioning as soon as possible
			api.onBeforeShow(function() {
				var c = this.getConf(), tip = this.getTip();		 
				setTimeout(function() { 
					c.position = [orig[0], orig[1]];
					c.offset = [orig[2], orig[3]];
				}, 0);
			});
			
			// remove custom class names and restore original effect
			api.onHide(function() {
				var tip = this.getTip(); 
				tip.removeClass(conf.classNames);
			});
				
			ret = api;
			
		});
		
		return conf.api ? ret : this;
	};	
	
}) (jQuery);
;
/* ------------------------------------------------------------------------
	Class: prettyPhoto
	Use: Lightbox clone for jQuery
	Author: Stephane Caron (http://www.no-margin-for-errors.com)
	Version: 3.0.1
------------------------------------------------------------------------- */

(function($) {
	$.prettyPhoto = {version: '3.0'};
	
	$.fn.prettyPhoto = function(pp_settings) {
		pp_settings = jQuery.extend({
			animation_speed: 'fast', /* fast/slow/normal */
			slideshow: false, /* false OR interval time in ms */
			autoplay_slideshow: false, /* true/false */
			opacity: 0.80, /* Value between 0 and 1 */
			show_title: true, /* true/false */
			allow_resize: true, /* Resize the photos bigger than viewport. true/false */
			default_width: 500,
			default_height: 344,
			counter_separator_label: '/', /* The separator for the gallery counter 1 "of" 2 */
			theme: 'facebook', /* light_rounded / dark_rounded / light_square / dark_square / facebook */
			hideflash: false, /* Hides all the flash object on a page, set to TRUE if flash appears over prettyPhoto */
			wmode: 'opaque', /* Set the flash wmode attribute */
			autoplay: true, /* Automatically start videos: True/False */
			modal: false, /* If set to true, only the close button will close the window */
			overlay_gallery: true, /* If set to true, a gallery will overlay the fullscreen image on mouse over */
			keyboard_shortcuts: true, /* Set to false if you open forms inside prettyPhoto */
			changepicturecallback: function(){}, /* Called everytime an item is shown/changed */
			callback: function(){}, /* Called when prettyPhoto is closed */
			markup: '<div class="pp_pic_holder"> \
						<div class="ppt">&nbsp;</div> \
						<div class="pp_top"> \
							<div class="pp_left"></div> \
							<div class="pp_middle"></div> \
							<div class="pp_right"></div> \
						</div> \
						<div class="pp_content_container"> \
							<div class="pp_left"> \
							<div class="pp_right"> \
								<div class="pp_content"> \
									<div class="pp_loaderIcon"></div> \
									<div class="pp_fade"> \
										<a href="#" class="pp_expand" title="Expand the image">Expand</a> \
										<div class="pp_hoverContainer"> \
											<a class="pp_next" href="#">next</a> \
											<a class="pp_previous" href="#">previous</a> \
										</div> \
										<div id="pp_full_res"></div> \
										<div class="pp_details clearfix"> \
											<p class="pp_description"></p> \
											<a class="pp_close" href="#">Close</a> \
											<div class="pp_nav"> \
												<a href="#" class="pp_arrow_previous">Previous</a> \
												<p class="currentTextHolder">0/0</p> \
												<a href="#" class="pp_arrow_next">Next</a> \
											</div> \
										</div> \
									</div> \
								</div> \
							</div> \
							</div> \
						</div> \
						<div class="pp_bottom"> \
							<div class="pp_left"></div> \
							<div class="pp_middle"></div> \
							<div class="pp_right"></div> \
						</div> \
					</div> \
					<div class="pp_overlay"></div>',
			gallery_markup: '<div class="pp_gallery"> \
								<a href="#" class="pp_arrow_previous">Previous</a> \
								<ul> \
									{gallery} \
								</ul> \
								<a href="#" class="pp_arrow_next">Next</a> \
							</div>',
			image_markup: '<img id="fullResImage" src="" />',
			flash_markup: '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="{width}" height="{height}"><param name="wmode" value="{wmode}" /><param name="allowfullscreen" value="true" /><param name="allowscriptaccess" value="always" /><param name="movie" value="{path}" /><embed src="{path}" type="application/x-shockwave-flash" allowfullscreen="true" allowscriptaccess="always" width="{width}" height="{height}" wmode="{wmode}"></embed></object>',
			quicktime_markup: '<object classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" codebase="http://www.apple.com/qtactivex/qtplugin.cab" height="{height}" width="{width}"><param name="src" value="{path}"><param name="autoplay" value="{autoplay}"><param name="type" value="video/quicktime"><embed src="{path}" height="{height}" width="{width}" autoplay="{autoplay}" type="video/quicktime" pluginspage="http://www.apple.com/quicktime/download/"></embed></object>',
			iframe_markup: '<iframe src ="{path}" width="{width}" height="{height}" frameborder="no"></iframe>',
			inline_markup: '<div class="pp_inline clearfix">{content}</div>',
			custom_markup: ''
		}, pp_settings);
		
		// Global variables accessible only by prettyPhoto
		var matchedObjects = this, percentBased = false, correctSizes, pp_open,
		
		// prettyPhoto container specific
		pp_contentHeight, pp_contentWidth, pp_containerHeight, pp_containerWidth,
		
		// Window size
		windowHeight = $(window).height(), windowWidth = $(window).width(),

		// Global elements
		pp_slideshow;
		
		doresize = true, scroll_pos = _get_scroll();
	
		// Window/Keyboard events
		$(window).unbind('resize').resize(function(){ _center_overlay(); _resize_overlay(); });
		
		if(pp_settings.keyboard_shortcuts) {
			$(document).unbind('keydown').keydown(function(e){
				if(typeof $pp_pic_holder != 'undefined'){
					if($pp_pic_holder.is(':visible')){
						switch(e.keyCode){
							case 37:
								$.prettyPhoto.changePage('previous');
								break;
							case 39:
								$.prettyPhoto.changePage('next');
								break;
							case 27:
								if(!settings.modal)
								$.prettyPhoto.close();
								break;
						};
						return false;
					};
				};
			});
		}
		
		
		/**
		* Initialize prettyPhoto.
		*/
		$.prettyPhoto.initialize = function() {
			settings = pp_settings;
			
			if($.browser.msie && parseInt($.browser.version) == 6) settings.theme = "light_square"; // Fallback to a supported theme for IE6
			
			_buildOverlay(this); // Build the overlay {this} being the caller
			
			if(settings.allow_resize)
				$(window).scroll(function(){ _center_overlay(); });
				
			_center_overlay();
			
			set_position = jQuery.inArray($(this).attr('href'), pp_images); // Define where in the array the clicked item is positionned
			
			$.prettyPhoto.open();
			
			return false;
		}


		/**
		* Opens the prettyPhoto modal box.
		* @param image {String,Array} Full path to the image to be open, can also be an array containing full images paths.
		* @param title {String,Array} The title to be displayed with the picture, can also be an array containing all the titles.
		* @param description {String,Array} The description to be displayed with the picture, can also be an array containing all the descriptions.
		*/
		$.prettyPhoto.open = function(event) {
			if(typeof settings == "undefined"){ // Means it's an API call, need to manually get the settings and set the variables
				settings = pp_settings;
				if($.browser.msie && $.browser.version == 6) settings.theme = "light_square"; // Fallback to a supported theme for IE6
				_buildOverlay(event.target); // Build the overlay {this} being the caller
				pp_images = $.makeArray(arguments[0]);
				pp_titles = (arguments[1]) ? $.makeArray(arguments[1]) : $.makeArray("");
				pp_descriptions = (arguments[2]) ? $.makeArray(arguments[2]) : $.makeArray("");
				isSet = (pp_images.length > 1) ? true : false;
				set_position = 0;
			}

			if($.browser.msie && $.browser.version == 6) $('select').css('visibility','hidden'); // To fix the bug with IE select boxes
			
			if(settings.hideflash) $('object,embed').css('visibility','hidden'); // Hide the flash

			_checkPosition($(pp_images).size()); // Hide the next/previous links if on first or last images.
		
			$('.pp_loaderIcon').show();
		
			// Fade the content in
			if($ppt.is(':hidden')) $ppt.css('opacity',0).show();
			$pp_overlay.show().fadeTo(settings.animation_speed,settings.opacity);

			// Display the current position
			$pp_pic_holder.find('.currentTextHolder').text((set_position+1) + settings.counter_separator_label + $(pp_images).size());

			// Set the description
			$pp_pic_holder.find('.pp_description').show().html(unescape(pp_descriptions[set_position]));

			// Set the title
			(settings.show_title && pp_titles[set_position] != "" && typeof pp_titles[set_position] != "undefined") ? $ppt.html(unescape(pp_titles[set_position])) : $ppt.html('&nbsp;');
			
			// Get the dimensions
			movie_width = ( parseFloat(grab_param('width',pp_images[set_position])) ) ? grab_param('width',pp_images[set_position]) : settings.default_width.toString();
			movie_height = ( parseFloat(grab_param('height',pp_images[set_position])) ) ? grab_param('height',pp_images[set_position]) : settings.default_height.toString();
			
			// If the size is % based, calculate according to window dimensions
			if(movie_width.indexOf('%') != -1 || movie_height.indexOf('%') != -1){
				movie_height = parseFloat(($(window).height() * parseFloat(movie_height) / 100) - 150);
				movie_width = parseFloat(($(window).width() * parseFloat(movie_width) / 100) - 150);
				percentBased = true;
			}else{
				percentBased = false;
			}
			
			// Fade the holder
			$pp_pic_holder.fadeIn(function(){
				imgPreloader = "";
				
				// Inject the proper content
				switch(_getFileType(pp_images[set_position])){
					case 'image':
						imgPreloader = new Image();

						// Preload the neighbour images
						nextImage = new Image();
						if(isSet && set_position > $(pp_images).size()) nextImage.src = pp_images[set_position + 1];
						prevImage = new Image();
						if(isSet && pp_images[set_position - 1]) prevImage.src = pp_images[set_position - 1];

						$pp_pic_holder.find('#pp_full_res')[0].innerHTML = settings.image_markup;
						$pp_pic_holder.find('#fullResImage').attr('src',pp_images[set_position]);

						imgPreloader.onload = function(){
							// Fit item to viewport
							correctSizes = _fitToViewport(imgPreloader.width,imgPreloader.height);

							_showContent();
						};

						imgPreloader.onerror = function(){
							alert('Image cannot be loaded. Make sure the path is correct and image exist.');
							$.prettyPhoto.close();
						};
					
						imgPreloader.src = pp_images[set_position];
					break;
				
					case 'youtube':
						correctSizes = _fitToViewport(movie_width,movie_height); // Fit item to viewport

						movie = 'http://www.youtube.com/v/'+grab_param('v',pp_images[set_position]);
						if(settings.autoplay) movie += "&autoplay=1";
					
						toInject = settings.flash_markup.replace(/{width}/g,correctSizes['width']).replace(/{height}/g,correctSizes['height']).replace(/{wmode}/g,settings.wmode).replace(/{path}/g,movie);
					break;
				
					case 'vimeo':
						correctSizes = _fitToViewport(movie_width,movie_height); // Fit item to viewport
					
						movie_id = pp_images[set_position];
						var regExp = /http:\/\/(www\.)?vimeo.com\/(\d+)/;
						var match = movie_id.match(regExp);
						
						movie = 'http://player.vimeo.com/video/'+ match[2] +'?title=0&amp;byline=0&amp;portrait=0';
						if(settings.autoplay) movie += "&autoplay=1;";
				
						vimeo_width = correctSizes['width'] + '/embed/?moog_width='+ correctSizes['width'];
				
						toInject = settings.iframe_markup.replace(/{width}/g,vimeo_width).replace(/{height}/g,correctSizes['height']).replace(/{path}/g,movie);
					break;
				
					case 'quicktime':
						correctSizes = _fitToViewport(movie_width,movie_height); // Fit item to viewport
						correctSizes['height']+=15; correctSizes['contentHeight']+=15; correctSizes['containerHeight']+=15; // Add space for the control bar
				
						toInject = settings.quicktime_markup.replace(/{width}/g,correctSizes['width']).replace(/{height}/g,correctSizes['height']).replace(/{wmode}/g,settings.wmode).replace(/{path}/g,pp_images[set_position]).replace(/{autoplay}/g,settings.autoplay);
					break;
				
					case 'flash':
						correctSizes = _fitToViewport(movie_width,movie_height); // Fit item to viewport
					
						flash_vars = pp_images[set_position];
						flash_vars = flash_vars.substring(pp_images[set_position].indexOf('flashvars') + 10,pp_images[set_position].length);

						filename = pp_images[set_position];
						filename = filename.substring(0,filename.indexOf('?'));
					
						toInject =  settings.flash_markup.replace(/{width}/g,correctSizes['width']).replace(/{height}/g,correctSizes['height']).replace(/{wmode}/g,settings.wmode).replace(/{path}/g,filename+'?'+flash_vars);
					break;
				
					case 'iframe':
						correctSizes = _fitToViewport(movie_width,movie_height); // Fit item to viewport
				
						frame_url = pp_images[set_position];
						frame_url = frame_url.substr(0,frame_url.indexOf('iframe')-1);
				
						toInject = settings.iframe_markup.replace(/{width}/g,correctSizes['width']).replace(/{height}/g,correctSizes['height']).replace(/{path}/g,frame_url);
					break;
					
					case 'custom':
						correctSizes = _fitToViewport(movie_width,movie_height); // Fit item to viewport
					
						toInject = settings.custom_markup;
					break;
				
					case 'inline':
						// to get the item height clone it, apply default width, wrap it in the prettyPhoto containers , then delete
						myClone = $(pp_images[set_position]).clone().css({'width':settings.default_width}).wrapInner('<div id="pp_full_res"><div class="pp_inline clearfix"></div></div>').appendTo($('body'));
						correctSizes = _fitToViewport($(myClone).width(),$(myClone).height());
						$(myClone).remove();
						toInject = settings.inline_markup.replace(/{content}/g,$(pp_images[set_position]).html());
					break;
				};

				if(!imgPreloader){
					$pp_pic_holder.find('#pp_full_res')[0].innerHTML = toInject;
				
					// Show content
					_showContent();
				};
			});

			return false;
		};

	
		/**
		* Change page in the prettyPhoto modal box
		* @param direction {String} Direction of the paging, previous or next.
		*/
		$.prettyPhoto.changePage = function(direction){
			currentGalleryPage = 0;
			
			if(direction == 'previous') {
				set_position--;
				if (set_position < 0){
					set_position = 0;
					return;
				};
			}else if(direction == 'next'){
				set_position++;
				if(set_position > $(pp_images).size()-1) {
					set_position = 0;
				}
			}else{
				set_position=direction;
			};

			if(!doresize) doresize = true; // Allow the resizing of the images
			$('.pp_contract').removeClass('pp_contract').addClass('pp_expand');

			_hideContent(function(){ $.prettyPhoto.open(); });
		};


		/**
		* Change gallery page in the prettyPhoto modal box
		* @param direction {String} Direction of the paging, previous or next.
		*/
		$.prettyPhoto.changeGalleryPage = function(direction){
			if(direction=='next'){
				currentGalleryPage ++;

				if(currentGalleryPage > totalPage){
					currentGalleryPage = 0;
				};
			}else if(direction=='previous'){
				currentGalleryPage --;

				if(currentGalleryPage < 0){
					currentGalleryPage = totalPage;
				};
			}else{
				currentGalleryPage = direction;
			};
			
			// Slide the pages, if we're on the last page, find out how many items we need to slide. To make sure we don't have an empty space.
			itemsToSlide = (currentGalleryPage == totalPage) ? pp_images.length - ((totalPage) * itemsPerPage) : itemsPerPage;
			
			$pp_pic_holder.find('.pp_gallery li').each(function(i){
				$(this).animate({
					'left': (i * itemWidth) - ((itemsToSlide * itemWidth) * currentGalleryPage)
				});
			});
		};


		/**
		* Start the slideshow...
		*/
		$.prettyPhoto.startSlideshow = function(){
			if(typeof pp_slideshow == 'undefined'){
				$pp_pic_holder.find('.pp_play').unbind('click').removeClass('pp_play').addClass('pp_pause').click(function(){
					$.prettyPhoto.stopSlideshow();
					return false;
				});
				pp_slideshow = setInterval($.prettyPhoto.startSlideshow,settings.slideshow);
			}else{
				$.prettyPhoto.changePage('next');	
			};
		}


		/**
		* Stop the slideshow...
		*/
		$.prettyPhoto.stopSlideshow = function(){
			$pp_pic_holder.find('.pp_pause').unbind('click').removeClass('pp_pause').addClass('pp_play').click(function(){
				$.prettyPhoto.startSlideshow();
				return false;
			});
			clearInterval(pp_slideshow);
			pp_slideshow=undefined;
		}


		/**
		* Closes prettyPhoto.
		*/
		$.prettyPhoto.close = function(){

			clearInterval(pp_slideshow);
			
			$pp_pic_holder.stop().find('object,embed').css('visibility','hidden');
			
			$('div.pp_pic_holder,div.ppt,.pp_fade').fadeOut(settings.animation_speed,function(){ $(this).remove(); });
			
			$pp_overlay.fadeOut(settings.animation_speed, function(){
				if($.browser.msie && $.browser.version == 6) $('select').css('visibility','visible'); // To fix the bug with IE select boxes
				
				if(settings.hideflash) $('object,embed').css('visibility','visible'); // Show the flash
				
				$(this).remove(); // No more need for the prettyPhoto markup
				
				$(window).unbind('scroll');
				
				settings.callback();
				
				doresize = true;
				
				pp_open = false;
				
				delete settings;
			});
		};
	
		/**
		* Set the proper sizes on the containers and animate the content in.
		*/
		_showContent = function(){
			$('.pp_loaderIcon').hide();
			
			$ppt.fadeTo(settings.animation_speed,1);

			// Calculate the opened top position of the pic holder
			projectedTop = scroll_pos['scrollTop'] + ((windowHeight/2) - (correctSizes['containerHeight']/2));
			if(projectedTop < 0) projectedTop = 0;

			// Resize the content holder
			$pp_pic_holder.find('.pp_content').animate({'height':correctSizes['contentHeight']},settings.animation_speed);
			
			// Resize picture the holder
			$pp_pic_holder.animate({
				'top': projectedTop,
				'left': (windowWidth/2) - (correctSizes['containerWidth']/2),
				'width': correctSizes['containerWidth']
			},settings.animation_speed,function(){
				$pp_pic_holder.find('.pp_hoverContainer,#fullResImage').height(correctSizes['height']).width(correctSizes['width']);

				$pp_pic_holder.find('.pp_fade').fadeIn(settings.animation_speed); // Fade the new content

				// Show the nav
				if(isSet && _getFileType(pp_images[set_position])=="image") { $pp_pic_holder.find('.pp_hoverContainer').show(); }else{ $pp_pic_holder.find('.pp_hoverContainer').hide(); }
			
				if(correctSizes['resized']) $('a.pp_expand,a.pp_contract').fadeIn(settings.animation_speed); // Fade the resizing link if the image is resized
				
				if(settings.autoplay_slideshow && !pp_slideshow && !pp_open) $.prettyPhoto.startSlideshow();
				
				settings.changepicturecallback(); // Callback!
				
				pp_open = true;
			});
			
			_insert_gallery();
		};
		
		/**
		* Hide the content...DUH!
		*/
		function _hideContent(callback){
			// Fade out the current picture
			$pp_pic_holder.find('#pp_full_res object,#pp_full_res embed').css('visibility','hidden');
			$pp_pic_holder.find('.pp_fade').fadeOut(settings.animation_speed,function(){
				$('.pp_loaderIcon').show();
				
				callback();
			});
		};
	
		/**
		* Check the item position in the gallery array, hide or show the navigation links
		* @param setCount {integer} The total number of items in the set
		*/
		function _checkPosition(setCount){
			// If at the end, hide the next link
			if(set_position == setCount-1) {
				$pp_pic_holder.find('a.pp_next').css('visibility','hidden');
				$pp_pic_holder.find('a.pp_next').addClass('disabled').unbind('click');
			}else{ 
				$pp_pic_holder.find('a.pp_next').css('visibility','visible');
				$pp_pic_holder.find('a.pp_next.disabled').removeClass('disabled').bind('click',function(){
					$.prettyPhoto.changePage('next');
					return false;
				});
			};
		
			// If at the beginning, hide the previous link
			if(set_position == 0) {
				$pp_pic_holder
					.find('a.pp_previous')
					.css('visibility','hidden')
					.addClass('disabled')
					.unbind('click');
			}else{
				$pp_pic_holder.find('a.pp_previous.disabled')
					.css('visibility','visible')
					.removeClass('disabled')
					.bind('click',function(){
						$.prettyPhoto.changePage('previous');
						return false;
					});
			};
			
			(setCount > 1) ? $('.pp_nav').show() : $('.pp_nav').hide(); // Hide the bottom nav if it's not a set.
		};
	
		/**
		* Resize the item dimensions if it's bigger than the viewport
		* @param width {integer} Width of the item to be opened
		* @param height {integer} Height of the item to be opened
		* @return An array containin the "fitted" dimensions
		*/
		function _fitToViewport(width,height){
			resized = false;

			_getDimensions(width,height);
			
			// Define them in case there's no resize needed
			imageWidth = width, imageHeight = height;

			if( ((pp_containerWidth > windowWidth) || (pp_containerHeight > windowHeight)) && doresize && settings.allow_resize && !percentBased) {
				resized = true, fitting = false;
			
				while (!fitting){
					if((pp_containerWidth > windowWidth)){
						imageWidth = (windowWidth - 200);
						imageHeight = (height/width) * imageWidth;
					}else if((pp_containerHeight > windowHeight)){
						imageHeight = (windowHeight - 200);
						imageWidth = (width/height) * imageHeight;
					}else{
						fitting = true;
					};

					pp_containerHeight = imageHeight, pp_containerWidth = imageWidth;
				};
			
				_getDimensions(imageWidth,imageHeight);
			};

			return {
				width:Math.floor(imageWidth),
				height:Math.floor(imageHeight),
				containerHeight:Math.floor(pp_containerHeight),
				containerWidth:Math.floor(pp_containerWidth) + 40, // 40 behind the side padding
				contentHeight:Math.floor(pp_contentHeight),
				contentWidth:Math.floor(pp_contentWidth),
				resized:resized
			};
		};
		
		/**
		* Get the containers dimensions according to the item size
		* @param width {integer} Width of the item to be opened
		* @param height {integer} Height of the item to be opened
		*/
		function _getDimensions(width,height){
			width = parseFloat(width);
			height = parseFloat(height);
			
			// Get the details height, to do so, I need to clone it since it's invisible
			$pp_details = $pp_pic_holder.find('.pp_details');
			$pp_details.width(width);
			detailsHeight = parseFloat($pp_details.css('marginTop')) + parseFloat($pp_details.css('marginBottom'));
			$pp_details = $pp_details.clone().appendTo($('body')).css({
				'position':'absolute',
				'top':-10000
			});
			detailsHeight += $pp_details.height();
			detailsHeight = (detailsHeight <= 34) ? 36 : detailsHeight; // Min-height for the details
			if($.browser.msie && $.browser.version==7) detailsHeight+=8;
			$pp_details.remove();
			
			// Get the container size, to resize the holder to the right dimensions
			pp_contentHeight = height + detailsHeight;
			pp_contentWidth = width;
			pp_containerHeight = pp_contentHeight + $ppt.height() + $pp_pic_holder.find('.pp_top').height() + $pp_pic_holder.find('.pp_bottom').height();
			pp_containerWidth = width;
		}
	
		function _getFileType(itemSrc){
			if (itemSrc.match(/youtube\.com\/watch/i)) {
				return 'youtube';
			}else if (itemSrc.match(/vimeo\.com/i)) {
				return 'vimeo';
			}else if(itemSrc.indexOf('.mov') != -1){ 
				return 'quicktime';
			}else if(itemSrc.indexOf('.swf') != -1){
				return 'flash';
			}else if(itemSrc.indexOf('iframe') != -1){
				return 'iframe';
			}else if(itemSrc.indexOf('custom') != -1){
				return 'custom';
			}else if(itemSrc.substr(0,1) == '#'){
				return 'inline';
			}else{
				return 'image';
			};
		};
	
		function _center_overlay(){
			if(doresize && typeof $pp_pic_holder != 'undefined') {
				scroll_pos = _get_scroll();
				
				titleHeight = $ppt.height(), contentHeight = $pp_pic_holder.height(), contentwidth = $pp_pic_holder.width();
				
				projectedTop = (windowHeight/2) + scroll_pos['scrollTop'] - (contentHeight/2);
				
				$pp_pic_holder.css({
					'top': projectedTop,
					'left': (windowWidth/2) + scroll_pos['scrollLeft'] - (contentwidth/2)
				});
			};
		};
	
		function _get_scroll(){
			if (self.pageYOffset) {
				return {scrollTop:self.pageYOffset,scrollLeft:self.pageXOffset};
			} else if (document.documentElement && document.documentElement.scrollTop) { // Explorer 6 Strict
				return {scrollTop:document.documentElement.scrollTop,scrollLeft:document.documentElement.scrollLeft};
			} else if (document.body) {// all other Explorers
				return {scrollTop:document.body.scrollTop,scrollLeft:document.body.scrollLeft};
			};
		};
	
		function _resize_overlay() {
			windowHeight = $(window).height(), windowWidth = $(window).width();
			
			if(typeof $pp_overlay != "undefined") $pp_overlay.height($(document).height());
		};
	
		function _insert_gallery(){
			if(isSet && settings.overlay_gallery && _getFileType(pp_images[set_position])=="image") {
				itemWidth = 52+5; // 52 beign the thumb width, 5 being the right margin.
				navWidth = (settings.theme == "facebook") ? 58 : 38; // Define the arrow width depending on the theme
				
				itemsPerPage = Math.floor((correctSizes['containerWidth'] - 100 - navWidth) / itemWidth);
				itemsPerPage = (itemsPerPage < pp_images.length) ? itemsPerPage : pp_images.length;
				totalPage = Math.ceil(pp_images.length / itemsPerPage) - 1;

				// Hide the nav in the case there's no need for links
				if(totalPage == 0){
					navWidth = 0; // No nav means no width!
					$pp_pic_holder.find('.pp_gallery .pp_arrow_next,.pp_gallery .pp_arrow_previous').hide();
				}else{
					$pp_pic_holder.find('.pp_gallery .pp_arrow_next,.pp_gallery .pp_arrow_previous').show();
				};

				galleryWidth = itemsPerPage * itemWidth + navWidth;
				
				// Set the proper width to the gallery items
				$pp_pic_holder.find('.pp_gallery')
					.width(galleryWidth)
					.css('margin-left',-(galleryWidth/2));
					
				$pp_pic_holder
					.find('.pp_gallery ul')
					.width(itemsPerPage * itemWidth)
					.find('li.selected')
					.removeClass('selected');
				
				goToPage = (Math.floor(set_position/itemsPerPage) <= totalPage) ? Math.floor(set_position/itemsPerPage) : totalPage;
				
				
				if(itemsPerPage) {
					$pp_pic_holder.find('.pp_gallery').hide().show().removeClass('disabled');
				}else{
					$pp_pic_holder.find('.pp_gallery').hide().addClass('disabled');
				}
				
				$.prettyPhoto.changeGalleryPage(goToPage);
				
				$pp_pic_holder
					.find('.pp_gallery ul li:eq('+set_position+')')
					.addClass('selected');
			}else{
				$pp_pic_holder.find('.pp_content').unbind('mouseenter mouseleave');
				$pp_pic_holder.find('.pp_gallery').hide();
			}
		}
	
		function _buildOverlay(caller){
			// Find out if the picture is part of a set
			theRel = $(caller).attr('rel');
			galleryRegExp = /\[(?:.*)\]/;
			isSet = (galleryRegExp.exec(theRel)) ? true : false;
			
			// Put the SRCs, TITLEs, ALTs into an array.
			pp_images = (isSet) ? jQuery.map(matchedObjects, function(n, i){ if($(n).attr('rel').indexOf(theRel) != -1) return $(n).attr('href'); }) : $.makeArray($(caller).attr('href'));
			pp_titles = (isSet) ? jQuery.map(matchedObjects, function(n, i){ if($(n).attr('rel').indexOf(theRel) != -1) return ($(n).find('img').attr('alt')) ? $(n).find('img').attr('alt') : ""; }) : $.makeArray($(caller).find('img').attr('alt'));
			pp_descriptions = (isSet) ? jQuery.map(matchedObjects, function(n, i){ if($(n).attr('rel').indexOf(theRel) != -1) return ($(n).attr('title')) ? $(n).attr('title') : ""; }) : $.makeArray($(caller).attr('title'));
			
			$('body').append(settings.markup); // Inject the markup
			
			$pp_pic_holder = $('.pp_pic_holder') , $ppt = $('.ppt'), $pp_overlay = $('div.pp_overlay'); // Set my global selectors
			
			// Inject the inline gallery!
			if(isSet && settings.overlay_gallery) {
				currentGalleryPage = 0;
				toInject = "";
				for (var i=0; i < pp_images.length; i++) {
					var regex = new RegExp("(.*?)\.(jpg|jpeg|png|gif)$");
					var results = regex.exec( pp_images[i] );
					if(!results){
						classname = 'default';
					}else{
						classname = '';
					}
					toInject += "<li class='"+classname+"'><a href='#'><img src='" + pp_images[i] + "' width='50' alt='' /></a></li>";
				};
				
				toInject = settings.gallery_markup.replace(/{gallery}/g,toInject);
				
				$pp_pic_holder.find('#pp_full_res').after(toInject);
				
				$pp_pic_holder.find('.pp_gallery .pp_arrow_next').click(function(){
					$.prettyPhoto.changeGalleryPage('next');
					$.prettyPhoto.stopSlideshow();
					return false;
				});
				
				$pp_pic_holder.find('.pp_gallery .pp_arrow_previous').click(function(){
					$.prettyPhoto.changeGalleryPage('previous');
					$.prettyPhoto.stopSlideshow();
					return false;
				});
				
				$pp_pic_holder.find('.pp_content').hover(
					function(){
						$pp_pic_holder.find('.pp_gallery:not(.disabled)').fadeIn();
					},
					function(){
						$pp_pic_holder.find('.pp_gallery:not(.disabled)').fadeOut();
					});

				itemWidth = 52+5; // 52 beign the thumb width, 5 being the right margin.
				$pp_pic_holder.find('.pp_gallery ul li').each(function(i){
					$(this).css({
						'position':'absolute',
						'left': i * itemWidth
					});

					$(this).find('a').unbind('click').click(function(){
						$.prettyPhoto.changePage(i);
						$.prettyPhoto.stopSlideshow();
						return false;
					});
				});
			};
			
			
			// Inject the play/pause if it's a slideshow
			if(settings.slideshow){
				$pp_pic_holder.find('.pp_nav').prepend('<a href="#" class="pp_play">Play</a>')
				$pp_pic_holder.find('.pp_nav .pp_play').click(function(){
					$.prettyPhoto.startSlideshow();
					return false;
				});
			}
			
			$pp_pic_holder.attr('class','pp_pic_holder ' + settings.theme); // Set the proper theme
			
			$pp_overlay
				.css({
					'opacity':0,
					'height':$(document).height(),
					'width':$(document).width()
					})
				.bind('click',function(){
					if(!settings.modal) $.prettyPhoto.close();
				});

			$('a.pp_close').bind('click',function(){ $.prettyPhoto.close(); return false; });

			$('a.pp_expand').bind('click',function(e){
				// Expand the image
				if($(this).hasClass('pp_expand')){
					$(this).removeClass('pp_expand').addClass('pp_contract');
					doresize = false;
				}else{
					$(this).removeClass('pp_contract').addClass('pp_expand');
					doresize = true;
				};
			
				_hideContent(function(){ $.prettyPhoto.open(); });
		
				return false;
			});
		
			$pp_pic_holder.find('.pp_previous, .pp_nav .pp_arrow_previous').bind('click',function(){
				$.prettyPhoto.changePage('previous');
				$.prettyPhoto.stopSlideshow();
				return false;
			});
		
			$pp_pic_holder.find('.pp_next, .pp_nav .pp_arrow_next').bind('click',function(){
				$.prettyPhoto.changePage('next');
				$.prettyPhoto.stopSlideshow();
				return false;
			});
			
			_center_overlay(); // Center it
		};
		
		return this.unbind('click').click($.prettyPhoto.initialize); // Return the jQuery object for chaining. The unbind method is used to avoid click conflict when the plugin is called more than once
	};
	
	function grab_param(name,url){
	  name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
	  var regexS = "[\\?&]"+name+"=([^&#]*)";
	  var regex = new RegExp( regexS );
	  var results = regex.exec( url );
	  return ( results == null ) ? "" : results[1];
	}
	
})(jQuery);;
/*!
 * Tiny Scrollbar 1.42
 * http://www.baijs.nl/tinyscrollbar/
 *
 * Copyright 2010, Maarten Baijs
 * Dual licensed under the MIT or GPL Version 2 licenses.
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.opensource.org/licenses/gpl-2.0.php
 *
 * Date: 06 / 11 / 2010
 * Depends on library: jQuery
 */
(function($){
	$.fn.tinyscrollbar = function(options){
		var defaults = { 
			axis: 'y', // vertical or horizontal scrollbar? ( x || y ).
			wheel: 40,  //how many pixels must the mouswheel scroll at a time.
			scroll: true, //enable or disable the mousewheel scrollbar
			size: 'auto', //set the size of the scrollbar to auto or a fixed number.
			sizethumb: 'auto' //set the size of the thumb to auto or a fixed number.
		};
		var options = $.extend(defaults, options);
		var oWrapper = $(this);
		var oViewport = { obj: $('.viewport', this) };
		var oContent = { obj: $('.overview', this) };		
		var oScrollbar = { obj: $('.scrollbar', this) };
		var oTrack = { obj: $('.track', oScrollbar.obj) };
		var oThumb = { obj: $('.thumb', oScrollbar.obj) };
		var sAxis = options.axis == 'x', sDirection = sAxis ? 'left' : 'top', sSize = sAxis ? 'Width' : 'Height';
		var iScroll, iPosition = { start: 0, now: 0 }, iMouse = {};
		
		if (this.length > 1){ 
			this.each(function(){$(this).tinyscrollbar(options)}); 
			return this;
		}   
		this.initialize = function(){
			this.update();
			setEvents();
		};
		this.update = function(){
			iScroll = 0;
			oViewport[options.axis] = oViewport.obj[0]['offset'+ sSize];
			oContent[options.axis] = oContent.obj[0]['scroll'+ sSize];
			oContent.ratio = oViewport[options.axis] / oContent[options.axis];
			oScrollbar.obj.toggleClass('disable', oContent.ratio >= 1);
			oTrack[options.axis] = options.size == 'auto' ? oViewport[options.axis] : options.size;
			oThumb[options.axis] = Math.min(oTrack[options.axis], Math.max(0, ( options.sizethumb == 'auto' ? (oTrack[options.axis] * oContent.ratio) : options.sizethumb )));
			oScrollbar.ratio = options.sizethumb == 'auto' ? (oContent[options.axis] / oTrack[options.axis]) : (oContent[options.axis] - oViewport[options.axis]) / (oTrack[options.axis] - oThumb[options.axis]);
			setSize();
		};
		function setSize(){
			oContent.obj.removeAttr('style'); 
			oThumb.obj.removeAttr('style');
			iMouse['start'] = oThumb.obj.offset()[sDirection];
			var sCssSize = sSize.toLowerCase(); 
			oScrollbar.obj.css(sCssSize, oTrack[options.axis]);
			oTrack.obj.css(sCssSize, oTrack[options.axis]);
			oThumb.obj.css(sCssSize, oThumb[options.axis]);
		};		
		function setEvents(){
			oThumb.obj.bind('mousedown', start);
			oTrack.obj.bind('mouseup', drag);
			if(options.scroll && this.addEventListener){
				oWrapper[0].addEventListener('DOMMouseScroll', wheel, false);
				oWrapper[0].addEventListener('mousewheel', wheel, false );
			}
			else if(options.scroll){oWrapper[0].onmousewheel = wheel;}
		};
		function start(oEvent){
			iMouse.start = sAxis ? oEvent.pageX : oEvent.pageY;
			iPosition.start = parseInt(oThumb.obj.css(sDirection));
			$(document).bind('mousemove', drag);
			$(document).bind('mouseup', end);
			oThumb.obj.bind('mouseup', end);
			return false;
		};		
		function wheel(oEvent){
			if(!(oContent.ratio >= 1)){
				oEvent = $.event.fix(oEvent || window.event);
				var iDelta = oEvent.wheelDelta ? oEvent.wheelDelta/120 : -oEvent.detail/3;
				iScroll -= iDelta * options.wheel;
				iScroll = Math.min((oContent[options.axis] - oViewport[options.axis]), Math.max(0, iScroll));
				oThumb.obj.css(sDirection, iScroll / oScrollbar.ratio);
				oContent.obj.css(sDirection, -iScroll);
				oEvent.preventDefault();
			};
		};
		function end(oEvent){
			$(document).unbind('mousemove', drag);
			$(document).unbind('mouseup', end);
			oThumb.obj.unbind('mouseup', end);
			return false;
		};
		function drag(oEvent){
			if(!(oContent.ratio >= 1)){
				iPosition.now = Math.min((oTrack[options.axis] - oThumb[options.axis]), Math.max(0, (iPosition.start + ((sAxis ? oEvent.pageX : oEvent.pageY) - iMouse.start))));
				iScroll = iPosition.now * oScrollbar.ratio;
				oContent.obj.css(sDirection, -iScroll);
				oThumb.obj.css(sDirection, iPosition.now);;
			}
			return false;
		};
		return this.initialize();
	};
})(jQuery);;
/**
 * jQuery Maxlength plugin
 * @version		$Id: jquery.maxlength.js 18 2009-05-16 15:37:08Z emil@anon-design.se $
 * @package		jQuery maxlength 1.0.5
 * @copyright	Copyright (C) 2009 Emil Stjerneman / http://www.anon-design.se
 * @license		GNU/GPL, see LICENSE.txt
 */

(function($) 
{

	$.fn.maxlength = function(options)
	{
		var settings = jQuery.extend(
		{
			events:				      [], // Array of events to be triggerd
			maxCharacters:		  10, // Characters limit
			status:				      true, // True to show status indicator bewlow the element
			statusClass:		    "status", // The class on the status div
			statusText:			    "character left", // The status text
			notificationClass:	"notification",	// Will be added to the emement when maxlength is reached
			showAlert: 			    false, // True to show a regular alert message
			alertText:			    "You have typed too many characters.", // Text in the alert message
			slider:				      false // Use counter slider
		}, options );
		
		// Add the default event
		$.merge(settings.events, ['keyup']);

		return this.each(function() 
		{
			var item = $(this);
			var charactersLength = $(this).val().length;
			
      // Update the status text
			function updateStatus()
			{
				var charactersLeft = settings.maxCharacters - charactersLength;
				
				if(charactersLeft < 0) 
				{
					charactersLeft = 0;
				}

				item.next("div").html(charactersLeft + " " + settings.statusText);
			}

			function checkChars() 
			{
				var valid = true;
				
				// Too many chars?
				if(charactersLength >= settings.maxCharacters) 
				{
					// Too may chars, set the valid boolean to false
					valid = false;
					// Add the notifycation class when we have too many chars
					item.addClass(settings.notificationClass);
					// Cut down the string
					item.val(item.val().substr(0,settings.maxCharacters));
					// Show the alert dialog box, if its set to true
					showAlert();
				} 
				else 
				{
					// Remove the notification class
					if(item.hasClass(settings.notificationClass)) 
					{
						item.removeClass(settings.notificationClass);
					}
				}

				if(settings.status)
				{
					updateStatus();
				}
			}
						
			// Shows an alert msg
			function showAlert() 
			{
				if(settings.showAlert)
				{
					alert(settings.alertText);
				}
			}

			// Check if the element is valid.
			function validateElement() 
			{
				var ret = false;
				
				if(item.is('textarea')) {
					ret = true;
				} else if(item.filter("input[type=text]")) {
					ret = true;
				} else if(item.filter("input[type=password]")) {
					ret = true;
				}

				return ret;
			}

			// Validate
			if(!validateElement()) 
			{
				return false;
			}
			
			// Loop through the events and bind them to the element
			$.each(settings.events, function (i, n) {
				item.bind(n, function(e) {
					charactersLength = item.val().length;
					checkChars();
				});
			});

			// Insert the status div
			if(settings.status) 
			{
				item.after($("<div id='status' />").addClass(settings.statusClass).html('-'));
				updateStatus();
			}

			// Remove the status div
			if(!settings.status) 
			{
				var removeThisDiv = item.next("div."+settings.statusClass);
				
				if(removeThisDiv) {
					removeThisDiv.remove();
				}

			}

			// Slide counter
			if(settings.slider) {
				item.next().hide();
				
				item.focus(function(){
					item.next().slideDown('fast');
				});

				item.blur(function(){
					item.next().slideUp('fast');
				}); 
			}

		});
	};
})(jQuery);
;
/*
 * jQuery miniColors: A small color selector
 *
 * Copyright 2011 Cory LaViska for A Beautiful Site, LLC. (http://abeautifulsite.net/)
 *
 * Dual licensed under the MIT or GPL Version 2 licenses
 *
 *
 * Usage:
 *
 *	1. Link to jQuery: <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.0/jquery.min.js"></script>
 *
 *  2. Link to miniColors: <script type="text/javascript" src="jquery.miniColors.js"></script>
 *
 *  3. Include miniColors stylesheet: <link type="text/css" rel="stylesheet" href="jquery.miniColors.css" />
 *
 *	4. Apply $([selector]).miniColors() to one or more INPUT elements
 *
 *
 * Options:
 *
 *	disabled		[true|false]
 *	readonly		[true|false]
 *
 *
 *  Specify options on creation:
 *
 *		$([selector]).miniColors({
 *
 *			optionName: value,
 *			optionName: value,
 *			...
 *
 *		});
 *
 *
 * Methods:
 *
 *	Call a method using: $([selector]).miniColors('methodName', [value]);
 *
 *	disabled		[true|false]
 *	readonly		[true|false]
 *	value			[hex value]
 *	destroy
 *
 *
 * Events:
 *
 *	Attach events on creation:
 *
 *		$([selector]).miniColors({
 *
 *			change: function(hex, rgb) { ... }
 *
 *		});
 *
 *	change(hex, rgb)	called when the color value changes; 'this' will refer to the original input element;
 *                      hex is the string hex value of the selected color; rgb is an object with the RGB values
 *
 *
 * Change log:
 *
 *	- v0.1 (2011-02-24) - Initial release
 *
 *
 * Attribution:
 *
 *	- The color picker icon is based on an icon from the amazing Fugue icon set: 
 *    http://p.yusukekamiyamane.com/
 *
 *	- The gradient image, the hue image, and the math functions are courtesy of 
 *    the eyecon.co jQuery color picker: http://www.eyecon.ro/colorpicker/
 *
 *
*/
if(jQuery) (function($) {
	
	$.extend($.fn, {
		
		miniColors: function(o, data) {
			
			
			var create = function(input, o, data) {
				
				//
				// Creates a new instance of the miniColors selector
				//
				
				// Determine initial color (defaults to white)
				var color = cleanHex(input.val());
				if( !color ) color = 'FFFFFF';
				var hsb = hex2hsb(color);
				
				// Create trigger
				var trigger = $('<a class="miniColors-trigger" style="background-color: #' + color + '" href="#"></a>');
				trigger.insertAfter(input);
				
				// Add necessary attributes
				input.addClass('miniColors').attr('maxlength', 7).attr('autocomplete', 'off');
				
				// Set input data
				input.data('trigger', trigger);
				input.data('hsb', hsb);
				if( o.change ) input.data('change', o.change);
				
				// Handle options
				if( o.readonly ) input.attr('readonly', true);
				if( o.disabled ) disable(input);
				
				// Show selector when trigger is clicked
				trigger.bind('click.miniColors', function(event) {
					event.preventDefault();
					input.trigger('focus');
				});
				
				// Show selector when input receives focus
				input.bind('focus.miniColors', function(event) {
					show(input);
				});
				
				// Hide on blur
				input.bind('blur.miniColors', function(event) {
					var hex = cleanHex(input.val());
					input.val( hex ? '#' + hex : '' );
				});
				
				// Hide when tabbing out of the input
				input.bind('keydown.miniColors', function(event) {
					if( event.keyCode === 9 ) hide(input);
				});
				
				// Update when color is typed in
				input.bind('keyup.miniColors', function(event) {
					// Remove non-hex characters
					var filteredHex = input.val().replace(/[^A-F0-9#]/ig, '');
					input.val(filteredHex);
					if( !setColorFromInput(input) ) {
						// Reset trigger color when color is invalid
						input.data('trigger').css('backgroundColor', '#FFF');
					}
				});
				
				// Handle pasting
				input.bind('paste.miniColors', function(event) {
					// Short pause to wait for paste to complete
					setTimeout( function() {
						input.trigger('keyup');
					}, 5);
				});
				
			};
			
			
			var destroy = function(input) {
				
				//
				// Destroys an active instance of the miniColors selector
				//
				
				hide();
				
				input = $(input);
				input.data('trigger').remove();
				input.removeAttr('autocomplete');
				input.removeData('trigger');
				input.removeData('selector');
				input.removeData('hsb');
				input.removeData('huePicker');
				input.removeData('colorPicker');
				input.removeData('mousebutton');
				input.removeData('moving');
				input.unbind('click.miniColors');
				input.unbind('focus.miniColors');
				input.unbind('blur.miniColors');
				input.unbind('keyup.miniColors');
				input.unbind('keydown.miniColors');
				input.unbind('paste.miniColors');
				$(document).unbind('mousedown.miniColors');
				$(document).unbind('mousemove.miniColors');
				
			};
			
			
			var enable = function(input) {
				
				//
				// Disables the input control and the selector
				//
				
				input.attr('disabled', false);
				input.data('trigger').css('opacity', 1);
				
			};
			
			
			var disable = function(input) {
				
				//
				// Disables the input control and the selector
				//
				
				hide(input);
				input.attr('disabled', true);
				input.data('trigger').css('opacity', .5);
				
			};
			
			
			var show = function(input) {
				
				//
				// Shows the miniColors selector
				//
				
				if( input.attr('disabled') ) return false;
				
				// Hide all other instances 
				hide();				
				
				// Generate the selector
				var selector = $('<div class="miniColors-selector"></div>');
				selector.append('<div class="miniColors-colors" style="background-color: #FFF;"><div class="miniColors-colorPicker"></div></div>');
				selector.append('<div class="miniColors-hues"><div class="miniColors-huePicker"></div></div>');
				selector.css({
					top: input.is(':visible') ? input.offset().top + input.outerHeight() : input.data('trigger').offset().top + input.data('trigger').outerHeight(),
					left: input.is(':visible') ? input.offset().left : input.data('trigger').offset().left,
					display: 'none'
				}).addClass( input.attr('class') );
				
				// Set background for colors
				var hsb = input.data('hsb');
				selector.find('.miniColors-colors').css('backgroundColor', '#' + hsb2hex({ h: hsb.h, s: 100, b: 100 }));
				
				// Set colorPicker position
				var colorPosition = input.data('colorPosition');
				if( !colorPosition ) colorPosition = getColorPositionFromHSB(hsb);
				selector.find('.miniColors-colorPicker').css('top', colorPosition.y + 'px').css('left', colorPosition.x + 'px');
				
				// Set huePicker position
				var huePosition = input.data('huePosition');
				if( !huePosition ) huePosition = getHuePositionFromHSB(hsb);
				selector.find('.miniColors-huePicker').css('top', huePosition.y + 'px');
				
				
				// Set input data
				input.data('selector', selector);
				input.data('huePicker', selector.find('.miniColors-huePicker'));
				input.data('colorPicker', selector.find('.miniColors-colorPicker'));
				input.data('mousebutton', 0);
				
				$('BODY').append(selector);
				selector.fadeIn(100);
				
				// Prevent text selection in IE
				selector.bind('selectstart', function() { return false; });
				
				$(document).bind('mousedown.miniColors', function(event) {
					input.data('mousebutton', 1);
					
					if( $(event.target).parents().andSelf().hasClass('miniColors-colors') ) {
						event.preventDefault();
						input.data('moving', 'colors');
						moveColor(input, event);
					}
					
					if( $(event.target).parents().andSelf().hasClass('miniColors-hues') ) {
						event.preventDefault();
						input.data('moving', 'hues');
						moveHue(input, event);
					}
					
					if( $(event.target).parents().andSelf().hasClass('miniColors-selector') ) {
						event.preventDefault();
						return;
					}
					
					if( $(event.target).parents().andSelf().hasClass('miniColors') ) return;
					
					hide(input);
				});
				
				$(document).bind('mouseup.miniColors', function(event) {
					input.data('mousebutton', 0);
					input.removeData('moving');
				});
				
				$(document).bind('mousemove.miniColors', function(event) {
					if( input.data('mousebutton') === 1 ) {
						if( input.data('moving') === 'colors' ) moveColor(input, event);
						if( input.data('moving') === 'hues' ) moveHue(input, event);
					}
				});
				
			};
			
			
			var hide = function(input) {
				
				//
				// Hides one or more miniColors selectors
				//
				
				// Hide all other instances if input isn't specified
				if( !input ) input = '.miniColors';
				
				$(input).each( function() {
					var selector = $(this).data('selector');
					$(this).removeData('selector');
					$(selector).fadeOut(100, function() {
						$(this).remove();
					});
				});
				
				$(document).unbind('mousedown.miniColors');
				$(document).unbind('mousemove.miniColors');
				
			};
			
			
			var moveColor = function(input, event) {
				
				var colorPicker = input.data('colorPicker');
				
				colorPicker.hide();
				
				var position = {
					x: event.clientX - input.data('selector').find('.miniColors-colors').offset().left + $(document).scrollLeft() - 5,
					y: event.clientY - input.data('selector').find('.miniColors-colors').offset().top + $(document).scrollTop() - 5
				};
				
				if( position.x <= -5 ) position.x = -5;
				if( position.x >= 144 ) position.x = 144;
				if( position.y <= -5 ) position.y = -5;
				if( position.y >= 144 ) position.y = 144;
				input.data('colorPosition', position);
				colorPicker.css('left', position.x).css('top', position.y).show();
				
				// Calculate saturation
				var s = Math.round((position.x + 5) * .67);
				if( s < 0 ) s = 0;
				if( s > 100 ) s = 100;
				
				// Calculate brightness
				var b = 100 - Math.round((position.y + 5) * .67);
				if( b < 0 ) b = 0;
				if( b > 100 ) b = 100;
				
				// Update HSB values
				var hsb = input.data('hsb');
				hsb.s = s;
				hsb.b = b;
				
				// Set color
				setColor(input, hsb, true);
				
			};
			
			
			var moveHue = function(input, event) {
				
				var huePicker = input.data('huePicker');
				
				huePicker.hide();
				
				var position = {
					y: event.clientY - input.data('selector').find('.miniColors-colors').offset().top + $(document).scrollTop() - 1
				};
				
				if( position.y <= -1 ) position.y = -1;
				if( position.y >= 149 ) position.y = 149;
				input.data('huePosition', position);
				huePicker.css('top', position.y).show();
				
				// Calculate hue
				var h = Math.round((150 - position.y - 1) * 2.4);
				if( h < 0 ) h = 0;
				if( h > 360 ) h = 360;
				
				// Update HSB values
				var hsb = input.data('hsb');
				hsb.h = h;
				
				// Set color
				setColor(input, hsb, true);
				
			};
			
			
			var setColor = function(input, hsb, updateInputValue) {
				
				input.data('hsb', hsb);
				var hex = hsb2hex(hsb);	
				if( updateInputValue ) input.val('#' + hex);
				input.data('trigger').css('backgroundColor', '#' + hex);
				if( input.data('selector') ) input.data('selector').find('.miniColors-colors').css('backgroundColor', '#' + hsb2hex({ h: hsb.h, s: 100, b: 100 }));
				
				if( input.data('change') ) {
					input.data('change').call(input, '#' + hex, hsb2rgb(hsb));
				}
				
			};
			
			
			var setColorFromInput = function(input) {
				
				// Don't update if the hex color is invalid
				var hex = cleanHex(input.val());
				if( !hex ) return false;
				
				// Get HSB equivalent
				var hsb = hex2hsb(hex);
				
				// If color is the same, no change required
				var currentHSB = input.data('hsb');
				if( hsb.h === currentHSB.h && hsb.s === currentHSB.s && hsb.b === currentHSB.b ) return true;
				
				// Set colorPicker position
				var colorPosition = getColorPositionFromHSB(hsb);
				var colorPicker = $(input.data('colorPicker'));
				colorPicker.css('top', colorPosition.y + 'px').css('left', colorPosition.x + 'px');
				
				// Set huePosition position
				var huePosition = getHuePositionFromHSB(hsb);
				var huePicker = $(input.data('huePicker'));
				huePicker.css('top', huePosition.y + 'px');
				
				setColor(input, hsb, false);
				
				return true;
				
			};
			
			
			var getColorPositionFromHSB = function(hsb) {
				
				var x = Math.ceil(hsb.s / .67);
				if( x < 0 ) x = 0;
				if( x > 150 ) x = 150;
				
				var y = 150 - Math.ceil(hsb.b / .67);
				if( y < 0 ) y = 0;
				if( y > 150 ) y = 150;
				
				return { x: x - 5, y: y - 5 };
				
			}
			
			
			var getHuePositionFromHSB = function(hsb) {
				
				var y = 150 - (hsb.h / 2.4);
				if( y < 0 ) h = 0;
				if( y > 150 ) h = 150;				
				
				return { y: y - 1 };
				
			}
			
			
			var cleanHex = function(hex) {
				
				//
				// Turns a dirty hex string into clean, 6-character hex color
				//
				
				hex = hex.replace(/[^A-Fa-f0-9]/, '');
				
				if( hex.length == 3 ) {
					hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
				}
				
				return hex.length === 6 ? hex : null;
				
			};			
			
			
			var hsb2rgb = function(hsb) {
				var rgb = {};
				var h = Math.round(hsb.h);
				var s = Math.round(hsb.s*255/100);
				var v = Math.round(hsb.b*255/100);
				if(s == 0) {
					rgb.r = rgb.g = rgb.b = v;
				} else {
					var t1 = v;
					var t2 = (255 - s) * v / 255;
					var t3 = (t1 - t2) * (h % 60) / 60;
					if( h == 360 ) h = 0;
					if( h < 60 ) { rgb.r = t1; rgb.b = t2; rgb.g = t2 + t3; }
					else if( h<120 ) {rgb.g = t1; rgb.b = t2; rgb.r = t1 - t3; }
					else if( h<180 ) {rgb.g = t1; rgb.r = t2; rgb.b = t2 + t3; }
					else if( h<240 ) {rgb.b = t1; rgb.r = t2; rgb.g = t1 - t3; }
					else if( h<300 ) {rgb.b = t1; rgb.g = t2; rgb.r = t2 + t3; }
					else if( h<360 ) {rgb.r = t1; rgb.g = t2; rgb.b = t1 - t3; }
					else { rgb.r = 0; rgb.g = 0; rgb.b = 0; }
				}
				return {
					r: Math.round(rgb.r),
					g: Math.round(rgb.g),
					b: Math.round(rgb.b)
				};
			};
			
			
			var rgb2hex = function(rgb) {
				
				var hex = [
					rgb.r.toString(16),
					rgb.g.toString(16),
					rgb.b.toString(16)
				];
				$.each(hex, function(nr, val) {
					if (val.length == 1) hex[nr] = '0' + val;
				});
				
				return hex.join('');
			};
			
			
			var hex2rgb = function(hex) {
				var hex = parseInt(((hex.indexOf('#') > -1) ? hex.substring(1) : hex), 16);
				
				return {
					r: hex >> 16,
					g: (hex & 0x00FF00) >> 8,
					b: (hex & 0x0000FF)
				};
			};
			
			
			var rgb2hsb = function(rgb) {
				var hsb = { h: 0, s: 0, b: 0 };
				var min = Math.min(rgb.r, rgb.g, rgb.b);
				var max = Math.max(rgb.r, rgb.g, rgb.b);
				var delta = max - min;
				hsb.b = max;
				hsb.s = max != 0 ? 255 * delta / max : 0;
				if( hsb.s != 0 ) {
					if( rgb.r == max ) {
						hsb.h = (rgb.g - rgb.b) / delta;
					} else if( rgb.g == max ) {
						hsb.h = 2 + (rgb.b - rgb.r) / delta;
					} else {
						hsb.h = 4 + (rgb.r - rgb.g) / delta;
					}
				} else {
					hsb.h = -1;
				}
				hsb.h *= 60;
				if( hsb.h < 0 ) {
					hsb.h += 360;
				}
				hsb.s *= 100/255;
				hsb.b *= 100/255;
				return hsb;
			};			
			
			
			var hex2hsb = function(hex) {
				var hsb = rgb2hsb(hex2rgb(hex));
				// Zero out hue marker for black, white, and grays (saturation === 0)
				if( hsb.s === 0 ) hsb.h = 360;
				return hsb;
			};
			
			
			var hsb2hex = function(hsb) {
				return rgb2hex(hsb2rgb(hsb));
			};

			
			//
			// Handle calls to $([selector]).miniColors()
			//
			switch(o) {
			
				case 'readonly':
					
					$(this).each( function() {
						$(this).attr('readonly', data);
					});
					
					return $(this);
					
					break;
				
				case 'disabled':
					
					$(this).each( function() {
						if( data ) {
							disable($(this));
						} else {
							enable($(this));
						}
					});
										
					return $(this);
			
				case 'value':
					
					$(this).each( function() {
						$(this).val(data).trigger('keyup');
					});
					
					return $(this);
					
					break;
					
				case 'destroy':
					
					$(this).each( function() {
						destroy($(this));
					});
										
					return $(this);
				
				default:
					
					if( !o ) o = {};
					
					$(this).each( function() {
						
						// Must be called on an input element
						if( $(this)[0].tagName.toLowerCase() !== 'input' ) return;
						
						// If a trigger is present, the control was already created
						if( $(this).data('trigger') ) return;
						
						// Create the control
						create($(this), o, data);
						
					});
										
					return $(this);
					
			}
			
			
		}

			
	});
	
})(jQuery);



;
/* Zotonic basic Javascript library
----------------------------------------------------------

@package:	Zotonic 2009	
@Author:	Tim Benniks <tim@timbenniks.nl>
@Author:	Marc Worrell <marc@worrell.nl>

Copyright 2009-2011 Tim Benniks, Marc Worrell

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
 
http://www.apache.org/licenses/LICENSE-2.0
 
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Based on nitrogen.js which is copyright 2008-2009 Rusty Klophaus

---------------------------------------------------------- */

var z_ws					= false;
var z_ws_opened				= false;
var z_comet_is_running		= false;
var z_doing_postback		= false;
var z_spinner_show_ct		= 0;
var z_postbacks				= [];
var z_default_form_postback = false;
var z_input_updater			= false;
var z_drag_tag				= [];
var z_registered_events		= new Object();
var z_on_visible_checks		= [];
var z_on_visible_timer		= undefined;
var z_unique_id_counter		= 0;
var z_language              = "en";

/* Non modal dialogs
---------------------------------------------------------- */

function z_dialog_open(options)
{
	$.dialogAdd(options);
}

function z_dialog_close()
{
	$.dialogClose();
}

/* Growl messages
---------------------------------------------------------- */

function z_growl_add(message, stay, type)
{
	stay = stay || false;
	type = type || 'notice';
	
	$.noticeAdd(
	{
		text: message,
		stay: stay,
		type: type
	});
	
	if(type == 'error' && window.console)
	{
		console.error(message);
	}
}

function z_growl_close()
{
	$.noticeRemove($('.notice-item-wrapper'), 400);
}


/* Registered events for javascript triggered actions/postbacks
---------------------------------------------------------- */

function z_event_register(name, func)
{
	z_registered_events[name] = func;
}

function z_event(name, extraParams)
{
	if (z_registered_events[name])
	{
		z_registered_events[name](ensure_name_value(extraParams));
	}
	else if (window.console)
	{
		console.error("z_event: no registered event named: '"+name+"'");
	}
}

/* Call the server side notifier for {postback_notify, Message, Context}
---------------------------------------------------------- */

function z_notify(message, extraParams)
{
	var trigger_id = '';
	if (extraParams != undefined && extraParams.z_trigger_id != undefined) {
	    trigger_id = extraParams.z_trigger_id;
	    extraParams.z_trigger_id = undefined;
	}

	var extra = ensure_name_value(extraParams);
	if (typeof extra != 'object')
	{
		extra = [];
	}
	extra.push({name: 'z_msg', value: message});
	z_queue_postback(trigger_id, 'notify', extra, true);
}

/* Postback loop
---------------------------------------------------------- */

function z_postback_check() 
{
	if (z_postbacks.length == 0)
	{
		z_doing_postback = false;
	}
	else
	{
		if (z_postback_connected())
		{
			// Send only a single postback at a time.
			z_doing_postback = true;

			var o = z_postbacks.shift();
			z_do_postback(o.triggerID, o.postback, o.extraParams);
		}
		else
		{
			setTimeout("z_postback_check()", 10);
		}
	}
}

function z_opt_cancel(obj)
{
	if(typeof obj.nodeName == 'undefined')
		return false;

	var nodeName = obj.nodeName.toLowerCase();
	var nodeType = $(obj).attr("type");

	if (nodeName == 'input' &&	(nodeType == 'checkbox' || nodeType == 'radio'))
	{
		return true;
	}
	else
	{
		return false;
	}
}

function z_httpdata( xhr, type, s ) 
{ 
	// lifted from jq1.4.4
	var ct = xhr.getResponseHeader("content-type") || "",
	  xml = type === "xml" || !type && ct.indexOf("xml") >= 0,
	  data = xml ? xhr.responseXML : xhr.responseText;

	if ( xml && data.documentElement.nodeName === "parsererror" ) {
	  $.error( "parsererror" );
	}
	if ( s && s.dataFilter ) {
	  data = s.dataFilter( data, type );
	}
	if ( typeof data === "string" ) {
	  if ( type === "json" || !type && ct.indexOf("json") >= 0 ) {
		data = $.parseJSON( data );
	  } else if ( type === "script" || !type && ct.indexOf("javascript") >= 0 ) {
		$.globalEval( data );
	  }
	}
	return data;
}

function z_queue_postback(triggerID, postback, extraParams, noTriggerValue) 
{
	var triggerValue = '';

	if (triggerID != '' && !noTriggerValue)
	{
		var trigger = $('#'+triggerID).get(0);
		if (trigger)
		{
			if ($(trigger).is(":checkbox") || $(trigger).is(":radio"))
			{
				if ($(trigger).is(":checked"))
				{
					triggerValue = $(trigger).val() || 'on';
				}
			}
			else
			{
				var nodeName = trigger.nodeName.toLowerCase();
		
				if (nodeName == 'input' || nodeName == 'button' || nodeName == 'textarea' || nodeName == 'select')
					triggerValue = $(trigger).val() || '';
			}
		}
	}

	extraParams = extraParams || new Array(); 
	extraParams.push({name: 'triggervalue', value: triggerValue})
	
	var o			= new Object();
	o.triggerID		= triggerID;
	o.postback		= postback;
	o.extraParams	= extraParams;
	z_postbacks.push(o);
	z_postback_check();
}


// Wait with sending postbacks till the websocket connection is open
function z_postback_connected()
{
	return !z_ws || z_ws.readyState != 0;
}


function z_do_postback(triggerID, postback, extraParams) 
{
	// Get params...
	var params = 
		"postback=" + urlencode(postback) + 
		"&z_trigger_id=" + urlencode(triggerID) +
		"&z_pageid=" + urlencode(z_pageid) + 
		"&" + $.param(extraParams);
	
	// logon_form and .setcookie forms are always posted, as they will set cookies.
	if (   z_ws
		&& z_ws.readyState == 1 
		&& triggerID != "logon_form" 
		&& (triggerID == '' || !$('#'+triggerID).hasClass("setcookie")))
	{
		z_ws.send(params);
	}
	else
	{
		z_ajax(triggerID, params);
	}
}

function z_ajax(triggerID, params)
{
	z_start_spinner();

	$.ajax({ 
		url:		'/postback',
		type:		'post',
		data:		params,
		dataType:	'text',
		success: function(data, textStatus) 
		{
			z_stop_spinner();
			
			try 
			{
				eval(data);
				z_init_postback_forms();
			} 
			catch(e)
			{
				$.misc.error("Error evaluating ajax return value: " + data);
				$.misc.warn(e);
			}
			setTimeout("z_postback_check()", 0);
		},
		error: function(xmlHttpRequest, textStatus, errorThrown) 
		{
			z_stop_spinner();
			
			$.misc.error("FAIL: " + textStatus);
			z_unmask_error(triggerID);
		}
	});
}

function z_unmask(id)
{
	if (id)
	{
		var trigger = $('#'+id).get(0);
	
		if (trigger && trigger.nodeName.toLowerCase() == 'form') 
		{
			try { $(trigger).unmask(); } catch (e) {};
		}
		$(trigger).removeClass("z_error_upload");
	}
}


function z_unmask_error(id)
{
	if (id)
	{
		z_unmask(id);
		$('#'+id).addClass("z_error_upload");
	}
}


function z_progress(id, value)
{
	if (id)
	{
		var trigger = $('#'+id).get(0);
	
		if (trigger.nodeName.toLowerCase() == 'form') 
		{
			try { $(trigger).maskProgress(value); } catch (e) {};
		}
	}
}

function z_reload(args)
{
	var page = $('#logon_form input[name="page"]');

	if (page.length > 0 && page.val() != "") {
		window.location.href = window.location.protocol+"//"+window.location.host+page.val();
	} else {
	    if (typeof args == "undefined")
		    window.location.reload(true);
		else {
    	    var qs = ensure_name_value(args);

    		if (qs.length == 1 &&  typeof args.z_language == "string") {
    		    var href;
    		    
    		    if (  window.location.pathname.substring(0,2+z_language.length) == "/"+z_language+"/") {
        		    href = window.location.protocol+"//"+window.location.host
        		            +"/"+args.z_language+"/"
        		            +window.location.pathname.substring(2+args.z_language.length);
        		} else {
        		    href = window.location.protocol+"//"+window.location.host
        		            +"/"+args.z_language
        		            +window.location.pathname;
        		}
    		    if (window.location.search == "")
    		        window.location.href = href;
    		    else
    		        window.location.href = href + "?" + window.location.search;
    		} else {
    		    var href = window.location.protocol+"//"+window.location.host+window.location.pathname;
    		    if (window.location.search == "") {
    		        window.location.href = href + '?' + $.param(qs);
    		    } else {
    		        var loc_qs = $.parseQuery();
        		    for (var prop in loc_qs) {
            		    if (typeof loc_qs[prop] != "undefined" && typeof args[prop] == "undefined")
            			    qs.push({name: prop, value: loc_qs[prop]});
            		}
    		        window.location.href = href+"?" + $.param(qs);
    		    }
    		}
    	}
	}
}

/* translations
---------------------------------------------------------- */

function z_translate(text)
{
	if (typeof z_translations != "undefined" && typeof z_translations[text] != "undefined")
		return z_translations[text];
	return text;
}


/* Render text as html nodes
---------------------------------------------------------- */

function z_text_to_nodes(text)
{
    if (text == "") {
        return $(text);
    } else {
        var len = text.length;
        
        if (text.charAt(0) == "<" && text.charAt(len-1) == ">") {
            return $(text);
        } else {
            return $("<span></span>"+text+"<span></span>").slice(1,-1);
        }
    }
}

/* tinyMCE stuff
---------------------------------------------------------- */

function z_tinymce_add(element) 
{
	$("textarea.tinymce", element).each(function() {
		if (typeof $(this).tinymce == 'function') {
			var self = $(this);
			setTimeout(function() { 
				if (typeof tinyInit == 'object') self.tinymce(tinyInit);
				else self.tinymce({}); 
			}, 200);
		} else if (typeof tinyMCE == 'object') {
			var mce_id = $(this).attr('id');
			setTimeout(function() { tinyMCE.execCommand('mceAddControl',false, mce_id); }, 200);
		}
	});
}

function z_tinymce_save(element)
{
	var tiny = $("textarea.tinymce", element);
	if (tiny.length > 0) {
		if (typeof tiny.tinymce == "function") {
			tiny.each(function() { $(this).tinymce().save(); });
		} else if (typeof tinyMCE == 'object') {
			tinyMCE.triggerSave(true,true);
		}
	}
}

function z_tinymce_remove(element) 
{
	$("textarea.tinymce", element).each( function() {
		if (typeof $(this).tinymce == 'function') {
			$(this).tinymce().remove();
		} else if (typeof(tinyMCE) != 'undefined') {
			tinyMCE.execCommand('mceRemoveControl',false, $(this).attr('id')); 
		}
	});
}


/* Comet long poll or WebSockets connection
---------------------------------------------------------- */

function z_stream_start(host)
{
	if (!z_ws && !z_comet_is_running)
	{
		if ("WebSocket" in window && window.location.protocol == "http:") 
		{
			z_websocket_start(host);
		}
		else
		{
			setTimeout(function() { z_comet(host); }, 2000);
			z_comet_is_running = true;
		}
	}
}

function z_comet(host) 
{
	if (host != window.location.host && window.location.protocol == "http:")
	{
		var url = window.location.protocol + '//' + host + "/comet/subdomain?z_pageid=" + urlencode(z_pageid);
		var comet = $('<iframe id="z_comet_connection" name="z_comet_connection" src="'+url+'" />');
		comet.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
		comet.appendTo("body");
	}
	else
	{
		z_comet_host()
	}
}

function z_comet_host()
{
	$.ajax({ 
		url: window.location.protocol + '//' + window.location.host + '/comet',
		type:'post',
		data: "z_pageid=" + urlencode(z_pageid),
		dataType: 'text',
		success: function(data, textStatus) 
		{
			z_comet_data(data);
			setTimeout(function() { z_comet_host(); }, 1000);
		},
		error: function(xmlHttpRequest, textStatus, errorThrown) 
		{
			setTimeout(function() { z_comet_host(); }, 1000);
		}
	});
}


function z_comet_data(data)
{
	try 
	{
		eval(data);
		z_init_postback_forms();
	} 
	catch (e)
	{
		$.misc.error("Error evaluating ajax return value: " + data);
		$.misc.warn(e);
	}
}


function z_websocket_start(host)
{
	z_ws = new WebSocket("ws://"+document.location.host+"/websocket?z_pageid="+z_pageid);

	z_ws.onopen = function() { z_ws_opened = true; };
	z_ws.onerror = function() { };

	z_ws.onclose = function (evt) 
	{
		if (z_ws_opened)
		{
			// Try to reopen once, might be closed due to an server side error
			z_ws_opened = false;
			setTimeout(function() { z_websocket_start(host); }, 100);
		}
		else
		{
			// Failed opening websocket connection - try to start comet
			z_ws = undefined;
			setTimeout(function() { z_comet(host); }, 2000);
			z_comet_is_running = true;
		}
	};

	z_ws.onmessage = function (evt)
	{
		z_comet_data(evt.data);
		setTimeout("z_postback_check()", 0);
	};
}


/* Utility functions
---------------------------------------------------------- */

function z_is_enter_key(event) 
{
	return (event && event.keyCode == 13);
}


function z_has_flash()
{
	if (navigator.plugins && navigator.plugins.length>0) {
		var type = 'application/x-shockwave-flash';
		var mimeTypes = navigator.mimeTypes;
		return (mimeTypes && mimeTypes[type] && mimeTypes[type].enabledPlugin);
	} else if(navigator.appVersion.indexOf("Mac")==-1 && window.execScript) {
		try {
			obj = new ActiveXObject("ShockwaveFlash.ShockwaveFlash");
			return true;
		} catch(err) {
			return false;
		}
	}
	return false;
}


function z_ensure_id(elt)
{
	var id = $(elt).attr('id');
	if (id == undefined) {
		id = z_unique_id();
		$(elt).attr('id', id);
	}
	return id;
}

function z_unique_id()
{
	do {
		var id = '-z-' + z_unique_id_counter++;
	} while ($('#'+id).length > 0);
	return id;
}


/* Spinner, show when waiting for a postback
---------------------------------------------------------- */

function z_start_spinner()
{
	if (z_spinner_show_ct++ == 0)
	{
		$(document.body).addClass('wait');
		$('#spinner').fadeIn(100);
	}
}

function z_stop_spinner() 
{
	if (--z_spinner_show_ct == 0)
	{
		$('#spinner').fadeOut(100);
		$(document.body).removeClass('wait');
	}
}


/* Drag & drop interface to the postback
---------------------------------------------------------- */

function z_draggable(dragObj, dragOptions, dragTag) 
{
	dragObj.draggable(dragOptions).data("z_drag_tag", dragTag);
	z_drag_tag[dragObj.attr('id')] = dragTag;
}

function z_droppable(dropObj, dropOptions, dropPostbackInfo) 
{
	dropOptions.greedy = true;
	dropOptions.drop = function(ev, ui) 
	{
		var dragTag = $(ui.draggable[0]).data("z_drag_tag");
		var dragItem = new Array({name: 'drag_item', value: dragTag});
		z_queue_postback(this.id, dropPostbackInfo, dragItem, true);
	};

	$(dropObj).droppable(dropOptions);
}


/* Sorter and sortables interface to the postback
---------------------------------------------------------- */

function z_sortable(sortableObj, sortTag) 
{
	sortableObj.data("z_sort_tag", sortTag);
}

function z_sorter(sortBlock, sortOptions, sortPostbackInfo) 
{
	sortOptions.update = function() 
	{
		var sortItems = "";

		for (var i = 0; i < this.childNodes.length; i++) 
		{
			var sortTag = $(this.childNodes[i]).data("z_sort_tag") 
			if (!sortTag && this.childNodes[i].id)
			{
				sortTag = z_drag_tag[this.childNodes[i].id];
			}
			if (sortTag)
			{
				if (sortItems != "") 
				{
					sortItems += ",";
				}
				
				sortItems += sortTag
			}
		}
		
		var sortItem = new Array({name: 'sort_items', value: sortItems});
		
		z_queue_postback(this.id, sortPostbackInfo, sortItem, true);
	};

	$(sortBlock).sortable(sortOptions);
}


/* typeselect input field
---------------------------------------------------------- */

function z_typeselect(ElementId, postbackInfo)
{
	if (z_input_updater)
	{
		clearTimeout(z_input_updater);
		z_input_updater = false;
	}
	
	z_input_updater = setTimeout(function()
	{
		var obj = $('#'+ElementId);

		if(obj.val().length >= 2)
		{
			obj.addClass('loading');
			z_queue_postback(ElementId, postbackInfo)
		}
	}, 400);
}


/* Lazy loading of content, based on visibility of an element
---------------------------------------------------------- */

function z_on_visible(CssSelector, Func)
{
	z_on_visible_checks.push({selector: CssSelector, func: Func});
	if (z_on_visible_timer == undefined) {
		z_on_visible_timer = setInterval(function() {
			z_on_visible_check();
		}, 350);
	}
}

function z_on_visible_check()
{
	for (var i = z_on_visible_checks.length-1; i>=0; i--) {
		var elt = $(z_on_visible_checks[i].selector).get(0);
		if (elt != undefined) {
			if ($(elt).is(":visible") && isScrolledIntoView(elt)) {
				z_on_visible_checks[i].func.call(elt);
				z_on_visible_checks.splice(i, 1);
			}
		}
	}
	if (z_on_visible_checks.length == 0) {
		clearInterval(z_on_visible_timer);
		z_on_visible_timer = undefined;
	}
}


function isScrolledIntoView(elem)
{
	var docViewTop = $(window).scrollTop();
	var docViewBottom = docViewTop + $(window).height();

	var elemTop = $(elem).offset().top;
	var elemBottom = elemTop + $(elem).height();

	return (elemBottom >= docViewTop) && (elemTop <= docViewBottom);
	// && (elemBottom <= docViewBottom) &&	(elemTop >= docViewTop);
}

/* Form element validations
----------------------------------------------------------

Grab all "postback" forms, let them be handled by Ajax postback calls.
This function can be run multiple times.

---------------------------------------------------------- */

function z_init_postback_forms()
{
	$("form[action*='postback']").each(function() 
	{
		// store options in hash
		$(":submit,input:image", this).bind('click.form-plugin',function(e) 
		{
			var form = this.form;
			form.clk = this;
		
			if (this.type == 'image') 
			{
				if (e.offsetX != undefined) 
				{
					form.clk_x = e.offsetX;
					form.clk_y = e.offsetY;
				} 
				else if (typeof $.fn.offset == 'function') 
				{ // try to use dimensions plugin
					var offset = $(this).offset();
					form.clk_x = e.pageX - offset.left;
					form.clk_y = e.pageY - offset.top;
				} 
				else 
				{
					form.clk_x = e.pageX - this.offsetLeft;
					form.clk_y = e.pageY - this.offsetTop;
				}
			}
		});
	})
	.submit(function(event)
	{
		theForm = this;
		z_tinymce_save(theForm);
		
		submitFunction = function(ev) {
			var arguments = $(theForm).formToArray();

			try { $(theForm).mask("", 100); } catch (e) {};

			var postback	= $(theForm).data("z_submit_postback");
			var action		= $(theForm).data("z_submit_action");
			var form_id		= $(theForm).attr('id');
			var validations = $(theForm).formValidationPostback();
		
			if(!postback) 
			{
				postback = z_default_form_postback;
			}
		
			if(action) 
			{
				setTimeout(action, 10);
			}

			var use_post = $(theForm).hasClass("z_cookie_form");
			if (typeof(z_only_post_forms) != "undefined" && z_only_post_forms)
			{
				use_post = true;
			}
			else
			{
				var files = $('input:file', theForm).fieldValue();
				for (var j=0; j < files.length && !use_post; j++) 
				{
					if (files[j])
					{
						use_post = true;
					}
				}
			}
		
			if (use_post) 
			{
				$(theForm).postbackFileForm(form_id, postback, validations);
			}
			else
			{
				theForm.clk = theForm.clk_x = theForm.clk_y = null;
				z_queue_postback(form_id, postback, arguments.concat(validations)); 
			}
			ev.stopPropagation();
			return false;
		};
		
		return z_form_submit_validated_delay(theForm, event, submitFunction);
	})
	.attr('action', '#pb-installed');
}

function z_form_submit_validated_delay(theForm, event, submitFunction) 
{
	var validations = $(theForm).formValidationPostback();
	
	if (validations.length > 0 && !event.zIsValidated)
	{
		// There are form validations and they are not done yet.
		if (!event.zAfterValidation) 
		{
			event.zAfterValidation = new Array();
		}
		event.zAfterValidation.push({ func: submitFunction, context: theForm });
		return true;
	}
	else
	{
		// No form validations, or already validated
		return submitFunction.call(theForm, event);
	}
}

function z_form_submit_validated_do(event)
{
	var ret = true;
	
	if (event.zAfterValidation)
	{
		$.each(event.zAfterValidation, function(){
			ret = typeof this.func == 'function' && this.func.call(this.context, event) && ret;
		});
		event.zAfterValidation.length = 0;
	}
	return ret;
}


$.fn.postbackFileForm = function(trigger_id, postback, validations)
{
	var a = validations;

	a.push({name: "postback", value: postback});
	a.push({name: "z_trigger_id", value: trigger_id});
	a.push({name: "z_pageid", value: z_pageid});
	a.push({name: "z_comet", value: z_comet_is_running || z_ws});
	
	var $form = this;
	var options = {
		url:  '/postback?' + $.param(a),
		type: 'POST',
		dataType: 'text/javascript'
	};

	// hack to fix Safari hang (thanks to Tim Molendijk for this)
	// see:	 http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
	if ($.browser.safari)
		$.get('/close-connection', fileUpload);
	else
		fileUpload();
	
	// private function for handling file uploads (hat tip to YAHOO!)
	function fileUpload() {
		var form = $form[0];

		if ($(':input[name=submit]', form).length) {
			alert('Error: Form elements must not be named "submit".');
			return;
		}

		var opts = $.extend({}, $.ajaxSettings, options);
		var s = $.extend(true, {}, $.extend(true, {}, $.ajaxSettings), opts);

		var id = 'jqFormIO' + (new Date().getTime());
		var $io = $('<iframe id="' + id + '" name="' + id + '" src="about:blank" />');
		var io = $io[0];

		$io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });

		var xhr = { // mock object
			aborted: 0,
			responseText: null,
			responseXML: null,
			status: 0,
			statusText: 'n/a',
			getAllResponseHeaders: function() {},
			getResponseHeader: function() {},
			setRequestHeader: function() {},
			abort: function() {
				this.aborted = 1;
				$io.attr('src','about:blank'); // abort op in progress
			}
		};

		var g = opts.global;
		
		// trigger ajax global events so that activity/block indicators work like normal
		if (g && ! $.active++) $.event.trigger("ajaxStart");
		if (g) $.event.trigger("ajaxSend", [xhr, opts]);

		if (s.beforeSend && s.beforeSend(xhr, s) === false) {
			s.global && $.active--;
			return;
		}
		if (xhr.aborted)
			return;

		var cbInvoked = 0;
		var timedOut = 0;

		// add submitting element to data if we know it
		var sub = form.clk;
		if (sub) {
			var n = sub.name;
			if (n && !sub.disabled) {
				options.extraData = options.extraData || {};
				options.extraData['z_submitter'] = n;
				options.extraData[n] = sub.value;
				if (sub.type == "image") {
					options.extraData[name+'.x'] = form.clk_x;
					options.extraData[name+'.y'] = form.clk_y;
				}
			}
		}
		form.clk = form.clk_x = form.clk_y = null;

		// take a breath so that pending repaints get some cpu time before the upload starts
		setTimeout(function() {
			// make sure form attrs are set
			var t = $form.attr('target');
			var a = $form.attr('action');

			// update form attrs in IE friendly way
			form.setAttribute('target',id);
			if (form.getAttribute('method') != 'POST')
				form.setAttribute('method', 'POST');
			if (form.getAttribute('action') != opts.url)
				form.setAttribute('action', opts.url);

			// ie borks in some cases when setting encoding
			if (! options.skipEncodingOverride) {
				$form.attr({
					encoding: 'multipart/form-data',
					enctype:  'multipart/form-data'
				});
			}

			// support timout
			if (opts.timeout)
				setTimeout(function() { timedOut = true; cb(); }, opts.timeout);

			// add "extra" data to form if provided in options
			var extraInputs = [];
			try {
				if (options.extraData)
					for (var n in options.extraData)
						extraInputs.push(
							$('<input type="hidden" name="'+n+'" value="'+options.extraData[n]+'" />')
								.appendTo(form)[0]);
				
				// add iframe to doc and submit the form
				$io.appendTo('body');
				io.attachEvent ? io.attachEvent('onload', cb) : io.addEventListener('load', cb, false);
				form.submit();
			}
			finally {
				// reset attrs and remove "extra" input elements
				form.setAttribute('action',a);
				t ? form.setAttribute('target', t) : $form.removeAttr('target');
				$(extraInputs).remove();
			}
		}, 10);

		var domCheckCount = 3;

		function cb() {
			if (cbInvoked++) return;

			io.detachEvent ? io.detachEvent('onload', cb) : io.removeEventListener('load', cb, false);

			var ok = true;
			try {
				if (timedOut) throw 'timeout';
				// extract the server response from the iframe
				var data, doc;

				doc = io.contentWindow ? io.contentWindow.document : io.contentDocument ? io.contentDocument : io.document;
				if (doc.body == null || doc.body.innerHTML == '') {
					if (--domCheckCount) {
						// in some browsers (Opera) the iframe DOM is not always traversable when
						// the onload callback fires, so we loop a bit to accommodate

						// MW: looks like this is not a timing issue but Opera triggering a
						//	   load event on the 100 continue.
						cbInvoked = 0;
						io.addEventListener('load', cb, false);
						return;
					}
					log('Could not access iframe DOM after 50 tries.');
					return;
				}

				xhr.responseText = doc.body ? doc.body.innerHTML : null;
				
				xhr.getResponseHeader = function(header){
					var headers = {'content-type': opts.dataType};
					return headers[header];
				};

				var ta = doc.getElementsByTagName('textarea')[0];
				xhr.responseText = ta ? ta.value : xhr.responseText;
				data = z_httpdata(xhr, opts.dataType);
			}
			catch(e){
				ok = false;
				$.event.trigger("ajaxError", [xhr, opts, e]);
			}
			
			// ordering of these callbacks/triggers is odd, but that's how $.ajax does it
			if (ok) {
				try {
					eval(data);
				} catch (e) {
					z_unmask_error(form.id);
				}
				if (g) 
				{
					$.event.trigger("ajaxSuccess", [xhr, opts]);
				}
			} else {
				z_unmask_error(form.id);
			}
			if (g) $.event.trigger("ajaxComplete", [xhr, opts]);
			if (g && ! --$.active) $.event.trigger("ajaxStop");
			if (opts.complete) opts.complete(xhr, ok ? 'success' : 'error');

			// clean up
			setTimeout(function() {
				$io.remove();
				xhr.responseXML = null;
			}, 100);
		};
	};
}


// Collect all postback validations from the form elements
$.fn.formValidationPostback = function() 
{
	var a = [];
	if(this.length == 0) return a;

	var form = this[0];
	var els	 = form.elements;

	if (!els) return a;

	for(var i=0, max=els.length; i < max; i++) 
	{
		var el = els[i];
		var n  = el.name;

		if (n && !el.disabled)
		{
			var v = $(el).data("z_postback_validation");
			if (v)
			{
				a.push({name: "z_v", value: n+":"+v})
			}
		}
	}
	return a;
}

// Initialize a validator for the element #id
function z_init_validator(id, args)
{
	var elt = $('#'+id);
	if (elt)
	{
		if (elt.attr('type') == 'radio')
		{
			$('input[name='+elt.attr('name')+']').each(function() {
				addLiveValidation(this, args);
			});
		}
		else
		{
			addLiveValidation(elt, args);
		}
	}
	else
	{
		$.misc.error('Validator error: no element with id #'+id, $(id));
	}
}

// Add a validator to the input field
function z_add_validator(id, type, args)
{
	var elt = $('#'+id);
	
	if (elt.attr('type') == 'radio')
		elt = $('input[name='+elt.attr('name')+']');

	elt.each(function() {
		var v = getLiveValidation(this);
		if (v)
		{
			if (args['pattern'])
			{
				args['pattern'] = new RegExp(args['pattern']);
			}
			switch (type)
			{
				case 'email':			v.add(Validate.Email, args);		break;
				case 'presence':		v.add(Validate.Presence, args);		break;
				case 'confirmation':	v.add(Validate.Confirmation, args); break;
				case 'acceptance':		v.add(Validate.Acceptance, args);	break;
				case 'length':			v.add(Validate.Length, args);		break;
				case 'format':			v.add(Validate.Format, args);		break;
				case 'numericality':	v.add(Validate.Numericality, args); break;
				case 'custom':			v.add(Validate.Custom, args);		break;
				case 'postback':		
					args['z_id'] = id;
					v.add(Validate.Postback, args);
					break;
				default:
					$.misc.error("unknown validation: "+type);
					break;
			}
		}
	});
}

function z_set_validator_postback(id, postback)
{
	if (postback)
	{
		var pb = $('#'+id).data("z_postback_validation");
		
		if (pb)
		{
			$.misc.error("Element #"+id+" had already a validation postback, add all validations as one batch.", $('#' +id));
		}

		$('#'+id).data("z_postback_validation", postback);
	}
}

function z_validation_on_invalid(id, on_invalid)
{
	$('#'+id).each(function() {
		if (this.tagName.toLowerCase() == 'form')
		{
			var formObj = LiveValidationForm.getInstance(this);
			formObj.onInvalid = on_invalid;
		}
	});
}


function z_async_validation_result(id, isValid, testedValue)
{
	var v = getLiveValidation($('#'+id));
	if (v && $('#'+id).val() == testedValue)
	{
		v.asyncValidationResult(isValid, testedValue);
	}
}

// Called by the server on validation errors
function z_validation_error(id, error)
{
	var v = getLiveValidation($('#'+id));
	if (v)
	{
		if (error == 'invalid')
		{
			// Generic error - handle it ourselves
			error = "please correct";
		}
		v.showErrorMessage(error);
	}
}


// Execute a function by name
function z_call_function_by_name(name, context)
{
	var args = Array.prototype.slice.call(arguments).splice(2);
	var namespaces = name.split(".");
	var func = namespaces.pop();
	for(var i = 0; i < namespaces.length; i++) {
		context = context[namespaces[i]];
	}
	return context[func].apply(this, args);
}

// URL encode function that is more RFC compatible.	 Also encodes +, *, / and @.
function urlencode(s)
{
	s = escape(s);
	s = s.replace(/\+/g, '%2B');
	s = s.replace(/\*/g, '%2A');
	s = s.replace(/\//g, '%2F');
	s = s.replace(/@/g, '%40');
	return s;
}

// HTML escape a string so it is safe to concatenate when making tags.
function html_escape(s)
{
	s.replace(/&/, "&amp;").replace(/</, "&lt;").replace(/>/, "&gt;").replace(/"/, "&quot;");
}


// Convert an object to an array with {name: xxx, value: yyy} pairs
function ensure_name_value(a)
{
	if ((typeof a == 'object') && !(a instanceof Array))
	{
		var n = []
		for (var prop in a)
		{
		    if (a[prop] != undefined)
			    n.push({name: prop, value: a[prop]});
		}
		return n;
	}
	else
	{
		return a;
	}
}

// From: http://malsup.com/jquery/form/jquery.form.js

/*
 * jQuery Form Plugin
 * version: 2.28 (10-MAY-2009)
 * @requires jQuery v1.2.2 or later
 *
 * Examples and documentation at: http://malsup.com/jquery/form/
 * Dual licensed under the MIT and GPL licenses:
 *	 http://www.opensource.org/licenses/mit-license.php
 *	 http://www.gnu.org/licenses/gpl.html
 */

/**
 * formToArray() gathers form element data into an array of objects that can
 * be passed to any of the following ajax functions: $.get, $.post, or load.
 * Each object in the array has both a 'name' and 'value' property.	 An example of
 * an array for a simple login form might be:
 *
 * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
 *
 * It is this array that is passed to pre-submit callback functions provided to the
 * ajaxSubmit() and ajaxForm() methods.
 */
$.fn.formToArray = function(semantic) {
	var a = [];
	if (this.length == 0) return a;

	var form = this[0];

	var els = semantic ? form.getElementsByTagName('*') : form.elements;
	if (!els) return a;
	for(var i=0, max=els.length; i < max; i++) {
		var el = els[i];
		var n = el.name;
		if (!n) continue;
		if ($(el).hasClass("nosubmit")) continue;
		if ($(el).attr("type") == 'submit') continue;

		var v = $.fieldValue(el, true);
		if (v && v.constructor == Array) {
			for(var j=0, jmax=v.length; j < jmax; j++)
				a.push({name: n, value: v[j]});
		}
		else if (v !== null && typeof v != 'undefined')
			a.push({name: n, value: v});
	}

	// add submitting element to data if we know it
	var sub = form.clk;
	if (sub) {
		var n = sub.name;
		if (n && !sub.disabled) {
			a.push({name: n, value: ''});
			a.push({name: 'z_submitter', value: n});
		}
	}

	return a;
};

/**
 * Serializes form data into a 'submittable' string. This method will return a string
 * in the format: name1=value1&amp;name2=value2
 */
$.fn.formSerialize = function(semantic) {
	//hand off to jQuery.param for proper encoding
	return $.param(this.formToArray(semantic));
};

/**
 * Serializes all field elements in the jQuery object into a query string.
 * This method will return a string in the format: name1=value1&amp;name2=value2
 */
$.fn.fieldSerialize = function(successful) {
	var a = [];
	this.each(function() {
		var n = this.name;
		if (!n) return;
		var v = $.fieldValue(this, successful);
		if (v && v.constructor == Array) {
			for (var i=0,max=v.length; i < max; i++)
				a.push({name: n, value: v[i]});
		}
		else if (v !== null && typeof v != 'undefined')
			a.push({name: this.name, value: v});
	});
	//hand off to jQuery.param for proper encoding
	return $.param(a);
};

/**
 * Returns the value(s) of the element in the matched set.	For example, consider the following form:
 *
 *	<form><fieldset>
 *		<input name="A" type="text" />
 *		<input name="A" type="text" />
 *		<input name="B" type="checkbox" value="B1" />
 *		<input name="B" type="checkbox" value="B2"/>
 *		<input name="C" type="radio" value="C1" />
 *		<input name="C" type="radio" value="C2" />
 *	</fieldset></form>
 *
 *	var v = $(':text').fieldValue();
 *	// if no values are entered into the text inputs
 *	v == ['','']
 *	// if values entered into the text inputs are 'foo' and 'bar'
 *	v == ['foo','bar']
 *
 *	var v = $(':checkbox').fieldValue();
 *	// if neither checkbox is checked
 *	v === undefined
 *	// if both checkboxes are checked
 *	v == ['B1', 'B2']
 *
 *	var v = $(':radio').fieldValue();
 *	// if neither radio is checked
 *	v === undefined
 *	// if first radio is checked
 *	v == ['C1']
 *
 * The successful argument controls whether or not the field element must be 'successful'
 * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
 * The default value of the successful argument is true.  If this value is false the value(s)
 * for each element is returned.
 *
 * Note: This method *always* returns an array.	 If no valid value can be determined the
 *		 array will be empty, otherwise it will contain one or more values.
 */
$.fn.fieldValue = function(successful) {
	for (var val=[], i=0, max=this.length; i < max; i++) {
		var el = this[i];
		var v = $.fieldValue(el, successful);
		if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length))
			continue;
		v.constructor == Array ? $.merge(val, v) : val.push(v);
	}
	return val;
};

/**
 * Returns the value of the field element.
 */
$.fieldValue = function(el, successful) {
	var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
	if (typeof successful == 'undefined') successful = true;

	if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
		t == 'radio' && !el.checked ||
		(t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
		tag == 'select' && el.selectedIndex == -1))
			return null;
	
	// Return empty value for non-checked checkboxes
	if (successful && t == 'checkbox' && !el.checked)
		return '';

	if (tag == 'select') {
		var index = el.selectedIndex;
		if (index < 0) return null;
		var a = [], ops = el.options;
		var one = (t == 'select-one');
		var max = (one ? index+1 : ops.length);
		for(var i=(one ? index : 0); i < max; i++) {
			var op = ops[i];
			if (op.selected) {
				var v = op.value;
				if (!v) // extra pain for IE...
					v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value;
				if (one) return v;
				a.push(v);
			}
		}
		return a;
	}
	return el.value;
};


/**
 * Clears the form data.  Takes the following actions on the form's input fields:
 *	- input text fields will have their 'value' property set to the empty string
 *	- select elements will have their 'selectedIndex' property set to -1
 *	- checkbox and radio inputs will have their 'checked' property set to false
 *	- inputs of type submit, button, reset, and hidden will *not* be effected
 *	- button elements will *not* be effected
 */
$.fn.clearForm = function() {
	return this.each(function() {
		$('input,select,textarea', this).clearFields();
	});
};

/**
 * Clears the selected form elements.
 */
$.fn.clearFields = $.fn.clearInputs = function() {
	return this.each(function() {
		var t = this.type, tag = this.tagName.toLowerCase();
		if (t == 'text' || t == 'password' || tag == 'textarea')
			this.value = '';
		else if (t == 'checkbox' || t == 'radio')
			this.checked = false;
		else if (tag == 'select')
			this.selectedIndex = -1;
	});
};

/**
 * Resets the form data.  Causes all form elements to be reset to their original value.
 */
$.fn.resetForm = function() {
	return this.each(function() {
		// guard against an input with the name of 'reset'
		// note that IE reports the reset function as an 'object'
		if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType))
			this.reset();
	});
};

/**
 * Enables or disables any matching elements.
 */
$.fn.enable = function(b) {
	if (b == undefined) b = true;
	return this.each(function() {
		this.disabled = !b;
	});
};

/**
 * Checks/unchecks any matching checkboxes or radio buttons and
 * selects/deselects and matching option elements.
 */
$.fn.selected = function(select) {
	if (select == undefined) select = true;
	return this.each(function() {
		var t = this.type;
		if (t == 'checkbox' || t == 'radio')
			this.checked = select;
		else if (this.tagName.toLowerCase() == 'option') {
			var $sel = $(this).parent('select');
			if (select && $sel[0] && $sel[0].type == 'select-one') {
				// deselect all other options
				$sel.find('option').selected(false);
			}
			this.selected = select;
		}
	});
};

// helper fn for console logging
// set $.fn.ajaxSubmit.debug to true to enable debug logging
function log() {
	if (window.console && window.console.log)
		window.console.log('[jquery.form] ' + Array.prototype.join.call(arguments,''));
}



function is_equal(x, y) {
    if ( x === y ) return true;
    if ( ! ( x instanceof Object ) || ! ( y instanceof Object ) ) return false;
    if ( x.constructor !== y.constructor ) return false;
    for ( var p in x ) {
        if ( ! x.hasOwnProperty( p ) ) continue;
        if ( ! y.hasOwnProperty( p ) ) return false;
        if ( x[ p ] === y[ p ] ) continue;
        if ( typeof( x[ p ] ) !== "object" ) return false;
        if ( ! is_equal( x[ p ],  y[ p ] ) ) return false;
    }
    for ( p in y ) {
        if ( y.hasOwnProperty( p ) && ! x.hasOwnProperty( p ) ) return false;
    }
    return true;
}


/**
 * A simple querystring parser.
 * Example usage: var q = $.parseQuery(); q.fooreturns  "bar" if query contains "?foo=bar"; multiple values are added to an array. 
 * Values are unescaped by default and plus signs replaced with spaces, or an alternate processing function can be passed in the params object .
 * http://actingthemaggot.com/jquery
 *
 * Copyright (c) 2008 Michael Manning (http://actingthemaggot.com)
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 **/
$.parseQuery = function(qs,options) {
	var q = (typeof qs === 'string'?qs:window.location.search), o = {'f':function(v){return unescape(v).replace(/\+/g,' ');}}, options = (typeof qs === 'object' && typeof options === 'undefined')?qs:options, o = jQuery.extend({}, o, options), params = {};
	jQuery.each(q.match(/^\??(.*)$/)[1].split('&'),function(i,p){
		p = p.split('=');
		p[1] = o.f(p[1]);
		params[p[0]] = params[p[0]]?((params[p[0]] instanceof Array)?(params[p[0]].push(p[1]),params[p[0]]):[params[p[0]],p[1]]):p[1];
	});
	return params;
}

;
/* Admin widgetManager class
----------------------------------------------------------

@package:	Zotonic 
@Author:	Tim Benniks <tim@timbenniks.nl>
@Author:	Marc Worrell <marc@worrell.nl>

Copyright 2009-2011 Tim Benniks

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
 
http://www.apache.org/licenses/LICENSE-2.0
 
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

---------------------------------------------------------- */

;(function($)
{
	$.extend(
	{
		widgetManager: function(context)
		{
			context		= context || document.body;
			var stack	= [context];

			while (stack.length > 0)
			{
				var objectOptions, defaults, element = stack.pop();
				if (element.className) 
				{
					var objectClass = element.className.match(/do_[a-zA-Z0-9_]+/g);
					if (objectClass) 
					{
						var n = objectClass.length;
						for (var i=0; i<n; i++)
						{
							var functionName = objectClass[i].substring(3);
							var defaultsName = functionName;
							
							if ('dialog' == functionName) functionName = 'show_dialog'; // work around to prevent ui.dialog redefinition

							if (typeof $(element)[functionName] == "function")
							{
								if ($.ui && $.ui[functionName] && $.ui[functionName].defaults)
								{
									defaults = $.ui[functionName].defaults;
								}
								else
								{
									defaults = {}
								}
								$(element)[functionName]( $.extend({}, defaults, $(element).metadata(defaultsName)) );
							}
						}
					}
				}

				if (element.childNodes) 
				{
					for (var i = 0; i< element.childNodes.length; i++)
					{
						if (element.childNodes[i].nodeType != 3)
						{
							stack.unshift(element.childNodes[i]);
						}
					}
				}
			}
		},
		
		misc: 
		{
			log: function(obj)
			{
				if(window.console) 
				{
					console.log(obj);
	
					if($.noticeAdd)
					{
						$.noticeAdd({
							text: 'Logging, check firebug: '+obj, 
							type: 'notice', 
							stay: 0
						});
					}
				} 
				else 
				{
					if($.noticeAdd)
					{
						$.noticeAdd({
							text: 'logged: '+obj, 
							type: 'notice', 
							stay: 0
						});
					}
					else
					{
						alert(obj.toSource());
					}
				}
			},
			
			warn: function(text, obj)
			{
				obj = obj || '';
				
				if(window.console) 
				{
					console.warn(text, obj);
				}
				
				if($.noticeAdd)
				{
					$.noticeAdd({
						text: text, 
						type: 'notice', 
						stay: 1
					});
				}
			},
			
			error: function(text, obj)
			{
				obj = obj || '';

				if(window.console) 
				{
					console.error(text, obj);
				}
				
				if($.noticeAdd)
				{
					$.noticeAdd({
						text: text, 
						type: 'error', 
						stay: 1
					});
				}
			}
		}
	});
	
	$.fn.metadata = function(functionName)
	{
		var elem = this[0];
		var data_name = 'widget-'+functionName;
		var data = $(elem).data(data_name);
		if(data)
		{
			return data;
		}
		data = elem.getAttribute("data-"+functionName);
		if(!data)
		{
			var m = /{(.*)}/.exec(elem.className);
			if(m)
			{
				data = m[1];
			}
			else
			{
				data = "";
			}
		}
		data = eval("({" + data.replace(/[\n\r]/g,' ') + "})");
		$(elem).data(data_name, data);
		return data;
	};
	
	$.fn.widgetManager = function()
	{
		this.each(function() { $.widgetManager(this); });
		return this;
	};

})(jQuery);
;
// LiveValidation 1.3 (standalone version)
// Copyright (c) 2007-2008 Alec Hill (www.livevalidation.com)
// LiveValidation is licensed under the terms of the MIT License

// MW: 20100316: Adapted for async usage with Zotonic.
// MW: 20100629: Added support for presence check on radio buttons
// MW: 20110329: Dynamically fetch the validation fields from the DOM, this makes it
//               possible to add/remove fields dynamically.

/*********************************************** LiveValidation class ***********************************/


function addLiveValidation(element, args) {
	if (!$(element).data("z_live_validation"))
		$(element).data("z_live_validation", new LiveValidation($(element).attr('id'), args));
}


function getLiveValidation(element) {
	return $(element).data("z_live_validation");
}


/**
 *  validates a form field in real-time based on validations you assign to it
 *  
 *  @var element {mixed} - either a dom element reference or the string id of the element to validate
 *  @var optionsObj {Object} - general options, see below for details
 *
 *  optionsObj properties:
 *              validMessage {String}   - the message to show when the field passes validation
 *                            (DEFAULT: "Thankyou!")
 *              onAsync {Function}    - function to execute when field passes is waiting for async validation
 *                            (DEFAULT: function(){ this.insertMessage(this.createSpinnerSpan()); this.addFieldClass(); } ) 
 *              onValid {Function}    - function to execute when field passes validation
 *                            (DEFAULT: function(){ this.insertMessage(this.createMessageSpan()); this.addFieldClass(); } ) 
 *              onInvalid {Function}  - function to execute when field fails validation
 *                            (DEFAULT: function(){ this.insertMessage(this.createMessageSpan()); this.addFieldClass(); })
 *              insertAfterWhatNode {Int}   - position to insert default message
 *                            (DEFAULT: the field that is being validated)  
 *              onlyOnBlur {Boolean} - whether you want it to validate as you type or only on blur
 *                            (DEFAULT: false)
 *              wait {Integer} - the time you want it to pause from the last keystroke before it validates (ms)
 *                            (DEFAULT: 0)
 *              onlyOnSubmit {Boolean} - whether should be validated only when the form it belongs to is submitted
 *                            (DEFAULT: false)            
 */

var LiveValidation = function(element, optionsObj){
    this.initialize(element, optionsObj);
}

LiveValidation.VERSION = '1.3 standalone';

/** element types constants ****/

LiveValidation.TEXTAREA = 1;
LiveValidation.TEXT     = 2;
LiveValidation.PASSWORD = 3;
LiveValidation.CHECKBOX = 4;
LiveValidation.SELECT   = 5;
LiveValidation.FILE     = 6;
LiveValidation.RADIO    = 7;


/****** prototype ******/

LiveValidation.prototype = 
{

    validClass: 'z_valid',
    invalidClass: 'z_invalid',
    messageClass: 'z_validation_message',
    validFieldClass: 'z_valid_field',
    invalidFieldClass: 'form-field-error',
    asyncFieldClass: 'z_async_validation',

    /**
     *  initialises all of the properties and events
     *
     * @var - Same as constructor above
     */
    initialize: function(element, optionsObj){
      var self = this;
      if(!element)
        throw new Error("LiveValidation::initialize - No element reference or element id has been provided!");
      this.element = element.nodeName ? element : document.getElementById(element);
      if(!this.element) 
        throw new Error("LiveValidation::initialize - No element with reference or id of '" + element + "' exists!");
      // default properties that could not be initialised above
      this.validations = [];
      this.elementType = this.getElementType();
      this.form = this.element.form;
      // options
      var options = optionsObj || {};
      this.validMessage = options.validMessage || '';
      var node = options.insertAfterWhatNode || this.element;
      this.insertAfterWhatNode = node.nodeType ? node : document.getElementById(node);
      this.onAsync = options.onAsync || function(){ this.insertSpinner(this.createSpinnerSpan()); this.addFieldClass(); };
      this.onValid = options.onValid || function(){ this.insertMessage(this.createMessageSpan()); this.addFieldClass(); };
      this.onInvalid = options.onInvalid || function(){ this.insertMessage(this.createMessageSpan()); this.addFieldClass(); };  
      this.onlyOnBlur =  options.onlyOnBlur || false;
      this.wait = options.wait || 0;
      this.onlyOnSubmit = options.onlyOnSubmit || false;
      this.validationAsync = false;
      
      // Initialize the form hooks, remember the LiveValidationForm object.
      var theForm = $(this.element).closest("form");
      if(theForm.length){
        this.formObj = LiveValidationForm.getInstance(theForm[0]);
      }

      // events
      // collect old events
      this.oldOnFocus = this.element.onfocus || function(){};
      this.oldOnBlur = this.element.onblur || function(){};
      this.oldOnClick = this.element.onclick || function(){};
      this.oldOnChange = this.element.onchange || function(){};
      this.oldOnKeyup = this.element.onkeyup || function(){};
      this.element.onfocus = function(e){ self.doOnFocus(e); return self.oldOnFocus.call(this, e); }
      if(!this.onlyOnSubmit){
        switch(this.elementType){
          case LiveValidation.RADIO:
          case LiveValidation.CHECKBOX:
            this.element.onclick = function(e){ self.validate(); return self.oldOnClick.call(this, e); }
          // let it run into the next to add a change event too
          case LiveValidation.SELECT:
          case LiveValidation.FILE:
            this.element.onchange = function(e){ self.validate(); return self.oldOnChange.call(this, e); }
            break;
          default:
            if(!this.onlyOnBlur) this.element.onkeyup = function(e){ self.deferValidation(); return self.oldOnKeyup.call(this, e); }
            this.element.onblur = function(e){ self.doOnBlur(e); return self.oldOnBlur.call(this, e); }
        }
      }
    },
  
    /**
     *  destroys the instance's events (restoring previous ones) and removes it from any LiveValidationForms
     */
    destroy: function(){
        // remove events - set them back to the previous events
        this.element.onfocus = this.oldOnFocus;
        if(!this.onlyOnSubmit){
            switch(this.elementType){
              case LiveValidation.RADIO:
              case LiveValidation.CHECKBOX:
                this.element.onclick = this.oldOnClick;
              // let it run into the next to add a change event too
              case LiveValidation.SELECT:
              case LiveValidation.FILE:
                this.element.onchange = this.oldOnChange;
                break;
              default:
                if(!this.onlyOnBlur) this.element.onkeyup = this.oldOnKeyup;
                this.element.onblur = this.oldOnBlur;
            }
        }
        this.validations = [];
        this.removeMessageAndFieldClass();
    },
    
    /**
     * Adds a validation to perform to a LiveValidation object
     *
     * @var validationFunction {Function} - validation function to be used (ie Validate.Presence )
     * @var validationParamsObj {Object} - parameters for doing the validation, if wanted or necessary
     * @return {Object} - the LiveValidation object itself so that calls can be chained
     */
    add: function(validationFunction, validationParamsObj){
      this.validations.push( {type: validationFunction, params: validationParamsObj || {} } );
      return this;
    },
    
    /**
     * Removes a validation from a LiveValidation object - must have exactly the same arguments as used to add it 
     *
     * @var validationFunction {Function} - validation function to be used (ie Validate.Presence )
     * @var validationParamsObj {Object} - parameters for doing the validation, if wanted or necessary
     * @return {Object} - the LiveValidation object itself so that calls can be chained
     */
    remove: function(validationFunction, validationParamsObj){
        var found = false;
        for( var i = 0, len = this.validations.length; i < len; i++ ){
            if( this.validations[i].type == validationFunction ){
                if (this.validations[i].params == validationParamsObj) {
                  found = true;
                  break;
                }
            }
        }
        if(found) this.validations.splice(i,1);
        return this;
    },
    
  
    /**
     * makes the validation wait the alotted time from the last keystroke 
     */
    deferValidation: function(e){
      if(this.wait >= 300) this.removeMessageAndFieldClass();
      var self = this;
      if(this.timeout) clearTimeout(self.timeout);
      this.timeout = setTimeout( function(){ self.validate() }, self.wait); 
    },
        
    /**
     * sets the focused flag to false when field loses focus 
     */
    doOnBlur: function(e){
      this.focused = false;
      this.validate(e);
    },
        
    /**
     * sets the focused flag to true when field gains focus 
     */
    doOnFocus: function(e){
      this.focused = true;
      this.removeMessageAndFieldClass();
    },
    
    /**
     *  gets the type of element, to check whether it is compatible
     *
     *  @var validationFunction {Function} - validation function to be used (ie Validate.Presence )
     *  @var validationParamsObj {Object} - parameters for doing the validation, if wanted or necessary
     */
    getElementType: function(){
        var nodeName = this.element.nodeName.toUpperCase();
        if (nodeName == 'TEXTAREA')
            return LiveValidation.TEXTAREA;
        if (nodeName == 'INPUT' && this.element.type.toUpperCase() == 'TEXT')
            return LiveValidation.TEXT;
        if (nodeName == 'INPUT' && this.element.type.toUpperCase() == 'PASSWORD')
            return LiveValidation.PASSWORD;
        if (nodeName == 'INPUT' && this.element.type.toUpperCase() == 'CHECKBOX')
            return LiveValidation.CHECKBOX;
        if (nodeName == 'INPUT' && this.element.type.toUpperCase() == 'FILE')
            return LiveValidation.FILE;
        if (nodeName == 'INPUT' && this.element.type.toUpperCase() == 'RADIO')
            return LiveValidation.RADIO;
        if (nodeName == 'INPUT' && this.element.type.toUpperCase() == 'EMAIL')
            return LiveValidation.TEXT;
        if (nodeName == 'INPUT' && this.element.type.toUpperCase() == 'TEL')
            return LiveValidation.TEXT;
        if (nodeName == 'INPUT' && this.element.type.toUpperCase() == 'NUMBER')
            return LiveValidation.TEXT;
        if (nodeName == 'INPUT' && this.element.type.toUpperCase() == 'URL')
            return LiveValidation.TEXT;
        if (nodeName == 'INPUT' && this.element.type.toUpperCase() == 'HIDDEN')
            return LiveValidation.TEXT;
        if (nodeName == 'SELECT')
            return LiveValidation.SELECT;
        if (nodeName == 'INPUT')
            throw new Error('LiveValidation::getElementType - Cannot use LiveValidation on an ' + this.element.type + ' input!');
        throw new Error('LiveValidation::getElementType - Element must be an input, select, or textarea!');
    },
    
    /**
     * Loops through all the validations added to the LiveValidation object and checks them one by one
     *
     * @var validationFunction {Function} - validation function to be used (ie Validate.Presence )
     * @var validationParamsObj {Object} - parameters for doing the validation, if wanted or necessary
     * @return {Boolean} - whether the all the validations passed or if one failed
     */
    doValidations: function(isSubmit, submitTrigger){
        var result;

        this.validationFailed = false;
        this.validationAsync = false;
        for(var i = 0, len = this.validations.length; i < len; ++i){
            var validation = this.validations[i];
            switch(validation.type){
                case Validate.Presence:
                case Validate.Confirmation:
                case Validate.Acceptance:
                  this.displayMessageWhenEmpty = true;
                  result = this.validateElement(validation.type, validation.params, isSubmit, submitTrigger); 
                  break;
                default:
                  result = this.validateElement(validation.type, validation.params, isSubmit, submitTrigger);
                  break;
            }
            this.validationFailed = !result;
            if(this.validationFailed) return false; 
          }
          this.message = this.validMessage;
          return true;
    },

    /**
     * Check if there is an async validation.
     */
    isAsync: function (){
        for(var i = 0, len = this.validations.length; i < len; ++i) {
            var validation = this.validations[i];
            if (validation.type == Validate.Postback)
                return true;
        }
        return false;
    },
    
    /**
     * Performs validation on the element and handles any error (validation or otherwise) it throws up
     *
     * @var validationFunction {Function} - validation function to be used (ie Validate.Presence )
     * @var validationParamsObj {Object} - parameters for doing the validation, if wanted or necessary
     * @var isSubmit {Boolean} - is this a form submit or an individual field check
     * @var submitTrigger {Object} - the element that triggered the submit
     * @return {Boolean} or {"async"} - whether the validation has passed, failed or waits for an async server side check
     */
    validateElement: function(validationFunction, validationParamsObj, isSubmit, submitTrigger){
        var value = this.getValue();
        if(validationFunction == Validate.Acceptance){
            if(this.elementType != LiveValidation.CHECKBOX) 
                throw new Error('LiveValidation::validateElement - Element to validate acceptance must be a checkbox!');
            value = this.element.checked;
        }
        var isValid = true;
        try {
            isValid = validationFunction(value, validationParamsObj, isSubmit, submitTrigger);
            if (isValid == 'async') {
                this.validationAsync = true;
            }
        } 
        catch(error) {
            if(error instanceof Validate.Error){
                if( value !== '' || (value === '' && this.displayMessageWhenEmpty) ){
                    this.validationFailed = true;
                    this.message = error.message;
                    isValid = false;
                }
            } else {
                throw error;
            }
        }
        return isValid;
    },
    
    
    getValue: function() {
		switch (this.elementType) {
		case LiveValidation.SELECT:
			if (this.element.selectedIndex >= 0) return this.element.options[this.element.selectedIndex].value;
			else return "";
		case LiveValidation.RADIO:
			var val = $('input[name='+this.element.name+']:checked').val();
			return val;
		default:
			return this.element.value;
		}
    },

    /**
     * Do all the validations and fires off the onValid or onInvalid callbacks
     *
     * @var isSubmit {Boolean} - is this a form submit or an individual field check
     * @var submitTrigger {Object} - the element that triggered the submit
     * @return {Boolean} - whether all the validations passed or if one failed
     */
    validate: function(isSubmit, submitTrigger){
        if(!this.element.disabled) {
            var isValid = this.doValidations(isSubmit, submitTrigger);
            if (this.validationAsync) {
                this.onAsync();
                return false;
            } else if (isValid) {
                this.onValid();
                return true;
            } else {
                this.onInvalid();
                return false;
            }
        } else {
            return true;
        }
    },
  
    /**
     * Called when there is an async validation result.
     * The caller has already checked if the current input value hasn't changed.
     */
    asyncValidationResult: function(isValid){
        if (this.validationAsync){
            // Find which validation was waiting for async, assume only one async postback per field.
            for(var i = 0, len = this.validations.length; i < len; ++i){
                var validation = this.validations[i];
                if(validation.type == Validate.Postback){
                    // Clear the async wait flag
                    this.validationAsync = false;
                    this.validationFailed = !isValid;
                    if (isValid){
                        this.onValid();
                    } else {
                        this.onInvalid();
                    }
                    this.formObj.asyncResult(this, isValid)
                }
            }
        }
    },
    
    /**
     *  enables the field
     *
     *  @return {LiveValidation} - the LiveValidation object for chaining
     */
    enable: function(){
        this.element.disabled = false;
        return this;
    },

    /**
     *  disables the field and removes any message and styles associated with the field
     *
     *  @return {LiveValidation} - the LiveValidation object for chaining
     */
    disable: function(){
        this.element.disabled = true;
        this.removeMessageAndFieldClass();
        return this;
    },
    
    /** Message insertion methods ****************************
     * 
     * These are only used in the onValid and onInvalid callback functions and so if you overide the default callbacks,
     * you must either impliment your own functions to do whatever you want, or call some of these from them if you 
     * want to keep some of the functionality
     */
 
     /**
      *  makes a span containing a spinner image
      *
      * @return {HTMLSpanObject} - a span element with the message in it
      */
     createSpinnerSpan: function(){
         var span = document.createElement('span');
         span.innerHTML = '<img src="/lib/images/spinner.gif" height="16" width="16" alt="Validating..." />';
         return span;
     },
   
    /**
     *  makes a span containg the passed or failed message
     *
     * @return {HTMLSpanObject} - a span element with the message in it
     */
    createMessageSpan: function(){
        var span = document.createElement('span');
        var textNode = document.createTextNode(this.message);
        span.appendChild(textNode);
        return span;
    },
    
    /**
     * Show an error message
     */
    showErrorMessage: function(message){
        this.message = message;
        this.onInvalid();
    },
    
    /** 
     * Insert a spinner in the message element.
     */
    insertSpinner: function (elementToInsert){
        this.removeMessage();
        if( (this.displayMessageWhenEmpty && (this.elementType == LiveValidation.CHECKBOX || this.element.value == ''))
          || this.element.value != '' ){

          elementToInsert.className += ' ' + this.messageClass + ' ' + this.asyncFieldClass;
          if(this.insertAfterWhatNode.nextSibling){
              this.insertAfterWhatNode.parentNode.insertBefore(elementToInsert, this.insertAfterWhatNode.nextSibling);
          }else{
              this.insertAfterWhatNode.parentNode.appendChild(elementToInsert);
          }
        }
        
    },
    
    /**
     *  inserts the element containing the message in place of the element that already exists (if it does)
     *
     * @var elementToIsert {HTMLElementObject} - an element node to insert
     */
    insertMessage: function(elementToInsert){
        this.removeMessage();
		if (this.elementType != LiveValidation.RADIO) {
	        if( (this.displayMessageWhenEmpty && (this.elementType == LiveValidation.CHECKBOX || this.element.value == ''))
	            || this.element.value != '' ){
            
	            var className = this.validationFailed ? this.invalidClass : this.validClass;
	            elementToInsert.className += ' ' + this.messageClass + ' ' + className;
	            if(this.insertAfterWhatNode.nextSibling){
	              this.insertAfterWhatNode.parentNode.insertBefore(elementToInsert, this.insertAfterWhatNode.nextSibling);
	            }else{
	                  this.insertAfterWhatNode.parentNode.appendChild(elementToInsert);
	            }
			 }
	      }
    },
    
    
    /**
     *  changes the class of the field based on whether it is valid or not
     */
    addFieldClass: function(){
        this.removeFieldClass();
        if(!this.validationFailed){
            if(this.displayMessageWhenEmpty || this.element.value != ''){
				switch (this.elementType) {
				case LiveValidation.RADIO:
	            	$('input[name='+this.element.name+']').closest('label').addClass(this.validFieldClass);
					break;
				default:
                	$(this.element).addClass(this.validFieldClass);
					break;
				}
            }
        }else{
			switch (this.elementType) {
			case LiveValidation.RADIO:
            	$('input[name='+this.element.name+']').closest('label').addClass(this.invalidFieldClass);
				break;
			default:
            	$(this.element).addClass(this.invalidFieldClass);
				break;
			}
        }
    },
    
    /**
     *  removes the message element if it exists, so that the new message will replace it
     */
    removeMessage: function(){
      var nextEl;
      var el = this.insertAfterWhatNode;
      while(el.nextSibling){
          if(el.nextSibling.nodeType === 1){
            nextEl = el.nextSibling;
            break;
        }
        el = el.nextSibling;
      }
        if(nextEl && nextEl.className.indexOf(this.messageClass) != -1) this.insertAfterWhatNode.parentNode.removeChild(nextEl);
    },
    
    /**
     *  removes the class that has been applied to the field to indicate if valid or not
     */
    removeFieldClass: function(){
		switch (this.elementType) {
		case LiveValidation.RADIO:
        	$('input[name='+this.element.name+']').closest('label').removeClass(this.invalidFieldClass).removeClass(this.validFieldClass);
			break;
		default:
    		$(this.element).removeClass(this.invalidFieldClass).removeClass(this.validFieldClass);
			break;
		}
    },
        
    /**
     *  removes the message and the field class
     */
    removeMessageAndFieldClass: function(){
      this.removeMessage();
      this.removeFieldClass();
    }

} // end of LiveValidation class




/*************************************** LiveValidationForm class ****************************************/
/**
 * This class is used internally by LiveValidation class to associate a LiveValidation field with a form it is icontained in one
 * 
 * It will therefore not really ever be needed to be used directly by the developer, unless they want to associate a LiveValidation 
 * field with a form that it is not a child of
 */

/**
   *  handles validation of LiveValidation fields belonging to this form on its submittal
   *  
   *  @var element {HTMLFormElement} - a dom element reference to the form to turn into a LiveValidationForm
   */
var LiveValidationForm = function(element){
  this.initialize(element);
}

/**
   *  gets the instance of the LiveValidationForm if it has already been made or creates it if it doesnt exist
   *  
   *  @var element {HTMLFormElement} - a dom element reference to a form
   */
LiveValidationForm.getInstance = function(element){
  var rand = Math.random() * Math.random();
  if(!$(element).attr("id"))
    $(element).attr("id", 'formId_' + rand.toString().replace(/\./, '') + new Date().valueOf());
  var instance = $(element).data("z_live_validation_instance");
  if (!instance) {
      instance = new LiveValidationForm(element);
      $(element).data("z_live_validation_instance", instance);
  }
  return instance;
}

LiveValidationForm.prototype = {
  validFormClass: 'z_form_valid',
  invalidFormClass: 'z_form_invalid',

  /**
   *  constructor for LiveValidationForm - handles validation of LiveValidation fields belonging to this form on its submittal
   *  
   *  @var element {HTMLFormElement} - a dom element reference to the form to turn into a LiveValidationForm
   */
  initialize: function(element){
    this.name = $(element).attr("id");
    this.element = element;
    this.skipValidations = 0;
    this.submitWaitForAsync = new Array();

    // preserve the old onsubmit event
    this.oldOnSubmit = this.element.onsubmit || function(){};
    var self = this;

    this.onInvalid = function() { 
		$(this).removeClass("z_form_valid").addClass("z_form_invalid"); 
		$(".z_form_valid", this).hide();
		$(".z_form_invalid", this).fadeIn();
	};
    this.onValid = function() { 
		$(this).removeClass("z_form_invalid").addClass("z_form_valid");
		$(".z_form_invalid", this).hide();
		$(".z_form_valid", this).fadeIn();
	};

    $(element).submit(function(event) {
        event.zIsValidated = true;
        if (self.skipValidations == 0) {
            var result = true;
            var async = new Array();
            var is_first = true;

			var fields = self.getFields();
            for(var i = 0, len = fields.length; i < len; ++i ) {
                if (!fields[i].element.disabled) {
                    if (fields[i].isAsync()) {
                        async.push(fields[i]);
                    } else {
						var valid = fields[i].validate(true, this.clk);
                        result = result && valid;
                    }
                }
            }

            if (async.length > 0){
                if (result)
					self.submitWaitForAsync = async;
				else 
					self.onInvalid.call(this);

                for(var i=0; i<async.length; i++){
                    async[i].validate(true, this.clk);
                }
                result = false;
            }
			else if (!result) {
				self.onInvalid.call(this);
			}
            
            if (!result) {
                // Either validation failed or we are waiting for more async results.
                event.stopImmediatePropagation();
                return false;
            } else {
				self.onValid.call(this);
                return z_form_submit_validated_do(event);
            }
        } else {
            self.skipValidations--;
            if (self.skipValidations == 0) {
				self.onValid.call(this);
                return z_form_submit_validated_do(event);
            } else {
                return false;
			}
        }
    })
  },
  
  /**
   *  destroy this instance and its events
   *
   * @var force {Boolean} - whether to force the destruction even if there are fields still associated
   */
  destroy: function(force){
	if (force || this.getFields().length == 0) {
	    // remove events - set back to previous events
	    this.element.onsubmit = this.oldOnSubmit;
	    // remove from the instances namespace
	    $(this.element).removeData("z_live_validation_instance");
	    return true;
	} else {
		return false;
	}
  },
  
  /**
   * get the to-be-validated fields
   */
  getFields: function() {
	var fields = [];
	$("input,select,textarea", this.element).each(function() {
		var field = $(this).data('z_live_validation');
		if (field) {
			fields.push(field);
		}
	});
	return fields;
  },

  asyncResult: function(Validation, isValid){
      if (isValid){
          var index = $.inArray(Validation, this.submitWaitForAsync);
          if (index >= 0){
              this.submitWaitForAsync.splice(index, 1);
              if (this.submitWaitForAsync.length == 0){
                  // All validations were successful, resubmit (and skip validations for once)
                  this.skipValidations = 1;
                  var formObj = this.element;
				  this.onValid.call(this);
                  setTimeout(function(){ $(formObj).submit(); }, 0);
              }
          }
      } else {
		  if (this.submitWaitForAsync.length > 0) {
			var formObj = this.element;
			this.onInvalid.call(this);
		  }
          this.submitWaitForAsync = new Array();
      }
  }
}// end of LiveValidationForm prototype




/*************************************** Validate class ****************************************/
/**
 * This class contains all the methods needed for doing the actual validation itself
 *
 * All methods are static so that they can be used outside the context of a form field
 * as they could be useful for validating stuff anywhere you want really
 *
 * All of them will return true if the validation is successful, but will raise a ValidationError if
 * they fail, so that this can be caught and the message explaining the error can be accessed ( as just 
 * returning false would leave you a bit in the dark as to why it failed )
 *
 * Can use validation methods alone and wrap in a try..catch statement yourself if you want to access the failure
 * message and handle the error, or use the Validate::now method if you just want true or false
 */

var Validate = {

    /**
     *  validates that the field has been filled in
     *
     *  @var value {mixed} - value to be checked
     *  @var paramsObj {Object} - parameters for this particular validation, see below for details
     *
     *  paramsObj properties:
     *              failureMessage {String} - the message to show when the field fails validation 
     *                            (DEFAULT: "Can't be empty!")
     */
    Presence: function(value, paramsObj){
        var paramsObj = paramsObj || {};
        var message = paramsObj.failureMessage || "*";
        if(value === '' || value === null || value === undefined){ 
            Validate.fail(message);
        }
        return true;
    },
    
    /**
     *  validates that the value is numeric, does not fall within a given range of numbers
     *  
     *  @var value {mixed} - value to be checked
     *  @var paramsObj {Object} - parameters for this particular validation, see below for details
     *
     *  paramsObj properties:
     *              notANumberMessage {String} - the message to show when the validation fails when value is not a number
     *                                (DEFAULT: "Must be a number!")
     *              notAnIntegerMessage {String} - the message to show when the validation fails when value is not an integer
     *                                (DEFAULT: "Must be a number!")
     *              wrongNumberMessage {String} - the message to show when the validation fails when is param is used
     *                                (DEFAULT: "Must be {is}!")
     *              tooLowMessage {String}    - the message to show when the validation fails when minimum param is used
     *                                (DEFAULT: "Must not be less than {minimum}!")
     *              tooHighMessage {String}   - the message to show when the validation fails when maximum param is used
     *                                (DEFAULT: "Must not be more than {maximum}!")
     *              is {Int}          - the length must be this long 
     *              minimum {Int}         - the minimum length allowed
     *              maximum {Int}         - the maximum length allowed
     *                         onlyInteger {Boolean} - if true will only allow integers to be valid
     *                                                             (DEFAULT: false)
     *
     *  NB. can be checked if it is within a range by specifying both a minimum and a maximum
     *  NB. will evaluate numbers represented in scientific form (ie 2e10) correctly as numbers       
     */
    Numericality: function(value, paramsObj){
        var suppliedValue = value;
        var value = Number(value);
        var paramsObj = paramsObj || {};
        var minimum = ((paramsObj.minimum) || (paramsObj.minimum == 0)) ? paramsObj.minimum : null;;
        var maximum = ((paramsObj.maximum) || (paramsObj.maximum == 0)) ? paramsObj.maximum : null;
        var is = ((paramsObj.is) || (paramsObj.is == 0)) ? paramsObj.is : null;
        var notANumberMessage = paramsObj.notANumberMessage || "Must be a number.";
        var notAnIntegerMessage = paramsObj.notAnIntegerMessage || "Must be an integer.";
        var wrongNumberMessage = paramsObj.wrongNumberMessage || "Must be " + is + ".";
        var tooLowMessage = paramsObj.tooLowMessage || "Must not be less than " + minimum + ".";
        var tooHighMessage = paramsObj.tooHighMessage || "Must not be more than " + maximum + ".";
        
        if (!isFinite(value)) 
            Validate.fail(notANumberMessage);
        if (paramsObj.onlyInteger && (/\.0+$|\.$/.test(String(suppliedValue))  || value != parseInt(value)) )
            Validate.fail(notAnIntegerMessage);
        switch(true){
            case (is !== null):
                if( value != Number(is) ) Validate.fail(wrongNumberMessage);
                break;
            case (minimum !== null && maximum !== null):
                Validate.Numericality(value, {tooLowMessage: tooLowMessage, minimum: minimum});
                Validate.Numericality(value, {tooHighMessage: tooHighMessage, maximum: maximum});
                break;
            case (minimum !== null):
                if( value < Number(minimum) ) Validate.fail(tooLowMessage);
                break;
            case (maximum !== null):
                if( value > Number(maximum) ) Validate.fail(tooHighMessage);
                break;
        }
        return true;
    },
    
    /**
     *  validates against a RegExp pattern
     *  
     *  @var value {mixed} - value to be checked
     *  @var paramsObj {Object} - parameters for this particular validation, see below for details
     *
     *  paramsObj properties:
     *              failureMessage {String} - the message to show when the field fails validation
     *                            (DEFAULT: "Not valid!")
     *              pattern {RegExp}    - the regular expression pattern
     *                            (DEFAULT: /./)
     *             negate {Boolean} - if set to true, will validate true if the pattern is not matched
   *                           (DEFAULT: false)
     *
     *  NB. will return true for an empty string, to allow for non-required, empty fields to validate.
     *    If you do not want this to be the case then you must either add a LiveValidation.PRESENCE validation
     *    or build it into the regular expression pattern
     */
    Format: function(value, paramsObj){
      var value = String(value);
      var paramsObj = paramsObj || {};
      var message = paramsObj.failureMessage || "Not valid.";
      var pattern = paramsObj.pattern || /./;
      var negate = paramsObj.negate || false;
      if(!negate && !pattern.test(value)) Validate.fail(message); // normal
      if(negate && pattern.test(value)) Validate.fail(message); // negated
      return true;
    },
    
    /**
     *  validates that the field contains a valid email address
     *  
     *  @var value {mixed} - value to be checked
     *  @var paramsObj {Object} - parameters for this particular validation, see below for details
     *
     *  paramsObj properties:
     *              failureMessage {String} - the message to show when the field fails validation
     *                            (DEFAULT: "Must be a number!" or "Must be an integer!")
     */
    Email: function(value, paramsObj){
      var paramsObj = paramsObj || {};
      var message = paramsObj.failureMessage || "Incorrect E-mail";
      value = $.trim(value);
      Validate.Format(value, { failureMessage: message, pattern: /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i } );
      return true;
    },
    
    /**
     *  validates the length of the value
     *  
     *  @var value {mixed} - value to be checked
     *  @var paramsObj {Object} - parameters for this particular validation, see below for details
     *
     *  paramsObj properties:
     *              wrongLengthMessage {String} - the message to show when the fails when is param is used
     *                                (DEFAULT: "Must be {is} characters long!")
     *              tooShortMessage {String}  - the message to show when the fails when minimum param is used
     *                                (DEFAULT: "Must not be less than {minimum} characters long!")
     *              tooLongMessage {String}   - the message to show when the fails when maximum param is used
     *                                (DEFAULT: "Must not be more than {maximum} characters long!")
     *              is {Int}          - the length must be this long 
     *              minimum {Int}         - the minimum length allowed
     *              maximum {Int}         - the maximum length allowed
     *
     *  NB. can be checked if it is within a range by specifying both a minimum and a maximum       
     */
    Length: function(value, paramsObj){
        var value = String(value);
        var paramsObj = paramsObj || {};
        var minimum = ((paramsObj.minimum) || (paramsObj.minimum == 0)) ? paramsObj.minimum : null;
        var maximum = ((paramsObj.maximum) || (paramsObj.maximum == 0)) ? paramsObj.maximum : null;
        var is = ((paramsObj.is) || (paramsObj.is == 0)) ? paramsObj.is : null;
        var wrongLengthMessage = paramsObj.wrongLengthMessage || "Must be " + is + " characters long.";
        var tooShortMessage = paramsObj.tooShortMessage || "Must not be less than " + minimum + " characters long.";
        var tooLongMessage = paramsObj.tooLongMessage || "Must not be more than " + maximum + " characters long.";
        switch(true){
            case (is !== null):
                if( value.length != Number(is) ) Validate.fail(wrongLengthMessage);
                break;
            case (minimum !== null && maximum !== null):
                Validate.Length(value, {tooShortMessage: tooShortMessage, minimum: minimum});
                Validate.Length(value, {tooLongMessage: tooLongMessage, maximum: maximum});
                break;
            case (minimum !== null):
                if( value.length < Number(minimum) ) Validate.fail(tooShortMessage);
                break;
            case (maximum !== null):
                if( value.length > Number(maximum) ) Validate.fail(tooLongMessage);
                break;
            default:
                throw new Error("Validate::Length - Length(s) to validate against must be provided");
        }
        return true;
    },
    
    /**
     *  validates that the value falls within a given set of values
     *  
     *  @var value {mixed} - value to be checked
     *  @var paramsObj {Object} - parameters for this particular validation, see below for details
     *
     *  paramsObj properties:
     *              failureMessage {String} - the message to show when the field fails validation
     *                            (DEFAULT: "Must be included in the list!")
     *              within {Array}      - an array of values that the value should fall in 
     *                            (DEFAULT: []) 
     *              allowNull {Bool}    - if true, and a null value is passed in, validates as true
     *                            (DEFAULT: false)
     *             partialMatch {Bool}  - if true, will not only validate against the whole value to check but also if it is a substring of the value 
     *                            (DEFAULT: false)
     *             caseSensitive {Bool} - if false will compare strings case insensitively
     *                          (DEFAULT: true)
     *             negate {Bool}    - if true, will validate that the value is not within the given set of values
     *                            (DEFAULT: false)      
     */
    Inclusion: function(value, paramsObj){
      var paramsObj = paramsObj || {};
      var message = paramsObj.failureMessage || "Must be included in the list!";
      var caseSensitive = (paramsObj.caseSensitive === false) ? false : true;
      if(paramsObj.allowNull && value == null) return true;
      if(!paramsObj.allowNull && value == null) Validate.fail(message);
      var within = paramsObj.within || [];
      //if case insensitive, make all strings in the array lowercase, and the value too
      if(!caseSensitive){ 
        var lowerWithin = [];
        for(var j = 0, length = within.length; j < length; ++j){
          var item = within[j];
          if(typeof item == 'string') item = item.toLowerCase();
          lowerWithin.push(item);
        }
        within = lowerWithin;
        if(typeof value == 'string') value = value.toLowerCase();
      }
      var found = false;
      for(var i = 0, length = within.length; i < length; ++i){
        if(within[i] == value) found = true;
        if(paramsObj.partialMatch){ 
          if(value.indexOf(within[i]) != -1) found = true;
        }
      }
      if( (!paramsObj.negate && !found) || (paramsObj.negate && found) ) Validate.fail(message);
      return true;
    },
    
    /**
     *  validates that the value does not fall within a given set of values
     *  
     *  @var value {mixed} - value to be checked
     *  @var paramsObj {Object} - parameters for this particular validation, see below for details
     *
     *  paramsObj properties:
     *              failureMessage {String} - the message to show when the field fails validation
     *                            (DEFAULT: "Must not be included in the list!")
     *              within {Array}      - an array of values that the value should not fall in 
     *                            (DEFAULT: [])
     *              allowNull {Bool}    - if true, and a null value is passed in, validates as true
     *                            (DEFAULT: false)
     *             partialMatch {Bool}  - if true, will not only validate against the whole value to check but also if it is a substring of the value 
     *                            (DEFAULT: false)
     *             caseSensitive {Bool} - if false will compare strings case insensitively
     *                          (DEFAULT: true)     
     */
    Exclusion: function(value, paramsObj){
      var paramsObj = paramsObj || {};
      paramsObj.failureMessage = paramsObj.failureMessage || "Must not be included in the list";
      paramsObj.negate = true;
      Validate.Inclusion(value, paramsObj);
      return true;
    },
    
    /**
     *  validates that the value matches that in another field
     *  
     *  @var value {mixed} - value to be checked
     *  @var paramsObj {Object} - parameters for this particular validation, see below for details
     *
     *  paramsObj properties:
     *              failureMessage {String} - the message to show when the field fails validation
     *                            (DEFAULT: "Does not match!")
     *              match {String}      - id of the field that this one should match            
     */
    Confirmation: function(value, paramsObj){
        if(!paramsObj.match) 
            throw new Error("Validate::Confirmation - Error validating confirmation: Id of element to match must be provided");
        var paramsObj = paramsObj || {};
        var message = paramsObj.failureMessage || "Does not match.";
        var match = paramsObj.match.nodeName ? paramsObj.match : document.getElementById(paramsObj.match);
        if(!match) 
            throw new Error("Validate::Confirmation - There is no reference with name of, or element with id of '" + paramsObj.match + "'");
        if(value != match.value){ 
          Validate.fail(message);
        }
        return true;
    },
    
    /**
     *  validates that the value is true (for use primarily in detemining if a checkbox has been checked)
     *  
     *  @var value {mixed} - value to be checked if true or not (usually a boolean from the checked value of a checkbox)
     *  @var paramsObj {Object} - parameters for this particular validation, see below for details
     *
     *  paramsObj properties:
     *              failureMessage {String} - the message to show when the field fails validation 
     *                            (DEFAULT: "Must be accepted!")
     */
    Acceptance: function(value, paramsObj){
        var paramsObj = paramsObj || {};
        var message = paramsObj.failureMessage || "Must be accepted.";
        if(!value){ 
        Validate.fail(message);
        }
        return true;
    },
    
   /**
     *  validates against a custom function that returns true or false (or throws a Validate.Error) when passed the value
     *  
     *  @var value {mixed} - value to be checked
     *  @var paramsObj {Object} - parameters for this particular validation, see below for details
     *
     *  paramsObj properties:
     *              failureMessage {String} - the message to show when the field fails validation
     *                            (DEFAULT: "Not valid!")
     *              against {Function}      - a function that will take the value and object of arguments and return true or false 
     *                            (DEFAULT: function(){ return true; })
     *              args {Object}     - an object of named arguments that will be passed to the custom function so are accessible through this object within it 
     *                            (DEFAULT: {})
     */
    Custom: function(value, paramsObj, isSubmit, submitTrigger){
        var paramsObj = paramsObj || {};
        var against = paramsObj.against || function(){ return true; };
        var args = paramsObj.args || {};
        var message = paramsObj.failureMessage || "Not valid.";
        if (typeof against == "string") {
            var result = z_call_function_by_name(against, window, value, args, isSubmit, submitTrigger);
        } else {
            var result = against(value, args, isSubmit, submitTrigger);
        }
        if(!result) Validate.fail(message);
        return true;
    },


    /**
     * Performs a postback, delays the check till the postback is returned. till then a spinner is shown
     * next to the input element. 
     */
    Postback: function(value, paramsObj, isSubmit, submitTrigger) {
        var paramsObj = paramsObj || {};
        var against = paramsObj.against || function(){ return true; };
        var args = paramsObj.args || {};
        var message = paramsObj.failureMessage || "Not valid.";

        if (!against(value, args, isSubmit, submitTrigger)) {
            Validate.fail(message);
        } else if (paramsObj.z_postback) {
            // Perform the async postback
            extraParams = new Array();
            if (isSubmit) {
                extraParams.push({name: 'z_submitter', value: (submitTrigger && submitTrigger.name) ? submitTrigger.name : ''});
            }
            z_queue_postback(paramsObj.z_id, paramsObj.z_postback, extraParams); 
            return 'async';
        } else {
            return true;
        }
     },

  
    /**
     *  validates whatever it is you pass in, and handles the validation error for you so it gives a nice true or false reply
     *
     *  @var validationFunction {Function} - validation function to be used (ie Validation.validatePresence )
     *  @var value {mixed} - value to be checked if true or not (usually a boolean from the checked value of a checkbox)
     *  @var validationParamsObj {Object} - parameters for doing the validation, if wanted or necessary
     */
    now: function(validationFunction, value, validationParamsObj){
        if(!validationFunction) throw new Error("Validate::now - Validation function must be provided!");
      var isValid = true;
        try{    
        validationFunction(value, validationParamsObj || {});
      } catch(error) {
        if(error instanceof Validate.Error){
          isValid =  false;
        }else{
          throw error;
        }
      }finally{ 
            return isValid 
        }
    },
    
    /**
     * shortcut for failing throwing a validation error
     *
     *  @var errorMessage {String} - message to display
     */
    fail: function(errorMessage){
            throw new Validate.Error(errorMessage);
    },
    
    Error: function(errorMessage){
      this.message = errorMessage;
      this.name = 'ValidationError';
    }

}
;
$.ui.datepicker.defaults = { 
	dateFormat: 'yy-mm-dd',
	changeYear: true,
	changeMonth: true,
	yearRange: 'c-100:+10',
	showAnim: 'fadeIn',
	firstDay: 1
};
;
/* dialog js
----------------------------------------------------------

@package:	Zotonic 2009	
@Author:	Tim Benniks <tim@timbenniks.nl>

Copyright 2009 Tim Benniks

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
 
http://www.apache.org/licenses/LICENSE-2.0
 
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

---------------------------------------------------------- */

(function($)
{
	$.extend({
		dialogAdd: function(options)
		{	
			$('.dialog').remove(); 

			if(!$('.dialog').length)
			{
				// declare varaibles
				var options, dialogWrapper, dialogTop, dialogTitle, dialogTLC, dialogTRC, dialogClose, dialogContent, dialogInnerContent, dialogRightContent, dialogBottom, dialogBLC, dialogBRC, leftPos, topPos;
				var width;
				
				options = $.extend({}, $.ui.dialog.defaults, options);
				
				var dialogClass = 'dialog'
				if (typeof(options.addclass) == "string")
					dialogClass = 'dialog '+options.addclass
					
				if (typeof(options.width) == "number")
					width = options.width + "px";
				else
					width = options.width;

				dialogTitle			= $('<h5></h5>').addClass('dialog-title').text(options.title);
				dialogTLC			= $('<span></span>').addClass('dialog-top-left');
				dialogTRC			= $('<span></span>').addClass('dialog-top-right');
				dialogClose			= $('<span></span>').addClass('dialog-close').click(function(){ $.dialogRemove(dialogWrapper); });
				dialogInnerContent	= $('<div></div>').addClass('dialog-inner-content').html(options.text);
				dialogRightContent	= $('<span></span>').addClass('dialog-content-right');
				dialogBLC			= $('<span></span>').addClass('dialog-bottom-left');
				dialogBRC			= $('<span></span>').addClass('dialog-bottom-right');
				leftPos				= Math.floor((parseInt($(window).width()) / 2) - (parseInt(width) / 2));
				
				var scrollTop = $(window).scrollTop();
				topPos				= scrollTop + 100;
			
				dialogTop			= $('<div></div>').addClass('dialog-top').append(dialogTitle, dialogTLC, dialogTRC, dialogClose);
				dialogContent		= $('<div></div>').addClass('dialog-content clearfix').append(dialogInnerContent, dialogRightContent);
				dialogBottom		= $('<div></div>').addClass('dialog-bottom').append(dialogBLC, dialogBRC);
			
				dialogWrapper		= $('<div></div>')
										.addClass(dialogClass)
										.append(dialogTop, dialogContent, dialogBottom)
										.fadeIn(300)
										.css({left: leftPos, top: topPos, width: width})
										.draggable({addClasses: false, handle: dialogTop, opacity: 0.90, zIndex: 2700, iframeFix: true, scroll: true});

				$(document).keypress(function(e)
				{
					if($.browser.msie)	{ var key = e.which }
					else				{ key = e.keyCode } 
					
					if(key == $.ui.keyCode.ESCAPE)
					{
						dialogClose.click();
					}
				});
				
				$('body').append(dialogWrapper);

				/* Make sure that the dialog is within the viewport */
				var dialogHeight = dialogWrapper.height();
				var windowHeight = $(window).height();
				if (100 + dialogHeight > windowHeight) {
					var newTop = scrollTop + windowHeight - dialogHeight - 20;
					
					$(dialogWrapper).css({top: newTop > scrollTop ? newTop : scrollTop});
				}

				$('input[type=text]', dialogWrapper).focus();
				if (typeof($.widgetManager) != 'undefined') {
					dialogWrapper.widgetManager();
				}
				z_tinymce_add(dialogWrapper);
			}
		},

		dialogClose: function()
		{
			$('.dialog-close').click();
		},
		
		dialogRemove: function(obj)
		{
			obj = obj || $('.dialog');
			z_tinymce_remove(obj);
			obj.draggable('destroy').resizable('destroy').fadeOut(300, function()
			{
				$(this).remove();
			});
		}
	});
	
	$.widget("ui.show_dialog", 
	{
		_init: function() 
		{
			var title	= this.options.title;
			var text	= this.options.text;
			var width	= this.options.width;
			
			this.element.click(function()
			{
				$.dialogAdd(
				{
					title: title,
					text:  text,
					width: width
				})
			})
		}
	});
	
	$.ui.dialog.defaults = {
		title: 'Title',
		text: 'tekst',
		width: '450px'
	}
})(jQuery);
;
/* feedback js
----------------------------------------------------------

@package:	Zotonic 2011	
@Author:	Marc Worrell <marc@worrell.nl>

Copyright 2011 Marc Worrell

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
 
http://www.apache.org/licenses/LICENSE-2.0
 
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

---------------------------------------------------------- */

$.widget("ui.feedback", 
{
	_init: function() 
	{
		var self = this;
		z_ensure_id(this.element);
		$('#'+this.options.trigger)
			.bind('keyup', function() { self.update(); })
			.bind('change', function() { self.update(); });
	},
	
	update: function() 
	{
		if (this.input_updater != undefined)
		{
			clearInterval(this.input_updater);
			this.input_updater = undefined;
		}
		
		var self = this;
		this.input_updater = setInterval(function()
		{
			// Fetch the update
			var args = [];
			var trigger = $('#'+self.options.trigger);
			if (trigger.length == 0) {
				clearInterval(self.input_updater);
				self.input_updater = undefined;
			} else {
				if (trigger.prop('tagName').toLowerCase() == 'form') {
					args = trigger.formToArray();
				} else {
					var v = $.fieldValue(trigger.get(0), true);
					if (v && v.constructor == Array) {
						for(var j=0, jmax=v.length; j < jmax; j++)
							args.push({name: "triggervalue", value: v[j]});
					}
					else if (v !== null && typeof v != 'undefined')
						args.push({name: "triggervalue", value: v});
				}

				// Stop when there is an 'in-flight' update with the same args
				if (self.element.hasClass('loading')) {
					if (self.last_args == $.param(args)) {
						clearInterval(self.input_updater);
						self.input_updater = undefined;
					}
				} else {
					self.last_args = $.param(args);
					clearInterval(self.input_updater);

					// Form changed, post it to the server
					var notify_args = {};
					for (var i=0; i<args.length; i++) {
						notify_args[args[i].name] = args[i].value;
					}
					notify_args.z_trigger_id = self.options.trigger;
					notify_args.z_target_id = $(self.element).attr('id');
					notify_args.z_delegate = self.options.delegate;
					for (var k in self.options) {
						if (k != "delegate" && k != "timeout" && k != "delegate") {
							notify_args[k] = self.options[k];
						}
					}
					
					if (typeof self.previous_feedback == 'undefined' || !is_equal(self.previous_feedback, notify_args)) {
						self.element.addClass('loading');
						self.previous_feedback = notify_args;
						z_notify("feedback", notify_args);
					}
				}
			}
		}, self.options.timeout);
	}
});

$.ui.feedback.defaults = {
	delegate: undefined,
	trigger: undefined,
	timeout: 400
}
;
/* inputoverlay js
----------------------------------------------------------

@package:       Zotonic 2010
@Author:        Konstantin Nikiforov <helllamer@gmail.com>

Copyright 2010 Konstantin Nikiforov

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

---------------------------------------------------------- */

/* switches the state of inplace_textbox to reading mode */
function inplace_textbox_write2read(div) {    
    div_read = div + "-r";
    div_nodata = div + "-n";
    input = div + "-i";
    div_edit = div + "-e";

    $("#" + div_edit).hide();
    
    value_input = $("#" + input).attr('value');
    $("#" + div_read + " span:first").html(value_input);
    if (jQuery.trim(value_input) == '') {
        $("#" + div_nodata).show();
    } else {
        $("#" + div_read).show();
    }
}

/* switches the state of inplace_textbox to editing mode */
function inplace_textbox_read2write(div) {
    div_read = div + "-r";
    $("#" + div_read).hide();
    
    div_nodata = div + "-n";
    $("#" + div_nodata).hide();

    div_edit = div + "-e";
    input = div + "-i";

    value = $("#" + div_read + " span:first").html();
    $("#" + input).attr('value', value); // copypaste value from text-span to input control
    $("#" + div_edit).show();
    $("#" + div_edit + " input:first").focus().select();
}


// show hint in read mode
function inplace_textbox_show_hint(div) { $("#" + div + "-r" + " > #hint:first").show(); }
function inplace_textbox_hide_hint(div) { $("#" + div + "-r" + " > #hint:first").hide(); }


// initialize inplace_textboxes
$(".inplace_textbox").each( function() {inplace_textbox_write2read($(this).attr('id')) } );

;
/* inputoverlay js
----------------------------------------------------------

@package:	Zotonic 2010	
@Author:	Marc Worrell <marc@worrell.nl>

Copyright 2010 Marc Worrell

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
 
http://www.apache.org/licenses/LICENSE-2.0
 
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

---------------------------------------------------------- */

/*
This widget overlays a label field with an input field.	 When the input
is empty the label is visible.	When there is content then the label is hidden.

HTML:

<p class="do_inputoverlay">
	<span>Username</span>
	<input type="text" id="username" name="username" value="" />
</p>

CSS:

p.do_inputoverlay {
	margin: 0px;
	padding: 0px;
	position: relative;
	height: 40px;
	font-size: 18px;
}

p.do_inputoverlay input {
	position: absolute;
	left: 0px;
	background: none;
	font-size: 18px;
}

p.do_inputoverlay span {
	position: absolute;
	left: 8px;
	top: 5px;
	color: #aaa;
}

p.do_inputoverlay span.focus {
	color: #d8d8d8;
}

p.do_inputoverlay span.hidden {
	display: none;
}

*/

$.widget("ui.inputoverlay", 
{
	_init: function() 
	{
		var self = this;
		var obj	 = this.element;
		var input = $('input', obj);
		var span = $('span', obj);
		
		if (!input.length) {
			input = $('textarea', obj);
		}
		if ($(input).val() != "") {
			$(span).addClass('hidden');
		}

		var func = function(focus) {
			if ($(input).val() == "") {
				if (focus) {
					$(span).removeClass('hidden').addClass('focus');
				} else {
					$(span).removeClass('hidden').removeClass('focus');
				}
			} else {
				$(span).removeClass('focus').addClass('hidden');
			}
		};
		
		input.change(function() {
			func(true);
		}).focus(function() {
			func(true);
		}).blur(function() {
			func(false);
		}).keydown(function() {
			setTimeout(function(){func(true);},10);
		}).keyup(function() {
			func(true);
		});
		
		input.closest("form").bind("reset", function() {
			setTimeout(function(){func(true);},10);
		});
		
		span.click(function() {
			input.focus();
		});
		
		if (input.attr('autocomplete') == 'on') {
			setInterval(function() {
				if ($(input).val() == "") {
					$(span).removeClass('hidden');
				} else {
					$(span).addClass('hidden');
				}
			}, 100);
		}
	}	 
});
;
/* listfilter js
----------------------------------------------------------

@package:	Zotonic 2009	
@Author: 	Tim Benniks <tim@timbenniks.nl>

Copyright 2009 Tim Benniks

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
 
http://www.apache.org/licenses/LICENSE-2.0
 
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

qs_score - Quicksilver Score

A port of the Quicksilver string ranking algorithm

"hello world".score("axl") //=> 0.0
"hello world".score("ow") //=> 0.6
"hello world".score("hello world") //=> 1.0

---------------------------------------------------------- */

String.prototype.score = function(abbreviation,offset) {
  offset = offset || 0 // TODO: I think this is unused... remove
 
  if(abbreviation.length == 0) return 0.9
  if(abbreviation.length > this.length) return 0.0

  for (var i = abbreviation.length; i > 0; i--) {
	var sub_abbreviation = abbreviation.substring(0,i)
	var index = this.indexOf(sub_abbreviation)


	if(index < 0) continue;
	if(index + abbreviation.length > this.length + offset) continue;

	var next_string		  = this.substring(index+sub_abbreviation.length)
	var next_abbreviation = null

	if(i >= abbreviation.length)
	  next_abbreviation = ''
	else
	  next_abbreviation = abbreviation.substring(i)
 
	var remaining_score	  = next_string.score(next_abbreviation,offset+index)
 
	if (remaining_score > 0) {
	  var score = this.length-next_string.length;

	  if(index != 0) {
		var j = 0;

		var c = this.charCodeAt(index-1)
		if(c==32 || c == 9) {
		  for(var j=(index-2); j >= 0; j--) {
			c = this.charCodeAt(j)
			score -= ((c == 32 || c == 9) ? 1 : 0.15)
		  }
		} else {
		  score -= index
		}
	  }
   
	  score += remaining_score * next_string.length
	  score /= this.length;
	  return score
	}
  }
  return 0.0
}

jQuery.fn.listfilter = function(options)
{
	var list = jQuery(options.list);

	if(list.length) 
	{
		var rows = list.children()
		var cache = rows.map(function()
		{
		    if (typeof options.text == 'string') {
			    return $(options.text, this).text().toLowerCase();
		    } else {
			    return $(this).text().toLowerCase();
			}
		});
			
		this.keyup(filter).keyup().parents('form').submit(function()
		{
			return false;
		});
	}
		
	return this;
		
	function filter()
	{
		var term = jQuery.trim(jQuery(this).val().toLowerCase()), scores = [];
		
		if(!term) 
		{
			rows.show();
		}
		else
		{
			rows.hide();

			cache.each(function(i)
			{
				var score = this.score(term);
				if(score > 0)
				{
					scores.push([score, i]); 
				}
			});

			jQuery.each(scores.sort(function(a, b){return b[0] - a[0];}), function()
			{
				jQuery(rows[ this[1] ]).show();
			});
		}
	}
};;
/* growl notice js
----------------------------------------------------------

@package:	Zotonic 2009	
@Author: 	Tim Benniks <tim@timbenniks.nl>

Copyright 2009 Tim Benniks

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
 
http://www.apache.org/licenses/LICENSE-2.0
 
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

---------------------------------------------------------- */

(function(jQuery)
{
	jQuery.extend({			
		noticeAdd: function(options)
		{	
			var defaults = {
				inEffect: 			{opacity: 'show'},	// in effect
				inEffectDuration: 	600,				// in effect duration in miliseconds
				stayTime: 			3000,				// time in miliseconds before the item has to disappear
				text: 				'',					// content of the item
				stay: 				false,				// should the notice item stay or not?
				type: 				'notice' 			// could also be error, succes
			}
			
			// declare varaibles
			var options, noticeWrapAll, noticeItemOuter, noticeItemInner, noticeItemClose;
								
			options 		= jQuery.extend({}, defaults, options);
			noticeWrapAll	= (!jQuery('.notice-wrap').length) ? jQuery('<div></div>').addClass('notice-wrap').appendTo('body') : jQuery('.notice-wrap');
			noticeItemOuter	= jQuery('<div></div>').addClass('notice-item-wrapper');
			noticeItemInner	= jQuery('<div></div>').hide().addClass('notice-item ' + options.type).appendTo(noticeWrapAll).html('<p>'+options.text+'</p>').animate(options.inEffect, options.inEffectDuration).wrap(noticeItemOuter);
			noticeItemClose	= jQuery('<div></div>').addClass('notice-item-close').prependTo(noticeItemInner).html('x').click(function() { jQuery.noticeRemove(noticeItemInner) });
			
			// hmmmz, zucht
			if(navigator.userAgent.match(/MSIE 6/i)) 
			{
		    	noticeWrapAll.css({top: document.documentElement.scrollTop});
		    }
			
			if(!options.stay)
			{
				setTimeout(function()
				{
					jQuery.noticeRemove(noticeItemInner);
				},
				options.stayTime);
			}
		},
		
		noticeRemove: function(obj)
		{
			obj.animate({opacity: '0'}, 600, function()
			{
				obj.parent().animate({height: '0px'}, 300, function()
				{
					obj.parent().remove();
				});
			});
		}
	});
})(jQuery);;
/*
 * jQuery Cycle Plugin (with Transition Definitions)
 * Examples and documentation at: http://jquery.malsup.com/cycle/
 * Copyright (c) 2007-2009 M. Alsup
 * Version: 2.73 (04-NOV-2009)
 * Dual licensed under the MIT and GPL licenses:
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl.html
 * Requires: jQuery v1.2.6 or later
 *
 * Originally based on the work of:
 *	1) Matt Oakes
 *	2) Torsten Baldes (http://medienfreunde.com/lab/innerfade/)
 *	3) Benjamin Sterling (http://www.benjaminsterling.com/experiments/jqShuffle/)
 */
(function(i){var l="2.73";if(i.support==undefined){i.support={opacity:!(i.browser.msie)}}function a(q){if(i.fn.cycle.debug){f(q)}}function f(){if(window.console&&window.console.log){window.console.log("[cycle] "+Array.prototype.join.call(arguments," "))}}i.fn.cycle=function(r,q){var s={s:this.selector,c:this.context};if(this.length===0&&r!="stop"){if(!i.isReady&&s.s){f("DOM not ready, queuing slideshow");i(function(){i(s.s,s.c).cycle(r,q)});return this}f("terminating; zero elements found by selector"+(i.isReady?"":" (DOM not ready)"));return this}return this.each(function(){var w=m(this,r,q);if(w===false){return}if(this.cycleTimeout){clearTimeout(this.cycleTimeout)}this.cycleTimeout=this.cyclePause=0;var x=i(this);var y=w.slideExpr?i(w.slideExpr,this):x.children();var u=y.get();if(u.length<2){f("terminating; too few slides: "+u.length);return}var t=k(x,y,u,w,s);if(t===false){return}var v=t.continuous?10:h(t.currSlide,t.nextSlide,t,!t.rev);if(v){v+=(t.delay||0);if(v<10){v=10}a("first timeout: "+v);this.cycleTimeout=setTimeout(function(){e(u,t,0,!t.rev)},v)}})};function m(q,t,r){if(q.cycleStop==undefined){q.cycleStop=0}if(t===undefined||t===null){t={}}if(t.constructor==String){switch(t){case"stop":q.cycleStop++;if(q.cycleTimeout){clearTimeout(q.cycleTimeout)}q.cycleTimeout=0;i(q).removeData("cycle.opts");return false;case"pause":q.cyclePause=1;return false;case"resume":q.cyclePause=0;if(r===true){t=i(q).data("cycle.opts");if(!t){f("options not found, can not resume");return false}if(q.cycleTimeout){clearTimeout(q.cycleTimeout);q.cycleTimeout=0}e(t.elements,t,1,1)}return false;case"prev":case"next":var u=i(q).data("cycle.opts");if(!u){f('options not found, "prev/next" ignored');return false}i.fn.cycle[t](u);return false;default:t={fx:t}}return t}else{if(t.constructor==Number){var s=t;t=i(q).data("cycle.opts");if(!t){f("options not found, can not advance slide");return false}if(s<0||s>=t.elements.length){f("invalid slide index: "+s);return false}t.nextSlide=s;if(q.cycleTimeout){clearTimeout(q.cycleTimeout);q.cycleTimeout=0}if(typeof r=="string"){t.oneTimeFx=r}e(t.elements,t,1,s>=t.currSlide);return false}}return t}function b(q,r){if(!i.support.opacity&&r.cleartype&&q.style.filter){try{q.style.removeAttribute("filter")}catch(s){}}}function k(y,J,u,t,E){var C=i.extend({},i.fn.cycle.defaults,t||{},i.metadata?y.metadata():i.meta?y.data():{});if(C.autostop){C.countdown=C.autostopCount||u.length}var r=y[0];y.data("cycle.opts",C);C.$cont=y;C.stopCount=r.cycleStop;C.elements=u;C.before=C.before?[C.before]:[];C.after=C.after?[C.after]:[];C.after.unshift(function(){C.busy=0});if(!i.support.opacity&&C.cleartype){C.after.push(function(){b(this,C)})}if(C.continuous){C.after.push(function(){e(u,C,0,!C.rev)})}n(C);if(!i.support.opacity&&C.cleartype&&!C.cleartypeNoBg){g(J)}if(y.css("position")=="static"){y.css("position","relative")}if(C.width){y.width(C.width)}if(C.height&&C.height!="auto"){y.height(C.height)}if(C.startingSlide){C.startingSlide=parseInt(C.startingSlide)}if(C.random){C.randomMap=[];for(var H=0;H<u.length;H++){C.randomMap.push(H)}C.randomMap.sort(function(L,w){return Math.random()-0.5});C.randomIndex=0;C.startingSlide=C.randomMap[0]}else{if(C.startingSlide>=u.length){C.startingSlide=0}}C.currSlide=C.startingSlide=C.startingSlide||0;var x=C.startingSlide;J.css({position:"absolute",top:0,left:0}).hide().each(function(w){var L=x?w>=x?u.length-(w-x):x-w:u.length-w;i(this).css("z-index",L)});i(u[x]).css("opacity",1).show();b(u[x],C);if(C.fit&&C.width){J.width(C.width)}if(C.fit&&C.height&&C.height!="auto"){J.height(C.height)}var D=C.containerResize&&!y.innerHeight();if(D){var v=0,B=0;for(var F=0;F<u.length;F++){var q=i(u[F]),K=q[0],A=q.outerWidth(),I=q.outerHeight();if(!A){A=K.offsetWidth}if(!I){I=K.offsetHeight}v=A>v?A:v;B=I>B?I:B}if(v>0&&B>0){y.css({width:v+"px",height:B+"px"})}}if(C.pause){y.hover(function(){this.cyclePause++},function(){this.cyclePause--})}if(c(C)===false){return false}var s=false;t.requeueAttempts=t.requeueAttempts||0;J.each(function(){var N=i(this);this.cycleH=(C.fit&&C.height)?C.height:N.height();this.cycleW=(C.fit&&C.width)?C.width:N.width();if(N.is("img")){var L=(i.browser.msie&&this.cycleW==28&&this.cycleH==30&&!this.complete);var O=(i.browser.mozilla&&this.cycleW==34&&this.cycleH==19&&!this.complete);var M=(i.browser.opera&&((this.cycleW==42&&this.cycleH==19)||(this.cycleW==37&&this.cycleH==17))&&!this.complete);var w=(this.cycleH==0&&this.cycleW==0&&!this.complete);if(L||O||M||w){if(E.s&&C.requeueOnImageNotLoaded&&++t.requeueAttempts<100){f(t.requeueAttempts," - img slide not loaded, requeuing slideshow: ",this.src,this.cycleW,this.cycleH);setTimeout(function(){i(E.s,E.c).cycle(t)},C.requeueTimeout);s=true;return false}else{f("could not determine size of image: "+this.src,this.cycleW,this.cycleH)}}}return true});if(s){return false}C.cssBefore=C.cssBefore||{};C.animIn=C.animIn||{};C.animOut=C.animOut||{};J.not(":eq("+x+")").css(C.cssBefore);if(C.cssFirst){i(J[x]).css(C.cssFirst)}if(C.timeout){C.timeout=parseInt(C.timeout);if(C.speed.constructor==String){C.speed=i.fx.speeds[C.speed]||parseInt(C.speed)}if(!C.sync){C.speed=C.speed/2}while((C.timeout-C.speed)<250){C.timeout+=C.speed}}if(C.easing){C.easeIn=C.easeOut=C.easing}if(!C.speedIn){C.speedIn=C.speed}if(!C.speedOut){C.speedOut=C.speed}C.slideCount=u.length;C.currSlide=C.lastSlide=x;if(C.random){C.nextSlide=C.currSlide;if(++C.randomIndex==u.length){C.randomIndex=0}C.nextSlide=C.randomMap[C.randomIndex]}else{C.nextSlide=C.startingSlide>=(u.length-1)?0:C.startingSlide+1}if(!C.multiFx){var G=i.fn.cycle.transitions[C.fx];if(i.isFunction(G)){G(y,J,C)}else{if(C.fx!="custom"&&!C.multiFx){f("unknown transition: "+C.fx,"; slideshow terminating");return false}}}var z=J[x];if(C.before.length){C.before[0].apply(z,[z,z,C,true])}if(C.after.length>1){C.after[1].apply(z,[z,z,C,true])}if(C.next){i(C.next).bind(C.prevNextEvent,function(){return o(C,C.rev?-1:1)})}if(C.prev){i(C.prev).bind(C.prevNextEvent,function(){return o(C,C.rev?1:-1)})}if(C.pager){d(u,C)}j(C,u);return C}function n(q){q.original={before:[],after:[]};q.original.cssBefore=i.extend({},q.cssBefore);q.original.cssAfter=i.extend({},q.cssAfter);q.original.animIn=i.extend({},q.animIn);q.original.animOut=i.extend({},q.animOut);i.each(q.before,function(){q.original.before.push(this)});i.each(q.after,function(){q.original.after.push(this)})}function c(w){var u,s,r=i.fn.cycle.transitions;if(w.fx.indexOf(",")>0){w.multiFx=true;w.fxs=w.fx.replace(/\s*/g,"").split(",");for(u=0;u<w.fxs.length;u++){var v=w.fxs[u];s=r[v];if(!s||!r.hasOwnProperty(v)||!i.isFunction(s)){f("discarding unknown transition: ",v);w.fxs.splice(u,1);u--}}if(!w.fxs.length){f("No valid transitions named; slideshow terminating.");return false}}else{if(w.fx=="all"){w.multiFx=true;w.fxs=[];for(p in r){s=r[p];if(r.hasOwnProperty(p)&&i.isFunction(s)){w.fxs.push(p)}}}}if(w.multiFx&&w.randomizeEffects){var t=Math.floor(Math.random()*20)+30;for(u=0;u<t;u++){var q=Math.floor(Math.random()*w.fxs.length);w.fxs.push(w.fxs.splice(q,1)[0])}a("randomized fx sequence: ",w.fxs)}return true}function j(r,q){r.addSlide=function(u,v){var t=i(u),w=t[0];if(!r.autostopCount){r.countdown++}q[v?"unshift":"push"](w);if(r.els){r.els[v?"unshift":"push"](w)}r.slideCount=q.length;t.css("position","absolute");t[v?"prependTo":"appendTo"](r.$cont);if(v){r.currSlide++;r.nextSlide++}if(!i.support.opacity&&r.cleartype&&!r.cleartypeNoBg){g(t)}if(r.fit&&r.width){t.width(r.width)}if(r.fit&&r.height&&r.height!="auto"){$slides.height(r.height)}w.cycleH=(r.fit&&r.height)?r.height:t.height();w.cycleW=(r.fit&&r.width)?r.width:t.width();t.css(r.cssBefore);if(r.pager){i.fn.cycle.createPagerAnchor(q.length-1,w,i(r.pager),q,r)}if(i.isFunction(r.onAddSlide)){r.onAddSlide(t)}else{t.hide()}}}i.fn.cycle.resetState=function(r,q){q=q||r.fx;r.before=[];r.after=[];r.cssBefore=i.extend({},r.original.cssBefore);r.cssAfter=i.extend({},r.original.cssAfter);r.animIn=i.extend({},r.original.animIn);r.animOut=i.extend({},r.original.animOut);r.fxFn=null;i.each(r.original.before,function(){r.before.push(this)});i.each(r.original.after,function(){r.after.push(this)});var s=i.fn.cycle.transitions[q];if(i.isFunction(s)){s(r.$cont,i(r.elements),r)}};function e(x,q,w,y){if(w&&q.busy&&q.manualTrump){i(x).stop(true,true);q.busy=false}if(q.busy){return}var u=q.$cont[0],A=x[q.currSlide],z=x[q.nextSlide];if(u.cycleStop!=q.stopCount||u.cycleTimeout===0&&!w){return}if(!w&&!u.cyclePause&&((q.autostop&&(--q.countdown<=0))||(q.nowrap&&!q.random&&q.nextSlide<q.currSlide))){if(q.end){q.end(q)}return}if(w||!u.cyclePause){var v=q.fx;A.cycleH=A.cycleH||i(A).height();A.cycleW=A.cycleW||i(A).width();z.cycleH=z.cycleH||i(z).height();z.cycleW=z.cycleW||i(z).width();if(q.multiFx){if(q.lastFx==undefined||++q.lastFx>=q.fxs.length){q.lastFx=0}v=q.fxs[q.lastFx];q.currFx=v}if(q.oneTimeFx){v=q.oneTimeFx;q.oneTimeFx=null}i.fn.cycle.resetState(q,v);if(q.before.length){i.each(q.before,function(B,C){if(u.cycleStop!=q.stopCount){return}C.apply(z,[A,z,q,y])})}var s=function(){i.each(q.after,function(B,C){if(u.cycleStop!=q.stopCount){return}C.apply(z,[A,z,q,y])})};if(q.nextSlide!=q.currSlide){q.busy=1;if(q.fxFn){q.fxFn(A,z,q,s,y)}else{if(i.isFunction(i.fn.cycle[q.fx])){i.fn.cycle[q.fx](A,z,q,s)}else{i.fn.cycle.custom(A,z,q,s,w&&q.fastOnEvent)}}}q.lastSlide=q.currSlide;if(q.random){q.currSlide=q.nextSlide;if(++q.randomIndex==x.length){q.randomIndex=0}q.nextSlide=q.randomMap[q.randomIndex]}else{var t=(q.nextSlide+1)==x.length;q.nextSlide=t?0:q.nextSlide+1;q.currSlide=t?x.length-1:q.nextSlide-1}if(q.pager){i.fn.cycle.updateActivePagerLink(q.pager,q.currSlide)}}var r=0;if(q.timeout&&!q.continuous){r=h(A,z,q,y)}else{if(q.continuous&&u.cyclePause){r=10}}if(r>0){u.cycleTimeout=setTimeout(function(){e(x,q,0,!q.rev)},r)}}i.fn.cycle.updateActivePagerLink=function(q,r){i(q).each(function(){i(this).find("a").removeClass("activeSlide").filter("a:eq("+r+")").addClass("activeSlide")})};function h(v,s,u,r){if(u.timeoutFn){var q=u.timeoutFn(v,s,u,r);while((q-u.speed)<250){q+=u.speed}a("calculated timeout: "+q+"; speed: "+u.speed);if(q!==false){return q}}return u.timeout}i.fn.cycle.next=function(q){o(q,q.rev?-1:1)};i.fn.cycle.prev=function(q){o(q,q.rev?1:-1)};function o(r,u){var q=r.elements;var t=r.$cont[0],s=t.cycleTimeout;if(s){clearTimeout(s);t.cycleTimeout=0}if(r.random&&u<0){r.randomIndex--;if(--r.randomIndex==-2){r.randomIndex=q.length-2}else{if(r.randomIndex==-1){r.randomIndex=q.length-1}}r.nextSlide=r.randomMap[r.randomIndex]}else{if(r.random){if(++r.randomIndex==q.length){r.randomIndex=0}r.nextSlide=r.randomMap[r.randomIndex]}else{r.nextSlide=r.currSlide+u;if(r.nextSlide<0){if(r.nowrap){return false}r.nextSlide=q.length-1}else{if(r.nextSlide>=q.length){if(r.nowrap){return false}r.nextSlide=0}}}}if(i.isFunction(r.prevNextClick)){r.prevNextClick(u>0,r.nextSlide,q[r.nextSlide])}e(q,r,1,u>=0);return false}function d(r,s){var q=i(s.pager);i.each(r,function(t,u){i.fn.cycle.createPagerAnchor(t,u,q,r,s)});i.fn.cycle.updateActivePagerLink(s.pager,s.startingSlide)}i.fn.cycle.createPagerAnchor=function(u,v,s,t,w){var r;if(i.isFunction(w.pagerAnchorBuilder)){r=w.pagerAnchorBuilder(u,v)}else{r='<a href="#">'+(u+1)+"</a>"}if(!r){return}var x=i(r);if(x.parents("body").length===0){var q=[];if(s.length>1){s.each(function(){var y=x.clone(true);i(this).append(y);q.push(y[0])});x=i(q)}else{x.appendTo(s)}}x.bind(w.pagerEvent,function(A){A.preventDefault();w.nextSlide=u;var z=w.$cont[0],y=z.cycleTimeout;if(y){clearTimeout(y);z.cycleTimeout=0}if(i.isFunction(w.pagerClick)){w.pagerClick(w.nextSlide,t[w.nextSlide])}e(t,w,1,w.currSlide<u);return false});if(w.pagerEvent!="click"){x.click(function(){return false})}if(w.pauseOnPagerHover){x.hover(function(){w.$cont[0].cyclePause++},function(){w.$cont[0].cyclePause--})}};i.fn.cycle.hopsFromLast=function(t,s){var r,q=t.lastSlide,u=t.currSlide;if(s){r=u>q?u-q:t.slideCount-q}else{r=u<q?q-u:q+t.slideCount-u}return r};function g(s){function r(t){t=parseInt(t).toString(16);return t.length<2?"0"+t:t}function q(w){for(;w&&w.nodeName.toLowerCase()!="html";w=w.parentNode){var t=i.css(w,"background-color");if(t.indexOf("rgb")>=0){var u=t.match(/\d+/g);return"#"+r(u[0])+r(u[1])+r(u[2])}if(t&&t!="transparent"){return t}}return"#ffffff"}s.each(function(){i(this).css("background-color",q(this))})}i.fn.cycle.commonReset=function(v,t,u,r,s,q){i(u.elements).not(v).hide();u.cssBefore.opacity=1;u.cssBefore.display="block";if(r!==false&&t.cycleW>0){u.cssBefore.width=t.cycleW}if(s!==false&&t.cycleH>0){u.cssBefore.height=t.cycleH}u.cssAfter=u.cssAfter||{};u.cssAfter.display="none";i(v).css("zIndex",u.slideCount+(q===true?1:0));i(t).css("zIndex",u.slideCount+(q===true?0:1))};i.fn.cycle.custom=function(B,v,q,s,r){var A=i(B),w=i(v);var t=q.speedIn,z=q.speedOut,u=q.easeIn,y=q.easeOut;w.css(q.cssBefore);if(r){if(typeof r=="number"){t=z=r}else{t=z=1}u=y=null}var x=function(){w.animate(q.animIn,t,u,s)};A.animate(q.animOut,z,y,function(){if(q.cssAfter){A.css(q.cssAfter)}if(!q.sync){x()}});if(q.sync){x()}};i.fn.cycle.transitions={fade:function(r,s,q){s.not(":eq("+q.currSlide+")").css("opacity",0);q.before.push(function(v,t,u){i.fn.cycle.commonReset(v,t,u);u.cssBefore.opacity=0});q.animIn={opacity:1};q.animOut={opacity:0};q.cssBefore={top:0,left:0}}};i.fn.cycle.ver=function(){return l};i.fn.cycle.defaults={fx:"fade",timeout:4000,timeoutFn:null,continuous:0,speed:1000,speedIn:null,speedOut:null,next:null,prev:null,prevNextClick:null,prevNextEvent:"click",pager:null,pagerClick:null,pagerEvent:"click",pagerAnchorBuilder:null,before:null,after:null,end:null,easing:null,easeIn:null,easeOut:null,shuffle:null,animIn:null,animOut:null,cssBefore:null,cssAfter:null,fxFn:null,height:"auto",startingSlide:0,sync:1,random:0,fit:0,containerResize:1,pause:0,pauseOnPagerHover:0,autostop:0,autostopCount:0,delay:0,slideExpr:null,cleartype:!i.support.opacity,cleartypeNoBg:false,nowrap:0,fastOnEvent:0,randomizeEffects:1,rev:0,manualTrump:true,requeueOnImageNotLoaded:true,requeueTimeout:250}})(jQuery);
/*
 * jQuery Cycle Plugin Transition Definitions
 * This script is a plugin for the jQuery Cycle Plugin
 * Examples and documentation at: http://malsup.com/jquery/cycle/
 * Copyright (c) 2007-2008 M. Alsup
 * Version:	 2.72
 * Dual licensed under the MIT and GPL licenses:
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl.html
 */
(function(a){a.fn.cycle.transitions.none=function(c,d,b){b.fxFn=function(g,e,f,h){a(e).show();a(g).hide();h()}};a.fn.cycle.transitions.scrollUp=function(d,e,c){d.css("overflow","hidden");c.before.push(a.fn.cycle.commonReset);var b=d.height();c.cssBefore={top:b,left:0};c.cssFirst={top:0};c.animIn={top:0};c.animOut={top:-b}};a.fn.cycle.transitions.scrollDown=function(d,e,c){d.css("overflow","hidden");c.before.push(a.fn.cycle.commonReset);var b=d.height();c.cssFirst={top:0};c.cssBefore={top:-b,left:0};c.animIn={top:0};c.animOut={top:b}};a.fn.cycle.transitions.scrollLeft=function(d,e,c){d.css("overflow","hidden");c.before.push(a.fn.cycle.commonReset);var b=d.width();c.cssFirst={left:0};c.cssBefore={left:b,top:0};c.animIn={left:0};c.animOut={left:0-b}};a.fn.cycle.transitions.scrollRight=function(d,e,c){d.css("overflow","hidden");c.before.push(a.fn.cycle.commonReset);var b=d.width();c.cssFirst={left:0};c.cssBefore={left:-b,top:0};c.animIn={left:0};c.animOut={left:b}};a.fn.cycle.transitions.scrollHorz=function(c,d,b){c.css("overflow","hidden").width();b.before.push(function(h,f,g,e){a.fn.cycle.commonReset(h,f,g);g.cssBefore.left=e?(f.cycleW-1):(1-f.cycleW);g.animOut.left=e?-h.cycleW:h.cycleW});b.cssFirst={left:0};b.cssBefore={top:0};b.animIn={left:0};b.animOut={top:0}};a.fn.cycle.transitions.scrollVert=function(c,d,b){c.css("overflow","hidden");b.before.push(function(h,f,g,e){a.fn.cycle.commonReset(h,f,g);g.cssBefore.top=e?(1-f.cycleH):(f.cycleH-1);g.animOut.top=e?h.cycleH:-h.cycleH});b.cssFirst={top:0};b.cssBefore={left:0};b.animIn={top:0};b.animOut={left:0}};a.fn.cycle.transitions.slideX=function(c,d,b){b.before.push(function(g,e,f){a(f.elements).not(g).hide();a.fn.cycle.commonReset(g,e,f,false,true);f.animIn.width=e.cycleW});b.cssBefore={left:0,top:0,width:0};b.animIn={width:"show"};b.animOut={width:0}};a.fn.cycle.transitions.slideY=function(c,d,b){b.before.push(function(g,e,f){a(f.elements).not(g).hide();a.fn.cycle.commonReset(g,e,f,true,false);f.animIn.height=e.cycleH});b.cssBefore={left:0,top:0,height:0};b.animIn={height:"show"};b.animOut={height:0}};a.fn.cycle.transitions.shuffle=function(e,f,d){var c,b=e.css("overflow","visible").width();f.css({left:0,top:0});d.before.push(function(i,g,h){a.fn.cycle.commonReset(i,g,h,true,true,true)});if(!d.speedAdjusted){d.speed=d.speed/2;d.speedAdjusted=true}d.random=0;d.shuffle=d.shuffle||{left:-b,top:15};d.els=[];for(c=0;c<f.length;c++){d.els.push(f[c])}for(c=0;c<d.currSlide;c++){d.els.push(d.els.shift())}d.fxFn=function(m,j,l,g,i){var h=i?a(m):a(j);a(j).css(l.cssBefore);var k=l.slideCount;h.animate(l.shuffle,l.speedIn,l.easeIn,function(){var o=a.fn.cycle.hopsFromLast(l,i);for(var q=0;q<o;q++){i?l.els.push(l.els.shift()):l.els.unshift(l.els.pop())}if(i){for(var r=0,n=l.els.length;r<n;r++){a(l.els[r]).css("z-index",n-r+k)}}else{var s=a(m).css("z-index");h.css("z-index",parseInt(s)+1+k)}h.animate({left:0,top:0},l.speedOut,l.easeOut,function(){a(i?this:m).hide();if(g){g()}})})};d.cssBefore={display:"block",opacity:1,top:0,left:0}};a.fn.cycle.transitions.turnUp=function(c,d,b){b.before.push(function(g,e,f){a.fn.cycle.commonReset(g,e,f,true,false);f.cssBefore.top=e.cycleH;f.animIn.height=e.cycleH});b.cssFirst={top:0};b.cssBefore={left:0,height:0};b.animIn={top:0};b.animOut={height:0}};a.fn.cycle.transitions.turnDown=function(c,d,b){b.before.push(function(g,e,f){a.fn.cycle.commonReset(g,e,f,true,false);f.animIn.height=e.cycleH;f.animOut.top=g.cycleH});b.cssFirst={top:0};b.cssBefore={left:0,top:0,height:0};b.animOut={height:0}};a.fn.cycle.transitions.turnLeft=function(c,d,b){b.before.push(function(g,e,f){a.fn.cycle.commonReset(g,e,f,false,true);f.cssBefore.left=e.cycleW;f.animIn.width=e.cycleW});b.cssBefore={top:0,width:0};b.animIn={left:0};b.animOut={width:0}};a.fn.cycle.transitions.turnRight=function(c,d,b){b.before.push(function(g,e,f){a.fn.cycle.commonReset(g,e,f,false,true);f.animIn.width=e.cycleW;f.animOut.left=g.cycleW});b.cssBefore={top:0,left:0,width:0};b.animIn={left:0};b.animOut={width:0}};a.fn.cycle.transitions.zoom=function(c,d,b){b.before.push(function(g,e,f){a.fn.cycle.commonReset(g,e,f,false,false,true);f.cssBefore.top=e.cycleH/2;f.cssBefore.left=e.cycleW/2;f.animIn={top:0,left:0,width:e.cycleW,height:e.cycleH};f.animOut={width:0,height:0,top:g.cycleH/2,left:g.cycleW/2}});b.cssFirst={top:0,left:0};b.cssBefore={width:0,height:0}};a.fn.cycle.transitions.fadeZoom=function(c,d,b){b.before.push(function(g,e,f){a.fn.cycle.commonReset(g,e,f,false,false);f.cssBefore.left=e.cycleW/2;f.cssBefore.top=e.cycleH/2;f.animIn={top:0,left:0,width:e.cycleW,height:e.cycleH}});b.cssBefore={width:0,height:0};b.animOut={opacity:0}};a.fn.cycle.transitions.blindX=function(d,e,c){var b=d.css("overflow","hidden").width();c.before.push(function(h,f,g){a.fn.cycle.commonReset(h,f,g);g.animIn.width=f.cycleW;g.animOut.left=h.cycleW});c.cssBefore={left:b,top:0};c.animIn={left:0};c.animOut={left:b}};a.fn.cycle.transitions.blindY=function(d,e,c){var b=d.css("overflow","hidden").height();c.before.push(function(h,f,g){a.fn.cycle.commonReset(h,f,g);g.animIn.height=f.cycleH;g.animOut.top=h.cycleH});c.cssBefore={top:b,left:0};c.animIn={top:0};c.animOut={top:b}};a.fn.cycle.transitions.blindZ=function(e,f,d){var c=e.css("overflow","hidden").height();var b=e.width();d.before.push(function(i,g,h){a.fn.cycle.commonReset(i,g,h);h.animIn.height=g.cycleH;h.animOut.top=i.cycleH});d.cssBefore={top:c,left:b};d.animIn={top:0,left:0};d.animOut={top:c,left:b}};a.fn.cycle.transitions.growX=function(c,d,b){b.before.push(function(g,e,f){a.fn.cycle.commonReset(g,e,f,false,true);f.cssBefore.left=this.cycleW/2;f.animIn={left:0,width:this.cycleW};f.animOut={left:0}});b.cssBefore={width:0,top:0}};a.fn.cycle.transitions.growY=function(c,d,b){b.before.push(function(g,e,f){a.fn.cycle.commonReset(g,e,f,true,false);f.cssBefore.top=this.cycleH/2;f.animIn={top:0,height:this.cycleH};f.animOut={top:0}});b.cssBefore={height:0,left:0}};a.fn.cycle.transitions.curtainX=function(c,d,b){b.before.push(function(g,e,f){a.fn.cycle.commonReset(g,e,f,false,true,true);f.cssBefore.left=e.cycleW/2;f.animIn={left:0,width:this.cycleW};f.animOut={left:g.cycleW/2,width:0}});b.cssBefore={top:0,width:0}};a.fn.cycle.transitions.curtainY=function(c,d,b){b.before.push(function(g,e,f){a.fn.cycle.commonReset(g,e,f,true,false,true);f.cssBefore.top=e.cycleH/2;f.animIn={top:0,height:e.cycleH};f.animOut={top:g.cycleH/2,height:0}});b.cssBefore={left:0,height:0}};a.fn.cycle.transitions.cover=function(f,g,e){var i=e.direction||"left";var b=f.css("overflow","hidden").width();var c=f.height();e.before.push(function(j,d,h){a.fn.cycle.commonReset(j,d,h);if(i=="right"){h.cssBefore.left=-b}else{if(i=="up"){h.cssBefore.top=c}else{if(i=="down"){h.cssBefore.top=-c}else{h.cssBefore.left=b}}}});e.animIn={left:0,top:0};e.animOut={opacity:1};e.cssBefore={top:0,left:0}};a.fn.cycle.transitions.uncover=function(f,g,e){var i=e.direction||"left";var b=f.css("overflow","hidden").width();var c=f.height();e.before.push(function(j,d,h){a.fn.cycle.commonReset(j,d,h,true,true,true);if(i=="right"){h.animOut.left=b}else{if(i=="up"){h.animOut.top=-c}else{if(i=="down"){h.animOut.top=c}else{h.animOut.left=-b}}}});e.animIn={left:0,top:0};e.animOut={opacity:1};e.cssBefore={top:0,left:0}};a.fn.cycle.transitions.toss=function(e,f,d){var b=e.css("overflow","visible").width();var c=e.height();d.before.push(function(i,g,h){a.fn.cycle.commonReset(i,g,h,true,true,true);if(!h.animOut.left&&!h.animOut.top){h.animOut={left:b*2,top:-c/2,opacity:0}}else{h.animOut.opacity=0}});d.cssBefore={left:0,top:0};d.animIn={left:0}};a.fn.cycle.transitions.wipe=function(s,m,e){var q=s.css("overflow","hidden").width();var j=s.height();e.cssBefore=e.cssBefore||{};var g;if(e.clip){if(/l2r/.test(e.clip)){g="rect(0px 0px "+j+"px 0px)"}else{if(/r2l/.test(e.clip)){g="rect(0px "+q+"px "+j+"px "+q+"px)"}else{if(/t2b/.test(e.clip)){g="rect(0px "+q+"px 0px 0px)"}else{if(/b2t/.test(e.clip)){g="rect("+j+"px "+q+"px "+j+"px 0px)"}else{if(/zoom/.test(e.clip)){var o=parseInt(j/2);var f=parseInt(q/2);g="rect("+o+"px "+f+"px "+o+"px "+f+"px)"}}}}}}e.cssBefore.clip=e.cssBefore.clip||g||"rect(0px 0px 0px 0px)";var k=e.cssBefore.clip.match(/(\d+)/g);var u=parseInt(k[0]),c=parseInt(k[1]),n=parseInt(k[2]),i=parseInt(k[3]);e.before.push(function(w,h,t){if(w==h){return}var d=a(w),b=a(h);a.fn.cycle.commonReset(w,h,t,true,true,false);t.cssAfter.display="block";var r=1,l=parseInt((t.speedIn/13))-1;(function v(){var y=u?u-parseInt(r*(u/l)):0;var z=i?i-parseInt(r*(i/l)):0;var A=n<j?n+parseInt(r*((j-n)/l||1)):j;var x=c<q?c+parseInt(r*((q-c)/l||1)):q;b.css({clip:"rect("+y+"px "+x+"px "+A+"px "+z+"px)"});(r++<=l)?setTimeout(v,13):d.css("display","none")})()});e.cssBefore={display:"block",opacity:1,top:0,left:0};e.animIn={left:0};e.animOut={left:0}}})(jQuery);;
/* z.gaq_track.js
----------------------------------------------------------

@author Marc Worrell <marc@worrell.nl>

Copyright 2011 Marc Worrell

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
 
http://www.apache.org/licenses/LICENSE-2.0
 
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

---------------------------------------------------------- */

$.widget("ui.gaq_track", {
	_init: function() {
		var self = this;
		
		if (self.options.on_click) {
			$(self.element).click(function() {
				self.track("Click");
			});
		}
		if (self.options.on_view) {
			$(self.element).each(function() {
				self.track("View");
			});
		}
	},
	
	track: function(action) {
		var args = ["_trackEvent"];
		args.push(this.options.category);
		args.push(action);
		if (this.options.label != undefined) {
			args.push(this.options.label)
			if (this.options.value != undefined) {
				args.push(this.options.value)
			}
		}
		if (typeof _gaq == "object") {
			_gaq.push(args);
		}
	}
});

$.ui.gaq_track.defaults = {
	category: 'Ad',
	label: undefined,
	value: undefined,
	on_view: true,
	on_click: true
};
;
/* z.popupwindow.js
----------------------------------------------------------

@author Cody Lindley
@author Marc Worrell <marc@worrell.nl>

http://code.google.com/p/jquery-swip/

Copyright 2009 Cody Lindley
Copyright 2011 Marc Worrell

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
 
http://www.apache.org/licenses/LICENSE-2.0
 
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

---------------------------------------------------------- */

$.widget("ui.popupwindow", {
	_init: function() {
		var self = this;
		$(this.element).click(function() {
			if (self.options.full) {
				self.options.toolbar = 1;
				self.options.scrollbars = 1;
				self.options.location = 1;
				self.options.menubar = 1;
				self.options.centerScreen = 1;
				self.options.centerBrowser = 0;
				self.options.width = screen.width-100;
				self.options.height = screen.height-100;
			}
			var windowFeatures =    'height=' + self.options.height +
									',width=' + self.options.width +
									',toolbar=' + (self.options.toolbar?'yes':'no') +
									',scrollbars=' + (self.options.scrollbars?'yes':'no') +
									',status=' + (self.options.status?'yes':'no') + 
									',resizable=' + (self.options.resizable?'yes':'no') +
									',location=' + (self.options.location?'yes':'no') +
									',menubar=' + (self.options.menubar?'yes':'no');

			var windowName = self.options.windowName;
			var windowURL = this.href || self.options.windowURL;
			var top, left;

			if (self.options.centerBrowser && !self.options.centerScreen) {
				if ($.browser.msie) {
					top = (window.screenTop - 120) + ((((document.documentElement.clientHeight + 120)/2) - (self.options.height/2)));
					left = window.screenLeft + ((((document.documentElement.clientWidth + 20)/2) - (self.options.width/2)));
				} else {
					top = window.screenY + (((window.outerHeight/2) - (self.options.height/2)));
					left = window.screenX + (((window.outerWidth/2) - (self.options.width/2)));
				}
			} else if (self.options.centerScreen) {
				top = (screen.height - self.options.height)/2;
				left = (screen.width - self.options.width)/2;
			} else {
				top = self.options.top;
				left = self.options.left;
			}
			
			/* Remove non-alphanumeric characters for msie */
			if (windowName) {
				windowName = windowName.replace(/[^a-zA-Z0-9]/g,'_');
			}
			var w = window.open(windowURL, windowName, windowFeatures+',left='+Math.ceil(left)+',top='+Math.ceil(top));
			setTimeout(function() {
						if (w.innerWidth != undefined && w.innerWidth > 0) {
							w.resizeBy(self.options.width - w.innerWidth, self.options.height - w.innerHeight);
						}
					}, 500);
			w.focus();
			return false;
		});
	}
});

$.ui.popupwindow.defaults = {
	full:0, // set the height/width to the current window, show scrollbars etc.
	centerBrowser:1, // center window over browser window? {1 (YES) or 0 (NO)}. overrides top and left
	centerScreen:0, // center window over entire screen? {1 (YES) or 0 (NO)}. overrides top and left
	height:500, // sets the height in pixels of the window.
	left:0, // left position when the window appears.
	location:0, // determines whether the address bar is displayed {1 (YES) or 0 (NO)}.
	menubar:0, // determines whether the menu bar is displayed {1 (YES) or 0 (NO)}.
	resizable:0, // whether the window can be resized {1 (YES) or 0 (NO)}. Can also be overloaded using resizable.
	scrollbars:0, // determines whether scrollbars appear on the window {1 (YES) or 0 (NO)}.
	status:0, // whether a status line appears at the bottom of the window {1 (YES) or 0 (NO)}.
	width:500, // sets the width in pixels of the window.
	windowName:null, // name of window
	windowURL:null, // url used for the popup
	top:0, // top position when the window appears.
	toolbar:0 // determines whether a toolbar (includes the forward and back buttons) is displayed {1 (YES) or 0 (NO)}.
};

;
/* z.rotate.js
----------------------------------------------------------

@author Marc Worrell <marc@worrell.nl>

Copyright 2011 Marc Worrell

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
 
http://www.apache.org/licenses/LICENSE-2.0
 
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

---------------------------------------------------------- */

$.widget("ui.rotate", {
	_init: function() {
		var self = this;

		setInterval(function() {
			var children = $(self.element).children();
			if (children.length > self.options.count) {
				var first = children.first();
				first.clone().appendTo(self.element);
				first.animate({marginLeft: "-"+first.width()+"px"}, self.options.animateSpeed, function(){ $(this).remove(); });
			}
		}, self.options.speed);
	}
});

$.ui.rotate.defaults = {
	count: 6,
	speed: 5000,
	animateSpeed: 500
};

;
/* z.scrollbottom.js
----------------------------------------------------------

@author Marc Worrell <marc@worrell.nl>

Copyright 2011 Marc Worrell

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
 
http://www.apache.org/licenses/LICENSE-2.0
 
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

---------------------------------------------------------- */

$.widget("ui.scrollbottom", {
	_init: function() {
		var self = this;

		self.options.lastScrollTop = $(self.element)[0].scrollTop;
		self.options.lastScrollHeight = $(self.element)[0].scrollHeight;
		self.options.isFirstRun = true;

		setInterval(function() {
			var scrollTop = $(self.element)[0].scrollTop;
			var scrollHeight = $(self.element)[0].scrollHeight;
			var height = $(self.element).height();
			var doScroll = self.options.isFirstRun;
			
			/* Scroll down when scrollbar was at bottom last time or when scrolled up less
			 * than 1.5 times the height of the visible area.
			 */
			var last = $(self.element).children().last();
			if (   self.options.isFirstRun
				|| (scrollHeight != self.options.lastScrollHeight 
					&& (   scrollHeight - scrollTop - height < height + height/2
						|| self.options.lastScrollHeight - self.options.lastScrollTop == height)
					)
			) {
				$(self.element)[0].scrollTop = scrollHeight;
				if (!self.options.isFirstRun)
					$(last).effect('highlight', {}, 1000);
			} else if (self.options.lastScrollHeight != scrollHeight) {
				/* Extract last message and show as a growl */
				z_growl_add("<b>"+$(".user", last).text()+"</b> "+$(".message", last).html());
				$(last).effect('highlight', {}, 1000);
			}

			self.options.lastScrollHeight = scrollHeight;
			self.options.lastScrollTop = scrollTop;
			self.options.isFirstRun = false;
		}, self.options.delay);
	}
});

$.ui.scrollbottom.defaults = {
	delay: 500
};

;
/* Slideshow popup
   collects all do_slideshow ids and starts slideshow
----------------------------------------------------------

@package:	Zotonic	
@Author: 	Marc Worrell <marc@worrell.nl>

Copyright 2011 Marc Worrell

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
 
http://www.apache.org/licenses/LICENSE-2.0
 
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

---------------------------------------------------------- */

$.widget("ui.slideshow", 
{
	_init: function() 
	{
		var self = this;
		var obj  = this.element;
		
		$(this.element).click(function() {
			// Collect all ids
			var data_name = 'widget-slideshow';
			var args = [ {name:"start_id", value:self.options.id} ];
			
			$(".do_slideshow").each(function() {
				var data = $(this).data(data_name);
				if (data && data.id) {
					args.push({name: "id", value: data.id});
				}
			});
			
			if (args.length) {
				z_notify("slideshow", args);
			}
		});
	}
});

$.ui.slideshow.defaults = {
	id: false
}
;
/* z.smiley.js
----------------------------------------------------------

@author Marc Worrell <marc@worrell.nl>
@author Ben Alman

Incorporates code by Ben Alman:
http://benalman.com/projects/jquery-replacetext-plugin/

Copyright 2011 Marc Worrell
Copyright 2009 Ben Alman

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
 
http://www.apache.org/licenses/LICENSE-2.0
 
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

---------------------------------------------------------- */

$.widget("ui.smiley", {
	_init: function() {
		var html = $(this.element).html;
		var smileys = this.options.smileys;

		this.element.each(function(){
			var node = this.firstChild, val, new_val, remove = [];
			if (node) {
				do {
					if ( node.nodeType === 3 ) {
						val = node.nodeValue;
						new_val = val;
						for (var i=0; i<smileys.length; i++) {
							var img = '<img src="'+smileys[i].u+'" alt="'+smileys[i].d+'"/>';
							new_val = new_val.replace(smileys[i].k, img);
						};
						if ( new_val !== val ) {
							$(node).before( new_val );
							remove.push( node );
						}
					}
				} while ( node = node.nextSibling );
			}
			remove.length && $(remove).remove();
		});
	}
});

$.ui.smiley.defaults = {
	smileys: [
		{ k:'(ff)', d:"big hug", u:"/lib/images/smileys/firefox.gif" },
		{ k:'>:D<', d:"big hug", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/6.gif" },
		{ k:'#:-S', d:"whew!", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/18.gif" },
		{ k:'O:-)', d:"angel", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/25.gif" },
		{ k:'<:-P', d:"party", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/36.gif" },
		{ k:':-SS', d:"nail biting", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/42.gif" },
		{ k:'<):)', d:"cowboy", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/48.gif" },
		{ k:':-bd', d:"thumbs up", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/113.gif" },
		{ k:'^#(^', d:"it wasn't me", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/114.gif" },
		{ k:':ar!', d:"pirate", u:"http://l.yimg.com/a/i/us/msg/emoticons/pirate_2.gif" },
		{ k:':-??', d:"I don't know", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/106.gif" },
		{ k:'3:-O', d:"cow", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/50.gif" },
		{ k:':(|)', d:"monkey", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/51.gif" },
		{ k:'@};-', d:"rose", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/53.gif" },
		{ k:'**==', d:"flag", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/55.gif" },
		{ k:'(~~)', d:"pumpkin", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/56.gif" },
		{ k:'*-:)', d:"idea", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/58.gif" },
		{ k:'[-O<', d:"praying", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/63.gif" },
		{ k:':)>-', d:"peace sign", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/67.gif" },
		{ k:'\\:D/', d:"dancing", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/69.gif" },
		{ k:'^:)^', d:"not worthy", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/77.gif" },
		{ k:'[..]', d:"transformer", u:"http://l.yimg.com/a/i/us/msg/emoticons/transformer.gif" },
		{ k:';;)', d:"batting eyelashes", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/5.gif" },
		{ k:':-/', d:"confused", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/7.gif" },
		{ k:':">', d:"blushing", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/9.gif" },
		{ k:':-*', d:"kiss", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/11.gif" },
		{ k:'=((', d:"broken heart", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/12.gif" },
		{ k:':-O', d:"surprise", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/13.gif" },
		{ k:'B-)', d:"cool", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/16.gif" },
		{ k:':-S', d:"worried", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/17.gif" },
		{ k:'>:)', d:"devil", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/19.gif" },
		{ k:':((', d:"crying", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/20.gif" },
		{ k:':))', d:"laughing", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/21.gif" },
		{ k:'/:)', d:"raised eyebrows", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/23.gif" },
		{ k:'=))', d:"rolling on the floor", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/24.gif" },
		{ k:':-B', d:"nerd", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/26.gif" },
		{ k:':-c', d:"call me", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/101.gif" },
		{ k:':)]', d:"on the phone", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/100.gif" },
		{ k:'~X(', d:"at wits' end", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/102.gif" },
		{ k:':-h', d:"wave", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/103.gif" },
		{ k:':-t', d:"time out", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/104.gif" },
		{ k:'8->', d:"day dreaming", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/105.gif" },
		{ k:'I-)', d:"sleepy", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/28.gif" },
		{ k:'8-|', d:"rolling eyes", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/29.gif" },
		{ k:'L-)', d:"loser", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/30.gif" },
		{ k:':-&', d:"sick", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/31.gif" },
		{ k:':-$', d:"don't tell anyone", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/32.gif" },
		{ k:'[-(', d:"no talking", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/33.gif" },
		{ k:':O)', d:"clown", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/34.gif" },
		{ k:'8-}', d:"silly", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/35.gif" },
		{ k:'(:|', d:"yawn", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/37.gif" },
		{ k:'=P~', d:"drooling", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/38.gif" },
		{ k:':-?', d:"thinking", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/39.gif" },
		{ k:'#-o', d:"d'oh", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/40.gif" },
		{ k:'=D>', d:"applause", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/41.gif" },
		{ k:'@-)', d:"hypnotized", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/43.gif" },
		{ k:':^o', d:"liar", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/44.gif" },
		{ k:':-w', d:"waiting", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/45.gif" },
		{ k:':-<', d:"sigh", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/46.gif" },
		{ k:'>:P', d:"phbbbbt", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/47.gif" },
		{ k:'X_X', d:"I don't want to see", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/109.gif" },
		{ k:':!!', d:"hurry up!", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/110.gif" },
		{ k:'\\m/', d:"rock on!", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/111.gif" },
		{ k:':-q', d:"thumbs down", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/112.gif" },
		{ k:':o3', d:"puppy dog eyes", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/108.gif" },
		{ k:'%-(', d:"not listening", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/107.gif" },
		{ k:':@)', d:"pig", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/49.gif" },
		{ k:'~:>', d:"chicken", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/52.gif" },
		{ k:'%%-', d:"good luck", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/54.gif" },
		{ k:'~O)', d:"coffee", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/57.gif" },
		{ k:'8-X', d:"skull", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/59.gif" },
		{ k:'=:)', d:"bug", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/60.gif" },
		{ k:'>-)', d:"alien", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/61.gif" },
		{ k:':-L', d:"frustrated", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/62.gif" },
		{ k:'$-)', d:"money eyes", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/64.gif" },
		{ k:':-"', d:"whistling", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/65.gif" },
		{ k:'b-(', d:"feeling beat up", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/66.gif" },
		{ k:'[-X', d:"shame on you", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/68.gif" },
		{ k:'>:/', d:"bring it on", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/70.gif" },
		{ k:';))', d:"hee hee", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/71.gif" },
		{ k:':-@', d:"chatterbox", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/76.gif" },
		{ k:':-j', d:"oh go on", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/78.gif" },
		{ k:'(*)', d:"star", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/79.gif" },
		{ k:'o->', d:"hiro", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/72.gif" },
		{ k:'o=>', d:"billy", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/73.gif" },
		{ k:'o-+', d:"april", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/74.gif" },
		{ k:'(%)', d:"yin yang", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/75.gif" },
		{ k:':bz', d:"bee", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/115.gif" },
		{ k:':)', d:"happy", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/1.gif" },
		{ k:':(', d:"sad", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/2.gif" },
		{ k:';)', d:"winking", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/3.gif" },
		{ k:':D', d:"big grin", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/4.gif" },
		{ k:':x', d:"love struck", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/8.gif" },
		{ k:':P', d:"tongue", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/10.gif" },
		{ k:'X(', d:"angry", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/14.gif" },
		{ k:':>', d:"smug", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/15.gif" },
		{ k:':|', d:"straight face", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/22.gif" },
		{ k:'=;', d:"talk to the hand", u:"http://l.yimg.com/us.yimg.com/i/mesg/emoticons7/27.gif" }
	]
};

;
/* z.timesince.js
----------------------------------------------------------

@author Marc Worrell <marc@worrell.nl>
@author Erik Hanson

Incorporates code by Erik Hanson:
http://www.eahanson.com/2008/12/04/relative-dates-in-javascript/

Simple relative date.

Shows a string like "4 days ago". Prefers to return values >= 2. For example, it would
show "26 hours ago" instead of "1 day ago", but would show "2 days ago" instead of
"49 hours ago".

Copyright 2011 Marc Worrell
Copyright (c) 2008 Erik Hanson http://www.eahanson.com/

Licensed under the MIT License http://www.opensource.org/licenses/mit-license.php

---------------------------------------------------------- */

$.widget("ui.timesince", {
	_init: function() {
		var self = this;
		var olderDate = this.options.time;

		if (typeof olderDate == "string") 
			olderDate = new Date(olderDate);
		else if (typeof olderDate == "number")
			olderDate = new Date(olderDate * 1000);

		self.element.html(self.timesince(olderDate));
		setInterval(function() {
						self.element.html(self.timesince(olderDate));
					}, 10000);
	},
		
	timesince: function(olderDate) {
		var newerDate = new Date();
		var milliseconds = newerDate - olderDate;
		var conversions = [
			["years", 31518720000],
			["months", 2626560000 /* assumes there are 30.4 days in a month */],
			["days", 86400000],
			["hours", 3600000],
			["minutes", 60000],
			["seconds", 1000]
		];

		for (var i = 0; i < conversions.length; i++) {
			var result = Math.floor(milliseconds / conversions[i][1]);
			if (result >= 10 || (result >= 2 && conversions[i][1] > 1000)) {
				return result + " " + z_translate(conversions[i][0] + " ago");
			}
		}
		return z_translate("moments ago");
	}
});

$.ui.timesince.defaults = {
	time: 0		/* Unix timestamp, seconds since Jan 1st, 1970 */
};

;
/**
 * Copyright (c) 2009 Sergiy Kovalchuk (serg472@gmail.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.
 *  
 * Following code is based on Element.mask() implementation from ExtJS framework (http://extjs.com/)
 *
 */
;(function($){
	
	/**
	 * Displays loading mask over selected element(s). Accepts both single and multiple selectors.
	 *
	 * @param label Text message that will be displayed on top of the mask besides a spinner (optional). 
	 * 				If not provided only mask will be displayed without a label or a spinner.  	
	 * @param delay Delay in milliseconds before element is masked (optional). If unmask() is called 
	 *              before the delay times out, no mask is displayed. This can be used to prevent unnecessary 
	 *              mask display for quick processes.   	
	 */
	$.fn.mask = function(label, delay){
		$(this).each(function() {
			if(delay !== undefined && delay > 0) {
		        var element = $(this);
		        element.data("_mask_timeout", setTimeout(function() { $.maskElement(element, label)}, delay));
			} else {
				$.maskElement($(this), label);
			}
		});
	};
	
	/**
	 * Removes mask from the element(s). Accepts both single and multiple selectors.
	 */
	$.fn.unmask = function(){
		$(this).each(function() {
			$.unmaskElement($(this));
		});
	};
	
	/**
	 * Checks if a single element is masked. Returns false if mask is delayed or not displayed. 
	 */
	$.fn.isMasked = function(){
		return this.hasClass("masked");
	};

	/**
	 * Show or update the upload progressbar
	 */
	$.fn.maskProgress = function(value){
		$(this).each(function() {
			$.maskProgress($(this), value);
		});
	};

	$.maskElement = function(element, label){
	
		//if this element has delayed mask scheduled then remove it and display the new one
		if (element.data("_mask_timeout") !== undefined) {
			clearTimeout(element.data("_mask_timeout"));
			element.removeData("_mask_timeout");
		}

		if(element.isMasked()) {
			$.unmaskElement(element);
		}
		
		if(element.css("position") == "static") {
			element.addClass("masked-relative");
		}
		
		element.addClass("masked");
		
		var maskDiv = $('<div class="loadmask"></div>');
		
		//auto height fix for IE
		if(navigator.userAgent.toLowerCase().indexOf("msie") > -1){
			maskDiv.height(element.height() + parseInt(element.css("padding-top")) + parseInt(element.css("padding-bottom")));
			maskDiv.width(element.width() + parseInt(element.css("padding-left")) + parseInt(element.css("padding-right")));
		}
		
		//fix for z-index bug with selects in IE6
		if(navigator.userAgent.toLowerCase().indexOf("msie 6") > -1){
			element.find("select").addClass("masked-hidden");
		}
		
		element.append(maskDiv);
		
		if ($(element).progressbar != undefined) {
			var maskProgressDiv = $('<div class="loadmask-progress" style="display:none;"></div>');
			element.append(maskProgressDiv);
			element.find(".loadmask-progress").progressbar({value: 0});
		}
		if(label !== undefined) {
			var maskMsgDiv = $('<div class="loadmask-msg" style="display:none;"></div>');
			maskMsgDiv.append('<div>' + label + '</div>');
			element.append(maskMsgDiv);
			
			//calculate center position
			maskMsgDiv.css("top", Math.round(element.height() / 2 - (maskMsgDiv.height() - parseInt(maskMsgDiv.css("padding-top")) - parseInt(maskMsgDiv.css("padding-bottom"))) / 2)+"px");
			maskMsgDiv.css("left", Math.round(element.width() / 2 - (maskMsgDiv.width() - parseInt(maskMsgDiv.css("padding-left")) - parseInt(maskMsgDiv.css("padding-right"))) / 2)+"px");
			
			maskMsgDiv.show();
		}
	};
	
	$.maskProgress = function(element, value){
		if ($(element).progressbar != undefined) {
			element.find(".loadmask-progress").show().progressbar('option', 'value', value);
			element.find(".loadmask-msg").hide();
		}
	};
	
	$.unmaskElement = function(element){
		//if this element has delayed mask scheduled then remove it
		if (element.data("_mask_timeout") !== undefined) {
			clearTimeout(element.data("_mask_timeout"));
			element.removeData("_mask_timeout");
		}
		
		element.find(".loadmask-msg,.loadmask-progress,.loadmask").remove();
		element.removeClass("masked");
		element.removeClass("masked-relative");
		element.find("select").removeClass("masked-hidden");
	};
 
})(jQuery);;
$.widget("ui.clickable", 
{
	_init: function() 
	{
		$(this.element).click(function(e) {
			var target = $(this).find("a").attr("href");
			if($(this).find("a").attr("rel") == "external"){
				window.open(target);
			} else {
				window.location=target;
			}
			return false;
		}).addClass("clickable").hover(
			function () {
				$(this).addClass(CLASS_HOVER);	    	
		    }, 
		    function () {		
				$(this).removeClass(CLASS_HOVER);	
		  	}
	 	);

		// Make sure that checkboxes still work
		$(":checkbox", this.element).click(function(e) {
			var checkbox = this;
			setTimeout(function() { 
					if ($(checkbox).attr('checked'))
						$(checkbox).attr('checked', false);
					else
						$(checkbox).attr('checked', true);
				 }, 10);
			e.stopPropagation();
			return false; 
		});
	}
});

$.ui.clickable.defaults = {
}
;
$.widget("ui.memberactions", 
{
	_init: function() 
	{
		// do_memberaction
		$(this.element).hoverIntent({
				sensitivity:7,
				interval:200,
				timeout:200,
				over: function() {
					$(this).toggleClass(CLASS_HOVER);
					if($(this).parent(".grid-view").length) $(".thumbnail, .online, .status",this).animate({left:"-10px"},200);
					$(".quick-actions",this).fadeIn(300);
				},
				out: function() {
					$(this).toggleClass(CLASS_HOVER);
					if($(this).parent(".grid-view").length) $(".thumbnail, .online, .status",this).animate({left:"0"},50);
					$(".quick-actions",this).fadeOut(100);
				}
		  	}
	 	).addClass("clickable");
	}
});

$.ui.memberactions.defaults = {
}
;
$.widget("ui.swapvalue", 
{
	_init: function() 
	{
		var elt = $(this.element);
		var value = elt.val();
		
		elt.focus(function() {
			if ($(this).val() == value) {
				$(this).val("");
			}
			$(this).addClass("focus");
		}).blur(function(){
			if ($.trim($(this).val()) == "") {
				$(this).val(value);
				$(this).removeClass("focus");
				$(this).parent().children("button").hide();
			}
		}).addClass('swapvalue');
	}
});

$.ui.swapvalue.defaults = {
}
;
$.widget("ui.equalize", 
{
	_init: function() 
	{
		// Equalize boxes
		MaxClass.EnhanceBoxes.setHeight($(".equal", $(this.element)));
		$(this.element).addClass("equalize");
	}
});

$.ui.equalize.defaults = {
}
;
$.widget("ui.hoverintent", 
{
	
	_init: function() 
	{
		var self = this;
		
		$(this.element).hoverIntent({
			sensitivity:self.options.sensitivity,
			interval:self.options.interval,
			timeout:self.options.timeout,
			over: function () {
				$(this).toggleClass(CLASS_HOVER);
		    },
		 	out:function () {
				$(this).toggleClass(CLASS_HOVER);
		  	}
	 	});
		
	}
});

$.ui.hoverintent.defaults = {
	sensitivity:7,
	interval:250,
	timeout:250
}
;
$.widget("ui.maxclasstag", 
{
	_init: function() 
	{
		var self = this;
		
		// Equalize boxes
		$(this.element).click(function() {
			if ($(self.element).closest('ul').hasClass('remove')) {
				if ($(self.element).hasClass('is_editable')) {
					z_notify("tag_delete", {
							tag_id: self.options.id, 
							z_delegate: "mod_maxclass_tag", 
							z_target_id: $(self.element).closest('li').attr('id')
					});
				}
				return false;
			} else {
				return true;
			}
		});
	}
});

$.ui.maxclasstag.defaults = {
}
;
/* chatpage.js
----------------------------------------------------------

@author Marc Worrell <marc@worrell.nl>

MaxClass specific chatpage support functions.

Copyright 2011 Marc Worrell

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
 
http://www.apache.org/licenses/LICENSE-2.0
 
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

---------------------------------------------------------- */
var z_chatpage_flash_timer = {};

function z_chatpage_message(chat_id, html)
{
	$(html).appendTo('#chatpage-'+chat_id+' .chatpage-message-list').widgetManager();
	$('#chatpage-flash')
		.html(
		    $('<a class="do_popupwindow show" href="/class/'+chat_id+'/chat/window" data-popupwindow="windowName: \'maxclass-chat-'+chat_id+'\', width: 800" />')
		    .html($(html).unwrap().html()))
		.widgetManager();
    $('#chatpage-flash a').effect("highlight");
    
	/* Copy the face of the user over as background to the chat link */
	var thumbnail = $(html).find('img.thumbnail');
	if (thumbnail.length > 0) {
		$('li .chatpage-status-'+chat_id)
			.addClass("thumb")
			.css({ backgroundImage: "url("+thumbnail.attr('src')+")", backgroundPosition: "center center" });
	}

	/* Hide the face/message after 10 seconds */
	if (z_chatpage_flash_timer[chat_id] != undefined) clearTimeout(z_chatpage_flash_timer[chat_id]);
	z_chatpage_flash_timer[chat_id] = setTimeout(function() { 
						$('#chatpage-flash a').fadeOut();
						$('.chatpage-status-'+chat_id).removeAttr('style').removeClass("thumb");
					}, 10000);
}

function z_chatpage_user(is_online, chat_id, user_id, html)
{
	if (is_online) {
		$(html).appendTo('#chatpage-'+chat_id+' .chatpage-online').widgetManager().fadeIn();	
	} else {
		$('#chatpage-user-'+chat_id+'-'+user_id).fadeOut(300, function() { $(this).remove(); });
	}
}
;
var CLASS_TICKLED	= 'tickled',
	CLASS_HOVER		= 'hover',
	CLASS_OPEN		= 'open',
	CLASS_REMOVE	= 'remove';

var MaxClass = {
	init: function() {
		window.isIE6 = /msie 6/i.test(navigator.userAgent)? true:false;
		window.isIE = /msie/i.test(navigator.userAgent)? true:false;
		window.isIPhone = /iPhone/i.test(navigator.userAgent)? true:false;
		window.isIPad = /iPad/i.test(navigator.userAgent)? true:false;
		
		// Disable feedbackform on iPad & iPhone
		if (isIPhone || isIPad) $(".feedback").hide();

		this.KeyboardManager.init();
		this.ActionsManager.init();
		this.MailManager.init();
		this.FeedbackForm.init();
		
		$("a.top").live("click",(function(e){
			e.preventDefault();
			$.scrollTo(this.hash, 500);
		}));

		// Member sorter init
		$(".fill-class .member .info,.fill-class .member .delete-role").hoverIntent({
			sensitivity:7,
			interval:250,
			timeout:250,
			over: function () {
				$(this).toggleClass(CLASS_HOVER);
		    },
		 	out:function () {
				$(this).toggleClass(CLASS_HOVER);
		  	}
	 	});
		if ($('.fill-class .families').height() > $('.fill-class .admins').height()) {
			$(document).scroll(function(){
				MaxClass.EnhanceBoxes.fixBox(".fill-class");
			});
		}
		
		$("[title]").tooltip({predelay:400, offset: [-10, -60], effect: 'slide'}).dynamic({ bottom: { direction: 'down', bounce: true } });;
		
		// Disabled menu items 
		$("nav li.disabled a, a.disabled").click(function(e){
			e.preventDefault();
		});	
		
		//toggle feedbackform
		$(".feedback .toggle, .feedback .cancel").click( function() {
			var feedback = $(".feedback");
			feedback.toggleClass('open');
	        if (feedback.hasClass("open")) {
	            $(feedback).animate({
	                bottom: "0px"
	                }, 500 );
	        } else {
	            $(feedback).animate({
	                bottom: "-427px"
	                }, 500 );
	        }
			return false;
		});
		
		// Scroll to -> to be do-alized!!!!
		$(".toggle-join-class").click(function(e){
			setTimeout("$.scrollTo('.join-class-code',200)", 100);
			return true;
		});
		$(".show-conversation").click(function(e){
			e.preventDefault();
			setTimeout("$.scrollTo('.thread-list',200)", 100);
		});
		$("..disclaimer .help").click(function(e){
			e.preventDefault();
			setTimeout("$.scrollTo('.families .disclaimer',200)", 100);
		});
		
		//toggle referral options
		$(".referral .toggle").click(function(){
			$(".referral dd").hide();
			$(this).parent().parent().next().fadeIn();
		});
		
		// Add lightbox for photos etc.
		$("a[rel^='prettyPhoto']").prettyPhoto();
		
		//Toggle stuff
		$('.books a, .settings h2').click(function(e){
			e.preventDefault();
			$(this).parent().toggleClass(CLASS_OPEN);
		});
		
		//Toggle stuff
		$('.page-content .sortable nav a.toggle').click(function(e){
			e.preventDefault();
			$(this).parent().parent().toggleClass(CLASS_OPEN);
			//$(this).parent().parent().find('.panel').slideToggle();
		});
		
		// Tree Menu
		$('.collapsible li.parent').click(function(e){
			e.stopPropagation();
			$(this).find('ul:first').slideToggle();
		});
	}
};

// Keyboard Manager
MaxClass.KeyboardManager = {
	init: function() {
		var grid = $("#grid");
		var notes = $(".notes");
					
		var isAlt = false;
		var isCtrl = false;
		
		$(document).keyup(function (e) { 
			if(e.which == 18) isAlt=false; 
			if(e.which == 17) isCtrl=false; 
		}).keydown(function (e) { 
			
			if(e.which == 18) isAlt=true;
			if(e.which == 17) isCtrl=true; 
			
			//toggle grid: alt + ctrl + g
			if(e.which == 71 && isAlt == true && isCtrl == true) { 
				grid.toggle();
			}
			//toggle notes alt + ctrl + n 
			if(e.which == 78 && isAlt == true && isCtrl == true) { 
				notes.toggle();
			}
		});
	}
};

// Equalize boxes in a row
MaxClass.EnhanceBoxes = {
	setHeight: function(obj) {
		var h = 0;
		obj.each(function() { h = Math.max(h, $(this).get(0).offsetHeight); });
		obj.each(function() {
			var p = parseInt($(this).css("paddingTop")) + parseInt($(this).css("paddingBottom"));
			$(this).css({ minHeight: (h - p) + "px" });
			// Compensate for stubborn IE6
			if(window.isIE6) $(this).css({ height: (h - p) + "px" });
		});
		// Tickle DOM, for some browser
		$("body").toggleClass(CLASS_TICKLED);
	},
	fixBox: function(obj) {
		var target = $(obj);
		
		if (target.length) {
			if($(document).scrollTop() > (target.offset().top+50)) {
				target.find(".fix").addClass("fixed");
				$("a.top:hidden").fadeIn("slow");
			
			} else {
				target.find(".fix").removeClass("fixed");
				$("a.top").hide();
			}
		} else {
			return;
		}
	}
};

// Toolbar manager
MaxClass.ActionsManager = {
	init: function() {
		MaxClass.ActionsManager.sortList($("ul.switch-view a.sort"), ".members ul:first");
	
		$("ul.switch-view a.switcher").click(function(e){
			e.preventDefault();
			var context = this;
			
			var type = $(this).attr("rel");
			switch (type) {
				case "sort-tool":
					MaxClass.ActionsManager.sortList($(this), ".members ul:first");
					break;
				case "list-tool":
					MaxClass.ActionsManager.toggleViewType("list");
					break;
				case "grid-tool":
					MaxClass.ActionsManager.toggleViewType("grid");
					break;
			}
			
		});
	},
	sortList: function(target, which) {
		target.parent().toggleClass("desc");
		
		var direction = (target.parent().hasClass("desc")) ? -1 : 1;
		
		$(which).each(function() {
			var sorted = $(this).find("> li").sort(function(a, b) {
				return (($(a).text()).replace(/\s/g, "")).toLowerCase() > (($(b).text()).replace(/\s/g, "")).toLowerCase() ? direction : -direction; 
			});
			$(this).append(sorted);
		});
			
	},
	toggleViewType: function(type) {			
		$(".members ul:first").addClass("grid-view");
		
		var target = $(".toggle-list ul:first"); //quick hack, todo: select .toggle-list in clicked context
		var listTool = $("a[rel='list-tool']");
		var gridTool = $("a[rel='grid-tool']");
		
		switch (type) {
			case "list":
				target.removeClass("grid-view").addClass("list-view");
				gridTool.parent().removeClass("active");
				listTool.parent().addClass("active");
				break;
			case "grid":
				target.removeClass("list-view").addClass("grid-view");
				listTool.parent().removeClass("active");
				gridTool.parent().addClass("active");
				break;
		}
	}
};

// Mail manager
MaxClass.MailManager = {
	init: function() {
		this.filter = $(".compose-filter");
		this.recipients = $(".compose-message .recipients");
		this.persons = $(".persons");
		this.explanation = $("em",this.recipients);
		
		var self = this;
		
		/*this.recipients.click(function(){
			if($(".compose-message .recipients .recipient").length == 0) {
				self.filter.effect("shake", 100);
			}
		});*/
		
		$(".persons a").live("click", function(e) {
		  	e.preventDefault();
			if ($(this).attr("class") == "disabled"){
				return false;
			}else{
				MaxClass.MailManager.addRecipient($(this));
			}
		});
		
		$(".recipients a.recipient").live('click', function(e) {
		  	e.preventDefault();
			MaxClass.MailManager.removeRecipient($(this));
		});

		$(".recipients a.recipient span").live('click', function(e) {
		  	e.preventDefault();
		  	var school_id = $("input", $(this).closest('a')).val().match(/\*([0-9]+)/)[1];
		  	z_event("add-recipients", { tab_school: true, school_id: school_id });
		    return false;
		});
		
	},
	addRecipient: function(recipient) {
		var selectedClass = $("#compose-class option:selected").text();
		var recipientId = recipient.attr("id").replace(/^t-/,"").replace(/^m-/, ""); 
		var tagspan = "";
		
		this.explanation.hide();
		this.recipients.addClass("shy");

		if (recipientId.match(/^maxclass-/)) {
			recipient.clone()
						.removeAttr("id")
						.addClass("recipient maxclass")
						.html(recipient.html()+"<input type='hidden' name='recipient' value='recipient-"+ recipient.attr("id") +"'' />")
						.appendTo(this.recipients);
		} else {
			var rclass;
			
			if (recipientId.match(/^person/)) rclass = "recipient single";
			else rclass="recipient";
			
			if (recipientId.match(/-tag-/)) tagspan = "<span class='tag'>"+z_translate("Everybody with tag")+":</span>";
			else tagspan = "";
			
			selectedClass = $("#compose-class option:selected").text();
			recipient.clone()
						.removeAttr("id")
						.addClass(rclass)
						.html(tagspan + recipient.html() +
							" <span>("+ selectedClass +")</span>"
							+"<input type='hidden' name='recipient' value='recipient-"+recipientId+"'' />")
						.appendTo(this.recipients);
		}		
		this.recipients.effect("highlight", 1000);
		$("#"+recipientId).addClass("disabled");
		$("#t-"+recipientId).addClass("disabled");
		$("#m-"+recipientId).addClass("disabled");
	},
	removeRecipient: function(recipient) {
		var recipientId = $("input", recipient).val().replace(/recipient-/, "");
		recipient.remove();
		if($(".compose-message .recipients .recipient").length == 0) {
			this.explanation.show();
			this.recipients.removeClass("shy");
		}
		$("#"+recipientId).removeClass("disabled");
		$("#t-"+recipientId).removeClass("disabled");
		$("#m-"+recipientId).removeClass("disabled");
	},
	disableMembers: function() {
		$("input", ".recipient").each(function(){
			if (!$(this).hasClass('multi')) {
				var personId = $(this).val().replace(/recipient-/, "");
				$(".persons a[id="+personId+"]").addClass("disabled");
				$(".persons a[id=t-"+personId+"]").addClass("disabled");
				$(".persons a[id=m-"+personId+"]").addClass("disabled");
			}
		})
	}
};

// Feedback form
MaxClass.FeedbackForm = {
	init: function() {
		var browser = BrowserDetect.browser+" "+BrowserDetect.version+" on "+BrowserDetect.OS;
		var browserNode = $(".feedback .browser");
		var url = document.location.pathname;
		var urlNode = $(".feedback .url");
		
		browserNode.find("input")
					.val(browser);
		browserNode.find("span")
					.html(browser);
					
		urlNode.find("input")
					.val(url);
		urlNode.find("span")
					.html(url);
		
	}
};

var BrowserDetect = {
	init: function () {
		this.browser = this.searchString(this.dataBrowser) || "An unknown browser";
		this.version = this.searchVersion(navigator.userAgent)
			|| this.searchVersion(navigator.appVersion)
			|| "an unknown version";
		this.OS = this.searchString(this.dataOS) || "an unknown OS";
	},
	searchString: function (data) {
		for (var i=0;i<data.length;i++)	{
			var dataString = data[i].string;
			var dataProp = data[i].prop;
			this.versionSearchString = data[i].versionSearch || data[i].identity;
			if (dataString) {
				if (dataString.indexOf(data[i].subString) != -1)
					return data[i].identity;
			}
			else if (dataProp)
				return data[i].identity;
		}
	},
	searchVersion: function (dataString) {
		var index = dataString.indexOf(this.versionSearchString);
		if (index == -1) return;
		return parseFloat(dataString.substring(index+this.versionSearchString.length+1));
	},
	dataBrowser: [
		{
			string: navigator.userAgent,
			subString: "Chrome",
			identity: "Chrome"
		},
		{ 	string: navigator.userAgent,
			subString: "OmniWeb",
			versionSearch: "OmniWeb/",
			identity: "OmniWeb"
		},
		{
			string: navigator.vendor,
			subString: "Apple",
			identity: "Safari",
			versionSearch: "Version"
		},
		{
			prop: window.opera,
			identity: "Opera"
		},
		{
			string: navigator.vendor,
			subString: "iCab",
			identity: "iCab"
		},
		{
			string: navigator.vendor,
			subString: "KDE",
			identity: "Konqueror"
		},
		{
			string: navigator.userAgent,
			subString: "Firefox",
			identity: "Firefox"
		},
		{
			string: navigator.vendor,
			subString: "Camino",
			identity: "Camino"
		},
		{		// for newer Netscapes (6+)
			string: navigator.userAgent,
			subString: "Netscape",
			identity: "Netscape"
		},
		{
			string: navigator.userAgent,
			subString: "MSIE",
			identity: "Explorer",
			versionSearch: "MSIE"
		},
		{
			string: navigator.userAgent,
			subString: "Gecko",
			identity: "Mozilla",
			versionSearch: "rv"
		},
		{ 		// for older Netscapes (4-)
			string: navigator.userAgent,
			subString: "Mozilla",
			identity: "Netscape",
			versionSearch: "Mozilla"
		}
	],
	dataOS : [
		{
			string: navigator.platform,
			subString: "Win",
			identity: "Windows"
		},
		{
			string: navigator.platform,
			subString: "Mac",
			identity: "Mac"
		},
		{
			   string: navigator.userAgent,
			   subString: "iPhone",
			   identity: "iPhone/iPod"
	    },
		{
			string: navigator.platform,
			subString: "Linux",
			identity: "Linux"
		}
	]

};


$(function() {
	BrowserDetect.init();
	MaxClass.init();
});	

;

var z_pre_upload_checker = false;

// Pre upload checks: trigger checks when selecting files to be uploaded
function pre_upload_check(trigger, delay)
{
	var form = $(trigger).closest('form');

	if (z_pre_upload_checker) {
		clearTimeout(z_pre_upload_checker);
		z_pre_upload_checker = false;
	}
	if (delay) {
		z_pre_upload_checker = setTimeout(function() {
							do_pre_upload_check(form);
						}, 400);
	} else {
		var form = $(trigger).closest('form');
		do_pre_upload_check(form);
	}
}

function do_pre_upload_check(form)
{
	var file = $("input[name=file]", form).val();
	if (file) {
		z_event("pre_upload_check", [
			{name: "title", value: $("input[name=title]", form).val()},
			{name: "collection", value: $("select[name=collection]", form).val()},
			{name: "file", value: file}
		]);
	}
}

function checkbox_toggle(selector)
{
    var boxes = $(selector + " :checkbox");
    var checked = $(selector + " :checkbox:checked");
    
    if (checked.length == boxes.length) {
        boxes.removeAttr('checked', '');
    } else {
        boxes.attr('checked', 'checked');
    }
}

