
/*--------------------------------------------------------------------------*
 *  
 *  EventManager jQuery Plugin beta1
 *  
 *  MIT-style license. 
 *  
 *  December 8th , 2009 Written by Koji Kimura @ STARRYWORKS inc. http://www.starryworks.co.jp/
 *  
 *--------------------------------------------------------------------------*/


(function(){
	
	//stopEventsが２回連続実行されると空になってしまうのを防ぐためのフラグ
	var stopped = false;
	
	/*--------------------------------------------------
	* 関数
	--------------------------------------------------*/
	
	//配列内に値が存在するかチェック
	function containsInArray( i_array, i_value ) {
		for(var i in i_array){
			if( i_array.hasOwnProperty(i) && i_array[i] === i_value ) return true;
	    }
	    return false
	}
	
	//配列やスペース区切りの文字列などで渡される引数を配列に変換
	function splitArgument( i_arg ) {
		if ( typeof(i_arg) == "array" ) return i_arg;
		return ( typeof(i_arg) == "string" ) ? (i_arg+"").split(/ ,/) : [];
	}
	
	//オブジェクトのインデックスを配列に
	function getKeysInObject( i_object ) {
		var returnArray = [];
		for ( var i in i_object ) returnArray.push(i);
		return returnArray;
	}
	
	//リンクを無効に
	function disableLink() { return false; }
	
	/*--------------------------------------------------
	* 引数で指定された名前とオブジェクトを元にイベントの状態を登録する
	--------------------------------------------------*/
	$.fn.registerEventSet = function( i_name, i_object ){
		
		//保存されているデータを取得
		var stat = $(this).data("eventSets");
		if ( typeof(stat) == "undefined" ) stat = {};
		
		//バインドされているイベントを取得
		stat[i_name] = i_object;
		
		//保存
		$(this).data("eventSets", stat );
		
		return $(this);
	};
	
	
	/*--------------------------------------------------
	* 引数で指定された名前で現在登録されているイベントの状態を保存する
	--------------------------------------------------*/
	$.fn.saveEventSet = function( i_name ){
		return $(this).registerEventSet( i_name, $(this).getEvents() );
	};
	
	/*--------------------------------------------------
	* 引数で指定された名前で保存されたイベントの状態をロードする
	--------------------------------------------------*/
	$.fn.loadEventSet = function( i_name ){
		
		//保存されているデータを取得
		var stat = $(this).data("eventSets");
		if ( typeof(stat) == "undefined" || typeof(stat[i_name]) == "undefined" ) return;
		
		//バインドされているイベントを全てアンバインド
		$(this).unbind();
		
		//イベントを再度バインドする
		$(this).bindEvents( stat[i_name] );
		
		return $(this);
	};
	
	
	/*--------------------------------------------------
	* 引数で指定された名前で保存されたイベントの状態のデータを削除
	--------------------------------------------------*/
	$.fn.clearEventSet = function( i_names ){
		
		//i_namesが省略された場合全て削除
		if ( !i_names ) {
			$(this).data("eventSets",{});
			return;
		}
		
		//保存されているデータを取得
		var stat = $(this).data("eventSets");
		if ( typeof(stat) == "undefined" ) return;
		
		//削除
		var names = splitArgument( i_names );
		for ( var n in names ) {
			if ( typeof(stat[n]) != "undefined" ) stat[n] = {};
		}
		
		return $(this);
	};
	
	
	/*--------------------------------------------------
	* 引数で渡されたタイプのイベントをすべて保存する
	--------------------------------------------------*/
	$.fn.saveEvents = function( i_types, i_returnIfEmpty ){
		
		//バインドされているイベントを取得
		var events = $(this).getEvents( i_types );
		
		//i_returnIfEmptyがtrueの場合はバインドされているイベントがなければ保存しない
		if ( !i_types && i_returnIfEmpty ) {
			//ハンドラがバインドされているイベントの数をカウント
			var cnt = 0;
			var type, inner_cnt, i;
			for ( type in events ) {
				inner_cnt = 0;
				for ( i in events[type] ) {
					inner_cnt++;
					break;
				}
				if ( inner_cnt ) cnt++;
			}
			if ( !cnt ) return;
		}
		
		//保存されたイベントを取得
		var savedEvents = {};
		if ( i_types ) savedEvents = $(this).data("savedEvents");
		
		//保存
		$(this).data("savedEvents", $.extend(events, savedEvents) );
		
		return $(this);
	};
	
	
	/*--------------------------------------------------
	* 引数で渡されたタイプのイベントをすべて一時停止する
	--------------------------------------------------*/
	$.fn.stopEvents = function( i_types, i_enableLink ){
		if ( i_types || !stopped ) $(this).saveEvents( i_types );
		if ( !i_types ) {
			if ( !stopped ) $(this).unbind();
			stopped = true;
			//タイプ指定がない場合、<A>が動作しないようにする
			if ( !i_enableLink ) $(this).bind("click",disableLink );
		} else {
			$(this).unbind( i_types );
		}
		return $(this);
	};
	
	/*--------------------------------------------------
	* 引数で渡されたタイプのイベントをすべて再バインドする
	--------------------------------------------------*/
	$.fn.resumeEvents = function( i_types ){
		if ( !i_types && !stopped ) {
			return $(this);
		}
		
		stopped = false;
		
		$(this).unbind("click",disableLink );
		
		var savedEvents = $(this).data("savedEvents");
		
		
		//イベントのタイプの引数チェック
		var typesArray = splitArgument( i_types );
		
		//タイプが指定された場合はtypesArrayの中にあるものだけを
		if ( typesArray.length ) {
			var _savedEvents = savedEvents;
			savedEvents = {};
			var type, i;
			for ( i in typesArray ) {
				type = typesArray[i];
				if ( typeof(_savedEvents[type]) == "undefined" ) continue;
				savedEvents[type] = _savedEvents[type];
			}
		}
		
		//イベントを再度バインドする
		$(this).bindEvents( savedEvents );
		
		return $(this);
	};
	
	/*--------------------------------------------------
	* 引数で渡されたタイプのイベントをすべてオブジェクトとして返す
	--------------------------------------------------*/
	$.fn.getEvents = function( i_types ){
		
		//バインドされているイベント
		var events = $(this).data("events");
		
		//イベントのタイプの引数チェック
		var typesArray = splitArgument( i_types );
		
		//イベントがひとつもbindされておらず、i_typesの指定がない場合
		if ( !events && !typesArray.length ) return {};
		
		//保存されたイベントを取得
		var returnObject = {};
		
		//タイプが省略された場合はeventsの中にあるもの全部
		if ( !typesArray.length ) typesArray = getKeysInObject( events );
		
		//イベントを走査
		var type, key, i;
		for ( var i in typesArray ) {
			type = typesArray[i];
			if ( typeof(events[type]) == "undefined" ) continue;
			if ( typeof(returnObject[type]) == "undefined" ) returnObject[type] = [];
			for ( key in events[type] ) {
				if ( !containsInArray(returnObject[type], events[type][key]) ) {
					returnObject[type].push( events[type][key] );
				}
			}
		}
		
		return returnObject;
	};
	
	/*--------------------------------------------------
	* オブジェクトを元にイベントをバインド
	--------------------------------------------------*/
	$.fn.bindEvents = function( i_object ) {
		var i, type;
		for ( var type in i_object ) {
			if ( typeof(i_object[type]) == "function" ) {
				$(this).bind( type, i_object[type] );
				continue;
			}
			for ( i in i_object[type] ) {
				$(this).bind( type, i_object[type][i] );
			}
		}
		return $(this);
	};
	
	/*--------------------------------------------------
	* 保存されたイベントを削除
	--------------------------------------------------*/
	$.fn.clearSavedEvents = function( i_types ) {
		
		//イベントのタイプの引数チェック
		var typesArray = splitArgument( i_types );
		
		//すべてのイベントを削除する場合
		if ( !typesArray.length ) {
			$(this).data("savedEvents", {});
			return;
		}
		
		//保存されたイベントを取得
		var savedEvents = $(this).data("savedEvents");
		if ( typeof(savedEvents) == "undefined") return;
		
		//イベントを走査
		var type, key, i;
		for ( var i in typesArray ) {
			type = typesArray[i];
			if ( typeof(savedEvents[type]) != "undefined" ) savedEvents[type] = {};
		}
		
		return $(this);
	};
	
	/*--------------------------------------------------
	* 現在イベントがストップしているかどうか : 暫定
	--------------------------------------------------*/
	$.fn.isStopped = function() {
		return stopped;
	}

})();


/*
  License:
  Jookie 1.0 jQuery Plugin

  Copyright (c) 2008 Jon Combe (http://joncom.be)

  Permission is hereby granted, free of charge, to any person
  obtaining a copy of this software and associated documentation
  files (the "Software"), to deal in the Software without
  restriction, including without limitation the rights to use,
  copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the
  Software is furnished to do so, subject to the following
  conditions:

  The above copyright notice and this permission notice shall be
  included in all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  OTHER DEALINGS IN THE SOFTWARE.
*/

(function($) {

    $.Jookie = {
        Data:         {},
        Debug:        function(a)     { Debug(a)        },
        Delete:       function(a)     { Delete(a)       },    // delete cookie
        Get:          function(a,b)   { return Get(a,b) },    // get a single value from a cookie
        Initialise:   function(a,b)   { Initialise(a,b) },
        Set:          function(a,b,c) { Set(a,b,c)      },    // set a single value to a cookie
        Unset:        function(a,b)   { Unset(a,b)      }     // remove a single value from a cookie
    }

    // PUBLIC: show debugging information
    function Debug(sName) {
        var lsRegExp = /¥+/g;
        var sJSON = unescape(String(Extract(sName)).replace(lsRegExp, " "));
        alert("Name: " + sName +
              "¥nLifespan: " + $.Jookie.Data[sName].iLifespan +
              " minutes¥nCookie Existed Prior to Init: " + $.Jookie.Data[sName].bMadeEarlier + "¥n¥n" +
              sJSON);
    }

    // PUBLIC: delete a cookie
    function Delete(sName) {
        delete $.Jookie.Data[sName];
        document.cookie = (sName + "=; expires=" + (new Date(1990, 6, 3)).toGMTString() + "; path=/");
    }

    // PRIVATE: extract the contents of a cookie
    function Extract(sName) {
        var vValue = null;
        var aContents = document.cookie.split(';');
        sName += "=";

        // loop through cookie strings
        for (var iIndex in aContents) {
            var sString = aContents[iIndex];
            while (sString.charAt(0) == " ") {
                sString = sString.substring(1, sString.length);
            }
            if (sString.indexOf(sName) == 0) {
                vValue = sString.substring(sName.length, sString.length);
                break;
            }
        }

        // return extracted value
        return vValue;
    }

    // PUBLIC: retrieve a cookie's value
    function Get(sName, sVariableName) {
        return $.Jookie.Data[sName].oValues[sVariableName];
    }

    // PUBLIC: Initialise the plugin
    function Initialise(sName, iLifespanInMinutes) {
        if (typeof $.Jookie.Data[sName] == "undefined") {
            var oRetrievedValues = {};
            var bCookieExists = false;

            // extract cookie value
            var vCookieValue = Extract(sName);
            if (vCookieValue !== null) {
                oRetrievedValues = JSON.parse( unescape(String(vCookieValue).replace(/¥+/g, " ")) );
                bCookieExists = true;
            }

            // add cookie details to object
            $.Jookie.Data[sName] = { iLifespan    : iLifespanInMinutes,
                                     bMadeEarlier : bCookieExists,
                                     oValues      : oRetrievedValues };
            Save(sName);
        }
    }

    // PRIVATE: write cookie to user's browser
    function Save(sName) {
        var sExpires = "";
        if ($.Jookie.Data[sName].iLifespan > 0) {
            var dtDate = new Date();
            dtDate.setMinutes(dtDate.getMinutes() + $.Jookie.Data[sName].iLifespan);
            sExpires = ("; expires=" + dtDate.toGMTString());
        }
        document.cookie = (sName + "=" +
                           escape(JSON.stringify($.Jookie.Data[sName].oValues)) +
                           sExpires + "; path=/");
    }

    // PUBLIC: set and save a cookie's value
    function Set(sName, sVariableName, vValue) {
        $.Jookie.Data[sName].oValues[sVariableName] = vValue;
        Save(sName);
    }

    // PUBLIC: delete a single variable from a cookie
    function Unset(sName, sVariableName) {
        delete $.Jookie.Data[sName].oValues[sVariableName];
        Save(sName);
    }

})(jQuery);

/*
    http://www.JSON.org/json2.js
    2009-09-29

    Public Domain.

    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.

    See http://www.JSON.org/js.html


    This code should be minified before deployment.
    See http://javascript.crockford.com/jsmin.html

    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
    NOT CONTROL.


    This file creates a global JSON object containing two methods: stringify
    and parse.

        JSON.stringify(value, replacer, space)
            value       any JavaScript value, usually an object or array.

            replacer    an optional parameter that determines how object
                        values are stringified for objects. It can be a
                        function or an array of strings.

            space       an optional parameter that specifies the indentation
                        of nested structures. If it is omitted, the text will
                        be packed without extra whitespace. If it is a number,
                        it will specify the number of spaces to indent at each
                        level. If it is a string (such as '\t' or '&nbsp;'),
                        it contains the characters used to indent at each level.

            This method produces a JSON text from a JavaScript value.

            When an object value is found, if the object contains a toJSON
            method, its toJSON method will be called and the result will be
            stringified. A toJSON method does not serialize: it returns the
            value represented by the name/value pair that should be serialized,
            or undefined if nothing should be serialized. The toJSON method
            will be passed the key associated with the value, and this will be
            bound to the value

            For example, this would serialize Dates as ISO strings.

                Date.prototype.toJSON = function (key) {
                    function f(n) {
                        // Format integers to have at least two digits.
                        return n < 10 ? '0' + n : n;
                    }

                    return this.getUTCFullYear()   + '-' +
                         f(this.getUTCMonth() + 1) + '-' +
                         f(this.getUTCDate())      + 'T' +
                         f(this.getUTCHours())     + ':' +
                         f(this.getUTCMinutes())   + ':' +
                         f(this.getUTCSeconds())   + 'Z';
                };

            You can provide an optional replacer method. It will be passed the
            key and value of each member, with this bound to the containing
            object. The value that is returned from your method will be
            serialized. If your method returns undefined, then the member will
            be excluded from the serialization.

            If the replacer parameter is an array of strings, then it will be
            used to select the members to be serialized. It filters the results
            such that only members with keys listed in the replacer array are
            stringified.

            Values that do not have JSON representations, such as undefined or
            functions, will not be serialized. Such values in objects will be
            dropped; in arrays they will be replaced with null. You can use
            a replacer function to replace those with JSON values.
            JSON.stringify(undefined) returns undefined.

            The optional space parameter produces a stringification of the
            value that is filled with line breaks and indentation to make it
            easier to read.

            If the space parameter is a non-empty string, then that string will
            be used for indentation. If the space parameter is a number, then
            the indentation will be that many spaces.

            Example:

            text = JSON.stringify(['e', {pluribus: 'unum'}]);
            // text is '["e",{"pluribus":"unum"}]'


            text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
            // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'

            text = JSON.stringify([new Date()], function (key, value) {
                return this[key] instanceof Date ?
                    'Date(' + this[key] + ')' : value;
            });
            // text is '["Date(---current time---)"]'


        JSON.parse(text, reviver)
            This method parses a JSON text to produce an object or array.
            It can throw a SyntaxError exception.

            The optional reviver parameter is a function that can filter and
            transform the results. It receives each of the keys and values,
            and its return value is used instead of the original value.
            If it returns what it received, then the structure is not modified.
            If it returns undefined then the member is deleted.

            Example:

            // Parse the text. Values that look like ISO date strings will
            // be converted to Date objects.

            myData = JSON.parse(text, function (key, value) {
                var a;
                if (typeof value === 'string') {
                    a =
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
                    if (a) {
                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
                            +a[5], +a[6]));
                    }
                }
                return value;
            });

            myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
                var d;
                if (typeof value === 'string' &&
                        value.slice(0, 5) === 'Date(' &&
                        value.slice(-1) === ')') {
                    d = new Date(value.slice(5, -1));
                    if (d) {
                        return d;
                    }
                }
                return value;
            });


    This is a reference implementation. You are free to copy, modify, or
    redistribute.
*/

/*jslint evil: true, strict: false */

/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
    call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
    getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
    lastIndex, length, parse, prototype, push, replace, slice, stringify,
    test, toJSON, toString, valueOf
*/


// Create a JSON object only if one does not already exist. We create the
// methods in a closure to avoid creating global variables.

if (!this.JSON) {
    this.JSON = {};
}

(function () {

    function f(n) {
        // Format integers to have at least two digits.
        return n < 10 ? '0' + n : n;
    }

    if (typeof Date.prototype.toJSON !== 'function') {

        Date.prototype.toJSON = function (key) {

            return isFinite(this.valueOf()) ?
                   this.getUTCFullYear()   + '-' +
                 f(this.getUTCMonth() + 1) + '-' +
                 f(this.getUTCDate())      + 'T' +
                 f(this.getUTCHours())     + ':' +
                 f(this.getUTCMinutes())   + ':' +
                 f(this.getUTCSeconds())   + 'Z' : null;
        };

        String.prototype.toJSON =
        Number.prototype.toJSON =
        Boolean.prototype.toJSON = function (key) {
            return this.valueOf();
        };
    }

    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        gap,
        indent,
        meta = {    // table of character substitutions
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"' : '\\"',
            '\\': '\\\\'
        },
        rep;


    function quote(string) {

// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.

        escapable.lastIndex = 0;
        return escapable.test(string) ?
            '"' + string.replace(escapable, function (a) {
                var c = meta[a];
                return typeof c === 'string' ? c :
                    '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
            }) + '"' :
            '"' + string + '"';
    }


    function str(key, holder) {

// Produce a string from holder[key].

        var i,          // The loop counter.
            k,          // The member key.
            v,          // The member value.
            length,
            mind = gap,
            partial,
            value = holder[key];

// If the value has a toJSON method, call it to obtain a replacement value.

        if (value && typeof value === 'object' &&
                typeof value.toJSON === 'function') {
            value = value.toJSON(key);
        }

// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.

        if (typeof rep === 'function') {
            value = rep.call(holder, key, value);
        }

// What happens next depends on the value's type.

        switch (typeof value) {
        case 'string':
            return quote(value);

        case 'number':

// JSON numbers must be finite. Encode non-finite numbers as null.

            return isFinite(value) ? String(value) : 'null';

        case 'boolean':
        case 'null':

// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.

            return String(value);

// If the type is 'object', we might be dealing with an object or an array or
// null.

        case 'object':

// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.

            if (!value) {
                return 'null';
            }

// Make an array to hold the partial results of stringifying this object value.

            gap += indent;
            partial = [];

// Is the value an array?

            if (Object.prototype.toString.apply(value) === '[object Array]') {

// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.

                length = value.length;
                for (i = 0; i < length; i += 1) {
                    partial[i] = str(i, value) || 'null';
                }

// Join all of the elements together, separated with commas, and wrap them in
// brackets.

                v = partial.length === 0 ? '[]' :
                    gap ? '[\n' + gap +
                            partial.join(',\n' + gap) + '\n' +
                                mind + ']' :
                          '[' + partial.join(',') + ']';
                gap = mind;
                return v;
            }

// If the replacer is an array, use it to select the members to be stringified.

            if (rep && typeof rep === 'object') {
                length = rep.length;
                for (i = 0; i < length; i += 1) {
                    k = rep[i];
                    if (typeof k === 'string') {
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
                        }
                    }
                }
            } else {

// Otherwise, iterate through all of the keys in the object.

                for (k in value) {
                    if (Object.hasOwnProperty.call(value, k)) {
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
                        }
                    }
                }
            }

// Join all of the member texts together, separated with commas,
// and wrap them in braces.

            v = partial.length === 0 ? '{}' :
                gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
                        mind + '}' : '{' + partial.join(',') + '}';
            gap = mind;
            return v;
        }
    }

// If the JSON object does not yet have a stringify method, give it one.

    if (typeof JSON.stringify !== 'function') {
        JSON.stringify = function (value, replacer, space) {

// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.

            var i;
            gap = '';
            indent = '';

// If the space parameter is a number, make an indent string containing that
// many spaces.

            if (typeof space === 'number') {
                for (i = 0; i < space; i += 1) {
                    indent += ' ';
                }

// If the space parameter is a string, it will be used as the indent string.

            } else if (typeof space === 'string') {
                indent = space;
            }

// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.

            rep = replacer;
            if (replacer && typeof replacer !== 'function' &&
                    (typeof replacer !== 'object' ||
                     typeof replacer.length !== 'number')) {
                throw new Error('JSON.stringify');
            }

// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.

            return str('', {'': value});
        };
    }


// If the JSON object does not yet have a parse method, give it one.

    if (typeof JSON.parse !== 'function') {
        JSON.parse = function (text, reviver) {

// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.

            var j;

            function walk(holder, key) {

// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.

                var k, v, value = holder[key];
                if (value && typeof value === 'object') {
                    for (k in value) {
                        if (Object.hasOwnProperty.call(value, k)) {
                            v = walk(value, k);
                            if (v !== undefined) {
                                value[k] = v;
                            } else {
                                delete value[k];
                            }
                        }
                    }
                }
                return reviver.call(holder, key, value);
            }


// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.

            cx.lastIndex = 0;
            if (cx.test(text)) {
                text = text.replace(cx, function (a) {
                    return '\\u' +
                        ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
                });
            }

// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.

// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.

            if (/^[\],:{}\s]*$/.
test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {

// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.

                j = eval('(' + text + ')');

// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.

                return typeof reviver === 'function' ?
                    walk({'': j}, '') : j;
            }

// If the text is not JSON parseable, then a SyntaxError is thrown.

            throw new SyntaxError('JSON.parse');
        };
    }
}());


/*
 * jQuery Easing v1.3 - http://gsgd.co.uk/sandbox/jquery/easing/
 *
 * Uses the built in easing capabilities added In jQuery 1.1
 * to offer multiple easing options
 *
 * TERMS OF USE - jQuery Easing
 * 
 * Open source under the BSD License. 
 * 
 * Copyright © 2008 George McGinley Smith
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 * 
 * Redistributions of source code must retain the above copyright notice, this list of 
 * conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice, this list 
 * of conditions and the following disclaimer in the documentation and/or other materials 
 * provided with the distribution.
 * 
 * Neither the name of the author nor the names of contributors may be used to endorse 
 * or promote products derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 *  GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 
 * OF THE POSSIBILITY OF SUCH DAMAGE. 
 *
*/

// t: current time, b: begInnIng value, c: change In value, d: duration
jQuery.easing['jswing'] = jQuery.easing['swing'];

jQuery.extend( jQuery.easing,
{
	def: 'easeOutQuad',
	swing: function (x, t, b, c, d) {
		//alert(jQuery.easing.default);
		return jQuery.easing[jQuery.easing.def](x, t, b, c, d);
	},
	easeInQuad: function (x, t, b, c, d) {
		return c*(t/=d)*t + b;
	},
	easeOutQuad: function (x, t, b, c, d) {
		return -c *(t/=d)*(t-2) + b;
	},
	easeInOutQuad: function (x, t, b, c, d) {
		if ((t/=d/2) < 1) return c/2*t*t + b;
		return -c/2 * ((--t)*(t-2) - 1) + b;
	},
	easeInCubic: function (x, t, b, c, d) {
		return c*(t/=d)*t*t + b;
	},
	easeOutCubic: function (x, t, b, c, d) {
		return c*((t=t/d-1)*t*t + 1) + b;
	},
	easeInOutCubic: function (x, t, b, c, d) {
		if ((t/=d/2) < 1) return c/2*t*t*t + b;
		return c/2*((t-=2)*t*t + 2) + b;
	},
	easeInQuart: function (x, t, b, c, d) {
		return c*(t/=d)*t*t*t + b;
	},
	easeOutQuart: function (x, t, b, c, d) {
		return -c * ((t=t/d-1)*t*t*t - 1) + b;
	},
	easeInOutQuart: function (x, t, b, c, d) {
		if ((t/=d/2) < 1) return c/2*t*t*t*t + b;
		return -c/2 * ((t-=2)*t*t*t - 2) + b;
	},
	easeInQuint: function (x, t, b, c, d) {
		return c*(t/=d)*t*t*t*t + b;
	},
	easeOutQuint: function (x, t, b, c, d) {
		return c*((t=t/d-1)*t*t*t*t + 1) + b;
	},
	easeInOutQuint: function (x, t, b, c, d) {
		if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b;
		return c/2*((t-=2)*t*t*t*t + 2) + b;
	},
	easeInSine: function (x, t, b, c, d) {
		return -c * Math.cos(t/d * (Math.PI/2)) + c + b;
	},
	easeOutSine: function (x, t, b, c, d) {
		return c * Math.sin(t/d * (Math.PI/2)) + b;
	},
	easeInOutSine: function (x, t, b, c, d) {
		return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b;
	},
	easeInExpo: function (x, t, b, c, d) {
		return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
	},
	easeOutExpo: function (x, t, b, c, d) {
		return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
	},
	easeInOutExpo: function (x, t, b, c, d) {
		if (t==0) return b;
		if (t==d) return b+c;
		if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
		return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
	},
	easeInCirc: function (x, t, b, c, d) {
		return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b;
	},
	easeOutCirc: function (x, t, b, c, d) {
		return c * Math.sqrt(1 - (t=t/d-1)*t) + b;
	},
	easeInOutCirc: function (x, t, b, c, d) {
		if ((t/=d/2) < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b;
		return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b;
	},
	easeInElastic: function (x, t, b, c, d) {
		var s=1.70158;var p=0;var a=c;
		if (t==0) return b;  if ((t/=d)==1) return b+c;  if (!p) p=d*.3;
		if (a < Math.abs(c)) { a=c; var s=p/4; }
		else var s = p/(2*Math.PI) * Math.asin (c/a);
		return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
	},
	easeOutElastic: function (x, t, b, c, d) {
		var s=1.70158;var p=0;var a=c;
		if (t==0) return b;  if ((t/=d)==1) return b+c;  if (!p) p=d*.3;
		if (a < Math.abs(c)) { a=c; var s=p/4; }
		else var s = p/(2*Math.PI) * Math.asin (c/a);
		return a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b;
	},
	easeInOutElastic: function (x, t, b, c, d) {
		var s=1.70158;var p=0;var a=c;
		if (t==0) return b;  if ((t/=d/2)==2) return b+c;  if (!p) p=d*(.3*1.5);
		if (a < Math.abs(c)) { a=c; var s=p/4; }
		else var s = p/(2*Math.PI) * Math.asin (c/a);
		if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
		return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )*.5 + c + b;
	},
	easeInBack: function (x, t, b, c, d, s) {
		if (s == undefined) s = 1.70158;
		return c*(t/=d)*t*((s+1)*t - s) + b;
	},
	easeOutBack: function (x, t, b, c, d, s) {
		if (s == undefined) s = 1.70158;
		return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
	},
	easeInOutBack: function (x, t, b, c, d, s) {
		if (s == undefined) s = 1.70158; 
		if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b;
		return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b;
	},
	easeInBounce: function (x, t, b, c, d) {
		return c - jQuery.easing.easeOutBounce (x, d-t, 0, c, d) + b;
	},
	easeOutBounce: function (x, t, b, c, d) {
		if ((t/=d) < (1/2.75)) {
			return c*(7.5625*t*t) + b;
		} else if (t < (2/2.75)) {
			return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b;
		} else if (t < (2.5/2.75)) {
			return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b;
		} else {
			return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b;
		}
	},
	easeInOutBounce: function (x, t, b, c, d) {
		if (t < d/2) return jQuery.easing.easeInBounce (x, t*2, 0, c, d) * .5 + b;
		return jQuery.easing.easeOutBounce (x, t*2-d, 0, c, d) * .5 + c*.5 + b;
	}
});

/*
 *
 * TERMS OF USE - EASING EQUATIONS
 * 
 * Open source under the BSD License. 
 * 
 * Copyright © 2001 Robert Penner
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 * 
 * Redistributions of source code must retain the above copyright notice, this list of 
 * conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice, this list 
 * of conditions and the following disclaimer in the documentation and/or other materials 
 * provided with the distribution.
 * 
 * Neither the name of the author nor the names of contributors may be used to endorse 
 * or promote products derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 *  GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 
 * OF THE POSSIBILITY OF SUCH DAMAGE. 
 *
 */
 
 
/* Copyright (c) 2006 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.
 * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.
 * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.
 *
 * $LastChangedDate: 2007-12-20 09:02:08 -0600 (Thu, 20 Dec 2007) $
 * $Rev: 4265 $
 *
 * Version: 3.0
 * 
 * Requires: $ 1.2.2+
 */

(function($) {

$.event.special.mousewheel = {
	setup: function() {
		var handler = $.event.special.mousewheel.handler;
		
		// Fix pageX, pageY, clientX and clientY for mozilla
		if ( $.browser.mozilla )
			$(this).bind('mousemove.mousewheel', function(event) {
				$.data(this, 'mwcursorposdata', {
					pageX: event.pageX,
					pageY: event.pageY,
					clientX: event.clientX,
					clientY: event.clientY
				});
			});
	
		if ( this.addEventListener )
			this.addEventListener( ($.browser.mozilla ? 'DOMMouseScroll' : 'mousewheel'), handler, false);
		else
			this.onmousewheel = handler;
	},
	
	teardown: function() {
		var handler = $.event.special.mousewheel.handler;
		
		$(this).unbind('mousemove.mousewheel');
		
		if ( this.removeEventListener )
			this.removeEventListener( ($.browser.mozilla ? 'DOMMouseScroll' : 'mousewheel'), handler, false);
		else
			this.onmousewheel = function(){};
		
		$.removeData(this, 'mwcursorposdata');
	},
	
	handler: function(event) {
		var args = Array.prototype.slice.call( arguments, 1 );
		
		event = $.event.fix(event || window.event);
		// Get correct pageX, pageY, clientX and clientY for mozilla
		$.extend( event, $.data(this, 'mwcursorposdata') || {} );
		var delta = 0, returnValue = true;
		
		if ( event.wheelDelta ) delta = event.wheelDelta/120;
		if ( event.detail     ) delta = -event.detail/3;
//		if ( $.browser.opera  ) delta = -event.wheelDelta;
		
		event.data  = event.data || {};
		event.type  = "mousewheel";
		
		// Add delta to the front of the arguments
		args.unshift(delta);
		// Add event to the front of the arguments
		args.unshift(event);

		return $.event.handle.apply(this, args);
	}
};

$.fn.extend({
	mousewheel: function(fn) {
		return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel");
	},
	
	unmousewheel: function(fn) {
		return this.unbind("mousewheel", fn);
	}
});

})(jQuery);


/*
 * jQuery validation plug-in 1.6
 *
 * http://bassistance.de/jquery-plugins/jquery-plugin-validation/
 * http://docs.jquery.com/Plugins/Validation
 *
 * Copyright (c) 2006 - 2008 Jörn Zaefferer
 *
 * $Id: jquery.validate.js 6403 2009-06-17 14:27:16Z joern.zaefferer $
 *
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 */

(function($) {

$.extend($.fn, {
	// http://docs.jquery.com/Plugins/Validation/validate
	validate: function( options ) {

		// if nothing is selected, return nothing; can't chain anyway
		if (!this.length) {
			options && options.debug && window.console && console.warn( "nothing selected, can't validate, returning nothing" );
			return;
		}

		// check if a validator for this form was already created
		var validator = $.data(this[0], 'validator');
		if ( validator ) {
			return validator;
		}
		
		validator = new $.validator( options, this[0] );
		$.data(this[0], 'validator', validator); 
		
		if ( validator.settings.onsubmit ) {
		
			// allow suppresing validation by adding a cancel class to the submit button
			this.find("input, button").filter(".cancel").click(function() {
				validator.cancelSubmit = true;
			});
			
			// when a submitHandler is used, capture the submitting button
			if (validator.settings.submitHandler) {
				this.find("input, button").filter(":submit").click(function() {
					validator.submitButton = this;
				});
			}
		
			// validate the form on submit
			this.submit( function( event ) {
				if ( validator.settings.debug )
					// prevent form submit to be able to see console output
					event.preventDefault();
					
				function handle() {
					if ( validator.settings.submitHandler ) {
						if (validator.submitButton) {
							// insert a hidden input as a replacement for the missing submit button
							var hidden = $("<input type='hidden'/>").attr("name", validator.submitButton.name).val(validator.submitButton.value).appendTo(validator.currentForm);
						}
						validator.settings.submitHandler.call( validator, validator.currentForm );
						if (validator.submitButton) {
							// and clean up afterwards; thanks to no-block-scope, hidden can be referenced
							hidden.remove();
						}
						return false;
					}
					return true;
				}
					
				// prevent submit for invalid forms or custom submit handlers
				if ( validator.cancelSubmit ) {
					validator.cancelSubmit = false;
					return handle();
				}
				if ( validator.form() ) {
					if ( validator.pendingRequest ) {
						validator.formSubmitted = true;
						return false;
					}
					return handle();
				} else {
					validator.focusInvalid();
					return false;
				}
			});
		}
		
		return validator;
	},
	// http://docs.jquery.com/Plugins/Validation/valid
	valid: function() {
        if ( $(this[0]).is('form')) {
            return this.validate().form();
        } else {
            var valid = true;
            var validator = $(this[0].form).validate();
            this.each(function() {
				valid &= validator.element(this);
            });
            return valid;
        }
    },
	// attributes: space seperated list of attributes to retrieve and remove
	removeAttrs: function(attributes) {
		var result = {},
			$element = this;
		$.each(attributes.split(/\s/), function(index, value) {
			result[value] = $element.attr(value);
			$element.removeAttr(value);
		});
		return result;
	},
	// http://docs.jquery.com/Plugins/Validation/rules
	rules: function(command, argument) {
		var element = this[0];
		
		if (command) {
			var settings = $.data(element.form, 'validator').settings;
			var staticRules = settings.rules;
			var existingRules = $.validator.staticRules(element);
			switch(command) {
			case "add":
				$.extend(existingRules, $.validator.normalizeRule(argument));
				staticRules[element.name] = existingRules;
				if (argument.messages)
					settings.messages[element.name] = $.extend( settings.messages[element.name], argument.messages );
				break;
			case "remove":
				if (!argument) {
					delete staticRules[element.name];
					return existingRules;
				}
				var filtered = {};
				$.each(argument.split(/\s/), function(index, method) {
					filtered[method] = existingRules[method];
					delete existingRules[method];
				});
				return filtered;
			}
		}
		
		var data = $.validator.normalizeRules(
		$.extend(
			{},
			$.validator.metadataRules(element),
			$.validator.classRules(element),
			$.validator.attributeRules(element),
			$.validator.staticRules(element)
		), element);
		
		// make sure required is at front
		if (data.required) {
			var param = data.required;
			delete data.required;
			data = $.extend({required: param}, data);
		}
		
		return data;
	}
});

// Custom selectors
$.extend($.expr[":"], {
	// http://docs.jquery.com/Plugins/Validation/blank
	blank: function(a) {return !$.trim("" + a.value);},
	// http://docs.jquery.com/Plugins/Validation/filled
	filled: function(a) {return !!$.trim("" + a.value);},
	// http://docs.jquery.com/Plugins/Validation/unchecked
	unchecked: function(a) {return !a.checked;}
});

// constructor for validator
$.validator = function( options, form ) {
	this.settings = $.extend( {}, $.validator.defaults, options );
	this.currentForm = form;
	this.init();
};

$.validator.format = function(source, params) {
	if ( arguments.length == 1 ) 
		return function() {
			var args = $.makeArray(arguments);
			args.unshift(source);
			return $.validator.format.apply( this, args );
		};
	if ( arguments.length > 2 && params.constructor != Array  ) {
		params = $.makeArray(arguments).slice(1);
	}
	if ( params.constructor != Array ) {
		params = [ params ];
	}
	$.each(params, function(i, n) {
		source = source.replace(new RegExp("\\{" + i + "\\}", "g"), n);
	});
	return source;
};

$.extend($.validator, {
	
	defaults: {
		messages: {},
		groups: {},
		rules: {},
		errorClass: "error",
		validClass: "valid",
		errorElement: "label",
		focusInvalid: true,
		errorContainer: $( [] ),
		errorLabelContainer: $( [] ),
		onsubmit: true,
		ignore: [],
		ignoreTitle: false,
		onfocusin: function(element) {
			this.lastActive = element;
				
			// hide error label and remove error class on focus if enabled
			if ( this.settings.focusCleanup && !this.blockFocusCleanup ) {
				this.settings.unhighlight && this.settings.unhighlight.call( this, element, this.settings.errorClass, this.settings.validClass );
				this.errorsFor(element).hide();
			}
		},
		onfocusout: function(element) {
			if ( !this.checkable(element) && (element.name in this.submitted || !this.optional(element)) ) {
				this.element(element);
			}
		},
		onkeyup: function(element) {
			if ( element.name in this.submitted || element == this.lastElement ) {
				this.element(element);
			}
		},
		onclick: function(element) {
			// click on selects, radiobuttons and checkboxes
			if ( element.name in this.submitted )
				this.element(element);
			// or option elements, check parent select in that case
			else if (element.parentNode.name in this.submitted)
				this.element(element.parentNode)
		},
		highlight: function( element, errorClass, validClass ) {
			$(element).addClass(errorClass).removeClass(validClass);
		},
		unhighlight: function( element, errorClass, validClass ) {
			$(element).removeClass(errorClass).addClass(validClass);
		}
	},

	// http://docs.jquery.com/Plugins/Validation/Validator/setDefaults
	setDefaults: function(settings) {
		$.extend( $.validator.defaults, settings );
	},

	messages: {
		required: "This field is required.",
		remote: "Please fix this field.",
		email: "Please enter a valid email address.",
		url: "Please enter a valid URL.",
		date: "Please enter a valid date.",
		dateISO: "Please enter a valid date (ISO).",
		number: "Please enter a valid number.",
		digits: "Please enter only digits.",
		creditcard: "Please enter a valid credit card number.",
		equalTo: "Please enter the same value again.",
		accept: "Please enter a value with a valid extension.",
		maxlength: $.validator.format("Please enter no more than {0} characters."),
		minlength: $.validator.format("Please enter at least {0} characters."),
		rangelength: $.validator.format("Please enter a value between {0} and {1} characters long."),
		range: $.validator.format("Please enter a value between {0} and {1}."),
		max: $.validator.format("Please enter a value less than or equal to {0}."),
		min: $.validator.format("Please enter a value greater than or equal to {0}.")
	},
	
	autoCreateRanges: false,
	
	prototype: {
		
		init: function() {
			this.labelContainer = $(this.settings.errorLabelContainer);
			this.errorContext = this.labelContainer.length && this.labelContainer || $(this.currentForm);
			this.containers = $(this.settings.errorContainer).add( this.settings.errorLabelContainer );
			this.submitted = {};
			this.valueCache = {};
			this.pendingRequest = 0;
			this.pending = {};
			this.invalid = {};
			this.reset();
			
			var groups = (this.groups = {});
			$.each(this.settings.groups, function(key, value) {
				$.each(value.split(/\s/), function(index, name) {
					groups[name] = key;
				});
			});
			var rules = this.settings.rules;
			$.each(rules, function(key, value) {
				rules[key] = $.validator.normalizeRule(value);
			});
			
			function delegate(event) {
				var validator = $.data(this[0].form, "validator");
				validator.settings["on" + event.type] && validator.settings["on" + event.type].call(validator, this[0] );
			}
			$(this.currentForm)
				.delegate("focusin focusout keyup", ":text, :password, :file, select, textarea", delegate)
				.delegate("click", ":radio, :checkbox, select, option", delegate);

			if (this.settings.invalidHandler)
				$(this.currentForm).bind("invalid-form.validate", this.settings.invalidHandler);
		},

		// http://docs.jquery.com/Plugins/Validation/Validator/form
		form: function() {
			this.checkForm();
			$.extend(this.submitted, this.errorMap);
			this.invalid = $.extend({}, this.errorMap);
			if (!this.valid())
				$(this.currentForm).triggerHandler("invalid-form", [this]);
			this.showErrors();
			return this.valid();
		},
		
		checkForm: function() {
			this.prepareForm();
			for ( var i = 0, elements = (this.currentElements = this.elements()); elements[i]; i++ ) {
				this.check( elements[i] );
			}
			return this.valid(); 
		},
		
		// http://docs.jquery.com/Plugins/Validation/Validator/element
		element: function( element ) {
			element = this.clean( element );
			this.lastElement = element;
			this.prepareElement( element );
			this.currentElements = $(element);
			var result = this.check( element );
			if ( result ) {
				delete this.invalid[element.name];
			} else {
				this.invalid[element.name] = true;
			}
			if ( !this.numberOfInvalids() ) {
				// Hide error containers on last error
				this.toHide = this.toHide.add( this.containers );
			}
			this.showErrors();
			return result;
		},

		// http://docs.jquery.com/Plugins/Validation/Validator/showErrors
		showErrors: function(errors) {
			if(errors) {
				// add items to error list and map
				$.extend( this.errorMap, errors );
				this.errorList = [];
				for ( var name in errors ) {
					this.errorList.push({
						message: errors[name],
						element: this.findByName(name)[0]
					});
				}
				// remove items from success list
				this.successList = $.grep( this.successList, function(element) {
					return !(element.name in errors);
				});
			}
			this.settings.showErrors
				? this.settings.showErrors.call( this, this.errorMap, this.errorList )
				: this.defaultShowErrors();
		},
		
		// http://docs.jquery.com/Plugins/Validation/Validator/resetForm
		resetForm: function() {
			if ( $.fn.resetForm )
				$( this.currentForm ).resetForm();
			this.submitted = {};
			this.prepareForm();
			this.hideErrors();
			this.elements().removeClass( this.settings.errorClass );
		},
		
		numberOfInvalids: function() {
			return this.objectLength(this.invalid);
		},
		
		objectLength: function( obj ) {
			var count = 0;
			for ( var i in obj )
				count++;
			return count;
		},
		
		hideErrors: function() {
			this.addWrapper( this.toHide ).hide();
		},
		
		valid: function() {
			return this.size() == 0;
		},
		
		size: function() {
			return this.errorList.length;
		},
		
		focusInvalid: function() {
			if( this.settings.focusInvalid ) {
				try {
					$(this.findLastActive() || this.errorList.length && this.errorList[0].element || []).filter(":visible").focus();
				} catch(e) {
					// ignore IE throwing errors when focusing hidden elements
				}
			}
		},
		
		findLastActive: function() {
			var lastActive = this.lastActive;
			return lastActive && $.grep(this.errorList, function(n) {
				return n.element.name == lastActive.name;
			}).length == 1 && lastActive;
		},
		
		elements: function() {
			var validator = this,
				rulesCache = {};
			
			// select all valid inputs inside the form (no submit or reset buttons)
			// workaround $Query([]).add until http://dev.jquery.com/ticket/2114 is solved
			return $([]).add(this.currentForm.elements)
			.filter(":input")
			.not(":submit, :reset, :image, [disabled]")
			.not( this.settings.ignore )
			.filter(function() {
				!this.name && validator.settings.debug && window.console && console.error( "%o has no name assigned", this);
			
				// select only the first element for each name, and only those with rules specified
				if ( this.name in rulesCache || !validator.objectLength($(this).rules()) )
					return false;
				
				rulesCache[this.name] = true;
				return true;
			});
		},
		
		clean: function( selector ) {
			return $( selector )[0];
		},
		
		errors: function() {
			return $( this.settings.errorElement + "." + this.settings.errorClass, this.errorContext );
		},
		
		reset: function() {
			this.successList = [];
			this.errorList = [];
			this.errorMap = {};
			this.toShow = $([]);
			this.toHide = $([]);
			this.currentElements = $([]);
		},
		
		prepareForm: function() {
			this.reset();
			this.toHide = this.errors().add( this.containers );
		},
		
		prepareElement: function( element ) {
			this.reset();
			this.toHide = this.errorsFor(element);
		},
	
		check: function( element ) {
			element = this.clean( element );
			
			// if radio/checkbox, validate first element in group instead
			if (this.checkable(element)) {
				element = this.findByName( element.name )[0];
			}
			
			var rules = $(element).rules();
			var dependencyMismatch = false;
			for( method in rules ) {
				var rule = { method: method, parameters: rules[method] };
				try {
					var result = $.validator.methods[method].call( this, element.value.replace(/\r/g, ""), element, rule.parameters );
					
					// if a method indicates that the field is optional and therefore valid,
					// don't mark it as valid when there are no other rules
					if ( result == "dependency-mismatch" ) {
						dependencyMismatch = true;
						continue;
					}
					dependencyMismatch = false;
					
					if ( result == "pending" ) {
						this.toHide = this.toHide.not( this.errorsFor(element) );
						return;
					}
					
					if( !result ) {
						this.formatAndAdd( element, rule );
						return false;
					}
				} catch(e) {
					this.settings.debug && window.console && console.log("exception occured when checking element " + element.id
						 + ", check the '" + rule.method + "' method", e);
					throw e;
				}
			}
			if (dependencyMismatch)
				return;
			if ( this.objectLength(rules) )
				this.successList.push(element);
			return true;
		},
		
		// return the custom message for the given element and validation method
		// specified in the element's "messages" metadata
		customMetaMessage: function(element, method) {
			if (!$.metadata)
				return;
			
			var meta = this.settings.meta
				? $(element).metadata()[this.settings.meta]
				: $(element).metadata();
			
			return meta && meta.messages && meta.messages[method];
		},
		
		// return the custom message for the given element name and validation method
		customMessage: function( name, method ) {
			var m = this.settings.messages[name];
			return m && (m.constructor == String
				? m
				: m[method]);
		},
		
		// return the first defined argument, allowing empty strings
		findDefined: function() {
			for(var i = 0; i < arguments.length; i++) {
				if (arguments[i] !== undefined)
					return arguments[i];
			}
			return undefined;
		},
		
		defaultMessage: function( element, method) {
			return this.findDefined(
				this.customMessage( element.name, method ),
				this.customMetaMessage( element, method ),
				// title is never undefined, so handle empty string as undefined
				!this.settings.ignoreTitle && element.title || undefined,
				$.validator.messages[method],
				"<strong>Warning: No message defined for " + element.name + "</strong>"
			);
		},
		
		formatAndAdd: function( element, rule ) {
			var message = this.defaultMessage( element, rule.method ),
				theregex = /\$?\{(\d+)\}/g;
			if ( typeof message == "function" ) {
				message = message.call(this, rule.parameters, element);
			} else if (theregex.test(message)) {
				message = jQuery.format(message.replace(theregex, '{$1}'), rule.parameters);
			}			
			this.errorList.push({
				message: message,
				element: element
			});
			
			this.errorMap[element.name] = message;
			this.submitted[element.name] = message;
		},
		
		addWrapper: function(toToggle) {
			if ( this.settings.wrapper )
				toToggle = toToggle.add( toToggle.parent( this.settings.wrapper ) );
			return toToggle;
		},
		
		defaultShowErrors: function() {
			for ( var i = 0; this.errorList[i]; i++ ) {
				var error = this.errorList[i];
				this.settings.highlight && this.settings.highlight.call( this, error.element, this.settings.errorClass, this.settings.validClass );
				this.showLabel( error.element, error.message );
			}
			if( this.errorList.length ) {
				this.toShow = this.toShow.add( this.containers );
			}
			if (this.settings.success) {
				for ( var i = 0; this.successList[i]; i++ ) {
					this.showLabel( this.successList[i] );
				}
			}
			if (this.settings.unhighlight) {
				for ( var i = 0, elements = this.validElements(); elements[i]; i++ ) {
					this.settings.unhighlight.call( this, elements[i], this.settings.errorClass, this.settings.validClass );
				}
			}
			this.toHide = this.toHide.not( this.toShow );
			this.hideErrors();
			this.addWrapper( this.toShow ).show();
		},
		
		validElements: function() {
			return this.currentElements.not(this.invalidElements());
		},
		
		invalidElements: function() {
			return $(this.errorList).map(function() {
				return this.element;
			});
		},
		
		showLabel: function(element, message) {
			var label = this.errorsFor( element );
			if ( label.length ) {
				// refresh error/success class
				label.removeClass().addClass( this.settings.errorClass );
			
				// check if we have a generated label, replace the message then
				label.attr("generated") && label.html(message);
			} else {
				// create label
				label = $("<" + this.settings.errorElement + "/>")
					.attr({"for":  this.idOrName(element), generated: true})
					.addClass(this.settings.errorClass)
					.html(message || "");
				if ( this.settings.wrapper ) {
					// make sure the element is visible, even in IE
					// actually showing the wrapped element is handled elsewhere
					label = label.hide().show().wrap("<" + this.settings.wrapper + "/>").parent();
				}
				if ( !this.labelContainer.append(label).length )
					this.settings.errorPlacement
						? this.settings.errorPlacement(label, $(element) )
						: label.insertAfter(element);
			}
			if ( !message && this.settings.success ) {
				label.text("");
				typeof this.settings.success == "string"
					? label.addClass( this.settings.success )
					: this.settings.success( label );
			}
			this.toShow = this.toShow.add(label);
		},
		
		errorsFor: function(element) {
			var name = this.idOrName(element);
    		return this.errors().filter(function() {
				return $(this).attr('for') == name
			});
		},
		
		idOrName: function(element) {
			return this.groups[element.name] || (this.checkable(element) ? element.name : element.id || element.name);
		},

		checkable: function( element ) {
			return /radio|checkbox/i.test(element.type);
		},
		
		findByName: function( name ) {
			// select by name and filter by form for performance over form.find("[name=...]")
			var form = this.currentForm;
			return $(document.getElementsByName(name)).map(function(index, element) {
				return element.form == form && element.name == name && element  || null;
			});
		},
		
		getLength: function(value, element) {
			switch( element.nodeName.toLowerCase() ) {
			case 'select':
				return $("option:selected", element).length;
			case 'input':
				if( this.checkable( element) )
					return this.findByName(element.name).filter(':checked').length;
			}
			return value.length;
		},
	
		depend: function(param, element) {
			return this.dependTypes[typeof param]
				? this.dependTypes[typeof param](param, element)
				: true;
		},
	
		dependTypes: {
			"boolean": function(param, element) {
				return param;
			},
			"string": function(param, element) {
				return !!$(param, element.form).length;
			},
			"function": function(param, element) {
				return param(element);
			}
		},
		
		optional: function(element) {
			return !$.validator.methods.required.call(this, $.trim(element.value), element) && "dependency-mismatch";
		},
		
		startRequest: function(element) {
			if (!this.pending[element.name]) {
				this.pendingRequest++;
				this.pending[element.name] = true;
			}
		},
		
		stopRequest: function(element, valid) {
			this.pendingRequest--;
			// sometimes synchronization fails, make sure pendingRequest is never < 0
			if (this.pendingRequest < 0)
				this.pendingRequest = 0;
			delete this.pending[element.name];
			if ( valid && this.pendingRequest == 0 && this.formSubmitted && this.form() ) {
				$(this.currentForm).submit();
				this.formSubmitted = false;
			} else if (!valid && this.pendingRequest == 0 && this.formSubmitted) {
				$(this.currentForm).triggerHandler("invalid-form", [this]);
				this.formSubmitted = false;
			}
		},
		
		previousValue: function(element) {
			return $.data(element, "previousValue") || $.data(element, "previousValue", {
				old: null,
				valid: true,
				message: this.defaultMessage( element, "remote" )
			});
		}
		
	},
	
	classRuleSettings: {
		required: {required: true},
		email: {email: true},
		url: {url: true},
		date: {date: true},
		dateISO: {dateISO: true},
		dateDE: {dateDE: true},
		number: {number: true},
		numberDE: {numberDE: true},
		digits: {digits: true},
		creditcard: {creditcard: true}
	},
	
	addClassRules: function(className, rules) {
		className.constructor == String ?
			this.classRuleSettings[className] = rules :
			$.extend(this.classRuleSettings, className);
	},
	
	classRules: function(element) {
		var rules = {};
		var classes = $(element).attr('class');
		classes && $.each(classes.split(' '), function() {
			if (this in $.validator.classRuleSettings) {
				$.extend(rules, $.validator.classRuleSettings[this]);
			}
		});
		return rules;
	},
	
	attributeRules: function(element) {
		var rules = {};
		var $element = $(element);
		
		for (method in $.validator.methods) {
			var value = $element.attr(method);
			if (value) {
				rules[method] = value;
			}
		}
		
		// maxlength may be returned as -1, 2147483647 (IE) and 524288 (safari) for text inputs
		if (rules.maxlength && /-1|2147483647|524288/.test(rules.maxlength)) {
			delete rules.maxlength;
		}
		
		return rules;
	},
	
	metadataRules: function(element) {
		if (!$.metadata) return {};
		
		var meta = $.data(element.form, 'validator').settings.meta;
		return meta ?
			$(element).metadata()[meta] :
			$(element).metadata();
	},
	
	staticRules: function(element) {
		var rules = {};
		var validator = $.data(element.form, 'validator');
		if (validator.settings.rules) {
			rules = $.validator.normalizeRule(validator.settings.rules[element.name]) || {};
		}
		return rules;
	},
	
	normalizeRules: function(rules, element) {
		// handle dependency check
		$.each(rules, function(prop, val) {
			// ignore rule when param is explicitly false, eg. required:false
			if (val === false) {
				delete rules[prop];
				return;
			}
			if (val.param || val.depends) {
				var keepRule = true;
				switch (typeof val.depends) {
					case "string":
						keepRule = !!$(val.depends, element.form).length;
						break;
					case "function":
						keepRule = val.depends.call(element, element);
						break;
				}
				if (keepRule) {
					rules[prop] = val.param !== undefined ? val.param : true;
				} else {
					delete rules[prop];
				}
			}
		});
		
		// evaluate parameters
		$.each(rules, function(rule, parameter) {
			rules[rule] = $.isFunction(parameter) ? parameter(element) : parameter;
		});
		
		// clean number parameters
		$.each(['minlength', 'maxlength', 'min', 'max'], function() {
			if (rules[this]) {
				rules[this] = Number(rules[this]);
			}
		});
		$.each(['rangelength', 'range'], function() {
			if (rules[this]) {
				rules[this] = [Number(rules[this][0]), Number(rules[this][1])];
			}
		});
		
		if ($.validator.autoCreateRanges) {
			// auto-create ranges
			if (rules.min && rules.max) {
				rules.range = [rules.min, rules.max];
				delete rules.min;
				delete rules.max;
			}
			if (rules.minlength && rules.maxlength) {
				rules.rangelength = [rules.minlength, rules.maxlength];
				delete rules.minlength;
				delete rules.maxlength;
			}
		}
		
		// To support custom messages in metadata ignore rule methods titled "messages"
		if (rules.messages) {
			delete rules.messages
		}
		
		return rules;
	},
	
	// Converts a simple string to a {string: true} rule, e.g., "required" to {required:true}
	normalizeRule: function(data) {
		if( typeof data == "string" ) {
			var transformed = {};
			$.each(data.split(/\s/), function() {
				transformed[this] = true;
			});
			data = transformed;
		}
		return data;
	},
	
	// http://docs.jquery.com/Plugins/Validation/Validator/addMethod
	addMethod: function(name, method, message) {
		$.validator.methods[name] = method;
		$.validator.messages[name] = message != undefined ? message : $.validator.messages[name];
		if (method.length < 3) {
			$.validator.addClassRules(name, $.validator.normalizeRule(name));
		}
	},

	methods: {

		// http://docs.jquery.com/Plugins/Validation/Methods/required
		required: function(value, element, param) {
			// check if dependency is met
			if ( !this.depend(param, element) )
				return "dependency-mismatch";
			switch( element.nodeName.toLowerCase() ) {
			case 'select':
				// could be an array for select-multiple or a string, both are fine this way
				var val = $(element).val();
				return val && val.length > 0;
			case 'input':
				if ( this.checkable(element) )
					return this.getLength(value, element) > 0;
			default:
				return $.trim(value).length > 0;
			}
		},
		
		// http://docs.jquery.com/Plugins/Validation/Methods/remote
		remote: function(value, element, param) {
			if ( this.optional(element) )
				return "dependency-mismatch";
			
			var previous = this.previousValue(element);
			if (!this.settings.messages[element.name] )
				this.settings.messages[element.name] = {};
			previous.originalMessage = this.settings.messages[element.name].remote;
			this.settings.messages[element.name].remote = previous.message;
			
			param = typeof param == "string" && {url:param} || param; 
			
			if ( previous.old !== value ) {
				previous.old = value;
				var validator = this;
				this.startRequest(element);
				var data = {};
				data[element.name] = value;
				$.ajax($.extend(true, {
					url: param,
					mode: "abort",
					port: "validate" + element.name,
					dataType: "json",
					data: data,
					success: function(response) {
						validator.settings.messages[element.name].remote = previous.originalMessage;
						var valid = response === true;
						if ( valid ) {
							var submitted = validator.formSubmitted;
							validator.prepareElement(element);
							validator.formSubmitted = submitted;
							validator.successList.push(element);
							validator.showErrors();
						} else {
							var errors = {};
							var message = (previous.message = response || validator.defaultMessage( element, "remote" ));
							errors[element.name] = $.isFunction(message) ? message(value) : message;
							validator.showErrors(errors);
						}
						previous.valid = valid;
						validator.stopRequest(element, valid);
					}
				}, param));
				return "pending";
			} else if( this.pending[element.name] ) {
				return "pending";
			}
			return previous.valid;
		},

		// http://docs.jquery.com/Plugins/Validation/Methods/minlength
		minlength: function(value, element, param) {
			return this.optional(element) || this.getLength($.trim(value), element) >= param;
		},
		
		// http://docs.jquery.com/Plugins/Validation/Methods/maxlength
		maxlength: function(value, element, param) {
			return this.optional(element) || this.getLength($.trim(value), element) <= param;
		},
		
		// http://docs.jquery.com/Plugins/Validation/Methods/rangelength
		rangelength: function(value, element, param) {
			var length = this.getLength($.trim(value), element);
			return this.optional(element) || ( length >= param[0] && length <= param[1] );
		},
		
		// http://docs.jquery.com/Plugins/Validation/Methods/min
		min: function( value, element, param ) {
			return this.optional(element) || value >= param;
		},
		
		// http://docs.jquery.com/Plugins/Validation/Methods/max
		max: function( value, element, param ) {
			return this.optional(element) || value <= param;
		},
		
		// http://docs.jquery.com/Plugins/Validation/Methods/range
		range: function( value, element, param ) {
			return this.optional(element) || ( value >= param[0] && value <= param[1] );
		},
		
		// http://docs.jquery.com/Plugins/Validation/Methods/email
		email: function(value, element) {
			// contributed by Scott Gonzalez: http://projects.scottsplayground.com/email_address_validation/
			return this.optional(element) || /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i.test(value);
		},
	
		// http://docs.jquery.com/Plugins/Validation/Methods/url
		url: function(value, element) {
			// contributed by Scott Gonzalez: http://projects.scottsplayground.com/iri/
			return this.optional(element) || /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(value);
		},
        
		// http://docs.jquery.com/Plugins/Validation/Methods/date
		date: function(value, element) {
			return this.optional(element) || !/Invalid|NaN/.test(new Date(value));
		},
	
		// http://docs.jquery.com/Plugins/Validation/Methods/dateISO
		dateISO: function(value, element) {
			return this.optional(element) || /^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(value);
		},
	
		// http://docs.jquery.com/Plugins/Validation/Methods/number
		number: function(value, element) {
			return this.optional(element) || /^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/.test(value);
		},
	
		// http://docs.jquery.com/Plugins/Validation/Methods/digits
		digits: function(value, element) {
			return this.optional(element) || /^\d+$/.test(value);
		},
		
		// http://docs.jquery.com/Plugins/Validation/Methods/creditcard
		// based on http://en.wikipedia.org/wiki/Luhn
		creditcard: function(value, element) {
			if ( this.optional(element) )
				return "dependency-mismatch";
			// accept only digits and dashes
			if (/[^0-9-]+/.test(value))
				return false;
			var nCheck = 0,
				nDigit = 0,
				bEven = false;

			value = value.replace(/\D/g, "");

			for (var n = value.length - 1; n >= 0; n--) {
				var cDigit = value.charAt(n);
				var nDigit = parseInt(cDigit, 10);
				if (bEven) {
					if ((nDigit *= 2) > 9)
						nDigit -= 9;
				}
				nCheck += nDigit;
				bEven = !bEven;
			}

			return (nCheck % 10) == 0;
		},
		
		// http://docs.jquery.com/Plugins/Validation/Methods/accept
		accept: function(value, element, param) {
			param = typeof param == "string" ? param.replace(/,/g, '|') : "png|jpe?g|gif";
			return this.optional(element) || value.match(new RegExp(".(" + param + ")$", "i")); 
		},
		
		// http://docs.jquery.com/Plugins/Validation/Methods/equalTo
		equalTo: function(value, element, param) {
			// bind to the blur event of the target in order to revalidate whenever the target field is updated
			// TODO find a way to bind the event just once, avoiding the unbind-rebind overhead
			var target = $(param).unbind(".validate-equalTo").bind("blur.validate-equalTo", function() {
				$(element).valid();
			});
			return value == target.val();
		}
		
	}
	
});

// deprecated, use $.validator.format instead
$.format = $.validator.format;

})(jQuery);

// ajax mode: abort
// usage: $.ajax({ mode: "abort"[, port: "uniqueport"]});
// if mode:"abort" is used, the previous request on that port (port can be undefined) is aborted via XMLHttpRequest.abort() 
;(function($) {
	var ajax = $.ajax;
	var pendingRequests = {};
	$.ajax = function(settings) {
		// create settings for compatibility with ajaxSetup
		settings = $.extend(settings, $.extend({}, $.ajaxSettings, settings));
		var port = settings.port;
		if (settings.mode == "abort") {
			if ( pendingRequests[port] ) {
				pendingRequests[port].abort();
			}
			return (pendingRequests[port] = ajax.apply(this, arguments));
		}
		return ajax.apply(this, arguments);
	};
})(jQuery);

// provides cross-browser focusin and focusout events
// IE has native support, in other browsers, use event caputuring (neither bubbles)

// provides delegate(type: String, delegate: Selector, handler: Callback) plugin for easier event delegation
// handler is only called when $(event.target).is(delegate), in the scope of the jquery-object for event.target 

// provides triggerEvent(type: String, target: Element) to trigger delegated events
;(function($) {
	$.each({
		focus: 'focusin',
		blur: 'focusout'	
	}, function( original, fix ){
		$.event.special[fix] = {
			setup:function() {
				if ( $.browser.msie ) return false;
				this.addEventListener( original, $.event.special[fix].handler, true );
			},
			teardown:function() {
				if ( $.browser.msie ) return false;
				this.removeEventListener( original,
				$.event.special[fix].handler, true );
			},
			handler: function(e) {
				arguments[0] = $.event.fix(e);
				arguments[0].type = fix;
				return $.event.handle.apply(this, arguments);
			}
		};
	});
	$.extend($.fn, {
		delegate: function(type, delegate, handler) {
			return this.bind(type, function(event) {
				var target = $(event.target);
				if (target.is(delegate)) {
					return handler.apply(target, arguments);
				}
			});
		},
		triggerEvent: function(type, target) {
			return this.triggerHandler(type, [$.event.fix({ type: type, target: target })]);
		}
	})
})(jQuery);


/* Copyright (c) 2009 Kelvin Luck (kelvin AT kelvinluck DOT com || http://www.kelvinluck.com)
 * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
 * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
 *
 * See http://kelvinluck.com/assets/jquery/jScrollPane/
 * $Id$
 */

/**
 * Replace the vertical scroll bars on any matched elements with a fancy
 * styleable (via CSS) version. With JS disabled the elements will
 * gracefully degrade to the browsers own implementation of overflow:auto.
 * If the mousewheel plugin has been included on the page then the scrollable areas will also
 * respond to the mouse wheel.
 *
 * @example jQuery(".scroll-pane").jScrollPane();
 *
 * @name jScrollPane
 * @type jQuery
 * @param Object        settings        hash with options, described below.
 *                                                              scrollbarWidth  -       The width of the generated scrollbar in pixels
 *                                                              scrollbarMargin -       The amount of space to leave on the side of the scrollbar in pixels
 *                                                              wheelSpeed              -       The speed the pane will scroll in response to the mouse wheel in pixels
 *                                                              showArrows              -       Whether to display arrows for the user to scroll with
 *                                                              arrowSize               -       The height of the arrow buttons if showArrows=true
 *                                                              animateTo               -       Whether to animate when calling scrollTo and scrollBy
 *                                                              dragMinHeight   -       The minimum height to allow the drag bar to be
 *                                                              dragMaxHeight   -       The maximum height to allow the drag bar to be
 *                                                              animateInterval -       The interval in milliseconds to update an animating scrollPane (default 100)
 *                                                              animateStep             -       The amount to divide the remaining scroll distance by when animating (default 3)
 *                                                              maintainPosition-       Whether you want the contents of the scroll pane to maintain it's position when you re-initialise it - so it doesn't scroll as you add more content (default true)
 *                                                              tabIndex                -       The tabindex for this jScrollPane to control when it is tabbed to when navigating via keyboard (default 0)
 *                                                              enableKeyboardNavigation - Whether to allow keyboard scrolling of this jScrollPane when it is focused (default true)
 *                                                              animateToInternalLinks - Whether the move to an internal link (e.g. when it's focused by tabbing or by a hash change in the URL) should be animated or instant (default false)
 *                                                              scrollbarOnLeft -       Display the scrollbar on the left side?  (needs stylesheet changes, see examples.html)
 *                                                              reinitialiseOnImageLoad - Whether the jScrollPane should automatically re-initialise itself when any contained images are loaded (default false)
 *                                                              topCapHeight    -       The height of the "cap" area between the top of the jScrollPane and the top of the track/ buttons
 *                                                              bottomCapHeight -       The height of the "cap" area between the bottom of the jScrollPane and the bottom of the track/ buttons
 *                                                              observeHash             -       Whether jScrollPane should attempt to automagically scroll to the correct place when an anchor inside the scrollpane is linked to (default true)
 * @return jQuery
 * @cat Plugins/jScrollPane
 * @author Kelvin Luck (kelvin AT kelvinluck DOT com || http://www.kelvinluck.com)
 */

(function($) {

$.jScrollPane = {
        active : []
};
$.fn.jScrollPane = function(settings)
{
        settings = $.extend({}, $.fn.jScrollPane.defaults, settings);

        var rf = function() { return false; };
       
        return this.each(
                function()
                {
                        var $this = $(this);
                        var paneEle = this;
                        var currentScrollPosition = 0;
                        var paneWidth;
                        var paneHeight;
                        var trackHeight;
                        var trackOffset = settings.topCapHeight;
                       
                        if ($(this).parent().is('.jScrollPaneContainer')) {
                                currentScrollPosition = settings.maintainPosition ? $this.position().top : 0;
                                var $c = $(this).parent();
                                paneWidth = $c.innerWidth();
                                paneHeight = $c.outerHeight();
                                $('>.jScrollPaneTrack, >.jScrollArrowUp, >.jScrollArrowDown, >.jScrollCap', $c).remove();
                                $this.css({'top':0});
                        } else {
                                $this.data('originalStyleTag', $this.attr('style'));
                                // Switch the element's overflow to hidden to ensure we get the size of the element without the scrollbars [http://plugins.jquery.com/node/1208]
                                $this.css('overflow', 'hidden');
                                this.originalPadding = $this.css('paddingTop') + ' ' + $this.css('paddingRight') + ' ' + $this.css('paddingBottom') + ' ' + $this.css('paddingLeft');
                                this.originalSidePaddingTotal = (parseInt($this.css('paddingLeft')) || 0) + (parseInt($this.css('paddingRight')) || 0);
                                paneWidth = $this.innerWidth();
                                paneHeight = $this.innerHeight();
                                var $container = $('<div></div>')
                                        .attr({'className':'jScrollPaneContainer'})
                                        .css(
                                                {
                                                        'height':paneHeight+'px',
                                                        'width':paneWidth+'px'
                                                }
                                        );
                                if (settings.enableKeyboardNavigation) {
                                        $container.attr(
                                                'tabindex',
                                                settings.tabIndex
                                        );
                                }
                                $this.wrap($container);
                                // deal with text size changes (if the jquery.em plugin is included)
                                // and re-initialise the scrollPane so the track maintains the
                                // correct size
                                $(document).bind(
                                        'emchange',
                                        function(e, cur, prev)
                                        {
                                                $this.jScrollPane(settings);
                                        }
                                );
                               
                        }
                        trackHeight = paneHeight;
                       
                        if (settings.reinitialiseOnImageLoad) {
                                // code inspired by jquery.onImagesLoad: http://plugins.jquery.com/project/onImagesLoad
                                // except we re-initialise the scroll pane when each image loads so that the scroll pane is always up to size...
                                // TODO: Do I even need to store it in $.data? Is a local variable here the same since I don't pass the reinitialiseOnImageLoad when I re-initialise?
                                var $imagesToLoad = $.data(paneEle, 'jScrollPaneImagesToLoad') || $('img', $this);
                                var loadedImages = [];
                               
                                if ($imagesToLoad.length) {
                                        $imagesToLoad.each(function(i, val)     {
                                                $(this).bind('load readystatechange', function() {
                                                        if($.inArray(i, loadedImages) == -1){ //don't double count images
                                                                loadedImages.push(val); //keep a record of images we've seen
                                                                $imagesToLoad = $.grep($imagesToLoad, function(n, i) {
                                                                        return n != val;
                                                                });
                                                                $.data(paneEle, 'jScrollPaneImagesToLoad', $imagesToLoad);
                                                                var s2 = $.extend(settings, {reinitialiseOnImageLoad:false});
                                                                $this.jScrollPane(s2); // re-initialise
                                                        }
                                                }).each(function(i, val) {
                                                        if(this.complete || this.complete===undefined) {
                                                                //needed for potential cached images
                                                                this.src = this.src;
                                                        }
                                                });
                                        });
                                };
                        }

                        var p = this.originalSidePaddingTotal;
                        var realPaneWidth = paneWidth - settings.scrollbarWidth - settings.scrollbarMargin - p;

                        var cssToApply = {
                                'height':'auto',
                                'width': realPaneWidth + 'px'
                        }

                        if(settings.scrollbarOnLeft) {
                                cssToApply.paddingLeft = settings.scrollbarMargin + settings.scrollbarWidth + 'px';
                        } else {
                                cssToApply.paddingRight = settings.scrollbarMargin + 'px';
                        }

                        $this.css(cssToApply);

                        var contentHeight = $this.outerHeight();
                        var percentInView = paneHeight / contentHeight;

                        if (percentInView < .99) {
                                var $container = $this.parent();
                                $container.append(
                                        $('<div></div>').addClass('jScrollCap jScrollCapTop').css({height:settings.topCapHeight}),
                                        $('<div></div>').attr({'className':'jScrollPaneTrack'}).css({'width':settings.scrollbarWidth+'px'}).append(
                                                $('<div></div>').attr({'className':'jScrollPaneDrag'}).css({'width':settings.scrollbarWidth+'px'}).append(
                                                        $('<div></div>').attr({'className':'jScrollPaneDragTop'}).css({'width':settings.scrollbarWidth+'px'}),
                                                        $('<div></div>').attr({'className':'jScrollPaneDragBottom'}).css({'width':settings.scrollbarWidth+'px'})
                                                )
                                        ),
                                        $('<div></div>').addClass('jScrollCap jScrollCapBottom').css({height:settings.bottomCapHeight})
                                );
                               
                                var $track = $('>.jScrollPaneTrack', $container);
                                var $drag = $('>.jScrollPaneTrack .jScrollPaneDrag', $container);
                               
                               
                                var currentArrowDirection;
                                var currentArrowTimerArr = [];// Array is used to store timers since they can stack up when dealing with keyboard events. This ensures all timers are cleaned up in the end, preventing an acceleration bug.
                                var currentArrowInc;
                                var whileArrowButtonDown = function()
                                {
                                        if (currentArrowInc > 4 || currentArrowInc % 4 == 0) {
                                                positionDrag(dragPosition + currentArrowDirection * mouseWheelMultiplier);
                                        }
                                        currentArrowInc++;
                                };

                                if (settings.enableKeyboardNavigation) {
                                        $container.bind(
                                                'keydown.jscrollpane',
                                                function(e)
                                                {
                                                        switch (e.keyCode) {
                                                                case 38: //up
                                                                        currentArrowDirection = -1;
                                                                        currentArrowInc = 0;
                                                                        whileArrowButtonDown();
                                                                        currentArrowTimerArr[currentArrowTimerArr.length] = setInterval(whileArrowButtonDown, 100);
                                                                        return false;
                                                                case 40: //down
                                                                        currentArrowDirection = 1;
                                                                        currentArrowInc = 0;
                                                                        whileArrowButtonDown();
                                                                        currentArrowTimerArr[currentArrowTimerArr.length] = setInterval(whileArrowButtonDown, 100);
                                                                        return false;
                                                                case 33: // page up
                                                                case 34: // page down
                                                                        // TODO
                                                                        return false;
                                                                default:
                                                        }
                                                }
                                        ).bind(
                                                'keyup.jscrollpane',
                                                function(e)
                                                {
                                                        if (e.keyCode == 38 || e.keyCode == 40) {
                                                                for (var i = 0; i < currentArrowTimerArr.length; i++) {
                                                                        clearInterval(currentArrowTimerArr[i]);
                                                                }
                                                                return false;
                                                        }
                                                }
                                        );
                                }


                                if (settings.showArrows) {
                                       
                                        var currentArrowButton;
                                        var currentArrowInterval;

                                        var onArrowMouseUp = function(event)
                                        {
                                                $('html').unbind('mouseup', onArrowMouseUp);
                                                currentArrowButton.removeClass('jScrollActiveArrowButton');
                                                clearInterval(currentArrowInterval);
                                        };
                                        var onArrowMouseDown = function() {
                                                $('html').bind('mouseup', onArrowMouseUp);
                                                currentArrowButton.addClass('jScrollActiveArrowButton');
                                                currentArrowInc = 0;
                                                whileArrowButtonDown();
                                                currentArrowInterval = setInterval(whileArrowButtonDown, 100);
                                        };
                                        $container
                                                .append(
                                                        $('<a></a>')
                                                                .attr(
                                                                        {
                                                                                'href':'javascript:;',
                                                                                'className':'jScrollArrowUp',
                                                                                'tabindex':-1
                                                                        }
                                                                )
                                                                .css(
                                                                        {
                                                                                'width':settings.scrollbarWidth+'px',
                                                                                'top':settings.topCapHeight + 'px'
                                                                        }
                                                                )
                                                                .html('Scroll up')
                                                                .bind('mousedown', function()
                                                                {
                                                                        currentArrowButton = $(this);
                                                                        currentArrowDirection = -1;
                                                                        onArrowMouseDown();
                                                                        this.blur();
                                                                        return false;
                                                                })
                                                                .bind('click', rf),
                                                        $('<a></a>')
                                                                .attr(
                                                                        {
                                                                                'href':'javascript:;',
                                                                                'className':'jScrollArrowDown',
                                                                                'tabindex':-1
                                                                        }
                                                                )
                                                                .css(
                                                                        {
                                                                                'width':settings.scrollbarWidth+'px',
                                                                                'bottom':settings.bottomCapHeight + 'px'
                                                                        }
                                                                )
                                                                .html('Scroll down')
                                                                .bind('mousedown', function()
                                                                {
                                                                        currentArrowButton = $(this);
                                                                        currentArrowDirection = 1;
                                                                        onArrowMouseDown();
                                                                        this.blur();
                                                                        return false;
                                                                })
                                                                .bind('click', rf)
                                                );
                                        var $upArrow = $('>.jScrollArrowUp', $container);
                                        var $downArrow = $('>.jScrollArrowDown', $container);
                                }
                               
                                if (settings.arrowSize) {
                                        trackHeight = paneHeight - settings.arrowSize - settings.arrowSize;
                                        trackOffset += settings.arrowSize;
                                } else if ($upArrow) {
                                        var topArrowHeight = $upArrow.height();
                                        settings.arrowSize = topArrowHeight;
                                        trackHeight = paneHeight - topArrowHeight - $downArrow.height();
                                        trackOffset += topArrowHeight;
                                }
                                trackHeight -= settings.topCapHeight + settings.bottomCapHeight;
                                $track.css({'height': trackHeight+'px', top:trackOffset+'px'})
                               
                                var $pane = $(this).css({'position':'absolute', 'overflow':'visible'});
                               
                                var currentOffset;
                                var maxY;
                                var mouseWheelMultiplier;
                                // store this in a seperate variable so we can keep track more accurately than just updating the css property..
                                var dragPosition = 0;
                                var dragMiddle = percentInView*paneHeight/2;
                               
                                // pos function borrowed from tooltip plugin and adapted...
                                var getPos = function (event, c) {
                                        var p = c == 'X' ? 'Left' : 'Top';
                                        return event['page' + c] || (event['client' + c] + (document.documentElement['scroll' + p] || document.body['scroll' + p])) || 0;
                                };
                               
                                var ignoreNativeDrag = function() {     return false; };
                               
                                var initDrag = function()
                                {
                                        ceaseAnimation();
                                        currentOffset = $drag.offset(false);
                                        currentOffset.top -= dragPosition;
                                        maxY = trackHeight - $drag[0].offsetHeight;
                                        mouseWheelMultiplier = 2 * settings.wheelSpeed * maxY / contentHeight;
                                };
                               
                                var onStartDrag = function(event)
                                {
                                        initDrag();
                                        dragMiddle = getPos(event, 'Y') - dragPosition - currentOffset.top;
                                        $('html').bind('mouseup', onStopDrag).bind('mousemove', updateScroll);
                                        if ($.browser.msie) {
                                                $('html').bind('dragstart', ignoreNativeDrag).bind('selectstart', ignoreNativeDrag);
                                        }
                                        return false;
                                };
                                var onStopDrag = function()
                                {
                                        $('html').unbind('mouseup', onStopDrag).unbind('mousemove', updateScroll);
                                        dragMiddle = percentInView*paneHeight/2;
                                        if ($.browser.msie) {
                                                $('html').unbind('dragstart', ignoreNativeDrag).unbind('selectstart', ignoreNativeDrag);
                                        }
                                };
                                var positionDrag = function(destY)
                                {
                                        $container.scrollTop(0);
                                        destY = destY < 0 ? 0 : (destY > maxY ? maxY : destY);
                                        dragPosition = destY;
                                        $drag.css({'top':destY+'px'});
                                        var p = destY / maxY;
                                        $this.data('jScrollPanePosition', (paneHeight-contentHeight)*-p);
                                        $pane.css({'top':((paneHeight-contentHeight)*p) + 'px'});
                                        $this.trigger('scroll');
                                        if (settings.showArrows) {
                                                $upArrow[destY == 0 ? 'addClass' : 'removeClass']('disabled');
                                                $downArrow[destY == maxY ? 'addClass' : 'removeClass']('disabled');
                                        }
                                };
                                var updateScroll = function(e)
                                {
                                        positionDrag(getPos(e, 'Y') - currentOffset.top - dragMiddle);
                                };
                               
                                var dragH = Math.max(Math.min(percentInView*(paneHeight-settings.arrowSize*2), settings.dragMaxHeight), settings.dragMinHeight);
                               
                                $drag.css(
                                        {'height':dragH+'px'}
                                ).bind('mousedown', onStartDrag);
                               
                                var trackScrollInterval;
                                var trackScrollInc;
                                var trackScrollMousePos;
                                var doTrackScroll = function()
                                {
                                        if (trackScrollInc > 8 || trackScrollInc%4==0) {
                                                positionDrag((dragPosition - ((dragPosition - trackScrollMousePos) / 2)));
                                        }
                                        trackScrollInc ++;
                                };
                                var onStopTrackClick = function()
                                {
                                        clearInterval(trackScrollInterval);
                                        $('html').unbind('mouseup', onStopTrackClick).unbind('mousemove', onTrackMouseMove);
                                };
                                var onTrackMouseMove = function(event)
                                {
                                        trackScrollMousePos = getPos(event, 'Y') - currentOffset.top - dragMiddle;
                                };
                                var onTrackClick = function(event)
                                {
                                        initDrag();
                                        onTrackMouseMove(event);
                                        trackScrollInc = 0;
                                        $('html').bind('mouseup', onStopTrackClick).bind('mousemove', onTrackMouseMove);
                                        trackScrollInterval = setInterval(doTrackScroll, 100);
                                        doTrackScroll();
                                        return false;
                                };
                               
                                $track.bind('mousedown', onTrackClick);
                               
                                $container.bind(
                                        'mousewheel',
                                        function (event, delta) {
                                                delta = delta || (event.wheelDelta ? event.wheelDelta / 120 : (event.detail) ?
-event.detail/3 : 0);
                                                initDrag();
                                                ceaseAnimation();
                                                var d = dragPosition;
                                                positionDrag(dragPosition - delta * mouseWheelMultiplier);
                                                var dragOccured = d != dragPosition;
                                                return !dragOccured;
                                        }
                                );


                                var _animateToPosition;
                                var _animateToInterval;
                                function animateToPosition()
                                {
                                        var diff = (_animateToPosition - dragPosition) / settings.animateStep;
                                        if (diff > 1 || diff < -1) {
                                                positionDrag(dragPosition + diff);
                                        } else {
                                                positionDrag(_animateToPosition);
                                                ceaseAnimation();
                                        }
                                }
                                var ceaseAnimation = function()
                                {
                                        if (_animateToInterval) {
                                                clearInterval(_animateToInterval);
                                                delete _animateToPosition;
                                        }
                                };
                                var scrollTo = function(pos, preventAni)
                                {
                                        if (typeof pos == "string") {
                                                $e = $(pos, $this);
                                                if (!$e.length) return;
                                                pos = $e.offset().top - $this.offset().top;
                                        }
                                        ceaseAnimation();
                                        var maxScroll = contentHeight - paneHeight;
                                        pos = pos > maxScroll ? maxScroll : pos;
                                        $this.data('jScrollPaneMaxScroll', maxScroll);
                                        var destDragPosition = pos/maxScroll * maxY;
                                        if (preventAni || !settings.animateTo) {
                                                positionDrag(destDragPosition);
                                        } else {
                                                $container.scrollTop(0);
                                                _animateToPosition = destDragPosition;
                                                _animateToInterval = setInterval(animateToPosition, settings.animateInterval);
                                        }
                                };
                                $this[0].scrollTo = scrollTo;
                               
                                $this[0].scrollBy = function(delta)
                                {
                                        var currentPos = -parseInt($pane.css('top')) || 0;
                                        scrollTo(currentPos + delta);
                                };
                               
                                initDrag();
                               
                                scrollTo(-currentScrollPosition, true);
                       
                                // Deal with it when the user tabs to a link or form element within this scrollpane
                                $('*', this).bind(
                                        'focus',
                                        function(event)
                                        {
                                                var $e = $(this);
                                               
                                                // loop through parents adding the offset top of any elements that are relatively positioned between
                                                // the focused element and the jScrollPaneContainer so we can get the true distance from the top
                                                // of the focused element to the top of the scrollpane...
                                                var eleTop = 0;
                                               
                                                while ($e[0] != $this[0]) {
                                                        eleTop += $e.position().top;
                                                        $e = $e.offsetParent();
                                                }
                                               
                                                var viewportTop = -parseInt($pane.css('top')) || 0;
                                                var maxVisibleEleTop = viewportTop + paneHeight;
                                                var eleInView = eleTop > viewportTop && eleTop < maxVisibleEleTop;
                                                if (!eleInView) {
                                                        var destPos = eleTop - settings.scrollbarMargin;
                                                        if (eleTop > viewportTop) { // element is below viewport - scroll so it is at bottom.
                                                                destPos += $(this).height() + 15 + settings.scrollbarMargin - paneHeight;
                                                        }
                                                        scrollTo(destPos);
                                                }
                                        }
                                )
                               
                               
                                if (settings.observeHash) {
                                        if (location.hash && location.hash.length > 1) {
                                                setTimeout(function(){
                                                        scrollTo(location.hash);
                                                }, $.browser.safari ? 100 : 0);
                                        }
                                       
                                        // use event delegation to listen for all clicks on links and hijack them if they are links to
                                        // anchors within our content...
                                        $(document).bind('click', function(e){
                                                $target = $(e.target);
                                                if ($target.is('a')) {
                                                        var h = $target.attr('href');
                                                        if (h && h.substr(0, 1) == '#' && h.length > 1) {
                                                                setTimeout(function(){
                                                                        scrollTo(h, !settings.animateToInternalLinks);
                                                                }, $.browser.safari ? 100 : 0);
                                                        }
                                                }
                                        });
                                }
                               
                                // Deal with dragging and selecting text to make the scrollpane scroll...
                                function onSelectScrollMouseDown(e)
                                {
                                   $(document).bind('mousemove.jScrollPaneDragging', onTextSelectionScrollMouseMove);
                                   $(document).bind('mouseup.jScrollPaneDragging',   onSelectScrollMouseUp);
                                 
                                }
                               
                                var textDragDistanceAway;
                                var textSelectionInterval;
                               
                                function onTextSelectionInterval()
                                {
                                        direction = textDragDistanceAway < 0 ? -1 : 1;
                                        $this[0].scrollBy(textDragDistanceAway / 2);
                                }

                                function clearTextSelectionInterval()
                                {
                                        if (textSelectionInterval) {
                                                clearInterval(textSelectionInterval);
                                                textSelectionInterval = undefined;
                                        }
                                }
                               
                                function onTextSelectionScrollMouseMove(e)
                                {
                                        var offset = $this.parent().offset().top;
                                        var maxOffset = offset + paneHeight;
                                        var mouseOffset = getPos(e, 'Y');
                                        textDragDistanceAway = mouseOffset < offset ? mouseOffset - offset : (mouseOffset > maxOffset ? mouseOffset - maxOffset : 0);
                                        if (textDragDistanceAway == 0) {
                                                clearTextSelectionInterval();
                                        } else {
                                                if (!textSelectionInterval) {
                                                        textSelectionInterval  = setInterval(onTextSelectionInterval, 100);
                                                }
                                        }
                                }

                                function onSelectScrollMouseUp(e)
                                {
                                   $(document)
                                          .unbind('mousemove.jScrollPaneDragging')
                                          .unbind('mouseup.jScrollPaneDragging');
                                   clearTextSelectionInterval();
                                }

                                $container.bind('mousedown.jScrollPane', onSelectScrollMouseDown);

                               
                                $.jScrollPane.active.push($this[0]);
                               
                        } else {
                                $this.css(
                                        {
                                                'height':paneHeight+'px',
                                                'width':paneWidth-this.originalSidePaddingTotal+'px',
                                                'padding':this.originalPadding
                                        }
                                );
                                $this[0].scrollTo = $this[0].scrollBy = function() {};
                                // clean up listeners
                                $this.parent().unbind('mousewheel').unbind('mousedown.jScrollPane').unbind('keydown.jscrollpane').unbind('keyup.jscrollpane');
                        }
                       
                }
        )
};

$.fn.jScrollPaneRemove = function()
{
        $(this).each(function()
        {
                $this = $(this);
                var $c = $this.parent();
                if ($c.is('.jScrollPaneContainer')) {
                        $this.css(
                                {
                                        'top':'',
                                        'height':'',
                                        'width':'',
                                        'padding':'',
                                        'overflow':'',
                                        'position':''
                                }
                        );
                        $this.attr('style', $this.data('originalStyleTag'));
                        $c.after($this).remove();
                }
        });
}

$.fn.jScrollPane.defaults = {
        scrollbarWidth : 10,
        scrollbarMargin : 5,
        wheelSpeed : 18,
        showArrows : false,
        arrowSize : 0,
        animateTo : false,
        dragMinHeight : 1,
        dragMaxHeight : 99999,
        animateInterval : 100,
        animateStep: 3,
        maintainPosition: true,
        scrollbarOnLeft: false,
        reinitialiseOnImageLoad: false,
        tabIndex : 0,
        enableKeyboardNavigation: true,
        animateToInternalLinks: false,
        topCapHeight: 0,
        bottomCapHeight: 0,
        observeHash: true
};


// clean up the scrollTo expandos
$(window)
        .bind('unload', function() {
                var els = $.jScrollPane.active;
                for (var i=0; i<els.length; i++) {
                        els[i].scrollTo = els[i].scrollBy = null;
                }
        }
);

})(jQuery);

/**
 * Cookie plugin
 *
 * Copyright (c) 2006 Klaus Hartl (stilbuero.de)
 * Dual licensed under the MIT and GPL licenses:
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl.html
 *
 */

/**
 * Create a cookie with the given name and value and other optional parameters.
 *
 * @example $.cookie('the_cookie', 'the_value');
 * @desc Set the value of a cookie.
 * @example $.cookie('the_cookie', 'the_value', { expires: 7, path: '/', domain: 'jquery.com', secure: true });
 * @desc Create a cookie with all available options.
 * @example $.cookie('the_cookie', 'the_value');
 * @desc Create a session cookie.
 * @example $.cookie('the_cookie', null);
 * @desc Delete a cookie by passing null as value. Keep in mind that you have to use the same path and domain
 *       used when the cookie was set.
 *
 * @param String name The name of the cookie.
 * @param String value The value of the cookie.
 * @param Object options An object literal containing key/value pairs to provide optional cookie attributes.
 * @option Number|Date expires Either an integer specifying the expiration date from now on in days or a Date object.
 *                             If a negative value is specified (e.g. a date in the past), the cookie will be deleted.
 *                             If set to null or omitted, the cookie will be a session cookie and will not be retained
 *                             when the the browser exits.
 * @option String path The value of the path atribute of the cookie (default: path of page that created the cookie).
 * @option String domain The value of the domain attribute of the cookie (default: domain of page that created the cookie).
 * @option Boolean secure If true, the secure attribute of the cookie will be set and the cookie transmission will
 *                        require a secure protocol (like HTTPS).
 * @type undefined
 *
 * @name $.cookie
 * @cat Plugins/Cookie
 * @author Klaus Hartl/klaus.hartl@stilbuero.de
 */

/**
 * Get the value of a cookie with the given name.
 *
 * @example $.cookie('the_cookie');
 * @desc Get the value of a cookie.
 *
 * @param String name The name of the cookie.
 * @return The value of the cookie.
 * @type String
 *
 * @name $.cookie
 * @cat Plugins/Cookie
 * @author Klaus Hartl/klaus.hartl@stilbuero.de
 */
jQuery.cookie = function(name, value, options) {
    if (typeof value != 'undefined') { // name and value given, set cookie
        options = options || {};
        if (value === null) {
            value = '';
            options.expires = -1;
        }
        var expires = '';
        if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) {
            var date;
            if (typeof options.expires == 'number') {
                date = new Date();
                date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000));
            } else {
                date = options.expires;
            }
            expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE
        }
        // CAUTION: Needed to parenthesize options.path and options.domain
        // in the following expressions, otherwise they evaluate to undefined
        // in the packed version for some reason...
        var path = options.path ? '; path=' + (options.path) : '';
        var domain = options.domain ? '; domain=' + (options.domain) : '';
        var secure = options.secure ? '; secure' : '';
        document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join('');
    } else { // only name given, get cookie
        var cookieValue = null;
        if (document.cookie && document.cookie != '') {
            var cookies = document.cookie.split(';');
            for (var i = 0; i < cookies.length; i++) {
                var cookie = jQuery.trim(cookies[i]);
                // Does this cookie string begin with the name we want?
                if (cookie.substring(0, name.length + 1) == (name + '=')) {
                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                    break;
                }
            }
        }
        return cookieValue;
    }
};



