/**
 * 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) { toggle(this, "text", a, b); };
     jQuery.fn.toggleHtml = function(a, b) { toggle(this, "html", a, b); };
     jQuery.fn.toggleVal = function(a, b) { toggle(this, "val", a, b); };

     function toggle($group, prop, a, b) {
         return $group.each(function(){
             $(this)[prop]($(this)[prop]() == a ? b : a);
         });
     }

})(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.filter("input, textarea").each(function(){
                 var $this = $(this);
                 var prevValue = $this.val();
                 var interval = null;
                 var $fn = function() {
                     var value = $this.val();
                     if (value != prevValue) {  fn($this, prevValue, value);  }
                     prevValue = value;
                 };

                 // save function for trigger action
                 var curr = ("undefined" == typeof $this.data(options.fn)
                    || $this.data(options.fn) === null)
                    ? [] : $this.data(options.fn);
                 curr[curr.length] = $fn;
                 $this.data(options.fn, curr);

                 // 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(){
                 var $this = $(this)
                 var fns = $this.data(options.fn);
                 if (fns) {
                    for (var i = 0; i < fns.length; i++) {
                        fns[i]($this, $this.val(), $this.val());
                    }
                 }
             });
         }

         // 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($) {

   /**
    * Automatically grow a text input or textarea based on its contents.
    * Requires the onvaluechange plugin.
    * Options:
    *   duration: how long the animation of autogrowing shoud take
    *   easing: the jQuery easing method
    *   checkInterval: the check interval passed to the onvaluechange plugin
    */
    jQuery.fn.autoheight = function(opts) {
        opts = $.extend({
            "duration": "fast",
            "easing": "swing",
            "checkInterval": 20
        }, opts);

        if (!opts.onComplete || !$.isFunction(opts.onComplete)) {
            opts.onComplete = function(){};
        }

        var $div = $("<div/>").css({
            "position": "absolute",
            "top": "-9999px",
            "left": "-9999px",
            "width": "100px"
        }).appendTo(document.body);

        this.filter("input, textarea").onvaluechange(function($elem, prev, curr){
            // escape tag characters so they aren't invisible within the div
            var html = $elem.val() || "&nbsp;"
            html = html.replace(/\</gi, "&lt;").replace(/\>/gi, "&gt;");

            $div.css({
                "width": $elem.width()+"px",
                "padding": $elem.css("padding"),
                "font-family": $elem.css("font-family"),
                "font-size": $elem.css("font-size")
            }).html(html);

            $elem.animate({"height": $div.height()+"px"}, opts.duration,
                opts.easing, opts.onComplete);
        }, {"checkInterval": opts.checkInterval});

        return this;
    };

   /**
    * Grow a text input or textarea based on its contents.
    * Options:
    *   duration: how long the animation of autogrowing shoud take
    *   easing: the jQuery easing method
    */
    jQuery.fn.doautoheight = function(opts) {
        opts = $.extend({
            "duration": "fast",
            "easing": "swing"
        }, opts);

        if (!opts.onComplete || !$.isFunction(opts.onComplete)) {
            opts.onComplete = function(){};
        }

        var $div = $("<div/>").css({
            "position": "absolute",
            "top": "-9999px",
            "left": "-9999px",
            "width": "100px"
        }).appendTo(document.body);

        this.filter("input, textarea").each(function(){
            $elem = $(this);

            // escape tag characters so they aren't invisible within the div
            var html = $elem.val() || "&nbsp;"
            html = html.replace(/\</gi, "&lt;").replace(/\>/gi, "&gt;");

            $div.css({
                "width": $elem.width()+"px",
                "padding": $elem.css("padding"),
                "font-family": $elem.css("font-family"),
                "font-size": $elem.css("font-size")
            }).html(html);

            $elem.animate({"height": $div.height()+"px"}, opts.duration,
                opts.easing, opts.onComplete);
        });

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