/**
 * Common jQuery plugins for the Scout Portal Toolkit.
 *
 * Part of the Scout Portal Toolkit
 * Copyright 2009 Internet Scout Project
 * http://scout.wisc.edu
 */

 (function($) {

  	/**
  	 * Combine multiple jQuery objects into one. Parameters can either be
  	 * jQuery objects, strings that would be passed to $(), or a combination
  	 * of both.
 	 *
 	 * @param mixed variable jQuery objects and strings passed to $()
 	 * @return jQuery object
  	 * @author Tim Baumgard
  	 */
     jQuery.combine = function() {
         if (arguments.length === 0) {
             return $([]);
         } else if (arguments.length == 1) {
             return $($(arguments[i]).get());
         } else if (arguments.length == 2) {
             return $($.merge($(arguments[0]).get(), $(arguments[1]).get()));
         } else {
             var merged = [];
     		for (var i = 0; i < arguments.length; i++) {
     		    $.merge(merged, $(arguments[i]).get());
     		}
     		return $(merged);
         }
     };

 })(jQuery);

 (function($) {

  	/**
  	 * Toggle a selector's elements between two strings.
     * http://dev.jquery.com/ticket/1092
 	 *
 	 * @param a string the first
 	 * @param b string the second
 	 * @return jQuery object
  	 */
     jQuery.fn.toggleText = function(a, b) {
         return this.each(function(){
             $(this).text($(this).text() == a ? b : a);
         });
     };

     jQuery.fn.toggleMyText = function(alternate){
         return this.each(function(){
             var $this = $(this);
             if ("undefined" == typeof $this.data("origtext")) {
                 $this.data("origtext", escape($this.html()));
                 $this.html(alternate);
             } else {
                 $this.html(unescape($this.data("origtext")));
                 $this.removeData("origtext");
             }
         });
     }

})(jQuery);

(function($) {

    /**
     * For debug use. Creates a floating div that logs events that occur on
     * the jQuery object.
   	 *
 	 * Options are:
 	 *     "events" => array of jQuery events to log
   	 *
   	 * @param object options optional settings
   	 * @return jQuery object
     * @author Tim Baumgard
     */
     jQuery.fn.eventlogger = function(options) {
         options = $.extend({
 			"events": ["blur", "change", "click", "dblclick", "error", "focus",
 			          "keydown", "keypress", "keyup", "load", "mousedown",
 			          "mouseenter", "mouseleave", "mousemove", "mouseout",
 			          "mouseover", "mouseup", "resize", "scroll", "select",
 			          "submit"]
 		}, options);

        if ("object" != typeof options.events) {  options.events = [];  }
		options.fn = function(event) {
            return "<p><b>"+event+"</b> fired.</p>";
        };

        // set up logger div
        var $this = this;
        var $obj = $(document.createElement("div"));
        $obj.css({
            "display": "none",
            "position": "absolute",
            "top": "0px",
            "left": "0px",
            "height": Math.round($(window).height()*0.85)+"px",
            "width": "150px",
            "background": "#FFFFFF",
            "border": "1px solid #AAAAAA",
            "padding": "10px",
            "overflow": "auto"
        });
        $(document).ready(function(){  $obj.appendTo(document.body);  });

        // bind each event
        $.each(options.events, function(){
            var event = this;
            $this.bind(event, function(){
                $obj.show();
                $obj.html($obj.html()+options.fn(event));
                $obj.scrollTop($obj.get(0).scrollHeight);
            });
        });

        // return jQuery object
        return $this;
    };

})(jQuery);

 (function($) {

   	/**
   	 * Enable and disable a button based on the return value of the given
   	 * predicate. The predicate should return a boolean true or false.
  	 *
  	 * @param function predicate function that returns true or false
  	 * @return jQuery object
   	 * @author Tim Baumgard
   	 */
     jQuery.fn.buttonToggle = function(predicate) {
         if (!$.isFunction(predicate))
             {  predicate = function(){ return true; };  }

         // enable or disable button, depending on the return of the predicate
         this.attr("disabled", (predicate()) ? false : true);
         return this;
     };

 })(jQuery);

 (function($) {

 	/**
 	 * Bind or trigger a given callback function when the $(...).val() value
 	 * of an element changes.
 	 *
	 * Options are:
	 *     "checkInterval" => time between checks, in ms
	 *
	 * @param function fn callback function
 	 * @param object options optional parameters
 	 * @return object jQuery object
 	 * @author Tim Baumgard
 	 */
     jQuery.fn.onvaluechange = function(fn, options) {
        options = $.extend({
 			"checkInterval": 125
 		}, options);

         options.checkInterval = parseInt(options.checkInterval, 10);
         options.fn = "$$ONVALUECHANGE_FN_POINTER$$";

         // bind event
         if ($.isFunction(fn)) {
             this.each(function(){
                 var $this = $(this);
                 var prevValue = $this.val();
                 var interval = null;
                 var $fn = function() {
                     var value = $this.val();
                     if (value != prevValue) {  fn();  }
                     prevValue = value;
                 };

                 // save function for trigger action
                 $this.data(options.fn, $fn);

                 // check for changes on when in focus
                 $this.focus(function(){
                     clearInterval(interval);
                     interval = setInterval($fn, options.checkInterval);
                 });

                 // stop checking for changes when blurred
                 $this.blur(function(){
                     clearInterval(interval);
                     $fn();
                 });
             });
         }

         // trigger event
         else {
             this.each(function(){  $(this).data(options.fn)();  });
         }

         // return jQuery object
         return this;
     };

 })(jQuery);

(function($) {

	/**
	 * Clobberer object. See jQuery.clobber and this.clobber. We need a separate
	 * object so that if we don't clobber an existing clobberer.
	 *
	 * @param number default delay for the given clobberer object
	 * @return void
	 * @author Tim Baumgard
	 */
	function clobberer(delay) {
		var TimeoutObj;  // timeout object with the current function to execute
		var Delay;       // delay time before executing Fn

		/**
		 * "Clobber" any existing function to be executed by replacing it with the
		 * given one. A delay of 0 will not use a timeout object: it will make this
		 * method clobber any existing actions and then immediately execute the
		 * given function.
		 *
		 * Options are:
		 *     "delay" => this time only, use a custom delay (in ms)
		 *
		 * @param mixed args arguments passed to fn
		 * @param function fn function to run
		 * @param object options optional settings, see above
		 */
		this.clobber = function(fn, args, options) {
			// options
			options = $.extend({
				"delay": Delay
			}, options);

			// make sure we have an integer
			options.delay = parseInt(options.delay, 10);

			// fail gracefully if not given a function
			if (!$.isFunction(fn)) {
				return this;
			}

			// clobber any current actions
			clearTimeout(TimeoutObj);

			if (options.delay <= 0) {
				// no delay, just run the action
				fn(args);
			} else {
				// set delayed action
				TimeoutObj = setTimeout(function(){  fn(args);  }, options.delay);
			}
		};

		// set the default delay
		Delay = parseInt(delay, 10);
	};

    /**
     * Returns a reference to a new clobberer object's clobber function that
     * "clobbers" an existing action to be performed by intentionally replacing
     * the action with itself.
     *
     * Settings are:
     *     "delay" => the delay time, in ms, before executing a function for
     *         every function passed to the returned object's clobber function.
     *         The default is 500. Note, this can be temporarily overridden when
     *         calling clobber.
     *
     * @param object settings various optional settings, see above
     * @author Tim Baumgard
     */
	jQuery.clobber = function(settings) {
		// default settings
		settings = $.extend({
			"delay": 500
		}, settings);

		// return new clobberer object's clobber function
		return new clobberer(settings.delay).clobber;
	};

})(jQuery);

//jQuery.wait
(function($) {

	/**
	 * Executes onComplete if the return value of predicate is true. The "wake up"
	 * interval (in ms) can be changed via the "wakeInterval" option.
	 *
	 * @param function predicate predicate function
	 * @param function onComplete function that is called when predicate returns true
	 * @param object options
	 * @author Tim Baumgard
	 */
	jQuery.wait = function(predicate, onComplete, options) {
		// options
		options = $.extend({
			"wakeInterval": 5
		}, options);

		// go to the auxiliary function if we have valid params
		if ($.isFunction(predicate) && $.isFunction(onComplete)) {
			aux(predicate, onComplete, parseInt(options.wakeInterval, 10));
		}

		// return the jQuery object
		return jQuery;
	};

	/**
	 * Auxiliary function for jQuery.wait. Will execute onComplete if the return
	 * value of predicate is true. "Wakes up" every interval ms.
	 *
	 * @param function predicate predicate function
	 * @param function onComplete function that is called when predicate returns true
	 * @param int interval interval to "wake" this check
	 * @author Tim Baumgard
	 */
	function aux(predicate, onComplete, interval) {
		if (predicate()) {
			onComplete();
		} else {
			setTimeout(function(){  aux(predicate, onComplete, interval);  }, interval);
		}
	};

})(jQuery);

(function($){

	/**
 	 * Confirm a response when triggered. The "affirm" parameter is called when a yes response is
 	 * given and the "cancel" parameter is called when a no response is given. The "cancel"
 	 * function can be ommited if not needed (and options passed in its place).
 	 *
 	 * 1) The text is configurable with the "question", "yes", "or", and "no" options.
 	 * 2) The trigger (e.g., click, mousover, etc) can can be specified with the "trigger" option.
 	 * 3) The show action (e.g., fade in) can be configured via the "showType" option and its
 	 *    speed can be configured via the "showSpeed" option.
     * 4) A class can be applied to the confirmation element via the "className" option.
 	 * 5) There are two special options: "setup" is a function that is called after the confirmation
 	 *    element has been setup and is shown and "takedown" is a function that is called after the
 	 *    confirmation element has been removed and the caller element is shown again.
	 *
	 * @param function affirm function called when a "yes" response is selected
	 * @param function cancel function called when a "no" response is selected
	 * @param object options
	 * @return jQuery jQuery object
	 * @author Tim Baumgard
	 */
    jQuery.fn.confirm = function(affirm, cancel, options) {
        // in case we don't want to specify the cancel function
        if (!$.isFunction(cancel) && "object" == typeof cancel) {
            options = cancel;
        }

        // default options
        options = $.extend({
            "question": "Are you sure?",
            "yes": "Yes",
            "or": "or",
            "no": "No",
			"trigger": "click",
			"showType": "fadeIn",
			"showSpeed": 150,
            "className": null,
            "setup": null,
            "takedown": null
        }, options);

        var $this = this;

        // set up trigger actions
        $this[options.trigger](function(){
            // setup
            var $parent = $(this);
            var $container = $(document.createElement("SPAN"));
            var okToRespond = false;
            var action = function(fn){
                if (okToRespond) {
					// remove the container, show the parent
                    $container.remove();
		            $parent.show();
		            delete $container;

					// call takedown function if given, then call the yes/no function
		            if ($.isFunction(options.takedown)) {
		                options.takedown();
		            }
                    if ($.isFunction(fn)) {
                        fn($parent);
                    }
                }
                return false;
            };

            // set up "yes" responder
            var $yes = $(document.createElement("A")).attr({"href":"#"});
            $yes.html(options.yes);
            $yes.click(function(){  action(affirm);  });

            // set up "no" responder"
            var $no = $(document.createElement("A")).attr({"href":"#"});
            $no.html(options.no);
            $no.click(function(){  action(cancel);  });

            // set up container
            if ("string" == typeof options.className) {  $container.addClass(options.className);  }
            $container.css({"display": "none"});
            $container.html("<b>"+options.question+"</b>&nbsp;");
            $container.append($yes);
            $container.append(" "+options.or+" ");
            $container.append($no);

            // insert the container before the parent and show it
            $parent.hide();
            $parent.before($container);
            $container[options.showType](options.showSpeed);

            // to prevent any quick-click mistakes
            setTimeout(function(){okToRespond = true;}, 300);

            // call setup function
            if ($.isFunction(options.setup)) {
                options.setup();
            }

            // don't follow an href value
            return false;
        });

        // return jQuery object
        return $this;
    };

})(jQuery);

(function($) {

	var attr = "jquery.highlight.origColor";

	/**
	 * Highlight the element with a yellow-ish background (#F6F583) or with a
	 * custom color (via the "color") option.
	 *
	 * @param object options optional parameters
	 * @return object jQuery object
	 * @author Tim Baumgard
	 */
	jQuery.fn.highlight = function(options) {
		// options
		options = $.extend({
			"color": "#F6F583"
		}, options);

		// set up vars
		var $this = this;
		var origColor = $this.css("background-color");

		// save original color and set the highlight one
		$this.data(attr, origColor);
		$this.css({"background-color": options.color});

		// return jQuery object
		return $this;
	};

	/**
	 * Unhighlight the element by setting the background color to "none" or to
	 * the color the background was before jQuery.fn.highlight was called.
	 *
	 * @return object jQuery object
	 * @author Tim Baumgard
	 */
	jQuery.fn.unhighlight = function() {
		// set up vars
		var $this = this;
		var origColor = "none";


		// if there was an original color set, use it
		if ($this.data(attr)) {
			origColor = $this.data(attr);
			$this.removeData(attr);
		}

		// set the background color
		$this.css({"background-color": origColor});

		// return jQuery object
		return $this;
	};

})(jQuery);

// date plugins
(function($) {

	// locale data
	var locales = {
		"en-US": {
			"months": ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
			"shortMonths": ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
			"weekdays": ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
			"shortWeekdays": ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
			"daysPerMonth": [31,28,31,30,31,30,31,31,30,31,30,31],
			"leapDaysPerMonth": [31,29,31,30,31,30,31,31,30,31,30,31]
		}
	};

	/**
	 * Create a timestamp in milliseconds of the given date values. Values not
	 * specified will default to those of the current date/time.
	 *
	 * Values = {
	 *   hour: hour
	 *   minute: minute
	 *   second: second
	 *   month: month
	 *   day: day
	 *   year: year
	 * }
	 *
	 * @param object values date/time values that can be set (e.g., hour, second, day, year, etc.)
	 * @return int timestamp of the date in milliseconds
	 * @author Tim Baumgard
	 */
	jQuery.mktime = function(values) {
		values = $.extend({
			"hour": $.date("H"),
			"minute": $.date("i"),
			"second": $.date("s"),
			"month": $.date("n"),
			"day": $.date("j"),
			"year": $.date("Y")
		}, values);

		// parse values
		for (var i in values) {
			if (1) {  values[i] = parseInt(values[i], 10);  }
		}

		// set values
		var date = new Date();
		date.setHours(values.hour, values.minute, values.second);
		date.setFullYear(values.year, values.month, values.day);

		// return timestamp
		return date.getTime();
	};

	/**
	 * Format a timestamp in milliseconds. Supports most of the characters
	 * recognized by PHP's date function. As of 2/4/2009, this excludes the e,
	 * I, T, W, and o characters. To escape a character, use "\\{character}"
	 * without the quotes, e.g., "\\T\\i\\m\\e\\\\" = "Time\". If not given a
	 * format, "r" will be used.
	 *
	 * Options = {
	 *   timestamp: custom timestamp in milliseconds (defaults to now)
	 *   locale: given an object, will use the values in it. otherwise gets the
	 *       locale from a string.
	 * }
	 *
	 * Additional Notes: the Date.parse(date-time-string) function can be used to
	 *     get timestamps from a date/time string, but it is somewhat conservative,
	 *     especially compared to PHP's strtotime function.
	 *
	 * Additional Infor about PHP's date function: http://php.net/date
	 *
	 * @param string format format the date should be translated to (optional)
	 * @param object options optional settings (optional)
	 * @return string date in the given format
	 * @author Tim Baumgard
	 */
	jQuery.date = function(format, options) {
		options = $.extend({
			"timestamp": (new Date()).getTime(), // Date.now() == probs in Safari
			"locale": "en-US"
		}, options);

		// parsing
		format = ("undefined" == typeof format) ? "r" : format+"" ;
		options.timestamp = parseInt(options.timestamp, 10);
		var consts = null;
		if ("object" == typeof options.locale) {
			consts = options.locale;
		} else if ("string" == typeof options.locale && "undefined" != locales[options.locale]) {
			consts = locales[options.locale];
		} else {
			// fail gracefully
			return "";
		}

		// set up date object
		var date = new Date();
		date.setTime(options.timestamp);

		// some necessary vars
		var $this = jQuery.date;
		var prevChar = null;
		var currChar = null;
		var dateString = "";

		// just so we don't have to type this out all the time...
		function a(value) {
			// also make sure it's a string
			dateString += value+"";
		};

		for (var i = 0; i < format.length; i++) {
			// set characters
			prevChar = (i === 0) ? null : format.charAt(i-1);
			currChar = format.charAt(i);

			if (prevChar == "\\" ) {
				a(currChar);
			} else if (currChar != "\\"){
				switch (currChar) {
					//- DAY
					case "d":
					case "j":
						a(pad(date.getDate(), (currChar == "j") ? 0 : 2));
						break;
					case "l":
						a(consts.weekdays[date.getDay()]);
						break;
					case "D":
						a(consts.shortWeekdays[date.getDay()]);
						break;
					case "N":
						a((date.getDay() !== 0) ? date.getDay() : 7);
						break;
					case "S":
						var day = parseInt(date.getDate(), 10);
						var dayString = day+"";
						if (day > 10 && day < 14) {
							// these don't follow the pattern
							a("th");
						} else {
							switch (dayString.charAt(dayString.length-1)) {
								case "1":
									a("st");
									break;
								case "2":
									a("nd");
									break;
								case "3":
									a("rd");
									break;
								default:
									a("th");
									break;
							}
						}
						break;
					case "w":
						a(date.getDay());
						break;
					case "z":
						var prevDays = 0;
						for (var j = 0; j < 5; j++) {
							if (date.getFullYear() % 4) {
								prevDays += consts.daysPerMonth[j];
							} else {
								prevDays += consts.leapDaysPerMonth[j];
							}
						}
						a(prevDays+date.getDate());
						break;
					//- MONTH
					case "F":
						a(consts.months[date.getMonth()]);
						break;
					case "M":
						a(consts.shortMonths[date.getMonth()]);
						break;
					case "m":
					case "n":
						a(pad(date.getMonth()+1, (currChar == "n") ? 0 : 2));
						break;
					case "t":
						var year = date.getFullYear();
						var month = date.getMonth();
						a((year % 4) ? consts.daysPerMonth[month] : consts.leapDaysPerMonth[month]);
						break;
					//- YEAR
					case "L":
						a((year % 4) ? 0 : 1);
						break;
					case "Y":
						a(date.getFullYear());
						break;
					case "y":
						var year = date.getFullYear()+"";
						a(year.charAt(year.length-2)+year.charAt(year.length-1));
						break;
					//- TIME
					case "a":
						a((date.getHours() < 12) ? "am" : "pm");
						break;
					case "A":
						a((date.getHours() < 12) ? "AM" : "PM");
						break;
					case "B":
						// need an extra offset for Biel Mean Time, i.e., UTC+1
						// accuracy depends on the time set
						var h2ms = (date.getUTCHours()+1)*60*60*1000;
						var m2ms = date.getUTCMinutes()*60*1000;
						var s2ms = date.getUTCSeconds()*1000;
						var ms = date.getUTCMilliseconds();
						a(Math.round((h2ms+m2ms+s2ms+ms)/(86400)));
						break;
					case "g":
					case "h":
						var hours = (date.getHours() == 12) ? 12 : date.getHours() % 12;
						a(pad(hours, (currChar == "h") ? 2 : 0));
						break;
					case "G":
					case "H":
						a(pad(date.getHours(), (currChar == "H") ? 2 : 0));
						break;
					case "i":
						a(pad(date.getMinutes(), 2));
						break;
					case "s":
						a(pad(date.getSeconds(), 2));
						break;
					case "u":
						a(date.getMilliseconds()+"000");
						break;
					//- TIMEZONE
					case "O":
					case "P":
						var offset = date.getTimezoneOffset();
						var hours = pad(Math.round(offset/60), 2);
						var minutes = pad(offset % 60, 2);
						a(((hours > 0)? "+" : "-")+hours+((currChar == "P") ? ":" : "")+minutes);
						break;
					case "Z":
						// not entirely sure about this one
						a(date.getTimezoneOffset()*60);
						break;
					// FULL DATE/TIME
					case "c":
						a($this("Y-m-d\\TH:i:sP", {"timestamp":date.getTime(), "lang":options.lang}));
						break;
					case "r":
						a($this("D, d M Y H:i:s O", {"timestamp":date.getTime(), "lang":options.lang}));
						break;
					case "U":
						a(Math.round(date.getTime()/1000));
						break;
					//- DEFAULT
					default:
						a(currChar);
						break;
				}
			}
		}

		return dateString;
	};

	// helper function to pad a string/number with a given character
	function pad(string, length, options) {
		options = $.extend({
			"padding": "0"
		}, options);

		// options parsing
		length = parseInt(length, 10);
		if ("string" != typeof string) {
			string = string+"";
		}

		// add the padding
		for (var i = string.length; i < length; i++) {
			string = options.padding + string;
		}

		// return padded string
		return string;
	};

})(jQuery);

(function($) {

	/* var isPositioning = false; // part of temp fix (see reposition)*/

	// function to actually reposition elements
	function reposition($of, $move, options, deltaFn) {
		// options
		options = $.extend({  "extraTop": 0,  "extraLeft": 0  }, options);

		// vars
		if (!$.isFunction(deltaFn)) {  return;  }
		options.extraTop = parseInt(options.extraTop, 10);
		options.extraLeft = parseInt(options.extraLeft, 10);
		$of = $($of);

		// positions and deltas
		var of_position = $of.offset();
		var deltas = deltaFn();

		$move.css({
			"position": "absolute",
			"top": $of.offset().top + deltas.top + options.extraTop + "px",
			"left": of_position.left + deltas.left + options.extraLeft + "px"
		});

		// temp fix for jQuery.offset issue in some instances of Safari
		/*$.wait(function(){  return isPositioning == false;  }, function(){
			isPositioning = true;
			// set final top/left values
			$move.css({
				"position": "absolute",
				"top": $of.offset().top + deltas.top + options.extraTop + "px",
				"left": of_position.left + deltas.left + options.extraLeft + "px"
			});
			isPositioning = false;
		}, {"wakeInterval": 50});*/
	};

	/**
	 * Position an object in relation to the top side of another object.
	 *
	 * Options = {
	 *   extraLeft: extra pixel size (int) to add to the left value
	 *   extraTop: extra pixel size (int) to add to the top value
	 * }
	 *
	 * @param jQuery $of the object to position in relation to
	 * @return jQuery object
	 * @author Tim Baumgard
	 */
	jQuery.fn.positionTopOf = function($of, options){
		// save this value
		var $this = this;

		reposition($of, $this, options, function(){
			// return deltas
			return {
				"top": -Math.ceil($this.innerHeight()), // + 0
				"left": Math.ceil($of.innerWidth()/2) -
					Math.ceil($this.innerWidth()/2)
			};
		});

		// return jQuery object
		return $this;
	};

	/**
	 * Position an object in relation to the leftside of another object.
	 *
	 * Options = {
	 *   extraLeft: extra pixel size (int) to add to the left value
	 *   extraTop: extra pixel size (int) to add to the top value
	 * }
	 *
	 * @param jQuery $of the object to position in relation to
	 * @return jQuery object
	 * @author Tim Baumgard
	 */
	jQuery.fn.positionLeftOf = function($of, options){
		// save this value
		var $this = this;

		reposition($of, $this, options, function(){
			// return deltas
			return {
				"top": Math.ceil($of.innerHeight()/2) -
					Math.ceil($this.innerHeight()/2),
				"left": -$this.innerWidth() // + 0
			};
		});

		// return jQuery object
		return $this;
	};

	/**
	 * Position an object in relation to the bottom side of another object.
	 *
	 * Options = {
	 *   extraLeft: extra pixel size (int) to add to the left value
	 *   extraTop: extra pixel size (int) to add to the top value
	 * }
	 *
	 * @param jQuery $of the object to position in relation to
	 * @return jQuery object
	 * @author Tim Baumgard
	 */
	jQuery.fn.positionBottomOf = function($of, options){
		// save this value
		var $this = this;

		reposition($of, $this, options, function(){
			// return deltas
			return {
				"top": $of.height(), // + 0,
				"left": Math.ceil($of.innerWidth()/2) -
					Math.ceil($this.innerWidth()/2)
			};
		});

		// return jQuery object
		return $this;
	};

	/**
	 * Position an object in relation to the right side of another object.
	 *
	 * Options = {
	 *   extraLeft: extra pixel size (int) to add to the left value
	 *   extraTop: extra pixel size (int) to add to the top value
	 * }
	 *
	 * @param jQuery $of the object to position in relation to
	 * @return jQuery object
	 * @author Tim Baumgard
	 */
	jQuery.fn.positionRightOf = function($of, options){
		// save this value
		var $this = this;

		reposition($of, $this, options, function(){
			// return deltas
			return {
				"top": Math.ceil($of.innerHeight()/2) -
					Math.ceil($this.innerHeight()/2),
				"left": $of.width() // + 0
			};
		});

		// return jQuery object
		return $this;
	};

})(jQuery);

(function($){

	/**
	 * Preload each image given by 1 or more path plus file name.
	 *
	 * @param string arguments 1 or more image path/file names
	 * @return jQuery object
	 */
	jQuery.preload = function() {
		// preload each image
		for (var i = 0; i < arguments.length; i++) {
			$("<img>").attr("src", arguments[i]).remove();
		}

		// return jQuery object
		return jQuery;
	};

})(jQuery);

//jQuery.stripTagsAttributes
(function($) {

	/**
	 * Strips a string of any tags and attributes that are not provided as
	 * exceptions. Stripping of tags or attributes can be disabled by options.
	 * Uses the \f (form feed) character as a token, so this will fail in the
	 * unlikely event that someone manages to get a \f character into the input
	 * string to this function.
	 *
	 * Options are:
	 *     "stripTags" => set to false to disable tag stripping
	 *     "stripAttributes" => set to false to disable attribute stripping
	 *     "tags" => string of allowed tags, whitespace delimited (e.g., "a b i")
	 *	   "attributes" => string of allowed attributes, whitespace delimited (e.g., "href target")
	 *
	 * @param string string string to parse
	 * @param object options (see above)
	 * @return string the parsed string
	 * @author Tim Baumgard
	 */
	jQuery.stripTagsAttributes = function(string, options) {
		// options
		options = $.extend({
			"stripTags": true,
			"stripAttributes": true,
			"tags": "",
			"attributes": ""
		}, options);

		var regExp = null;

		// phase 1: strip invalid tags if necessary
		if (options.stripTags) {
			// remove bad chars, trim, and replace whitespace with "|"
			if ("string" != typeof options.tags) {  options.tags = "";  }
			var tags = options.tags.replace(/[^a-zA-z0-9 ]/gi, "")
					.replace(/^\s+|\s+$/g, "").replace(/\s+/gi, "|");

			// escape all allowed tags if there are any to allow
			if (tags.length > 0) {
				regExp = new RegExp("<\\s*("+tags+")(\\s[^>]*[^>\/])?"+
					"(\\s*(\/))?\\s*>|<\\s*(\/)\\s*("+tags+")[^>]*>", "ig");
				string = string.replace(regExp, "\f$1$2$4$5$6\f");
			}

			// remove all other tags and then unescape allowed tags
			string = string.replace(/<[^>]*>/ig, "").replace(/\f([^\f]*)\f/gi, "<$1>");
		}

		// phase 2: strip attributes if necessary
		if (options.stripAttributes) {
			// remove bad chars, trim, and split by whitespace
			if ("string" != typeof options.attributes) {  options.attributes = "";  }
			var attributes = options.attributes.replace(/[^a-zA-z0-9 ]/gi, "")
					.replace(/^\s+|\s+$/g, "").split(/[\s]+/);

			// move all of the attributes into separate contexts for validation
			string = string.replace(/<\s*([A-Za-z0-9]+)\s+([^>]*[^>\/])(\/)?\s*>/ig,
				"<$1$3>\f$2\f");

			// extract each allowed attribute from its context
			for (var i = 0; i <= attributes.length &&
				"undefined" != typeof attributes[i] &&
				attributes[i].length > 0; i++) {
				regExp = new RegExp("<([A-Za-z0-9]+)(\\s[^>]*[^>\/])?(\/)?>"+
					"\\f([^\\f]*\\s*)"+attributes[i]+"=(\"[^\"]*\"|'[^']*')"+
					"([^\\f]*\\s*)\\f", "ig");
				string = string.replace(regExp, "<$1$2 "+attributes[i]+
					"=$5$3>\f$4$6\f");
			}

			// destroy all the contexts created (thus deleting invalid) and make
			// well-formed singleton tags
			string = string.replace(/\f[^\f]*\f/ig, "").replace(/<([^>]+)\/>/gi,
				"<$1 />");
		}

		// return the string
		return string;
	};

})(jQuery);

/**
 * Expose the mouseenter and mouseleave methods of the jQuery object.
 */
(function($) {

	/**
	 * Execute a function, if given, whenever the cursor goes over the element, but
	 * do not bubble up child events. If called without giving a function, current
	 * mouseenter functions will be executed.
	 *
	 * @param function fn function to execute on mouseenter
	 */
	jQuery.fn.mouseenter = function(fn) {
		return this[ fn ? "bind" : "trigger" ]( "mouseenter", fn );
	};

	/**
	 * Execute a function, if given, whenever the cursor moves out from the element, but
	 * do not bubble up child events. If called without giving a function, current
	 * mouseleave functions will be executed.
	 *
	 * @param function fn function to execute on mouseleave
	 */
	jQuery.fn.mouseleave = function(fn) {
		return this[ fn ? "bind" : "trigger" ]( "mouseleave", fn );
	};

})(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 );
