var Prototype={Version:"1.6.1",Browser:(function(){var B=navigator.userAgent;var A=Object.prototype.toString.call(window.opera)=="[object Opera]";return{IE:!!window.attachEvent&&!A,Opera:A,WebKit:B.indexOf("AppleWebKit/")>-1,Gecko:B.indexOf("Gecko")>-1&&B.indexOf("KHTML")===-1,MobileSafari:/Apple.*Mobile.*Safari/.test(B)}})(),BrowserFeatures:{XPath:!!document.evaluate,SelectorsAPI:!!document.querySelector,ElementExtensions:(function(){var A=window.Element||window.HTMLElement;return !!(A&&A.prototype)})(),SpecificElementExtensions:(function(){if(typeof window.HTMLDivElement!=="undefined"){return true}var C=document.createElement("div");var B=document.createElement("form");var A=false;if(C.__proto__&&(C.__proto__!==B.__proto__)){A=true}C=B=null;return A})()},ScriptFragment:"<script[^>]*>([\\S\\s]*?)<\/script>",JSONFilter:/^\/\*-secure-([\s\S]*)\*\/\s*$/,emptyFunction:function(){},K:function(A){return A}};if(Prototype.Browser.MobileSafari){Prototype.BrowserFeatures.SpecificElementExtensions=false}var Abstract={};var Try={these:function(){var C;for(var B=0,D=arguments.length;B<D;B++){var A=arguments[B];try{C=A();break}catch(E){}}return C}};var Class=(function(){function A(){}function B(){var G=null,F=$A(arguments);if(Object.isFunction(F[0])){G=F.shift()}function D(){this.initialize.apply(this,arguments)}Object.extend(D,Class.Methods);D.superclass=G;D.subclasses=[];if(G){A.prototype=G.prototype;D.prototype=new A;G.subclasses.push(D)}for(var E=0;E<F.length;E++){D.addMethods(F[E])}if(!D.prototype.initialize){D.prototype.initialize=Prototype.emptyFunction}D.prototype.constructor=D;return D}function C(J){var F=this.superclass&&this.superclass.prototype;var E=Object.keys(J);if(!Object.keys({toString:true}).length){if(J.toString!=Object.prototype.toString){E.push("toString")}if(J.valueOf!=Object.prototype.valueOf){E.push("valueOf")}}for(var D=0,G=E.length;D<G;D++){var I=E[D],H=J[I];if(F&&Object.isFunction(H)&&H.argumentNames().first()=="$super"){var K=H;H=(function(L){return function(){return F[L].apply(this,arguments)}})(I).wrap(K);H.valueOf=K.valueOf.bind(K);H.toString=K.toString.bind(K)}this.prototype[I]=H}return this}return{create:B,Methods:{addMethods:C}}})();(function(){var D=Object.prototype.toString;function I(Q,S){for(var R in S){Q[R]=S[R]}return Q}function L(Q){try{if(E(Q)){return"undefined"}if(Q===null){return"null"}return Q.inspect?Q.inspect():String(Q)}catch(R){if(R instanceof RangeError){return"..."}throw R}}function K(Q){var S=typeof Q;switch(S){case"undefined":case"function":case"unknown":return ;case"boolean":return Q.toString()}if(Q===null){return"null"}if(Q.toJSON){return Q.toJSON()}if(H(Q)){return }var R=[];for(var U in Q){var T=K(Q[U]);if(!E(T)){R.push(U.toJSON()+": "+T)}}return"{"+R.join(", ")+"}"}function C(Q){return $H(Q).toQueryString()}function F(Q){return Q&&Q.toHTML?Q.toHTML():String.interpret(Q)}function O(Q){var R=[];for(var S in Q){R.push(S)}return R}function M(Q){var R=[];for(var S in Q){R.push(Q[S])}return R}function J(Q){return I({},Q)}function H(Q){return !!(Q&&Q.nodeType==1)}function G(Q){return D.call(Q)=="[object Array]"}function P(Q){return Q instanceof Hash}function B(Q){return typeof Q==="function"}function A(Q){return D.call(Q)=="[object String]"}function N(Q){return D.call(Q)=="[object Number]"}function E(Q){return typeof Q==="undefined"}I(Object,{extend:I,inspect:L,toJSON:K,toQueryString:C,toHTML:F,keys:O,values:M,clone:J,isElement:H,isArray:G,isHash:P,isFunction:B,isString:A,isNumber:N,isUndefined:E})})();Object.extend(Function.prototype,(function(){var K=Array.prototype.slice;function D(O,L){var N=O.length,M=L.length;while(M--){O[N+M]=L[M]}return O}function I(M,L){M=K.call(M,0);return D(M,L)}function G(){var L=this.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1].replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g,"").replace(/\s+/g,"").split(",");return L.length==1&&!L[0]?[]:L}function H(N){if(arguments.length<2&&Object.isUndefined(arguments[0])){return this}var L=this,M=K.call(arguments,1);return function(){var O=I(M,arguments);return L.apply(N,O)}}function F(N){var L=this,M=K.call(arguments,1);return function(P){var O=D([P||window.event],M);return L.apply(N,O)}}function J(){if(!arguments.length){return this}var L=this,M=K.call(arguments,0);return function(){var N=I(M,arguments);return L.apply(this,N)}}function E(N){var L=this,M=K.call(arguments,1);N=N*1000;return window.setTimeout(function(){return L.apply(L,M)},N)}function A(){var L=D([0.01],arguments);return this.delay.apply(this,L)}function C(M){var L=this;return function(){var N=D([L.bind(this)],arguments);return M.apply(this,N)}}function B(){if(this._methodized){return this._methodized}var L=this;return this._methodized=function(){var M=D([this],arguments);return L.apply(null,M)}}return{argumentNames:G,bind:H,bindAsEventListener:F,curry:J,delay:E,defer:A,wrap:C,methodize:B}})());Date.prototype.toJSON=function(){return'"'+this.getUTCFullYear()+"-"+(this.getUTCMonth()+1).toPaddedString(2)+"-"+this.getUTCDate().toPaddedString(2)+"T"+this.getUTCHours().toPaddedString(2)+":"+this.getUTCMinutes().toPaddedString(2)+":"+this.getUTCSeconds().toPaddedString(2)+'Z"'};RegExp.prototype.match=RegExp.prototype.test;RegExp.escape=function(A){return String(A).replace(/([.*+?^=!:${}()|[\]\/\\])/g,"\\$1")};var PeriodicalExecuter=Class.create({initialize:function(B,A){this.callback=B;this.frequency=A;this.currentlyExecuting=false;this.registerCallback()},registerCallback:function(){this.timer=setInterval(this.onTimerEvent.bind(this),this.frequency*1000)},execute:function(){this.callback(this)},stop:function(){if(!this.timer){return }clearInterval(this.timer);this.timer=null},onTimerEvent:function(){if(!this.currentlyExecuting){try{this.currentlyExecuting=true;this.execute();this.currentlyExecuting=false}catch(A){this.currentlyExecuting=false;throw A}}}});Object.extend(String,{interpret:function(A){return A==null?"":String(A)},specialChar:{"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r","\\":"\\\\"}});Object.extend(String.prototype,(function(){function prepareReplacement(replacement){if(Object.isFunction(replacement)){return replacement}var template=new Template(replacement);return function(match){return template.evaluate(match)}}function gsub(pattern,replacement){var result="",source=this,match;replacement=prepareReplacement(replacement);if(Object.isString(pattern)){pattern=RegExp.escape(pattern)}if(!(pattern.length||pattern.source)){replacement=replacement("");return replacement+source.split("").join(replacement)+replacement}while(source.length>0){if(match=source.match(pattern)){result+=source.slice(0,match.index);result+=String.interpret(replacement(match));source=source.slice(match.index+match[0].length)}else{result+=source,source=""}}return result}function sub(pattern,replacement,count){replacement=prepareReplacement(replacement);count=Object.isUndefined(count)?1:count;return this.gsub(pattern,function(match){if(--count<0){return match[0]}return replacement(match)})}function scan(pattern,iterator){this.gsub(pattern,iterator);return String(this)}function truncate(length,truncation){length=length||30;truncation=Object.isUndefined(truncation)?"...":truncation;return this.length>length?this.slice(0,length-truncation.length)+truncation:String(this)}function strip(){return this.replace(/^\s+/,"").replace(/\s+$/,"")}function stripTags(){return this.replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi,"")}function stripScripts(){return this.replace(new RegExp(Prototype.ScriptFragment,"img"),"")}function extractScripts(){var matchAll=new RegExp(Prototype.ScriptFragment,"img");var matchOne=new RegExp(Prototype.ScriptFragment,"im");return(this.match(matchAll)||[]).map(function(scriptTag){return(scriptTag.match(matchOne)||["",""])[1]})}function evalScripts(){return this.extractScripts().map(function(script){return eval(script)})}function escapeHTML(){return this.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function unescapeHTML(){return this.stripTags().replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&amp;/g,"&")}function toQueryParams(separator){var match=this.strip().match(/([^?#]*)(#.*)?$/);if(!match){return{}}return match[1].split(separator||"&").inject({},function(hash,pair){if((pair=pair.split("="))[0]){var key=decodeURIComponent(pair.shift());var value=pair.length>1?pair.join("="):pair[0];if(value!=undefined){value=decodeURIComponent(value)}if(key in hash){if(!Object.isArray(hash[key])){hash[key]=[hash[key]]}hash[key].push(value)}else{hash[key]=value}}return hash})}function toArray(){return this.split("")}function succ(){return this.slice(0,this.length-1)+String.fromCharCode(this.charCodeAt(this.length-1)+1)}function times(count){return count<1?"":new Array(count+1).join(this)}function camelize(){var parts=this.split("-"),len=parts.length;if(len==1){return parts[0]}var camelized=this.charAt(0)=="-"?parts[0].charAt(0).toUpperCase()+parts[0].substring(1):parts[0];for(var i=1;i<len;i++){camelized+=parts[i].charAt(0).toUpperCase()+parts[i].substring(1)}return camelized}function capitalize(){return this.charAt(0).toUpperCase()+this.substring(1).toLowerCase()}function underscore(){return this.replace(/::/g,"/").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/([a-z\d])([A-Z])/g,"$1_$2").replace(/-/g,"_").toLowerCase()}function dasherize(){return this.replace(/_/g,"-")}function inspect(useDoubleQuotes){var escapedString=this.replace(/[\x00-\x1f\\]/g,function(character){if(character in String.specialChar){return String.specialChar[character]}return"\\u00"+character.charCodeAt().toPaddedString(2,16)});if(useDoubleQuotes){return'"'+escapedString.replace(/"/g,'\\"')+'"'}return"'"+escapedString.replace(/'/g,"\\'")+"'"}function toJSON(){return this.inspect(true)}function unfilterJSON(filter){return this.replace(filter||Prototype.JSONFilter,"$1")}function isJSON(){var str=this;if(str.blank()){return false}str=this.replace(/\\./g,"@").replace(/"[^"\\\n\r]*"/g,"");return(/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str)}function evalJSON(sanitize){var json=this.unfilterJSON();try{if(!sanitize||json.isJSON()){return eval("("+json+")")}}catch(e){}throw new SyntaxError("Badly formed JSON string: "+this.inspect())}function include(pattern){return this.indexOf(pattern)>-1}function startsWith(pattern){return this.indexOf(pattern)===0}function endsWith(pattern){var d=this.length-pattern.length;return d>=0&&this.lastIndexOf(pattern)===d}function empty(){return this==""}function blank(){return/^\s*$/.test(this)}function interpolate(object,pattern){return new Template(this,pattern).evaluate(object)}return{gsub:gsub,sub:sub,scan:scan,truncate:truncate,strip:String.prototype.trim?String.prototype.trim:strip,stripTags:stripTags,stripScripts:stripScripts,extractScripts:extractScripts,evalScripts:evalScripts,escapeHTML:escapeHTML,unescapeHTML:unescapeHTML,toQueryParams:toQueryParams,parseQuery:toQueryParams,toArray:toArray,succ:succ,times:times,camelize:camelize,capitalize:capitalize,underscore:underscore,dasherize:dasherize,inspect:inspect,toJSON:toJSON,unfilterJSON:unfilterJSON,isJSON:isJSON,evalJSON:evalJSON,include:include,startsWith:startsWith,endsWith:endsWith,empty:empty,blank:blank,interpolate:interpolate}})());var Template=Class.create({initialize:function(A,B){this.template=A.toString();this.pattern=B||Template.Pattern},evaluate:function(A){if(A&&Object.isFunction(A.toTemplateReplacements)){A=A.toTemplateReplacements()}return this.template.gsub(this.pattern,function(D){if(A==null){return(D[1]+"")}var F=D[1]||"";if(F=="\\"){return D[2]}var B=A,G=D[3];var E=/^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;D=E.exec(G);if(D==null){return F}while(D!=null){var C=D[1].startsWith("[")?D[2].replace(/\\\\]/g,"]"):D[1];B=B[C];if(null==B||""==D[3]){break}G=G.substring("["==D[3]?D[1].length:D[0].length);D=E.exec(G)}return F+String.interpret(B)})}});Template.Pattern=/(^|.|\r|\n)(#\{(.*?)\})/;var $break={};var Enumerable=(function(){function C(Y,X){var W=0;try{this._each(function(a){Y.call(X,a,W++)})}catch(Z){if(Z!=$break){throw Z}}return this}function R(Z,Y,X){var W=-Z,a=[],b=this.toArray();if(Z<1){return b}while((W+=Z)<b.length){a.push(b.slice(W,W+Z))}return a.collect(Y,X)}function B(Y,X){Y=Y||Prototype.K;var W=true;this.each(function(a,Z){W=W&&!!Y.call(X,a,Z);if(!W){throw $break}});return W}function I(Y,X){Y=Y||Prototype.K;var W=false;this.each(function(a,Z){if(W=!!Y.call(X,a,Z)){throw $break}});return W}function J(Y,X){Y=Y||Prototype.K;var W=[];this.each(function(a,Z){W.push(Y.call(X,a,Z))});return W}function T(Y,X){var W;this.each(function(a,Z){if(Y.call(X,a,Z)){W=a;throw $break}});return W}function H(Y,X){var W=[];this.each(function(a,Z){if(Y.call(X,a,Z)){W.push(a)}});return W}function G(Z,Y,X){Y=Y||Prototype.K;var W=[];if(Object.isString(Z)){Z=new RegExp(RegExp.escape(Z))}this.each(function(b,a){if(Z.match(b)){W.push(Y.call(X,b,a))}});return W}function A(W){if(Object.isFunction(this.indexOf)){if(this.indexOf(W)!=-1){return true}}var X=false;this.each(function(Y){if(Y==W){X=true;throw $break}});return X}function Q(X,W){W=Object.isUndefined(W)?null:W;return this.eachSlice(X,function(Y){while(Y.length<X){Y.push(W)}return Y})}function L(W,Y,X){this.each(function(a,Z){W=Y.call(X,W,a,Z)});return W}function V(X){var W=$A(arguments).slice(1);return this.map(function(Y){return Y[X].apply(Y,W)})}function P(Y,X){Y=Y||Prototype.K;var W;this.each(function(a,Z){a=Y.call(X,a,Z);if(W==null||a>=W){W=a}});return W}function N(Y,X){Y=Y||Prototype.K;var W;this.each(function(a,Z){a=Y.call(X,a,Z);if(W==null||a<W){W=a}});return W}function E(Z,X){Z=Z||Prototype.K;var Y=[],W=[];this.each(function(b,a){(Z.call(X,b,a)?Y:W).push(b)});return[Y,W]}function F(X){var W=[];this.each(function(Y){W.push(Y[X])});return W}function D(Y,X){var W=[];this.each(function(a,Z){if(!Y.call(X,a,Z)){W.push(a)}});return W}function M(X,W){return this.map(function(Z,Y){return{value:Z,criteria:X.call(W,Z,Y)}}).sort(function(d,c){var Z=d.criteria,Y=c.criteria;return Z<Y?-1:Z>Y?1:0}).pluck("value")}function O(){return this.map()}function S(){var X=Prototype.K,W=$A(arguments);if(Object.isFunction(W.last())){X=W.pop()}var Y=[this].concat(W).map($A);return this.map(function(a,Z){return X(Y.pluck(Z))})}function K(){return this.toArray().length}function U(){return"#<Enumerable:"+this.toArray().inspect()+">"}return{each:C,eachSlice:R,all:B,every:B,any:I,some:I,collect:J,map:J,detect:T,findAll:H,select:H,filter:H,grep:G,include:A,member:A,inGroupsOf:Q,inject:L,invoke:V,max:P,min:N,partition:E,pluck:F,reject:D,sortBy:M,toArray:O,entries:O,zip:S,size:K,inspect:U,find:T}})();function $A(C){if(!C){return[]}if("toArray" in Object(C)){return C.toArray()}var B=C.length||0,A=new Array(B);while(B--){A[B]=C[B]}return A}function $w(A){if(!Object.isString(A)){return[]}A=A.strip();return A?A.split(/\s+/):[]}Array.from=$A;(function(){var S=Array.prototype,M=S.slice,O=S.forEach;function B(W){for(var V=0,X=this.length;V<X;V++){W(this[V])}}if(!O){O=B}function L(){this.length=0;return this}function D(){return this[0]}function G(){return this[this.length-1]}function I(){return this.select(function(V){return V!=null})}function U(){return this.inject([],function(W,V){if(Object.isArray(V)){return W.concat(V.flatten())}W.push(V);return W})}function H(){var V=M.call(arguments,0);return this.select(function(W){return !V.include(W)})}function F(V){return(V!==false?this:this.toArray())._reverse()}function K(V){return this.inject([],function(Y,X,W){if(0==W||(V?Y.last()!=X:!Y.include(X))){Y.push(X)}return Y})}function P(V){return this.uniq().findAll(function(W){return V.detect(function(X){return W===X})})}function Q(){return M.call(this,0)}function J(){return this.length}function T(){return"["+this.map(Object.inspect).join(", ")+"]"}function R(){var V=[];this.each(function(W){var X=Object.toJSON(W);if(!Object.isUndefined(X)){V.push(X)}});return"["+V.join(", ")+"]"}function A(X,V){V||(V=0);var W=this.length;if(V<0){V=W+V}for(;V<W;V++){if(this[V]===X){return V}}return -1}function N(W,V){V=isNaN(V)?this.length:(V<0?this.length+V:V)+1;var X=this.slice(0,V).reverse().indexOf(W);return(X<0)?X:V-X-1}function C(){var a=M.call(this,0),Y;for(var W=0,X=arguments.length;W<X;W++){Y=arguments[W];if(Object.isArray(Y)&&!("callee" in Y)){for(var V=0,Z=Y.length;V<Z;V++){a.push(Y[V])}}else{a.push(Y)}}return a}Object.extend(S,Enumerable);if(!S._reverse){S._reverse=S.reverse}Object.extend(S,{_each:O,clear:L,first:D,last:G,compact:I,flatten:U,without:H,reverse:F,uniq:K,intersect:P,clone:Q,toArray:Q,size:J,inspect:T,toJSON:R});var E=(function(){return[].concat(arguments)[0][0]!==1})(1,2);if(E){S.concat=C}if(!S.indexOf){S.indexOf=A}if(!S.lastIndexOf){S.lastIndexOf=N}})();function $H(A){return new Hash(A)}var Hash=Class.create(Enumerable,(function(){function E(Q){this._object=Object.isHash(Q)?Q.toObject():Object.clone(Q)}function F(R){for(var Q in this._object){var S=this._object[Q],T=[Q,S];T.key=Q;T.value=S;R(T)}}function K(Q,R){return this._object[Q]=R}function C(Q){if(this._object[Q]!==Object.prototype[Q]){return this._object[Q]}}function N(Q){var R=this._object[Q];delete this._object[Q];return R}function P(){return Object.clone(this._object)}function O(){return this.pluck("key")}function M(){return this.pluck("value")}function G(R){var Q=this.detect(function(S){return S.value===R});return Q&&Q.key}function I(Q){return this.clone().update(Q)}function D(Q){return new Hash(Q).inject(this,function(R,S){R.set(S.key,S.value);return R})}function B(Q,R){if(Object.isUndefined(R)){return Q}return Q+"="+encodeURIComponent(String.interpret(R))}function A(){return this.inject([],function(S,T){var R=encodeURIComponent(T.key),Q=T.value;if(Q&&typeof Q=="object"){if(Object.isArray(Q)){return S.concat(Q.map(B.curry(R)))}}else{S.push(B(R,Q))}return S}).join("&")}function L(){return"#<Hash:{"+this.map(function(Q){return Q.map(Object.inspect).join(": ")}).join(", ")+"}>"}function J(){return Object.toJSON(this.toObject())}function H(){return new Hash(this)}return{initialize:E,_each:F,set:K,get:C,unset:N,toObject:P,toTemplateReplacements:P,keys:O,values:M,index:G,merge:I,update:D,toQueryString:A,inspect:L,toJSON:J,clone:H}})());Hash.from=$H;Object.extend(Number.prototype,(function(){function D(){return this.toPaddedString(2,16)}function E(){return this+1}function A(K,J){$R(0,this,true).each(K,J);return this}function B(L,K){var J=this.toString(K||10);return"0".times(L-J.length)+J}function F(){return isFinite(this)?this.toString():"null"}function I(){return Math.abs(this)}function H(){return Math.round(this)}function G(){return Math.ceil(this)}function C(){return Math.floor(this)}return{toColorPart:D,succ:E,times:A,toPaddedString:B,toJSON:F,abs:I,round:H,ceil:G,floor:C}})());function $R(C,A,B){return new ObjectRange(C,A,B)}var ObjectRange=Class.create(Enumerable,(function(){function B(F,D,E){this.start=F;this.end=D;this.exclusive=E}function C(D){var E=this.start;while(this.include(E)){D(E);E=E.succ()}}function A(D){if(D<this.start){return false}if(this.exclusive){return D<this.end}return D<=this.end}return{initialize:B,_each:C,include:A}})());var Ajax={getTransport:function(){return Try.these(function(){return new XMLHttpRequest()},function(){return new ActiveXObject("Msxml2.XMLHTTP")},function(){return new ActiveXObject("Microsoft.XMLHTTP")})||false},activeRequestCount:0};Ajax.Responders={responders:[],_each:function(A){this.responders._each(A)},register:function(A){if(!this.include(A)){this.responders.push(A)}},unregister:function(A){this.responders=this.responders.without(A)},dispatch:function(D,B,C,A){this.each(function(E){if(Object.isFunction(E[D])){try{E[D].apply(E,[B,C,A])}catch(F){}}})}};Object.extend(Ajax.Responders,Enumerable);Ajax.Responders.register({onCreate:function(){Ajax.activeRequestCount++},onComplete:function(){Ajax.activeRequestCount--}});Ajax.Base=Class.create({initialize:function(A){this.options={method:"post",asynchronous:true,contentType:"application/x-www-form-urlencoded",encoding:"UTF-8",parameters:"",evalJSON:true,evalJS:true};Object.extend(this.options,A||{});this.options.method=this.options.method.toLowerCase();if(Object.isString(this.options.parameters)){this.options.parameters=this.options.parameters.toQueryParams()}else{if(Object.isHash(this.options.parameters)){this.options.parameters=this.options.parameters.toObject()}}}});Ajax.Request=Class.create(Ajax.Base,{_complete:false,initialize:function($super,B,A){$super(A);this.transport=Ajax.getTransport();this.request(B)},request:function(B){this.url=B;this.method=this.options.method;var D=Object.clone(this.options.parameters);if(!["get","post"].include(this.method)){D._method=this.method;this.method="post"}this.parameters=D;if(D=Object.toQueryString(D)){if(this.method=="get"){this.url+=(this.url.include("?")?"&":"?")+D}else{if(/Konqueror|Safari|KHTML/.test(navigator.userAgent)){D+="&_="}}}try{var A=new Ajax.Response(this);if(this.options.onCreate){this.options.onCreate(A)}Ajax.Responders.dispatch("onCreate",this,A);this.transport.open(this.method.toUpperCase(),this.url,this.options.asynchronous);if(this.options.asynchronous){this.respondToReadyState.bind(this).defer(1)}this.transport.onreadystatechange=this.onStateChange.bind(this);this.setRequestHeaders();this.body=this.method=="post"?(this.options.postBody||D):null;this.transport.send(this.body);if(!this.options.asynchronous&&this.transport.overrideMimeType){this.onStateChange()}}catch(C){this.dispatchException(C)}},onStateChange:function(){var A=this.transport.readyState;if(A>1&&!((A==4)&&this._complete)){this.respondToReadyState(this.transport.readyState)}},setRequestHeaders:function(){var E={"X-Requested-With":"XMLHttpRequest","X-Prototype-Version":Prototype.Version,Accept:"text/javascript, text/html, application/xml, text/xml, */*"};if(this.method=="post"){E["Content-type"]=this.options.contentType+(this.options.encoding?"; charset="+this.options.encoding:"");if(this.transport.overrideMimeType&&(navigator.userAgent.match(/Gecko\/(\d{4})/)||[0,2005])[1]<2005){E.Connection="close"}}if(typeof this.options.requestHeaders=="object"){var C=this.options.requestHeaders;if(Object.isFunction(C.push)){for(var B=0,D=C.length;B<D;B+=2){E[C[B]]=C[B+1]}}else{$H(C).each(function(F){E[F.key]=F.value})}}for(var A in E){this.transport.setRequestHeader(A,E[A])}},success:function(){var A=this.getStatus();return !A||(A>=200&&A<300)},getStatus:function(){try{return this.transport.status||0}catch(A){return 0}},respondToReadyState:function(A){var C=Ajax.Request.Events[A],B=new Ajax.Response(this);if(C=="Complete"){try{this._complete=true;(this.options["on"+B.status]||this.options["on"+(this.success()?"Success":"Failure")]||Prototype.emptyFunction)(B,B.headerJSON)}catch(D){this.dispatchException(D)}var E=B.getHeader("Content-type");if(this.options.evalJS=="force"||(this.options.evalJS&&this.isSameOrigin()&&E&&E.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))){this.evalResponse()}}try{(this.options["on"+C]||Prototype.emptyFunction)(B,B.headerJSON);Ajax.Responders.dispatch("on"+C,this,B,B.headerJSON)}catch(D){this.dispatchException(D)}if(C=="Complete"){this.transport.onreadystatechange=Prototype.emptyFunction}},isSameOrigin:function(){var A=this.url.match(/^\s*https?:\/\/[^\/]*/);return !A||(A[0]=="#{protocol}//#{domain}#{port}".interpolate({protocol:location.protocol,domain:document.domain,port:location.port?":"+location.port:""}))},getHeader:function(A){try{return this.transport.getResponseHeader(A)||null}catch(B){return null}},evalResponse:function(){try{return eval((this.transport.responseText||"").unfilterJSON())}catch(e){this.dispatchException(e)}},dispatchException:function(A){(this.options.onException||Prototype.emptyFunction)(this,A);Ajax.Responders.dispatch("onException",this,A)}});Ajax.Request.Events=["Uninitialized","Loading","Loaded","Interactive","Complete"];Ajax.Response=Class.create({initialize:function(C){this.request=C;var D=this.transport=C.transport,A=this.readyState=D.readyState;if((A>2&&!Prototype.Browser.IE)||A==4){this.status=this.getStatus();this.statusText=this.getStatusText();this.responseText=String.interpret(D.responseText);this.headerJSON=this._getHeaderJSON()}if(A==4){var B=D.responseXML;this.responseXML=Object.isUndefined(B)?null:B;this.responseJSON=this._getResponseJSON()}},status:0,statusText:"",getStatus:Ajax.Request.prototype.getStatus,getStatusText:function(){try{return this.transport.statusText||""}catch(A){return""}},getHeader:Ajax.Request.prototype.getHeader,getAllHeaders:function(){try{return this.getAllResponseHeaders()}catch(A){return null}},getResponseHeader:function(A){return this.transport.getResponseHeader(A)},getAllResponseHeaders:function(){return this.transport.getAllResponseHeaders()},_getHeaderJSON:function(){var A=this.getHeader("X-JSON");if(!A){return null}A=decodeURIComponent(escape(A));try{return A.evalJSON(this.request.options.sanitizeJSON||!this.request.isSameOrigin())}catch(B){this.request.dispatchException(B)}},_getResponseJSON:function(){var A=this.request.options;if(!A.evalJSON||(A.evalJSON!="force"&&!(this.getHeader("Content-type")||"").include("application/json"))||this.responseText.blank()){return null}try{return this.responseText.evalJSON(A.sanitizeJSON||!this.request.isSameOrigin())}catch(B){this.request.dispatchException(B)}}});Ajax.Updater=Class.create(Ajax.Request,{initialize:function($super,A,C,B){this.container={success:(A.success||A),failure:(A.failure||(A.success?null:A))};B=Object.clone(B);var D=B.onComplete;B.onComplete=(function(E,F){this.updateContent(E.responseText);if(Object.isFunction(D)){D(E,F)}}).bind(this);$super(C,B)},updateContent:function(D){var C=this.container[this.success()?"success":"failure"],A=this.options;if(!A.evalScripts){D=D.stripScripts()}if(C=$(C)){if(A.insertion){if(Object.isString(A.insertion)){var B={};B[A.insertion]=D;C.insert(B)}else{A.insertion(C,D)}}else{C.update(D)}}}});Ajax.PeriodicalUpdater=Class.create(Ajax.Base,{initialize:function($super,A,C,B){$super(B);this.onComplete=this.options.onComplete;this.frequency=(this.options.frequency||2);this.decay=(this.options.decay||1);this.updater={};this.container=A;this.url=C;this.start()},start:function(){this.options.onComplete=this.updateComplete.bind(this);this.onTimerEvent()},stop:function(){this.updater.options.onComplete=undefined;clearTimeout(this.timer);(this.onComplete||Prototype.emptyFunction).apply(this,arguments)},updateComplete:function(A){if(this.options.decay){this.decay=(A.responseText==this.lastText?this.decay*this.options.decay:1);this.lastText=A.responseText}this.timer=this.onTimerEvent.bind(this).delay(this.decay*this.frequency)},onTimerEvent:function(){this.updater=new Ajax.Updater(this.container,this.url,this.options)}});function $(B){if(arguments.length>1){for(var A=0,D=[],C=arguments.length;A<C;A++){D.push($(arguments[A]))}return D}if(Object.isString(B)){B=document.getElementById(B)}return Element.extend(B)}if(Prototype.BrowserFeatures.XPath){document._getElementsByXPath=function(F,A){var C=[];var E=document.evaluate(F,$(A)||document,null,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,null);for(var B=0,D=E.snapshotLength;B<D;B++){C.push(Element.extend(E.snapshotItem(B)))}return C}}if(!window.Node){var Node={}}if(!Node.ELEMENT_NODE){Object.extend(Node,{ELEMENT_NODE:1,ATTRIBUTE_NODE:2,TEXT_NODE:3,CDATA_SECTION_NODE:4,ENTITY_REFERENCE_NODE:5,ENTITY_NODE:6,PROCESSING_INSTRUCTION_NODE:7,COMMENT_NODE:8,DOCUMENT_NODE:9,DOCUMENT_TYPE_NODE:10,DOCUMENT_FRAGMENT_NODE:11,NOTATION_NODE:12})}(function(C){var B=(function(){var F=document.createElement("form");var E=document.createElement("input");var D=document.documentElement;E.setAttribute("name","test");F.appendChild(E);D.appendChild(F);var G=F.elements?(typeof F.elements.test=="undefined"):null;D.removeChild(F);F=E=null;return G})();var A=C.Element;C.Element=function(F,E){E=E||{};F=F.toLowerCase();var D=Element.cache;if(B&&E.name){F="<"+F+' name="'+E.name+'">';delete E.name;return Element.writeAttribute(document.createElement(F),E)}if(!D[F]){D[F]=Element.extend(document.createElement(F))}return Element.writeAttribute(D[F].cloneNode(false),E)};Object.extend(C.Element,A||{});if(A){C.Element.prototype=A.prototype}})(this);Element.cache={};Element.idCounter=1;Element.Methods={visible:function(A){return $(A).style.display!="none"},toggle:function(A){A=$(A);Element[Element.visible(A)?"hide":"show"](A);return A},hide:function(A){A=$(A);A.style.display="none";return A},show:function(A){A=$(A);A.style.display="";return A},remove:function(A){A=$(A);A.parentNode.removeChild(A);return A},update:(function(){var B=(function(){var E=document.createElement("select"),F=true;E.innerHTML='<option value="test">test</option>';if(E.options&&E.options[0]){F=E.options[0].nodeName.toUpperCase()!=="OPTION"}E=null;return F})();var A=(function(){try{var E=document.createElement("table");if(E&&E.tBodies){E.innerHTML="<tbody><tr><td>test</td></tr></tbody>";var G=typeof E.tBodies[0]=="undefined";E=null;return G}}catch(F){return true}})();var D=(function(){var E=document.createElement("script"),G=false;try{E.appendChild(document.createTextNode(""));G=!E.firstChild||E.firstChild&&E.firstChild.nodeType!==3}catch(F){G=true}E=null;return G})();function C(F,G){F=$(F);if(G&&G.toElement){G=G.toElement()}if(Object.isElement(G)){return F.update().insert(G)}G=Object.toHTML(G);var E=F.tagName.toUpperCase();if(E==="SCRIPT"&&D){F.text=G;return F}if(B||A){if(E in Element._insertionTranslations.tags){while(F.firstChild){F.removeChild(F.firstChild)}Element._getContentFromAnonymousElement(E,G.stripScripts()).each(function(H){F.appendChild(H)})}else{F.innerHTML=G.stripScripts()}}else{F.innerHTML=G.stripScripts()}G.evalScripts.bind(G).defer();return F}return C})(),replace:function(B,C){B=$(B);if(C&&C.toElement){C=C.toElement()}else{if(!Object.isElement(C)){C=Object.toHTML(C);var A=B.ownerDocument.createRange();A.selectNode(B);C.evalScripts.bind(C).defer();C=A.createContextualFragment(C.stripScripts())}}B.parentNode.replaceChild(C,B);return B},insert:function(C,E){C=$(C);if(Object.isString(E)||Object.isNumber(E)||Object.isElement(E)||(E&&(E.toElement||E.toHTML))){E={bottom:E}}var D,F,B,G;for(var A in E){D=E[A];A=A.toLowerCase();F=Element._insertionTranslations[A];if(D&&D.toElement){D=D.toElement()}if(Object.isElement(D)){F(C,D);continue}D=Object.toHTML(D);B=((A=="before"||A=="after")?C.parentNode:C).tagName.toUpperCase();G=Element._getContentFromAnonymousElement(B,D.stripScripts());if(A=="top"||A=="after"){G.reverse()}G.each(F.curry(C));D.evalScripts.bind(D).defer()}return C},wrap:function(B,C,A){B=$(B);if(Object.isElement(C)){$(C).writeAttribute(A||{})}else{if(Object.isString(C)){C=new Element(C,A)}else{C=new Element("div",C)}}if(B.parentNode){B.parentNode.replaceChild(C,B)}C.appendChild(B);return C},inspect:function(B){B=$(B);var A="<"+B.tagName.toLowerCase();$H({id:"id",className:"class"}).each(function(F){var E=F.first(),C=F.last();var D=(B[E]||"").toString();if(D){A+=" "+C+"="+D.inspect(true)}});return A+">"},recursivelyCollect:function(A,C){A=$(A);var B=[];while(A=A[C]){if(A.nodeType==1){B.push(Element.extend(A))}}return B},ancestors:function(A){return Element.recursivelyCollect(A,"parentNode")},descendants:function(A){return Element.select(A,"*")},firstDescendant:function(A){A=$(A).firstChild;while(A&&A.nodeType!=1){A=A.nextSibling}return $(A)},immediateDescendants:function(A){if(!(A=$(A).firstChild)){return[]}while(A&&A.nodeType!=1){A=A.nextSibling}if(A){return[A].concat($(A).nextSiblings())}return[]},previousSiblings:function(A){return Element.recursivelyCollect(A,"previousSibling")},nextSiblings:function(A){return Element.recursivelyCollect(A,"nextSibling")},siblings:function(A){A=$(A);return Element.previousSiblings(A).reverse().concat(Element.nextSiblings(A))},match:function(B,A){if(Object.isString(A)){A=new Selector(A)}return A.match($(B))},up:function(B,D,A){B=$(B);if(arguments.length==1){return $(B.parentNode)}var C=Element.ancestors(B);return Object.isNumber(D)?C[D]:Selector.findElement(C,D,A)},down:function(B,C,A){B=$(B);if(arguments.length==1){return Element.firstDescendant(B)}return Object.isNumber(C)?Element.descendants(B)[C]:Element.select(B,C)[A||0]},previous:function(B,D,A){B=$(B);if(arguments.length==1){return $(Selector.handlers.previousElementSibling(B))}var C=Element.previousSiblings(B);return Object.isNumber(D)?C[D]:Selector.findElement(C,D,A)},next:function(C,D,B){C=$(C);if(arguments.length==1){return $(Selector.handlers.nextElementSibling(C))}var A=Element.nextSiblings(C);return Object.isNumber(D)?A[D]:Selector.findElement(A,D,B)},select:function(B){var A=Array.prototype.slice.call(arguments,1);return Selector.findChildElements(B,A)},adjacent:function(B){var A=Array.prototype.slice.call(arguments,1);return Selector.findChildElements(B.parentNode,A).without(B)},identify:function(A){A=$(A);var B=Element.readAttribute(A,"id");if(B){return B}do{B="anonymous_element_"+Element.idCounter++}while($(B));Element.writeAttribute(A,"id",B);return B},readAttribute:function(C,A){C=$(C);if(Prototype.Browser.IE){var B=Element._attributeTranslations.read;if(B.values[A]){return B.values[A](C,A)}if(B.names[A]){A=B.names[A]}if(A.include(":")){return(!C.attributes||!C.attributes[A])?null:C.attributes[A].value}}return C.getAttribute(A)},writeAttribute:function(E,C,F){E=$(E);var B={},D=Element._attributeTranslations.write;if(typeof C=="object"){B=C}else{B[C]=Object.isUndefined(F)?true:F}for(var A in B){C=D.names[A]||A;F=B[A];if(D.values[A]){C=D.values[A](E,F)}if(F===false||F===null){E.removeAttribute(C)}else{if(F===true){E.setAttribute(C,C)}else{E.setAttribute(C,F)}}}return E},getHeight:function(A){return Element.getDimensions(A).height},getWidth:function(A){return Element.getDimensions(A).width},classNames:function(A){return new Element.ClassNames(A)},hasClassName:function(A,B){if(!(A=$(A))){return }var C=A.className;return(C.length>0&&(C==B||new RegExp("(^|\\s)"+B+"(\\s|$)").test(C)))},addClassName:function(A,B){if(!(A=$(A))){return }if(!Element.hasClassName(A,B)){A.className+=(A.className?" ":"")+B}return A},removeClassName:function(A,B){if(!(A=$(A))){return }A.className=A.className.replace(new RegExp("(^|\\s+)"+B+"(\\s+|$)")," ").strip();return A},toggleClassName:function(A,B){if(!(A=$(A))){return }return Element[Element.hasClassName(A,B)?"removeClassName":"addClassName"](A,B)},cleanWhitespace:function(B){B=$(B);var C=B.firstChild;while(C){var A=C.nextSibling;if(C.nodeType==3&&!/\S/.test(C.nodeValue)){B.removeChild(C)}C=A}return B},empty:function(A){return $(A).innerHTML.blank()},descendantOf:function(B,A){B=$(B),A=$(A);if(B.compareDocumentPosition){return(B.compareDocumentPosition(A)&8)===8}if(A.contains){return A.contains(B)&&A!==B}while(B=B.parentNode){if(B==A){return true}}return false},scrollTo:function(A){A=$(A);var B=Element.cumulativeOffset(A);window.scrollTo(B[0],B[1]);return A},getStyle:function(B,C){B=$(B);C=C=="float"?"cssFloat":C.camelize();var D=B.style[C];if(!D||D=="auto"){var A=document.defaultView.getComputedStyle(B,null);D=A?A[C]:null}if(C=="opacity"){return D?parseFloat(D):1}return D=="auto"?null:D},getOpacity:function(A){return $(A).getStyle("opacity")},setStyle:function(B,C){B=$(B);var E=B.style,A;if(Object.isString(C)){B.style.cssText+=";"+C;return C.include("opacity")?B.setOpacity(C.match(/opacity:\s*(\d?\.?\d*)/)[1]):B}for(var D in C){if(D=="opacity"){B.setOpacity(C[D])}else{E[(D=="float"||D=="cssFloat")?(Object.isUndefined(E.styleFloat)?"cssFloat":"styleFloat"):D]=C[D]}}return B},setOpacity:function(A,B){A=$(A);A.style.opacity=(B==1||B==="")?"":(B<0.00001)?0:B;return A},getDimensions:function(C){C=$(C);var G=Element.getStyle(C,"display");if(G!="none"&&G!=null){return{width:C.offsetWidth,height:C.offsetHeight}}var B=C.style;var F=B.visibility;var D=B.position;var A=B.display;B.visibility="hidden";if(D!="fixed"){B.position="absolute"}B.display="block";var H=C.clientWidth;var E=C.clientHeight;B.display=A;B.position=D;B.visibility=F;return{width:H,height:E}},makePositioned:function(A){A=$(A);var B=Element.getStyle(A,"position");if(B=="static"||!B){A._madePositioned=true;A.style.position="relative";if(Prototype.Browser.Opera){A.style.top=0;A.style.left=0}}return A},undoPositioned:function(A){A=$(A);if(A._madePositioned){A._madePositioned=undefined;A.style.position=A.style.top=A.style.left=A.style.bottom=A.style.right=""}return A},makeClipping:function(A){A=$(A);if(A._overflow){return A}A._overflow=Element.getStyle(A,"overflow")||"auto";if(A._overflow!=="hidden"){A.style.overflow="hidden"}return A},undoClipping:function(A){A=$(A);if(!A._overflow){return A}A.style.overflow=A._overflow=="auto"?"":A._overflow;A._overflow=null;return A},cumulativeOffset:function(B){var A=0,C=0;do{A+=B.offsetTop||0;C+=B.offsetLeft||0;B=B.offsetParent}while(B);return Element._returnOffset(C,A)},positionedOffset:function(B){var A=0,D=0;do{A+=B.offsetTop||0;D+=B.offsetLeft||0;B=B.offsetParent;if(B){if(B.tagName.toUpperCase()=="BODY"){break}var C=Element.getStyle(B,"position");if(C!=="static"){break}}}while(B);return Element._returnOffset(D,A)},absolutize:function(B){B=$(B);if(Element.getStyle(B,"position")=="absolute"){return B}var D=Element.positionedOffset(B);var F=D[1];var E=D[0];var C=B.clientWidth;var A=B.clientHeight;B._originalLeft=E-parseFloat(B.style.left||0);B._originalTop=F-parseFloat(B.style.top||0);B._originalWidth=B.style.width;B._originalHeight=B.style.height;B.style.position="absolute";B.style.top=F+"px";B.style.left=E+"px";B.style.width=C+"px";B.style.height=A+"px";return B},relativize:function(A){A=$(A);if(Element.getStyle(A,"position")=="relative"){return A}A.style.position="relative";var C=parseFloat(A.style.top||0)-(A._originalTop||0);var B=parseFloat(A.style.left||0)-(A._originalLeft||0);A.style.top=C+"px";A.style.left=B+"px";A.style.height=A._originalHeight;A.style.width=A._originalWidth;return A},cumulativeScrollOffset:function(B){var A=0,C=0;do{A+=B.scrollTop||0;C+=B.scrollLeft||0;B=B.parentNode}while(B);return Element._returnOffset(C,A)},getOffsetParent:function(A){if(A.offsetParent){return $(A.offsetParent)}if(A==document.body){return $(A)}while((A=A.parentNode)&&A!=document.body){if(Element.getStyle(A,"position")!="static"){return $(A)}}return $(document.body)},viewportOffset:function(D){var A=0,C=0;var B=D;do{A+=B.offsetTop||0;C+=B.offsetLeft||0;if(B.offsetParent==document.body&&Element.getStyle(B,"position")=="absolute"){break}}while(B=B.offsetParent);B=D;do{if(!Prototype.Browser.Opera||(B.tagName&&(B.tagName.toUpperCase()=="BODY"))){A-=B.scrollTop||0;C-=B.scrollLeft||0}}while(B=B.parentNode);return Element._returnOffset(C,A)},clonePosition:function(B,D){var A=Object.extend({setLeft:true,setTop:true,setWidth:true,setHeight:true,offsetTop:0,offsetLeft:0},arguments[2]||{});D=$(D);var E=Element.viewportOffset(D);B=$(B);var F=[0,0];var C=null;if(Element.getStyle(B,"position")=="absolute"){C=Element.getOffsetParent(B);F=Element.viewportOffset(C)}if(C==document.body){F[0]-=document.body.offsetLeft;F[1]-=document.body.offsetTop}if(A.setLeft){B.style.left=(E[0]-F[0]+A.offsetLeft)+"px"}if(A.setTop){B.style.top=(E[1]-F[1]+A.offsetTop)+"px"}if(A.setWidth){B.style.width=D.offsetWidth+"px"}if(A.setHeight){B.style.height=D.offsetHeight+"px"}return B}};Object.extend(Element.Methods,{getElementsBySelector:Element.Methods.select,childElements:Element.Methods.immediateDescendants});Element._attributeTranslations={write:{names:{className:"class",htmlFor:"for"},values:{}}};if(Prototype.Browser.Opera){Element.Methods.getStyle=Element.Methods.getStyle.wrap(function(D,B,C){switch(C){case"left":case"top":case"right":case"bottom":if(D(B,"position")==="static"){return null}case"height":case"width":if(!Element.visible(B)){return null}var E=parseInt(D(B,C),10);if(E!==B["offset"+C.capitalize()]){return E+"px"}var A;if(C==="height"){A=["border-top-width","padding-top","padding-bottom","border-bottom-width"]}else{A=["border-left-width","padding-left","padding-right","border-right-width"]}return A.inject(E,function(F,G){var H=D(B,G);return H===null?F:F-parseInt(H,10)})+"px";default:return D(B,C)}});Element.Methods.readAttribute=Element.Methods.readAttribute.wrap(function(C,A,B){if(B==="title"){return A.title}return C(A,B)})}else{if(Prototype.Browser.IE){Element.Methods.getOffsetParent=Element.Methods.getOffsetParent.wrap(function(C,B){B=$(B);try{B.offsetParent}catch(E){return $(document.body)}var A=B.getStyle("position");if(A!=="static"){return C(B)}B.setStyle({position:"relative"});var D=C(B);B.setStyle({position:A});return D});$w("positionedOffset viewportOffset").each(function(A){Element.Methods[A]=Element.Methods[A].wrap(function(E,C){C=$(C);try{C.offsetParent}catch(G){return Element._returnOffset(0,0)}var B=C.getStyle("position");if(B!=="static"){return E(C)}var D=C.getOffsetParent();if(D&&D.getStyle("position")==="fixed"){D.setStyle({zoom:1})}C.setStyle({position:"relative"});var F=E(C);C.setStyle({position:B});return F})});Element.Methods.cumulativeOffset=Element.Methods.cumulativeOffset.wrap(function(B,A){try{A.offsetParent}catch(C){return Element._returnOffset(0,0)}return B(A)});Element.Methods.getStyle=function(A,B){A=$(A);B=(B=="float"||B=="cssFloat")?"styleFloat":B.camelize();var C=A.style[B];if(!C&&A.currentStyle){C=A.currentStyle[B]}if(B=="opacity"){if(C=(A.getStyle("filter")||"").match(/alpha\(opacity=(.*)\)/)){if(C[1]){return parseFloat(C[1])/100}}return 1}if(C=="auto"){if((B=="width"||B=="height")&&(A.getStyle("display")!="none")){return A["offset"+B.capitalize()]+"px"}return null}return C};Element.Methods.setOpacity=function(B,E){function F(G){return G.replace(/alpha\([^\)]*\)/gi,"")}B=$(B);var A=B.currentStyle;if((A&&!A.hasLayout)||(!A&&B.style.zoom=="normal")){B.style.zoom=1}var D=B.getStyle("filter"),C=B.style;if(E==1||E===""){(D=F(D))?C.filter=D:C.removeAttribute("filter");return B}else{if(E<0.00001){E=0}}C.filter=F(D)+"alpha(opacity="+(E*100)+")";return B};Element._attributeTranslations=(function(){var B="className";var A="for";var C=document.createElement("div");C.setAttribute(B,"x");if(C.className!=="x"){C.setAttribute("class","x");if(C.className==="x"){B="class"}}C=null;C=document.createElement("label");C.setAttribute(A,"x");if(C.htmlFor!=="x"){C.setAttribute("htmlFor","x");if(C.htmlFor==="x"){A="htmlFor"}}C=null;return{read:{names:{"class":B,className:B,"for":A,htmlFor:A},values:{_getAttr:function(D,E){return D.getAttribute(E)},_getAttr2:function(D,E){return D.getAttribute(E,2)},_getAttrNode:function(D,F){var E=D.getAttributeNode(F);return E?E.value:""},_getEv:(function(){var D=document.createElement("div");D.onclick=Prototype.emptyFunction;var F=D.getAttribute("onclick");var E;if(String(F).indexOf("{")>-1){E=function(G,H){H=G.getAttribute(H);if(!H){return null}H=H.toString();H=H.split("{")[1];H=H.split("}")[0];return H.strip()}}else{if(F===""){E=function(G,H){H=G.getAttribute(H);if(!H){return null}return H.strip()}}}D=null;return E})(),_flag:function(D,E){return $(D).hasAttribute(E)?E:null},style:function(D){return D.style.cssText.toLowerCase()},title:function(D){return D.title}}}}})();Element._attributeTranslations.write={names:Object.extend({cellpadding:"cellPadding",cellspacing:"cellSpacing"},Element._attributeTranslations.read.names),values:{checked:function(A,B){A.checked=!!B},style:function(A,B){A.style.cssText=B?B:""}}};Element._attributeTranslations.has={};$w("colSpan rowSpan vAlign dateTime accessKey tabIndex encType maxLength readOnly longDesc frameBorder").each(function(A){Element._attributeTranslations.write.names[A.toLowerCase()]=A;Element._attributeTranslations.has[A.toLowerCase()]=A});(function(A){Object.extend(A,{href:A._getAttr2,src:A._getAttr2,type:A._getAttr,action:A._getAttrNode,disabled:A._flag,checked:A._flag,readonly:A._flag,multiple:A._flag,onload:A._getEv,onunload:A._getEv,onclick:A._getEv,ondblclick:A._getEv,onmousedown:A._getEv,onmouseup:A._getEv,onmouseover:A._getEv,onmousemove:A._getEv,onmouseout:A._getEv,onfocus:A._getEv,onblur:A._getEv,onkeypress:A._getEv,onkeydown:A._getEv,onkeyup:A._getEv,onsubmit:A._getEv,onreset:A._getEv,onselect:A._getEv,onchange:A._getEv})})(Element._attributeTranslations.read.values);if(Prototype.BrowserFeatures.ElementExtensions){(function(){function A(E){var B=E.getElementsByTagName("*"),D=[];for(var C=0,F;F=B[C];C++){if(F.tagName!=="!"){D.push(F)}}return D}Element.Methods.down=function(C,D,B){C=$(C);if(arguments.length==1){return C.firstDescendant()}return Object.isNumber(D)?A(C)[D]:Element.select(C,D)[B||0]}})()}}else{if(Prototype.Browser.Gecko&&/rv:1\.8\.0/.test(navigator.userAgent)){Element.Methods.setOpacity=function(A,B){A=$(A);A.style.opacity=(B==1)?0.999999:(B==="")?"":(B<0.00001)?0:B;return A}}else{if(Prototype.Browser.WebKit){Element.Methods.setOpacity=function(A,B){A=$(A);A.style.opacity=(B==1||B==="")?"":(B<0.00001)?0:B;if(B==1){if(A.tagName.toUpperCase()=="IMG"&&A.width){A.width++;A.width--}else{try{var D=document.createTextNode(" ");A.appendChild(D);A.removeChild(D)}catch(C){}}}return A};Element.Methods.cumulativeOffset=function(B){var A=0,C=0;do{A+=B.offsetTop||0;C+=B.offsetLeft||0;if(B.offsetParent==document.body){if(Element.getStyle(B,"position")=="absolute"){break}}B=B.offsetParent}while(B);return Element._returnOffset(C,A)}}}}}if("outerHTML" in document.documentElement){Element.Methods.replace=function(C,E){C=$(C);if(E&&E.toElement){E=E.toElement()}if(Object.isElement(E)){C.parentNode.replaceChild(E,C);return C}E=Object.toHTML(E);var D=C.parentNode,B=D.tagName.toUpperCase();if(Element._insertionTranslations.tags[B]){var F=C.next();var A=Element._getContentFromAnonymousElement(B,E.stripScripts());D.removeChild(C);if(F){A.each(function(G){D.insertBefore(G,F)})}else{A.each(function(G){D.appendChild(G)})}}else{C.outerHTML=E.stripScripts()}E.evalScripts.bind(E).defer();return C}}Element._returnOffset=function(B,C){var A=[B,C];A.left=B;A.top=C;return A};Element._getContentFromAnonymousElement=function(C,B){var D=new Element("div"),A=Element._insertionTranslations.tags[C];if(A){D.innerHTML=A[0]+B+A[1];A[2].times(function(){D=D.firstChild})}else{D.innerHTML=B}return $A(D.childNodes)};Element._insertionTranslations={before:function(A,B){A.parentNode.insertBefore(B,A)},top:function(A,B){A.insertBefore(B,A.firstChild)},bottom:function(A,B){A.appendChild(B)},after:function(A,B){A.parentNode.insertBefore(B,A.nextSibling)},tags:{TABLE:["<table>","</table>",1],TBODY:["<table><tbody>","</tbody></table>",2],TR:["<table><tbody><tr>","</tr></tbody></table>",3],TD:["<table><tbody><tr><td>","</td></tr></tbody></table>",4],SELECT:["<select>","</select>",1]}};(function(){var A=Element._insertionTranslations.tags;Object.extend(A,{THEAD:A.TBODY,TFOOT:A.TBODY,TH:A.TD})})();Element.Methods.Simulated={hasAttribute:function(A,C){C=Element._attributeTranslations.has[C]||C;var B=$(A).getAttributeNode(C);return !!(B&&B.specified)}};Element.Methods.ByTag={};Object.extend(Element,Element.Methods);(function(A){if(!Prototype.BrowserFeatures.ElementExtensions&&A.__proto__){window.HTMLElement={};window.HTMLElement.prototype=A.__proto__;Prototype.BrowserFeatures.ElementExtensions=true}A=null})(document.createElement("div"));Element.extend=(function(){function C(G){if(typeof window.Element!="undefined"){var I=window.Element.prototype;if(I){var K="_"+(Math.random()+"").slice(2);var H=document.createElement(G);I[K]="x";var J=(H[K]!=="x");delete I[K];H=null;return J}}return false}function B(H,G){for(var J in G){var I=G[J];if(Object.isFunction(I)&&!(J in H)){H[J]=I.methodize()}}}var D=C("object");if(Prototype.BrowserFeatures.SpecificElementExtensions){if(D){return function(H){if(H&&typeof H._extendedByPrototype=="undefined"){var G=H.tagName;if(G&&(/^(?:object|applet|embed)$/i.test(G))){B(H,Element.Methods);B(H,Element.Methods.Simulated);B(H,Element.Methods.ByTag[G.toUpperCase()])}}return H}}return Prototype.K}var A={},E=Element.Methods.ByTag;var F=Object.extend(function(I){if(!I||typeof I._extendedByPrototype!="undefined"||I.nodeType!=1||I==window){return I}var G=Object.clone(A),H=I.tagName.toUpperCase();if(E[H]){Object.extend(G,E[H])}B(I,G);I._extendedByPrototype=Prototype.emptyFunction;return I},{refresh:function(){if(!Prototype.BrowserFeatures.ElementExtensions){Object.extend(A,Element.Methods);Object.extend(A,Element.Methods.Simulated)}}});F.refresh();return F})();Element.hasAttribute=function(A,B){if(A.hasAttribute){return A.hasAttribute(B)}return Element.Methods.Simulated.hasAttribute(A,B)};Element.addMethods=function(C){var J=Prototype.BrowserFeatures,D=Element.Methods.ByTag;if(!C){Object.extend(Form,Form.Methods);Object.extend(Form.Element,Form.Element.Methods);Object.extend(Element.Methods.ByTag,{FORM:Object.clone(Form.Methods),INPUT:Object.clone(Form.Element.Methods),SELECT:Object.clone(Form.Element.Methods),TEXTAREA:Object.clone(Form.Element.Methods)})}if(arguments.length==2){var B=C;C=arguments[1]}if(!B){Object.extend(Element.Methods,C||{})}else{if(Object.isArray(B)){B.each(H)}else{H(B)}}function H(F){F=F.toUpperCase();if(!Element.Methods.ByTag[F]){Element.Methods.ByTag[F]={}}Object.extend(Element.Methods.ByTag[F],C)}function A(M,L,F){F=F||false;for(var O in M){var N=M[O];if(!Object.isFunction(N)){continue}if(!F||!(O in L)){L[O]=N.methodize()}}}function E(N){var F;var M={OPTGROUP:"OptGroup",TEXTAREA:"TextArea",P:"Paragraph",FIELDSET:"FieldSet",UL:"UList",OL:"OList",DL:"DList",DIR:"Directory",H1:"Heading",H2:"Heading",H3:"Heading",H4:"Heading",H5:"Heading",H6:"Heading",Q:"Quote",INS:"Mod",DEL:"Mod",A:"Anchor",IMG:"Image",CAPTION:"TableCaption",COL:"TableCol",COLGROUP:"TableCol",THEAD:"TableSection",TFOOT:"TableSection",TBODY:"TableSection",TR:"TableRow",TH:"TableCell",TD:"TableCell",FRAMESET:"FrameSet",IFRAME:"IFrame"};if(M[N]){F="HTML"+M[N]+"Element"}if(window[F]){return window[F]}F="HTML"+N+"Element";if(window[F]){return window[F]}F="HTML"+N.capitalize()+"Element";if(window[F]){return window[F]}var L=document.createElement(N);var O=L.__proto__||L.constructor.prototype;L=null;return O}var I=window.HTMLElement?HTMLElement.prototype:Element.prototype;if(J.ElementExtensions){A(Element.Methods,I);A(Element.Methods.Simulated,I,true)}if(J.SpecificElementExtensions){for(var K in Element.Methods.ByTag){var G=E(K);if(Object.isUndefined(G)){continue}A(D[K],G.prototype)}}Object.extend(Element,Element.Methods);delete Element.ByTag;if(Element.extend.refresh){Element.extend.refresh()}Element.cache={}};document.viewport={getDimensions:function(){return{width:this.getWidth(),height:this.getHeight()}},getScrollOffsets:function(){return Element._returnOffset(window.pageXOffset||document.documentElement.scrollLeft||document.body.scrollLeft,window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop)}};(function(C){var H=Prototype.Browser,F=document,D,E={};function A(){if(H.WebKit&&!F.evaluate){return document}if(H.Opera&&window.parseFloat(window.opera.version())<9.5){return document.body}return document.documentElement}function G(B){if(!D){D=A()}E[B]="client"+B;C["get"+B]=function(){return D[E[B]]};return C["get"+B]()}C.getWidth=G.curry("Width");C.getHeight=G.curry("Height")})(document.viewport);Element.Storage={UID:1};Element.addMethods({getStorage:function(B){if(!(B=$(B))){return }var A;if(B===window){A=0}else{if(typeof B._prototypeUID==="undefined"){B._prototypeUID=[Element.Storage.UID++]}A=B._prototypeUID[0]}if(!Element.Storage[A]){Element.Storage[A]=$H()}return Element.Storage[A]},store:function(B,A,C){if(!(B=$(B))){return }if(arguments.length===2){Element.getStorage(B).update(A)}else{Element.getStorage(B).set(A,C)}return B},retrieve:function(C,B,A){if(!(C=$(C))){return }var E=Element.getStorage(C),D=E.get(B);if(Object.isUndefined(D)){E.set(B,A);D=A}return D},clone:function(C,A){if(!(C=$(C))){return }var E=C.cloneNode(A);E._prototypeUID=void 0;if(A){var D=Element.select(E,"*"),B=D.length;while(B--){D[B]._prototypeUID=void 0}}return Element.extend(E)}});var Selector=Class.create({initialize:function(A){this.expression=A.strip();if(this.shouldUseSelectorsAPI()){this.mode="selectorsAPI"}else{if(this.shouldUseXPath()){this.mode="xpath";this.compileXPathMatcher()}else{this.mode="normal";this.compileMatcher()}}},shouldUseXPath:(function(){var A=(function(){var E=false;if(document.evaluate&&window.XPathResult){var D=document.createElement("div");D.innerHTML="<ul><li></li></ul><div><ul><li></li></ul></div>";var C=".//*[local-name()='ul' or local-name()='UL']//*[local-name()='li' or local-name()='LI']";var B=document.evaluate(C,D,null,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,null);E=(B.snapshotLength!==2);D=null}return E})();return function(){if(!Prototype.BrowserFeatures.XPath){return false}var B=this.expression;if(Prototype.Browser.WebKit&&(B.include("-of-type")||B.include(":empty"))){return false}if((/(\[[\w-]*?:|:checked)/).test(B)){return false}if(A){return false}return true}})(),shouldUseSelectorsAPI:function(){if(!Prototype.BrowserFeatures.SelectorsAPI){return false}if(Selector.CASE_INSENSITIVE_CLASS_NAMES){return false}if(!Selector._div){Selector._div=new Element("div")}try{Selector._div.querySelector(this.expression)}catch(A){return false}return true},compileMatcher:function(){var e=this.expression,ps=Selector.patterns,h=Selector.handlers,c=Selector.criteria,le,p,m,len=ps.length,name;if(Selector._cache[e]){this.matcher=Selector._cache[e];return }this.matcher=["this.matcher = function(root) {","var r = root, h = Selector.handlers, c = false, n;"];while(e&&le!=e&&(/\S/).test(e)){le=e;for(var i=0;i<len;i++){p=ps[i].re;name=ps[i].name;if(m=e.match(p)){this.matcher.push(Object.isFunction(c[name])?c[name](m):new Template(c[name]).evaluate(m));e=e.replace(m[0],"");break}}}this.matcher.push("return h.unique(n);\n}");eval(this.matcher.join("\n"));Selector._cache[this.expression]=this.matcher},compileXPathMatcher:function(){var G=this.expression,H=Selector.patterns,C=Selector.xpath,F,B,A=H.length,D;if(Selector._cache[G]){this.xpath=Selector._cache[G];return }this.matcher=[".//*"];while(G&&F!=G&&(/\S/).test(G)){F=G;for(var E=0;E<A;E++){D=H[E].name;if(B=G.match(H[E].re)){this.matcher.push(Object.isFunction(C[D])?C[D](B):new Template(C[D]).evaluate(B));G=G.replace(B[0],"");break}}}this.xpath=this.matcher.join("");Selector._cache[this.expression]=this.xpath},findElements:function(A){A=A||document;var C=this.expression,B;switch(this.mode){case"selectorsAPI":if(A!==document){var D=A.id,E=$(A).identify();E=E.replace(/([\.:])/g,"\\$1");C="#"+E+" "+C}B=$A(A.querySelectorAll(C)).map(Element.extend);A.id=D;return B;case"xpath":return document._getElementsByXPath(this.xpath,A);default:return this.matcher(A)}},match:function(I){this.tokens=[];var M=this.expression,A=Selector.patterns,E=Selector.assertions;var B,D,F,L=A.length,C;while(M&&B!==M&&(/\S/).test(M)){B=M;for(var H=0;H<L;H++){D=A[H].re;C=A[H].name;if(F=M.match(D)){if(E[C]){this.tokens.push([C,Object.clone(F)]);M=M.replace(F[0],"")}else{return this.findElements(document).include(I)}}}}var K=true,C,J;for(var H=0,G;G=this.tokens[H];H++){C=G[0],J=G[1];if(!Selector.assertions[C](I,J)){K=false;break}}return K},toString:function(){return this.expression},inspect:function(){return"#<Selector:"+this.expression.inspect()+">"}});if(Prototype.BrowserFeatures.SelectorsAPI&&document.compatMode==="BackCompat"){Selector.CASE_INSENSITIVE_CLASS_NAMES=(function(){var C=document.createElement("div"),A=document.createElement("span");C.id="prototype_test_id";A.className="Test";C.appendChild(A);var B=(C.querySelector("#prototype_test_id .test")!==null);C=A=null;return B})()}Object.extend(Selector,{_cache:{},xpath:{descendant:"//*",child:"/*",adjacent:"/following-sibling::*[1]",laterSibling:"/following-sibling::*",tagName:function(A){if(A[1]=="*"){return""}return"[local-name()='"+A[1].toLowerCase()+"' or local-name()='"+A[1].toUpperCase()+"']"},className:"[contains(concat(' ', @class, ' '), ' #{1} ')]",id:"[@id='#{1}']",attrPresence:function(A){A[1]=A[1].toLowerCase();return new Template("[@#{1}]").evaluate(A)},attr:function(A){A[1]=A[1].toLowerCase();A[3]=A[5]||A[6];return new Template(Selector.xpath.operators[A[2]]).evaluate(A)},pseudo:function(A){var B=Selector.xpath.pseudos[A[1]];if(!B){return""}if(Object.isFunction(B)){return B(A)}return new Template(Selector.xpath.pseudos[A[1]]).evaluate(A)},operators:{"=":"[@#{1}='#{3}']","!=":"[@#{1}!='#{3}']","^=":"[starts-with(@#{1}, '#{3}')]","$=":"[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']","*=":"[contains(@#{1}, '#{3}')]","~=":"[contains(concat(' ', @#{1}, ' '), ' #{3} ')]","|=":"[contains(concat('-', @#{1}, '-'), '-#{3}-')]"},pseudos:{"first-child":"[not(preceding-sibling::*)]","last-child":"[not(following-sibling::*)]","only-child":"[not(preceding-sibling::* or following-sibling::*)]",empty:"[count(*) = 0 and (count(text()) = 0)]",checked:"[@checked]",disabled:"[(@disabled) and (@type!='hidden')]",enabled:"[not(@disabled) and (@type!='hidden')]",not:function(E){var H=E[6],C=Selector.patterns,I=Selector.xpath,A,J,G=C.length,B;var D=[];while(H&&A!=H&&(/\S/).test(H)){A=H;for(var F=0;F<G;F++){B=C[F].name;if(E=H.match(C[F].re)){J=Object.isFunction(I[B])?I[B](E):new Template(I[B]).evaluate(E);D.push("("+J.substring(1,J.length-1)+")");H=H.replace(E[0],"");break}}}return"[not("+D.join(" and ")+")]"},"nth-child":function(A){return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ",A)},"nth-last-child":function(A){return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ",A)},"nth-of-type":function(A){return Selector.xpath.pseudos.nth("position() ",A)},"nth-last-of-type":function(A){return Selector.xpath.pseudos.nth("(last() + 1 - position()) ",A)},"first-of-type":function(A){A[6]="1";return Selector.xpath.pseudos["nth-of-type"](A)},"last-of-type":function(A){A[6]="1";return Selector.xpath.pseudos["nth-last-of-type"](A)},"only-of-type":function(A){var B=Selector.xpath.pseudos;return B["first-of-type"](A)+B["last-of-type"](A)},nth:function(E,C){var F,G=C[6],B;if(G=="even"){G="2n+0"}if(G=="odd"){G="2n+1"}if(F=G.match(/^(\d+)$/)){return"["+E+"= "+F[1]+"]"}if(F=G.match(/^(-?\d*)?n(([+-])(\d+))?/)){if(F[1]=="-"){F[1]=-1}var D=F[1]?Number(F[1]):1;var A=F[2]?Number(F[2]):0;B="[((#{fragment} - #{b}) mod #{a} = 0) and ((#{fragment} - #{b}) div #{a} >= 0)]";return new Template(B).evaluate({fragment:E,a:D,b:A})}}}},criteria:{tagName:'n = h.tagName(n, r, "#{1}", c);      c = false;',className:'n = h.className(n, r, "#{1}", c);    c = false;',id:'n = h.id(n, r, "#{1}", c);           c = false;',attrPresence:'n = h.attrPresence(n, r, "#{1}", c); c = false;',attr:function(A){A[3]=(A[5]||A[6]);return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(A)},pseudo:function(A){if(A[6]){A[6]=A[6].replace(/"/g,'\\"')}return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(A)},descendant:'c = "descendant";',child:'c = "child";',adjacent:'c = "adjacent";',laterSibling:'c = "laterSibling";'},patterns:[{name:"laterSibling",re:/^\s*~\s*/},{name:"child",re:/^\s*>\s*/},{name:"adjacent",re:/^\s*\+\s*/},{name:"descendant",re:/^\s/},{name:"tagName",re:/^\s*(\*|[\w\-]+)(\b|$)?/},{name:"id",re:/^#([\w\-\*]+)(\b|$)/},{name:"className",re:/^\.([\w\-\*]+)(\b|$)/},{name:"pseudo",re:/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/},{name:"attrPresence",re:/^\[((?:[\w-]+:)?[\w-]+)\]/},{name:"attr",re:/\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/}],assertions:{tagName:function(A,B){return B[1].toUpperCase()==A.tagName.toUpperCase()},className:function(A,B){return Element.hasClassName(A,B[1])},id:function(A,B){return A.id===B[1]},attrPresence:function(A,B){return Element.hasAttribute(A,B[1])},attr:function(B,C){var A=Element.readAttribute(B,C[1]);return A&&Selector.operators[C[2]](A,C[5]||C[6])}},handlers:{concat:function(B,A){for(var C=0,D;D=A[C];C++){B.push(D)}return B},mark:function(A){var D=Prototype.emptyFunction;for(var B=0,C;C=A[B];B++){C._countedByPrototype=D}return A},unmark:(function(){var A=(function(){var B=document.createElement("div"),E=false,D="_countedByPrototype",C="x";B[D]=C;E=(B.getAttribute(D)===C);B=null;return E})();return A?function(B){for(var C=0,D;D=B[C];C++){D.removeAttribute("_countedByPrototype")}return B}:function(B){for(var C=0,D;D=B[C];C++){D._countedByPrototype=void 0}return B}})(),index:function(A,D,G){A._countedByPrototype=Prototype.emptyFunction;if(D){for(var B=A.childNodes,E=B.length-1,C=1;E>=0;E--){var F=B[E];if(F.nodeType==1&&(!G||F._countedByPrototype)){F.nodeIndex=C++}}}else{for(var E=0,C=1,B=A.childNodes;F=B[E];E++){if(F.nodeType==1&&(!G||F._countedByPrototype)){F.nodeIndex=C++}}}},unique:function(B){if(B.length==0){return B}var D=[],E;for(var C=0,A=B.length;C<A;C++){if(typeof (E=B[C])._countedByPrototype=="undefined"){E._countedByPrototype=Prototype.emptyFunction;D.push(Element.extend(E))}}return Selector.handlers.unmark(D)},descendant:function(A){var D=Selector.handlers;for(var C=0,B=[],E;E=A[C];C++){D.concat(B,E.getElementsByTagName("*"))}return B},child:function(A){var E=Selector.handlers;for(var D=0,C=[],F;F=A[D];D++){for(var B=0,G;G=F.childNodes[B];B++){if(G.nodeType==1&&G.tagName!="!"){C.push(G)}}}return C},adjacent:function(A){for(var C=0,B=[],E;E=A[C];C++){var D=this.nextElementSibling(E);if(D){B.push(D)}}return B},laterSibling:function(A){var D=Selector.handlers;for(var C=0,B=[],E;E=A[C];C++){D.concat(B,Element.nextSiblings(E))}return B},nextElementSibling:function(A){while(A=A.nextSibling){if(A.nodeType==1){return A}}return null},previousElementSibling:function(A){while(A=A.previousSibling){if(A.nodeType==1){return A}}return null},tagName:function(A,H,C,B){var I=C.toUpperCase();var E=[],G=Selector.handlers;if(A){if(B){if(B=="descendant"){for(var F=0,D;D=A[F];F++){G.concat(E,D.getElementsByTagName(C))}return E}else{A=this[B](A)}if(C=="*"){return A}}for(var F=0,D;D=A[F];F++){if(D.tagName.toUpperCase()===I){E.push(D)}}return E}else{return H.getElementsByTagName(C)}},id:function(A,I,B,C){var H=$(B),G=Selector.handlers;if(I==document){if(!H){return[]}if(!A){return[H]}}else{if(!I.sourceIndex||I.sourceIndex<1){var A=I.getElementsByTagName("*");for(var E=0,D;D=A[E];E++){if(D.id===B){return[D]}}}}if(A){if(C){if(C=="child"){for(var F=0,D;D=A[F];F++){if(H.parentNode==D){return[H]}}}else{if(C=="descendant"){for(var F=0,D;D=A[F];F++){if(Element.descendantOf(H,D)){return[H]}}}else{if(C=="adjacent"){for(var F=0,D;D=A[F];F++){if(Selector.handlers.previousElementSibling(H)==D){return[H]}}}else{A=G[C](A)}}}}for(var F=0,D;D=A[F];F++){if(D==H){return[H]}}return[]}return(H&&Element.descendantOf(H,I))?[H]:[]},className:function(B,A,C,D){if(B&&D){B=this[D](B)}return Selector.handlers.byClassName(B,A,C)},byClassName:function(C,B,F){if(!C){C=Selector.handlers.descendant([B])}var H=" "+F+" ";for(var E=0,D=[],G,A;G=C[E];E++){A=G.className;if(A.length==0){continue}if(A==F||(" "+A+" ").include(H)){D.push(G)}}return D},attrPresence:function(C,B,A,G){if(!C){C=B.getElementsByTagName("*")}if(C&&G){C=this[G](C)}var E=[];for(var D=0,F;F=C[D];D++){if(Element.hasAttribute(F,A)){E.push(F)}}return E},attr:function(A,I,H,J,C,B){if(!A){A=I.getElementsByTagName("*")}if(A&&B){A=this[B](A)}var K=Selector.operators[C],F=[];for(var E=0,D;D=A[E];E++){var G=Element.readAttribute(D,H);if(G===null){continue}if(K(G,J)){F.push(D)}}return F},pseudo:function(B,C,E,A,D){if(B&&D){B=this[D](B)}if(!B){B=A.getElementsByTagName("*")}return Selector.pseudos[C](B,E,A)}},pseudos:{"first-child":function(B,F,A){for(var D=0,C=[],E;E=B[D];D++){if(Selector.handlers.previousElementSibling(E)){continue}C.push(E)}return C},"last-child":function(B,F,A){for(var D=0,C=[],E;E=B[D];D++){if(Selector.handlers.nextElementSibling(E)){continue}C.push(E)}return C},"only-child":function(B,G,A){var E=Selector.handlers;for(var D=0,C=[],F;F=B[D];D++){if(!E.previousElementSibling(F)&&!E.nextElementSibling(F)){C.push(F)}}return C},"nth-child":function(B,C,A){return Selector.pseudos.nth(B,C,A)},"nth-last-child":function(B,C,A){return Selector.pseudos.nth(B,C,A,true)},"nth-of-type":function(B,C,A){return Selector.pseudos.nth(B,C,A,false,true)},"nth-last-of-type":function(B,C,A){return Selector.pseudos.nth(B,C,A,true,true)},"first-of-type":function(B,C,A){return Selector.pseudos.nth(B,"1",A,false,true)},"last-of-type":function(B,C,A){return Selector.pseudos.nth(B,"1",A,true,true)},"only-of-type":function(B,D,A){var C=Selector.pseudos;return C["last-of-type"](C["first-of-type"](B,D,A),D,A)},getIndices:function(B,A,C){if(B==0){return A>0?[A]:[]}return $R(1,C).inject([],function(D,E){if(0==(E-A)%B&&(E-A)/B>=0){D.push(E)}return D})},nth:function(A,L,N,K,C){if(A.length==0){return[]}if(L=="even"){L="2n+0"}if(L=="odd"){L="2n+1"}var J=Selector.handlers,I=[],B=[],E;J.mark(A);for(var H=0,D;D=A[H];H++){if(!D.parentNode._countedByPrototype){J.index(D.parentNode,K,C);B.push(D.parentNode)}}if(L.match(/^\d+$/)){L=Number(L);for(var H=0,D;D=A[H];H++){if(D.nodeIndex==L){I.push(D)}}}else{if(E=L.match(/^(-?\d*)?n(([+-])(\d+))?/)){if(E[1]=="-"){E[1]=-1}var O=E[1]?Number(E[1]):1;var M=E[2]?Number(E[2]):0;var P=Selector.pseudos.getIndices(O,M,A.length);for(var H=0,D,F=P.length;D=A[H];H++){for(var G=0;G<F;G++){if(D.nodeIndex==P[G]){I.push(D)}}}}}J.unmark(A);J.unmark(B);return I},empty:function(B,F,A){for(var D=0,C=[],E;E=B[D];D++){if(E.tagName=="!"||E.firstChild){continue}C.push(E)}return C},not:function(A,D,I){var G=Selector.handlers,J,C;var H=new Selector(D).findElements(I);G.mark(H);for(var F=0,E=[],B;B=A[F];F++){if(!B._countedByPrototype){E.push(B)}}G.unmark(H);return E},enabled:function(B,F,A){for(var D=0,C=[],E;E=B[D];D++){if(!E.disabled&&(!E.type||E.type!=="hidden")){C.push(E)}}return C},disabled:function(B,F,A){for(var D=0,C=[],E;E=B[D];D++){if(E.disabled){C.push(E)}}return C},checked:function(B,F,A){for(var D=0,C=[],E;E=B[D];D++){if(E.checked){C.push(E)}}return C}},operators:{"=":function(B,A){return B==A},"!=":function(B,A){return B!=A},"^=":function(B,A){return B==A||B&&B.startsWith(A)},"$=":function(B,A){return B==A||B&&B.endsWith(A)},"*=":function(B,A){return B==A||B&&B.include(A)},"~=":function(B,A){return(" "+B+" ").include(" "+A+" ")},"|=":function(B,A){return("-"+(B||"").toUpperCase()+"-").include("-"+(A||"").toUpperCase()+"-")}},split:function(B){var A=[];B.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/,function(C){A.push(C[1].strip())});return A},matchElements:function(F,G){var E=$$(G),D=Selector.handlers;D.mark(E);for(var C=0,B=[],A;A=F[C];C++){if(A._countedByPrototype){B.push(A)}}D.unmark(E);return B},findElement:function(B,C,A){if(Object.isNumber(C)){A=C;C=false}return Selector.matchElements(B,C||"*")[A||0]},findChildElements:function(E,G){G=Selector.split(G.join(","));var D=[],F=Selector.handlers;for(var C=0,B=G.length,A;C<B;C++){A=new Selector(G[C].strip());F.concat(D,A.findElements(E))}return(B>1)?F.unique(D):D}});if(Prototype.Browser.IE){Object.extend(Selector.handlers,{concat:function(B,A){for(var C=0,D;D=A[C];C++){if(D.tagName!=="!"){B.push(D)}}return B}})}function $$(){return Selector.findChildElements(document,$A(arguments))}var Form={reset:function(A){A=$(A);A.reset();return A},serializeElements:function(G,B){if(typeof B!="object"){B={hash:!!B}}else{if(Object.isUndefined(B.hash)){B.hash=true}}var C,F,A=false,E=B.submit;var D=G.inject({},function(H,I){if(!I.disabled&&I.name){C=I.name;F=$(I).getValue();if(F!=null&&I.type!="file"&&(I.type!="submit"||(!A&&E!==false&&(!E||C==E)&&(A=true)))){if(C in H){if(!Object.isArray(H[C])){H[C]=[H[C]]}H[C].push(F)}else{H[C]=F}}}return H});return B.hash?D:Object.toQueryString(D)}};Form.Methods={serialize:function(B,A){return Form.serializeElements(Form.getElements(B),A)},getElements:function(E){var F=$(E).getElementsByTagName("*"),D,A=[],C=Form.Element.Serializers;for(var B=0;D=F[B];B++){A.push(D)}return A.inject([],function(G,H){if(C[H.tagName.toLowerCase()]){G.push(Element.extend(H))}return G})},getInputs:function(G,C,D){G=$(G);var A=G.getElementsByTagName("input");if(!C&&!D){return $A(A).map(Element.extend)}for(var E=0,H=[],F=A.length;E<F;E++){var B=A[E];if((C&&B.type!=C)||(D&&B.name!=D)){continue}H.push(Element.extend(B))}return H},disable:function(A){A=$(A);Form.getElements(A).invoke("disable");return A},enable:function(A){A=$(A);Form.getElements(A).invoke("enable");return A},findFirstElement:function(B){var C=$(B).getElements().findAll(function(D){return"hidden"!=D.type&&!D.disabled});var A=C.findAll(function(D){return D.hasAttribute("tabIndex")&&D.tabIndex>=0}).sortBy(function(D){return D.tabIndex}).first();return A?A:C.find(function(D){return/^(?:input|select|textarea)$/i.test(D.tagName)})},focusFirstElement:function(A){A=$(A);A.findFirstElement().activate();return A},request:function(B,A){B=$(B),A=Object.clone(A||{});var D=A.parameters,C=B.readAttribute("action")||"";if(C.blank()){C=window.location.href}A.parameters=B.serialize(true);if(D){if(Object.isString(D)){D=D.toQueryParams()}Object.extend(A.parameters,D)}if(B.hasAttribute("method")&&!A.method){A.method=B.method}return new Ajax.Request(C,A)}};Form.Element={focus:function(A){$(A).focus();return A},select:function(A){$(A).select();return A}};Form.Element.Methods={serialize:function(A){A=$(A);if(!A.disabled&&A.name){var B=A.getValue();if(B!=undefined){var C={};C[A.name]=B;return Object.toQueryString(C)}}return""},getValue:function(A){A=$(A);var B=A.tagName.toLowerCase();return Form.Element.Serializers[B](A)},setValue:function(A,B){A=$(A);var C=A.tagName.toLowerCase();Form.Element.Serializers[C](A,B);return A},clear:function(A){$(A).value="";return A},present:function(A){return $(A).value!=""},activate:function(A){A=$(A);try{A.focus();if(A.select&&(A.tagName.toLowerCase()!="input"||!(/^(?:button|reset|submit)$/i.test(A.type)))){A.select()}}catch(B){}return A},disable:function(A){A=$(A);A.disabled=true;return A},enable:function(A){A=$(A);A.disabled=false;return A}};var Field=Form.Element;var $F=Form.Element.Methods.getValue;Form.Element.Serializers={input:function(A,B){switch(A.type.toLowerCase()){case"checkbox":case"radio":return Form.Element.Serializers.inputSelector(A,B);default:return Form.Element.Serializers.textarea(A,B)}},inputSelector:function(A,B){if(Object.isUndefined(B)){return A.checked?A.value:null}else{A.checked=!!B}},textarea:function(A,B){if(Object.isUndefined(B)){return A.value}else{A.value=B}},select:function(C,F){if(Object.isUndefined(F)){return this[C.type=="select-one"?"selectOne":"selectMany"](C)}else{var B,D,G=!Object.isArray(F);for(var A=0,E=C.length;A<E;A++){B=C.options[A];D=this.optionValue(B);if(G){if(D==F){B.selected=true;return }}else{B.selected=F.include(D)}}}},selectOne:function(B){var A=B.selectedIndex;return A>=0?this.optionValue(B.options[A]):null},selectMany:function(D){var A,E=D.length;if(!E){return null}for(var C=0,A=[];C<E;C++){var B=D.options[C];if(B.selected){A.push(this.optionValue(B))}}return A},optionValue:function(A){return Element.extend(A).hasAttribute("value")?A.value:A.text}};Abstract.TimedObserver=Class.create(PeriodicalExecuter,{initialize:function($super,A,B,C){$super(C,B);this.element=$(A);this.lastValue=this.getValue()},execute:function(){var A=this.getValue();if(Object.isString(this.lastValue)&&Object.isString(A)?this.lastValue!=A:String(this.lastValue)!=String(A)){this.callback(this.element,A);this.lastValue=A}}});Form.Element.Observer=Class.create(Abstract.TimedObserver,{getValue:function(){return Form.Element.getValue(this.element)}});Form.Observer=Class.create(Abstract.TimedObserver,{getValue:function(){return Form.serialize(this.element)}});Abstract.EventObserver=Class.create({initialize:function(A,B){this.element=$(A);this.callback=B;this.lastValue=this.getValue();if(this.element.tagName.toLowerCase()=="form"){this.registerFormCallbacks()}else{this.registerCallback(this.element)}},onElementEvent:function(){var A=this.getValue();if(this.lastValue!=A){this.callback(this.element,A);this.lastValue=A}},registerFormCallbacks:function(){Form.getElements(this.element).each(this.registerCallback,this)},registerCallback:function(A){if(A.type){switch(A.type.toLowerCase()){case"checkbox":case"radio":Event.observe(A,"click",this.onElementEvent.bind(this));break;default:Event.observe(A,"change",this.onElementEvent.bind(this));break}}}});Form.Element.EventObserver=Class.create(Abstract.EventObserver,{getValue:function(){return Form.Element.getValue(this.element)}});Form.EventObserver=Class.create(Abstract.EventObserver,{getValue:function(){return Form.serialize(this.element)}});(function(){var V={KEY_BACKSPACE:8,KEY_TAB:9,KEY_RETURN:13,KEY_ESC:27,KEY_LEFT:37,KEY_UP:38,KEY_RIGHT:39,KEY_DOWN:40,KEY_DELETE:46,KEY_HOME:36,KEY_END:35,KEY_PAGEUP:33,KEY_PAGEDOWN:34,KEY_INSERT:45,cache:{}};var E=document.documentElement;var W="onmouseenter" in E&&"onmouseleave" in E;var O;if(Prototype.Browser.IE){var H={0:1,1:4,2:2};O=function(Y,X){return Y.button===H[X]}}else{if(Prototype.Browser.WebKit){O=function(Y,X){switch(X){case 0:return Y.which==1&&!Y.metaKey;case 1:return Y.which==1&&Y.metaKey;default:return false}}}else{O=function(Y,X){return Y.which?(Y.which===X+1):(Y.button===X)}}}function R(X){return O(X,0)}function Q(X){return O(X,1)}function K(X){return O(X,2)}function C(Z){Z=V.extend(Z);var Y=Z.target,X=Z.type,a=Z.currentTarget;if(a&&a.tagName){if(X==="load"||X==="error"||(X==="click"&&a.tagName.toLowerCase()==="input"&&a.type==="radio")){Y=a}}if(Y.nodeType==Node.TEXT_NODE){Y=Y.parentNode}return Element.extend(Y)}function M(Y,a){var X=V.element(Y);if(!a){return X}var Z=[X].concat(X.ancestors());return Selector.findElement(Z,a,0)}function P(X){return{x:B(X),y:A(X)}}function B(Z){var Y=document.documentElement,X=document.body||{scrollLeft:0};return Z.pageX||(Z.clientX+(Y.scrollLeft||X.scrollLeft)-(Y.clientLeft||0))}function A(Z){var Y=document.documentElement,X=document.body||{scrollTop:0};return Z.pageY||(Z.clientY+(Y.scrollTop||X.scrollTop)-(Y.clientTop||0))}function N(X){V.extend(X);X.preventDefault();X.stopPropagation();X.stopped=true}V.Methods={isLeftClick:R,isMiddleClick:Q,isRightClick:K,element:C,findElement:M,pointer:P,pointerX:B,pointerY:A,stop:N};var T=Object.keys(V.Methods).inject({},function(X,Y){X[Y]=V.Methods[Y].methodize();return X});if(Prototype.Browser.IE){function G(Y){var X;switch(Y.type){case"mouseover":X=Y.fromElement;break;case"mouseout":X=Y.toElement;break;default:return null}return Element.extend(X)}Object.extend(T,{stopPropagation:function(){this.cancelBubble=true},preventDefault:function(){this.returnValue=false},inspect:function(){return"[object Event]"}});V.extend=function(Y,X){if(!Y){return false}if(Y._extendedByPrototype){return Y}Y._extendedByPrototype=Prototype.emptyFunction;var Z=V.pointer(Y);Object.extend(Y,{target:Y.srcElement||X,relatedTarget:G(Y),pageX:Z.x,pageY:Z.y});return Object.extend(Y,T)}}else{V.prototype=window.Event.prototype||document.createEvent("HTMLEvents").__proto__;Object.extend(V.prototype,T);V.extend=Prototype.K}function L(b,a,c){var Z=Element.retrieve(b,"prototype_event_registry");if(Object.isUndefined(Z)){D.push(b);Z=Element.retrieve(b,"prototype_event_registry",$H())}var X=Z.get(a);if(Object.isUndefined(X)){X=[];Z.set(a,X)}if(X.pluck("handler").include(c)){return false}var Y;if(a.include(":")){Y=function(d){if(Object.isUndefined(d.eventName)){return false}if(d.eventName!==a){return false}V.extend(d,b);c.call(b,d)}}else{if(!W&&(a==="mouseenter"||a==="mouseleave")){if(a==="mouseenter"||a==="mouseleave"){Y=function(f){V.extend(f,b);var d=f.relatedTarget;while(d&&d!==b){try{d=d.parentNode}catch(g){d=b}}if(d===b){return }c.call(b,f)}}}else{Y=function(d){V.extend(d,b);c.call(b,d)}}}Y.handler=c;X.push(Y);return Y}function F(){for(var X=0,Y=D.length;X<Y;X++){V.stopObserving(D[X]);D[X]=null}}var D=[];if(Prototype.Browser.IE){window.attachEvent("onunload",F)}if(Prototype.Browser.WebKit){window.addEventListener("unload",Prototype.emptyFunction,false)}var J=Prototype.K;if(!W){J=function(Y){var X={mouseenter:"mouseover",mouseleave:"mouseout"};return Y in X?X[Y]:Y}}function S(a,Z,b){a=$(a);var Y=L(a,Z,b);if(!Y){return a}if(Z.include(":")){if(a.addEventListener){a.addEventListener("dataavailable",Y,false)}else{a.attachEvent("ondataavailable",Y);a.attachEvent("onfilterchange",Y)}}else{var X=J(Z);if(a.addEventListener){a.addEventListener(X,Y,false)}else{a.attachEvent("on"+X,Y)}}return a}function I(c,a,d){c=$(c);var Z=Element.retrieve(c,"prototype_event_registry");if(Object.isUndefined(Z)){return c}if(a&&!d){var b=Z.get(a);if(Object.isUndefined(b)){return c}b.each(function(e){Element.stopObserving(c,a,e.handler)});return c}else{if(!a){Z.each(function(g){var e=g.key,f=g.value;f.each(function(h){Element.stopObserving(c,e,h.handler)})});return c}}var b=Z.get(a);if(!b){return }var Y=b.find(function(e){return e.handler===d});if(!Y){return c}var X=J(a);if(a.include(":")){if(c.removeEventListener){c.removeEventListener("dataavailable",Y,false)}else{c.detachEvent("ondataavailable",Y);c.detachEvent("onfilterchange",Y)}}else{if(c.removeEventListener){c.removeEventListener(X,Y,false)}else{c.detachEvent("on"+X,Y)}}Z.set(a,b.without(Y));return c}function U(a,Z,Y,X){a=$(a);if(Object.isUndefined(X)){X=true}if(a==document&&document.createEvent&&!a.dispatchEvent){a=document.documentElement}var b;if(document.createEvent){b=document.createEvent("HTMLEvents");b.initEvent("dataavailable",true,true)}else{b=document.createEventObject();b.eventType=X?"ondataavailable":"onfilterchange"}b.eventName=Z;b.memo=Y||{};if(document.createEvent){a.dispatchEvent(b)}else{a.fireEvent(b.eventType,b)}return V.extend(b)}Object.extend(V,V.Methods);Object.extend(V,{fire:U,observe:S,stopObserving:I});Element.addMethods({fire:U,observe:S,stopObserving:I});Object.extend(document,{fire:U.methodize(),observe:S.methodize(),stopObserving:I.methodize(),loaded:false});if(window.Event){Object.extend(window.Event,V)}else{window.Event=V}})();(function(){var D;function A(){if(document.loaded){return }if(D){window.clearTimeout(D)}document.loaded=true;document.fire("dom:loaded")}function C(){if(document.readyState==="complete"){document.stopObserving("readystatechange",C);A()}}function B(){try{document.documentElement.doScroll("left")}catch(E){D=B.defer();return }A()}if(document.addEventListener){document.addEventListener("DOMContentLoaded",A,false)}else{document.observe("readystatechange",C);if(window==top){D=B.defer()}}Event.observe(window,"load",A)})();Element.addMethods();Hash.toQueryString=Object.toQueryString;var Toggle={display:Element.toggle};Element.Methods.childOf=Element.Methods.descendantOf;var Insertion={Before:function(A,B){return Element.insert(A,{before:B})},Top:function(A,B){return Element.insert(A,{top:B})},Bottom:function(A,B){return Element.insert(A,{bottom:B})},After:function(A,B){return Element.insert(A,{after:B})}};var $continue=new Error('"throw $continue" is deprecated, use "return" instead');var Position={includeScrollOffsets:false,prepare:function(){this.deltaX=window.pageXOffset||document.documentElement.scrollLeft||document.body.scrollLeft||0;this.deltaY=window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0},within:function(B,A,C){if(this.includeScrollOffsets){return this.withinIncludingScrolloffsets(B,A,C)}this.xcomp=A;this.ycomp=C;this.offset=Element.cumulativeOffset(B);return(C>=this.offset[1]&&C<this.offset[1]+B.offsetHeight&&A>=this.offset[0]&&A<this.offset[0]+B.offsetWidth)},withinIncludingScrolloffsets:function(B,A,D){var C=Element.cumulativeScrollOffset(B);this.xcomp=A+C[0]-this.deltaX;this.ycomp=D+C[1]-this.deltaY;this.offset=Element.cumulativeOffset(B);return(this.ycomp>=this.offset[1]&&this.ycomp<this.offset[1]+B.offsetHeight&&this.xcomp>=this.offset[0]&&this.xcomp<this.offset[0]+B.offsetWidth)},overlap:function(B,A){if(!B){return 0}if(B=="vertical"){return((this.offset[1]+A.offsetHeight)-this.ycomp)/A.offsetHeight}if(B=="horizontal"){return((this.offset[0]+A.offsetWidth)-this.xcomp)/A.offsetWidth}},cumulativeOffset:Element.Methods.cumulativeOffset,positionedOffset:Element.Methods.positionedOffset,absolutize:function(A){Position.prepare();return Element.absolutize(A)},relativize:function(A){Position.prepare();return Element.relativize(A)},realOffset:Element.Methods.cumulativeScrollOffset,offsetParent:Element.Methods.getOffsetParent,page:Element.Methods.viewportOffset,clone:function(B,C,A){A=A||{};return Element.clonePosition(C,B,A)}};if(!document.getElementsByClassName){document.getElementsByClassName=function(B){function A(C){return C.blank()?null:"[contains(concat(' ', @class, ' '), ' "+C+" ')]"}B.getElementsByClassName=Prototype.BrowserFeatures.XPath?function(C,E){E=E.toString().strip();var D=/\s/.test(E)?$w(E).map(A).join(""):A(E);return D?document._getElementsByXPath(".//*"+D,C):[]}:function(E,F){F=F.toString().strip();var G=[],H=(/\s/.test(F)?$w(F):null);if(!H&&!F){return G}var C=$(E).getElementsByTagName("*");F=" "+F+" ";for(var D=0,J,I;J=C[D];D++){if(J.className&&(I=" "+J.className+" ")&&(I.include(F)||(H&&H.all(function(K){return !K.toString().blank()&&I.include(" "+K+" ")})))){G.push(Element.extend(J))}}return G};return function(D,C){return $(C||document.body).getElementsByClassName(D)}}(Element.Methods)}Element.ClassNames=Class.create();Element.ClassNames.prototype={initialize:function(A){this.element=$(A)},_each:function(A){this.element.className.split(/\s+/).select(function(B){return B.length>0})._each(A)},set:function(A){this.element.className=A},add:function(A){if(this.include(A)){return }this.set($A(this).concat(A).join(" "))},remove:function(A){if(!this.include(A)){return }this.set($A(this).without(A).join(" "))},toString:function(){return $A(this).join(" ")}};Object.extend(Element.ClassNames.prototype,Enumerable);var Scriptaculous={Version:"1.8.3",require:function(B){try{document.write('<script type="text/javascript" src="'+B+'"><\/script>')}catch(C){var A=document.createElement("script");A.type="text/javascript";A.src=B;document.getElementsByTagName("head")[0].appendChild(A)}},REQUIRED_PROTOTYPE:"1.6.0.3",load:function(){function A(B){var C=B.replace(/_.*|\./g,"");C=parseInt(C+"0".times(4-C.length));return B.indexOf("_")>-1?C-1:C}if((typeof Prototype=="undefined")||(typeof Element=="undefined")||(typeof Element.Methods=="undefined")||(A(Prototype.Version)<A(Scriptaculous.REQUIRED_PROTOTYPE))){throw ("script.aculo.us requires the Prototype JavaScript framework >= "+Scriptaculous.REQUIRED_PROTOTYPE)}}};Scriptaculous.load();var Builder={NODEMAP:{AREA:"map",CAPTION:"table",COL:"table",COLGROUP:"table",LEGEND:"fieldset",OPTGROUP:"select",OPTION:"select",PARAM:"object",TBODY:"table",TD:"table",TFOOT:"table",TH:"table",THEAD:"table",TR:"table"},node:function(A){A=A.toUpperCase();var F=this.NODEMAP[A]||"div";var B=document.createElement(F);try{B.innerHTML="<"+A+"></"+A+">"}catch(E){}var D=B.firstChild||null;if(D&&(D.tagName.toUpperCase()!=A)){D=D.getElementsByTagName(A)[0]}if(!D){D=document.createElement(A)}if(!D){return }if(arguments[1]){if(this._isStringOrNumber(arguments[1])||(arguments[1] instanceof Array)||arguments[1].tagName){this._children(D,arguments[1])}else{var C=this._attributes(arguments[1]);if(C.length){try{B.innerHTML="<"+A+" "+C+"></"+A+">"}catch(E){}D=B.firstChild||null;if(!D){D=document.createElement(A);for(attr in arguments[1]){D[attr=="class"?"className":attr]=arguments[1][attr]}}if(D.tagName.toUpperCase()!=A){D=B.getElementsByTagName(A)[0]}}}}if(arguments[2]){this._children(D,arguments[2])}return $(D)},_text:function(A){return document.createTextNode(A)},ATTR_MAP:{className:"class",htmlFor:"for"},_attributes:function(A){var B=[];for(attribute in A){B.push((attribute in this.ATTR_MAP?this.ATTR_MAP[attribute]:attribute)+'="'+A[attribute].toString().escapeHTML().gsub(/"/,"&quot;")+'"')}return B.join(" ")},_children:function(B,A){if(A.tagName){B.appendChild(A);return }if(typeof A=="object"){A.flatten().each(function(C){if(typeof C=="object"){B.appendChild(C)}else{if(Builder._isStringOrNumber(C)){B.appendChild(Builder._text(C))}}})}else{if(Builder._isStringOrNumber(A)){B.appendChild(Builder._text(A))}}},_isStringOrNumber:function(A){return(typeof A=="string"||typeof A=="number")},build:function(B){var A=this.node("div");$(A).update(B.strip());return A.down()},dump:function(B){if(typeof B!="object"&&typeof B!="function"){B=window}var A=("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/);A.each(function(C){B[C]=function(){return Builder.node.apply(Builder,[C].concat($A(arguments)))}})}};String.prototype.parseColor=function(){var A="#";if(this.slice(0,4)=="rgb("){var C=this.slice(4,this.length-1).split(",");var B=0;do{A+=parseInt(C[B]).toColorPart()}while(++B<3)}else{if(this.slice(0,1)=="#"){if(this.length==4){for(var B=1;B<4;B++){A+=(this.charAt(B)+this.charAt(B)).toLowerCase()}}if(this.length==7){A=this.toLowerCase()}}}return(A.length==7?A:(arguments[0]||this))};Element.collectTextNodes=function(A){return $A($(A).childNodes).collect(function(B){return(B.nodeType==3?B.nodeValue:(B.hasChildNodes()?Element.collectTextNodes(B):""))}).flatten().join("")};Element.collectTextNodesIgnoreClass=function(A,B){return $A($(A).childNodes).collect(function(C){return(C.nodeType==3?C.nodeValue:((C.hasChildNodes()&&!Element.hasClassName(C,B))?Element.collectTextNodesIgnoreClass(C,B):""))}).flatten().join("")};Element.setContentZoom=function(A,B){A=$(A);A.setStyle({fontSize:(B/100)+"em"});if(Prototype.Browser.WebKit){window.scrollBy(0,0)}return A};Element.getInlineOpacity=function(A){return $(A).style.opacity||""};Element.forceRerendering=function(A){try{A=$(A);var C=document.createTextNode(" ");A.appendChild(C);A.removeChild(C)}catch(B){}};var Effect={_elementDoesNotExistError:{name:"ElementDoesNotExistError",message:"The specified DOM element does not exist, but is required for this effect to operate"},Transitions:{linear:Prototype.K,sinoidal:function(A){return(-Math.cos(A*Math.PI)/2)+0.5},reverse:function(A){return 1-A},flicker:function(A){var A=((-Math.cos(A*Math.PI)/4)+0.75)+Math.random()/4;return A>1?1:A},wobble:function(A){return(-Math.cos(A*Math.PI*(9*A))/2)+0.5},pulse:function(B,A){return(-Math.cos((B*((A||5)-0.5)*2)*Math.PI)/2)+0.5},spring:function(A){return 1-(Math.cos(A*4.5*Math.PI)*Math.exp(-A*6))},none:function(A){return 0},full:function(A){return 1}},DefaultOptions:{duration:1,fps:100,sync:false,from:0,to:1,delay:0,queue:"parallel"},tagifyText:function(A){var B="position:relative";if(Prototype.Browser.IE){B+=";zoom:1"}A=$(A);$A(A.childNodes).each(function(C){if(C.nodeType==3){C.nodeValue.toArray().each(function(D){A.insertBefore(new Element("span",{style:B}).update(D==" "?String.fromCharCode(160):D),C)});Element.remove(C)}})},multiple:function(B,C){var E;if(((typeof B=="object")||Object.isFunction(B))&&(B.length)){E=B}else{E=$(B).childNodes}var A=Object.extend({speed:0.1,delay:0},arguments[2]||{});var D=A.delay;$A(E).each(function(G,F){new C(G,Object.extend(A,{delay:F*A.speed+D}))})},PAIRS:{slide:["SlideDown","SlideUp"],blind:["BlindDown","BlindUp"],appear:["Appear","Fade"]},toggle:function(B,C,A){B=$(B);C=(C||"appear").toLowerCase();return Effect[Effect.PAIRS[C][B.visible()?1:0]](B,Object.extend({queue:{position:"end",scope:(B.id||"global"),limit:1}},A||{}))}};Effect.DefaultOptions.transition=Effect.Transitions.sinoidal;Effect.ScopedQueue=Class.create(Enumerable,{initialize:function(){this.effects=[];this.interval=null},_each:function(A){this.effects._each(A)},add:function(B){var C=new Date().getTime();var A=Object.isString(B.options.queue)?B.options.queue:B.options.queue.position;switch(A){case"front":this.effects.findAll(function(D){return D.state=="idle"}).each(function(D){D.startOn+=B.finishOn;D.finishOn+=B.finishOn});break;case"with-last":C=this.effects.pluck("startOn").max()||C;break;case"end":C=this.effects.pluck("finishOn").max()||C;break}B.startOn+=C;B.finishOn+=C;if(!B.options.queue.limit||(this.effects.length<B.options.queue.limit)){this.effects.push(B)}if(!this.interval){this.interval=setInterval(this.loop.bind(this),15)}},remove:function(A){this.effects=this.effects.reject(function(B){return B==A});if(this.effects.length==0){clearInterval(this.interval);this.interval=null}},loop:function(){var C=new Date().getTime();for(var B=0,A=this.effects.length;B<A;B++){this.effects[B]&&this.effects[B].loop(C)}}});Effect.Queues={instances:$H(),get:function(A){if(!Object.isString(A)){return A}return this.instances.get(A)||this.instances.set(A,new Effect.ScopedQueue())}};Effect.Queue=Effect.Queues.get("global");Effect.Base=Class.create({position:null,start:function(A){if(A&&A.transition===false){A.transition=Effect.Transitions.linear}this.options=Object.extend(Object.extend({},Effect.DefaultOptions),A||{});this.currentFrame=0;this.state="idle";this.startOn=this.options.delay*1000;this.finishOn=this.startOn+(this.options.duration*1000);this.fromToDelta=this.options.to-this.options.from;this.totalTime=this.finishOn-this.startOn;this.totalFrames=this.options.fps*this.options.duration;this.render=(function(){function B(D,C){if(D.options[C+"Internal"]){D.options[C+"Internal"](D)}if(D.options[C]){D.options[C](D)}}return function(C){if(this.state==="idle"){this.state="running";B(this,"beforeSetup");if(this.setup){this.setup()}B(this,"afterSetup")}if(this.state==="running"){C=(this.options.transition(C)*this.fromToDelta)+this.options.from;this.position=C;B(this,"beforeUpdate");if(this.update){this.update(C)}B(this,"afterUpdate")}}})();this.event("beforeStart");if(!this.options.sync){Effect.Queues.get(Object.isString(this.options.queue)?"global":this.options.queue.scope).add(this)}},loop:function(C){if(C>=this.startOn){if(C>=this.finishOn){this.render(1);this.cancel();this.event("beforeFinish");if(this.finish){this.finish()}this.event("afterFinish");return }var B=(C-this.startOn)/this.totalTime,A=(B*this.totalFrames).round();if(A>this.currentFrame){this.render(B);this.currentFrame=A}}},cancel:function(){if(!this.options.sync){Effect.Queues.get(Object.isString(this.options.queue)?"global":this.options.queue.scope).remove(this)}this.state="finished"},event:function(A){if(this.options[A+"Internal"]){this.options[A+"Internal"](this)}if(this.options[A]){this.options[A](this)}},inspect:function(){var A=$H();for(property in this){if(!Object.isFunction(this[property])){A.set(property,this[property])}}return"#<Effect:"+A.inspect()+",options:"+$H(this.options).inspect()+">"}});Effect.Parallel=Class.create(Effect.Base,{initialize:function(A){this.effects=A||[];this.start(arguments[1])},update:function(A){this.effects.invoke("render",A)},finish:function(A){this.effects.each(function(B){B.render(1);B.cancel();B.event("beforeFinish");if(B.finish){B.finish(A)}B.event("afterFinish")})}});Effect.Tween=Class.create(Effect.Base,{initialize:function(C,F,E){C=Object.isString(C)?$(C):C;var B=$A(arguments),D=B.last(),A=B.length==5?B[3]:null;this.method=Object.isFunction(D)?D.bind(C):Object.isFunction(C[D])?C[D].bind(C):function(G){C[D]=G};this.start(Object.extend({from:F,to:E},A||{}))},update:function(A){this.method(A)}});Effect.Event=Class.create(Effect.Base,{initialize:function(){this.start(Object.extend({duration:0},arguments[0]||{}))},update:Prototype.emptyFunction});Effect.Opacity=Class.create(Effect.Base,{initialize:function(B){this.element=$(B);if(!this.element){throw (Effect._elementDoesNotExistError)}if(Prototype.Browser.IE&&(!this.element.currentStyle.hasLayout)){this.element.setStyle({zoom:1})}var A=Object.extend({from:this.element.getOpacity()||0,to:1},arguments[1]||{});this.start(A)},update:function(A){this.element.setOpacity(A)}});Effect.Move=Class.create(Effect.Base,{initialize:function(B){this.element=$(B);if(!this.element){throw (Effect._elementDoesNotExistError)}var A=Object.extend({x:0,y:0,mode:"relative"},arguments[1]||{});this.start(A)},setup:function(){this.element.makePositioned();this.originalLeft=parseFloat(this.element.getStyle("left")||"0");this.originalTop=parseFloat(this.element.getStyle("top")||"0");if(this.options.mode=="absolute"){this.options.x=this.options.x-this.originalLeft;this.options.y=this.options.y-this.originalTop}},update:function(A){this.element.setStyle({left:(this.options.x*A+this.originalLeft).round()+"px",top:(this.options.y*A+this.originalTop).round()+"px"})}});Effect.MoveBy=function(B,A,C){return new Effect.Move(B,Object.extend({x:C,y:A},arguments[3]||{}))};Effect.Scale=Class.create(Effect.Base,{initialize:function(B,C){this.element=$(B);if(!this.element){throw (Effect._elementDoesNotExistError)}var A=Object.extend({scaleX:true,scaleY:true,scaleContent:true,scaleFromCenter:false,scaleMode:"box",scaleFrom:100,scaleTo:C},arguments[2]||{});this.start(A)},setup:function(){this.restoreAfterFinish=this.options.restoreAfterFinish||false;this.elementPositioning=this.element.getStyle("position");this.originalStyle={};["top","left","width","height","fontSize"].each(function(B){this.originalStyle[B]=this.element.style[B]}.bind(this));this.originalTop=this.element.offsetTop;this.originalLeft=this.element.offsetLeft;var A=this.element.getStyle("font-size")||"100%";["em","px","%","pt"].each(function(B){if(A.indexOf(B)>0){this.fontSize=parseFloat(A);this.fontSizeType=B}}.bind(this));this.factor=(this.options.scaleTo-this.options.scaleFrom)/100;this.dims=null;if(this.options.scaleMode=="box"){this.dims=[this.element.offsetHeight,this.element.offsetWidth]}if(/^content/.test(this.options.scaleMode)){this.dims=[this.element.scrollHeight,this.element.scrollWidth]}if(!this.dims){this.dims=[this.options.scaleMode.originalHeight,this.options.scaleMode.originalWidth]}},update:function(A){var B=(this.options.scaleFrom/100)+(this.factor*A);if(this.options.scaleContent&&this.fontSize){this.element.setStyle({fontSize:this.fontSize*B+this.fontSizeType})}this.setDimensions(this.dims[0]*B,this.dims[1]*B)},finish:function(A){if(this.restoreAfterFinish){this.element.setStyle(this.originalStyle)}},setDimensions:function(A,D){var E={};if(this.options.scaleX){E.width=D.round()+"px"}if(this.options.scaleY){E.height=A.round()+"px"}if(this.options.scaleFromCenter){var C=(A-this.dims[0])/2;var B=(D-this.dims[1])/2;if(this.elementPositioning=="absolute"){if(this.options.scaleY){E.top=this.originalTop-C+"px"}if(this.options.scaleX){E.left=this.originalLeft-B+"px"}}else{if(this.options.scaleY){E.top=-C+"px"}if(this.options.scaleX){E.left=-B+"px"}}}this.element.setStyle(E)}});Effect.Highlight=Class.create(Effect.Base,{initialize:function(B){this.element=$(B);if(!this.element){throw (Effect._elementDoesNotExistError)}var A=Object.extend({startcolor:"#ffff99"},arguments[1]||{});this.start(A)},setup:function(){if(this.element.getStyle("display")=="none"){this.cancel();return }this.oldStyle={};if(!this.options.keepBackgroundImage){this.oldStyle.backgroundImage=this.element.getStyle("background-image");this.element.setStyle({backgroundImage:"none"})}if(!this.options.endcolor){this.options.endcolor=this.element.getStyle("background-color").parseColor("#ffffff")}if(!this.options.restorecolor){this.options.restorecolor=this.element.getStyle("background-color")}this._base=$R(0,2).map(function(A){return parseInt(this.options.startcolor.slice(A*2+1,A*2+3),16)}.bind(this));this._delta=$R(0,2).map(function(A){return parseInt(this.options.endcolor.slice(A*2+1,A*2+3),16)-this._base[A]}.bind(this))},update:function(A){this.element.setStyle({backgroundColor:$R(0,2).inject("#",function(B,C,D){return B+((this._base[D]+(this._delta[D]*A)).round().toColorPart())}.bind(this))})},finish:function(){this.element.setStyle(Object.extend(this.oldStyle,{backgroundColor:this.options.restorecolor}))}});Effect.ScrollTo=function(C){var B=arguments[1]||{},A=document.viewport.getScrollOffsets(),D=$(C).cumulativeOffset();if(B.offset){D[1]+=B.offset}return new Effect.Tween(null,A.top,D[1],B,function(E){scrollTo(A.left,E.round())})};Effect.Fade=function(C){C=$(C);var A=C.getInlineOpacity();var B=Object.extend({from:C.getOpacity()||1,to:0,afterFinishInternal:function(D){if(D.options.to!=0){return }D.element.hide().setStyle({opacity:A})}},arguments[1]||{});return new Effect.Opacity(C,B)};Effect.Appear=function(B){B=$(B);var A=Object.extend({from:(B.getStyle("display")=="none"?0:B.getOpacity()||0),to:1,afterFinishInternal:function(C){C.element.forceRerendering()},beforeSetup:function(C){C.element.setOpacity(C.options.from).show()}},arguments[1]||{});return new Effect.Opacity(B,A)};Effect.Puff=function(B){B=$(B);var A={opacity:B.getInlineOpacity(),position:B.getStyle("position"),top:B.style.top,left:B.style.left,width:B.style.width,height:B.style.height};return new Effect.Parallel([new Effect.Scale(B,200,{sync:true,scaleFromCenter:true,scaleContent:true,restoreAfterFinish:true}),new Effect.Opacity(B,{sync:true,to:0})],Object.extend({duration:1,beforeSetupInternal:function(C){Position.absolutize(C.effects[0].element)},afterFinishInternal:function(C){C.effects[0].element.hide().setStyle(A)}},arguments[1]||{}))};Effect.BlindUp=function(A){A=$(A);A.makeClipping();return new Effect.Scale(A,0,Object.extend({scaleContent:false,scaleX:false,restoreAfterFinish:true,afterFinishInternal:function(B){B.element.hide().undoClipping()}},arguments[1]||{}))};Effect.BlindDown=function(B){B=$(B);var A=B.getDimensions();return new Effect.Scale(B,100,Object.extend({scaleContent:false,scaleX:false,scaleFrom:0,scaleMode:{originalHeight:A.height,originalWidth:A.width},restoreAfterFinish:true,afterSetup:function(C){C.element.makeClipping().setStyle({height:"0px"}).show()},afterFinishInternal:function(C){C.element.undoClipping()}},arguments[1]||{}))};Effect.SwitchOff=function(B){B=$(B);var A=B.getInlineOpacity();return new Effect.Appear(B,Object.extend({duration:0.4,from:0,transition:Effect.Transitions.flicker,afterFinishInternal:function(C){new Effect.Scale(C.element,1,{duration:0.3,scaleFromCenter:true,scaleX:false,scaleContent:false,restoreAfterFinish:true,beforeSetup:function(D){D.element.makePositioned().makeClipping()},afterFinishInternal:function(D){D.element.hide().undoClipping().undoPositioned().setStyle({opacity:A})}})}},arguments[1]||{}))};Effect.DropOut=function(B){B=$(B);var A={top:B.getStyle("top"),left:B.getStyle("left"),opacity:B.getInlineOpacity()};return new Effect.Parallel([new Effect.Move(B,{x:0,y:100,sync:true}),new Effect.Opacity(B,{sync:true,to:0})],Object.extend({duration:0.5,beforeSetup:function(C){C.effects[0].element.makePositioned()},afterFinishInternal:function(C){C.effects[0].element.hide().undoPositioned().setStyle(A)}},arguments[1]||{}))};Effect.Shake=function(D){D=$(D);var B=Object.extend({distance:20,duration:0.5},arguments[1]||{});var E=parseFloat(B.distance);var C=parseFloat(B.duration)/10;var A={top:D.getStyle("top"),left:D.getStyle("left")};return new Effect.Move(D,{x:E,y:0,duration:C,afterFinishInternal:function(F){new Effect.Move(F.element,{x:-E*2,y:0,duration:C*2,afterFinishInternal:function(G){new Effect.Move(G.element,{x:E*2,y:0,duration:C*2,afterFinishInternal:function(H){new Effect.Move(H.element,{x:-E*2,y:0,duration:C*2,afterFinishInternal:function(I){new Effect.Move(I.element,{x:E*2,y:0,duration:C*2,afterFinishInternal:function(J){new Effect.Move(J.element,{x:-E,y:0,duration:C,afterFinishInternal:function(K){K.element.undoPositioned().setStyle(A)}})}})}})}})}})}})};Effect.SlideDown=function(C){C=$(C).cleanWhitespace();var A=C.down().getStyle("bottom");var B=C.getDimensions();return new Effect.Scale(C,100,Object.extend({scaleContent:false,scaleX:false,scaleFrom:window.opera?0:1,scaleMode:{originalHeight:B.height,originalWidth:B.width},restoreAfterFinish:true,afterSetup:function(D){D.element.makePositioned();D.element.down().makePositioned();if(window.opera){D.element.setStyle({top:""})}D.element.makeClipping().setStyle({height:"0px"}).show()},afterUpdateInternal:function(D){D.element.down().setStyle({bottom:(D.dims[0]-D.element.clientHeight)+"px"})},afterFinishInternal:function(D){D.element.undoClipping().undoPositioned();D.element.down().undoPositioned().setStyle({bottom:A})}},arguments[1]||{}))};Effect.SlideUp=function(C){C=$(C).cleanWhitespace();var A=C.down().getStyle("bottom");var B=C.getDimensions();return new Effect.Scale(C,window.opera?0:1,Object.extend({scaleContent:false,scaleX:false,scaleMode:"box",scaleFrom:100,scaleMode:{originalHeight:B.height,originalWidth:B.width},restoreAfterFinish:true,afterSetup:function(D){D.element.makePositioned();D.element.down().makePositioned();if(window.opera){D.element.setStyle({top:""})}D.element.makeClipping().show()},afterUpdateInternal:function(D){D.element.down().setStyle({bottom:(D.dims[0]-D.element.clientHeight)+"px"})},afterFinishInternal:function(D){D.element.hide().undoClipping().undoPositioned();D.element.down().undoPositioned().setStyle({bottom:A})}},arguments[1]||{}))};Effect.Squish=function(A){return new Effect.Scale(A,window.opera?1:0,{restoreAfterFinish:true,beforeSetup:function(B){B.element.makeClipping()},afterFinishInternal:function(B){B.element.hide().undoClipping()}})};Effect.Grow=function(C){C=$(C);var B=Object.extend({direction:"center",moveTransition:Effect.Transitions.sinoidal,scaleTransition:Effect.Transitions.sinoidal,opacityTransition:Effect.Transitions.full},arguments[1]||{});var A={top:C.style.top,left:C.style.left,height:C.style.height,width:C.style.width,opacity:C.getInlineOpacity()};var G=C.getDimensions();var H,F;var E,D;switch(B.direction){case"top-left":H=F=E=D=0;break;case"top-right":H=G.width;F=D=0;E=-G.width;break;case"bottom-left":H=E=0;F=G.height;D=-G.height;break;case"bottom-right":H=G.width;F=G.height;E=-G.width;D=-G.height;break;case"center":H=G.width/2;F=G.height/2;E=-G.width/2;D=-G.height/2;break}return new Effect.Move(C,{x:H,y:F,duration:0.01,beforeSetup:function(I){I.element.hide().makeClipping().makePositioned()},afterFinishInternal:function(I){new Effect.Parallel([new Effect.Opacity(I.element,{sync:true,to:1,from:0,transition:B.opacityTransition}),new Effect.Move(I.element,{x:E,y:D,sync:true,transition:B.moveTransition}),new Effect.Scale(I.element,100,{scaleMode:{originalHeight:G.height,originalWidth:G.width},sync:true,scaleFrom:window.opera?1:0,transition:B.scaleTransition,restoreAfterFinish:true})],Object.extend({beforeSetup:function(J){J.effects[0].element.setStyle({height:"0px"}).show()},afterFinishInternal:function(J){J.effects[0].element.undoClipping().undoPositioned().setStyle(A)}},B))}})};Effect.Shrink=function(C){C=$(C);var B=Object.extend({direction:"center",moveTransition:Effect.Transitions.sinoidal,scaleTransition:Effect.Transitions.sinoidal,opacityTransition:Effect.Transitions.none},arguments[1]||{});var A={top:C.style.top,left:C.style.left,height:C.style.height,width:C.style.width,opacity:C.getInlineOpacity()};var F=C.getDimensions();var E,D;switch(B.direction){case"top-left":E=D=0;break;case"top-right":E=F.width;D=0;break;case"bottom-left":E=0;D=F.height;break;case"bottom-right":E=F.width;D=F.height;break;case"center":E=F.width/2;D=F.height/2;break}return new Effect.Parallel([new Effect.Opacity(C,{sync:true,to:0,from:1,transition:B.opacityTransition}),new Effect.Scale(C,window.opera?1:0,{sync:true,transition:B.scaleTransition,restoreAfterFinish:true}),new Effect.Move(C,{x:E,y:D,sync:true,transition:B.moveTransition})],Object.extend({beforeStartInternal:function(G){G.effects[0].element.makePositioned().makeClipping()},afterFinishInternal:function(G){G.effects[0].element.hide().undoClipping().undoPositioned().setStyle(A)}},B))};Effect.Pulsate=function(C){C=$(C);var B=arguments[1]||{},A=C.getInlineOpacity(),E=B.transition||Effect.Transitions.linear,D=function(F){return 1-E((-Math.cos((F*(B.pulses||5)*2)*Math.PI)/2)+0.5)};return new Effect.Opacity(C,Object.extend(Object.extend({duration:2,from:0,afterFinishInternal:function(F){F.element.setStyle({opacity:A})}},B),{transition:D}))};Effect.Fold=function(B){B=$(B);var A={top:B.style.top,left:B.style.left,width:B.style.width,height:B.style.height};B.makeClipping();return new Effect.Scale(B,5,Object.extend({scaleContent:false,scaleX:false,afterFinishInternal:function(C){new Effect.Scale(B,1,{scaleContent:false,scaleY:false,afterFinishInternal:function(D){D.element.hide().undoClipping().setStyle(A)}})}},arguments[1]||{}))};Effect.Morph=Class.create(Effect.Base,{initialize:function(C){this.element=$(C);if(!this.element){throw (Effect._elementDoesNotExistError)}var A=Object.extend({style:{}},arguments[1]||{});if(!Object.isString(A.style)){this.style=$H(A.style)}else{if(A.style.include(":")){this.style=A.style.parseStyle()}else{this.element.addClassName(A.style);this.style=$H(this.element.getStyles());this.element.removeClassName(A.style);var B=this.element.getStyles();this.style=this.style.reject(function(D){return D.value==B[D.key]});A.afterFinishInternal=function(D){D.element.addClassName(D.options.style);D.transforms.each(function(E){D.element.style[E.style]=""})}}}this.start(A)},setup:function(){function A(B){if(!B||["rgba(0, 0, 0, 0)","transparent"].include(B)){B="#ffffff"}B=B.parseColor();return $R(0,2).map(function(C){return parseInt(B.slice(C*2+1,C*2+3),16)})}this.transforms=this.style.map(function(G){var F=G[0],E=G[1],D=null;if(E.parseColor("#zzzzzz")!="#zzzzzz"){E=E.parseColor();D="color"}else{if(F=="opacity"){E=parseFloat(E);if(Prototype.Browser.IE&&(!this.element.currentStyle.hasLayout)){this.element.setStyle({zoom:1})}}else{if(Element.CSS_LENGTH.test(E)){var C=E.match(/^([\+\-]?[0-9\.]+)(.*)$/);E=parseFloat(C[1]);D=(C.length==3)?C[2]:null}}}var B=this.element.getStyle(F);return{style:F.camelize(),originalValue:D=="color"?A(B):parseFloat(B||0),targetValue:D=="color"?A(E):E,unit:D}}.bind(this)).reject(function(B){return((B.originalValue==B.targetValue)||(B.unit!="color"&&(isNaN(B.originalValue)||isNaN(B.targetValue))))})},update:function(A){var D={},B,C=this.transforms.length;while(C--){D[(B=this.transforms[C]).style]=B.unit=="color"?"#"+(Math.round(B.originalValue[0]+(B.targetValue[0]-B.originalValue[0])*A)).toColorPart()+(Math.round(B.originalValue[1]+(B.targetValue[1]-B.originalValue[1])*A)).toColorPart()+(Math.round(B.originalValue[2]+(B.targetValue[2]-B.originalValue[2])*A)).toColorPart():(B.originalValue+(B.targetValue-B.originalValue)*A).toFixed(3)+(B.unit===null?"":B.unit)}this.element.setStyle(D,true)}});Effect.Transform=Class.create({initialize:function(A){this.tracks=[];this.options=arguments[1]||{};this.addTracks(A)},addTracks:function(A){A.each(function(B){B=$H(B);var C=B.values().first();this.tracks.push($H({ids:B.keys().first(),effect:Effect.Morph,options:{style:C}}))}.bind(this));return this},play:function(){return new Effect.Parallel(this.tracks.map(function(A){var D=A.get("ids"),C=A.get("effect"),B=A.get("options");var E=[$(D)||$$(D)].flatten();return E.map(function(F){return new C(F,Object.extend({sync:true},B))})}).flatten(),this.options)}});Element.CSS_PROPERTIES=$w("backgroundColor backgroundPosition borderBottomColor borderBottomStyle borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth borderRightColor borderRightStyle borderRightWidth borderSpacing borderTopColor borderTopStyle borderTopWidth bottom clip color fontSize fontWeight height left letterSpacing lineHeight marginBottom marginLeft marginRight marginTop markerOffset maxHeight maxWidth minHeight minWidth opacity outlineColor outlineOffset outlineWidth paddingBottom paddingLeft paddingRight paddingTop right textIndent top width wordSpacing zIndex");Element.CSS_LENGTH=/^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;String.__parseStyleElement=document.createElement("div");String.prototype.parseStyle=function(){var B,A=$H();if(Prototype.Browser.WebKit){B=new Element("div",{style:this}).style}else{String.__parseStyleElement.innerHTML='<div style="'+this+'"></div>';B=String.__parseStyleElement.childNodes[0].style}Element.CSS_PROPERTIES.each(function(C){if(B[C]){A.set(C,B[C])}});if(Prototype.Browser.IE&&this.include("opacity")){A.set("opacity",this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1])}return A};if(document.defaultView&&document.defaultView.getComputedStyle){Element.getStyles=function(B){var A=document.defaultView.getComputedStyle($(B),null);return Element.CSS_PROPERTIES.inject({},function(C,D){C[D]=A[D];return C})}}else{Element.getStyles=function(B){B=$(B);var A=B.currentStyle,C;C=Element.CSS_PROPERTIES.inject({},function(D,E){D[E]=A[E];return D});if(!C.opacity){C.opacity=B.getOpacity()}return C}}Effect.Methods={morph:function(A,B){A=$(A);new Effect.Morph(A,Object.extend({style:B},arguments[2]||{}));return A},visualEffect:function(C,E,B){C=$(C);var D=E.dasherize().camelize(),A=D.charAt(0).toUpperCase()+D.substring(1);new Effect[A](C,B);return C},highlight:function(B,A){B=$(B);new Effect.Highlight(B,A);return B}};$w("fade appear grow shrink fold blindUp blindDown slideUp slideDown pulsate shake puff squish switchOff dropOut").each(function(A){Effect.Methods[A]=function(C,B){C=$(C);Effect[A.charAt(0).toUpperCase()+A.substring(1)](C,B);return C}});$w("getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles").each(function(A){Effect.Methods[A]=Element[A]});Element.addMethods(Effect.Methods);if(Object.isUndefined(Effect)){throw ("dragdrop.js requires including script.aculo.us' effects.js library")}var Droppables={drops:[],remove:function(A){this.drops=this.drops.reject(function(B){return B.element==$(A)})},add:function(B){B=$(B);var A=Object.extend({greedy:true,hoverclass:null,tree:false},arguments[1]||{});if(A.containment){A._containers=[];var C=A.containment;if(Object.isArray(C)){C.each(function(D){A._containers.push($(D))})}else{A._containers.push($(C))}}if(A.accept){A.accept=[A.accept].flatten()}Element.makePositioned(B);A.element=B;this.drops.push(A)},findDeepestChild:function(A){deepest=A[0];for(i=1;i<A.length;++i){if(Element.isParent(A[i].element,deepest.element)){deepest=A[i]}}return deepest},isContained:function(B,A){var C;if(A.tree){C=B.treeNode}else{C=B.parentNode}return A._containers.detect(function(D){return C==D})},isAffected:function(A,C,B){return((B.element!=C)&&((!B._containers)||this.isContained(C,B))&&((!B.accept)||(Element.classNames(C).detect(function(D){return B.accept.include(D)})))&&Position.within(B.element,A[0],A[1]))},deactivate:function(A){if(A.hoverclass){Element.removeClassName(A.element,A.hoverclass)}this.last_active=null},activate:function(A){if(A.hoverclass){Element.addClassName(A.element,A.hoverclass)}this.last_active=A},show:function(A,C){if(!this.drops.length){return }var B,D=[];this.drops.each(function(E){if(Droppables.isAffected(A,C,E)){D.push(E)}});if(D.length>0){B=Droppables.findDeepestChild(D)}if(this.last_active&&this.last_active!=B){this.deactivate(this.last_active)}if(B){Position.within(B.element,A[0],A[1]);if(B.onHover){B.onHover(C,B.element,Position.overlap(B.overlap,B.element))}if(B!=this.last_active){Droppables.activate(B)}}},fire:function(B,A){if(!this.last_active){return }Position.prepare();if(this.isAffected([Event.pointerX(B),Event.pointerY(B)],A,this.last_active)){if(this.last_active.onDrop){this.last_active.onDrop(A,this.last_active.element,B);return true}}},reset:function(){if(this.last_active){this.deactivate(this.last_active)}}};var Draggables={drags:[],observers:[],register:function(A){if(this.drags.length==0){this.eventMouseUp=this.endDrag.bindAsEventListener(this);this.eventMouseMove=this.updateDrag.bindAsEventListener(this);this.eventKeypress=this.keyPress.bindAsEventListener(this);Event.observe(document,"mouseup",this.eventMouseUp);Event.observe(document,"mousemove",this.eventMouseMove);Event.observe(document,"keypress",this.eventKeypress)}this.drags.push(A)},unregister:function(A){this.drags=this.drags.reject(function(B){return B==A});if(this.drags.length==0){Event.stopObserving(document,"mouseup",this.eventMouseUp);Event.stopObserving(document,"mousemove",this.eventMouseMove);Event.stopObserving(document,"keypress",this.eventKeypress)}},activate:function(A){if(A.options.delay){this._timeout=setTimeout(function(){Draggables._timeout=null;window.focus();Draggables.activeDraggable=A}.bind(this),A.options.delay)}else{window.focus();this.activeDraggable=A}},deactivate:function(){this.activeDraggable=null},updateDrag:function(A){if(!this.activeDraggable){return }var B=[Event.pointerX(A),Event.pointerY(A)];if(this._lastPointer&&(this._lastPointer.inspect()==B.inspect())){return }this._lastPointer=B;this.activeDraggable.updateDrag(A,B)},endDrag:function(A){if(this._timeout){clearTimeout(this._timeout);this._timeout=null}if(!this.activeDraggable){return }this._lastPointer=null;this.activeDraggable.endDrag(A);this.activeDraggable=null},keyPress:function(A){if(this.activeDraggable){this.activeDraggable.keyPress(A)}},addObserver:function(A){this.observers.push(A);this._cacheObserverCallbacks()},removeObserver:function(A){this.observers=this.observers.reject(function(B){return B.element==A});this._cacheObserverCallbacks()},notify:function(B,A,C){if(this[B+"Count"]>0){this.observers.each(function(D){if(D[B]){D[B](B,A,C)}})}if(A.options[B]){A.options[B](A,C)}},_cacheObserverCallbacks:function(){["onStart","onEnd","onDrag"].each(function(A){Draggables[A+"Count"]=Draggables.observers.select(function(B){return B[A]}).length})}};var Draggable=Class.create({initialize:function(B){var C={handle:false,reverteffect:function(F,E,D){var G=Math.sqrt(Math.abs(E^2)+Math.abs(D^2))*0.02;new Effect.Move(F,{x:-D,y:-E,duration:G,queue:{scope:"_draggable",position:"end"}})},endeffect:function(E){var D=Object.isNumber(E._opacity)?E._opacity:1;new Effect.Opacity(E,{duration:0.2,from:0.7,to:D,queue:{scope:"_draggable",position:"end"},afterFinish:function(){Draggable._dragging[E]=false}})},zindex:1000,revert:false,quiet:false,scroll:false,scrollSensitivity:20,scrollSpeed:15,snap:false,delay:0};if(!arguments[1]||Object.isUndefined(arguments[1].endeffect)){Object.extend(C,{starteffect:function(D){D._opacity=Element.getOpacity(D);Draggable._dragging[D]=true;new Effect.Opacity(D,{duration:0.2,from:D._opacity,to:0.7})}})}var A=Object.extend(C,arguments[1]||{});this.element=$(B);if(A.handle&&Object.isString(A.handle)){this.handle=this.element.down("."+A.handle,0)}if(!this.handle){this.handle=$(A.handle)}if(!this.handle){this.handle=this.element}if(A.scroll&&!A.scroll.scrollTo&&!A.scroll.outerHTML){A.scroll=$(A.scroll);this._isScrollChild=Element.childOf(this.element,A.scroll)}Element.makePositioned(this.element);this.options=A;this.dragging=false;this.eventMouseDown=this.initDrag.bindAsEventListener(this);Event.observe(this.handle,"mousedown",this.eventMouseDown);Draggables.register(this)},destroy:function(){Event.stopObserving(this.handle,"mousedown",this.eventMouseDown);Draggables.unregister(this)},currentDelta:function(){return([parseInt(Element.getStyle(this.element,"left")||"0"),parseInt(Element.getStyle(this.element,"top")||"0")])},initDrag:function(A){if(!Object.isUndefined(Draggable._dragging[this.element])&&Draggable._dragging[this.element]){return }if(Event.isLeftClick(A)){var C=Event.element(A);if((tag_name=C.tagName.toUpperCase())&&(tag_name=="INPUT"||tag_name=="SELECT"||tag_name=="OPTION"||tag_name=="BUTTON"||tag_name=="TEXTAREA")){return }var B=[Event.pointerX(A),Event.pointerY(A)];var D=this.element.cumulativeOffset();this.offset=[0,1].map(function(E){return(B[E]-D[E])});Draggables.activate(this);Event.stop(A)}},startDrag:function(B){this.dragging=true;if(!this.delta){this.delta=this.currentDelta()}if(this.options.zindex){this.originalZ=parseInt(Element.getStyle(this.element,"z-index")||0);this.element.style.zIndex=this.options.zindex}if(this.options.ghosting){this._clone=this.element.cloneNode(true);this._originallyAbsolute=(this.element.getStyle("position")=="absolute");if(!this._originallyAbsolute){Position.absolutize(this.element)}this.element.parentNode.insertBefore(this._clone,this.element)}if(this.options.scroll){if(this.options.scroll==window){var A=this._getWindowScroll(this.options.scroll);this.originalScrollLeft=A.left;this.originalScrollTop=A.top}else{this.originalScrollLeft=this.options.scroll.scrollLeft;this.originalScrollTop=this.options.scroll.scrollTop}}Draggables.notify("onStart",this,B);if(this.options.starteffect){this.options.starteffect(this.element)}},updateDrag:function(event,pointer){if(!this.dragging){this.startDrag(event)}if(!this.options.quiet){Position.prepare();Droppables.show(pointer,this.element)}Draggables.notify("onDrag",this,event);this.draw(pointer);if(this.options.change){this.options.change(this)}if(this.options.scroll){this.stopScrolling();var p;if(this.options.scroll==window){with(this._getWindowScroll(this.options.scroll)){p=[left,top,left+width,top+height]}}else{p=Position.page(this.options.scroll);p[0]+=this.options.scroll.scrollLeft+Position.deltaX;p[1]+=this.options.scroll.scrollTop+Position.deltaY;p.push(p[0]+this.options.scroll.offsetWidth);p.push(p[1]+this.options.scroll.offsetHeight)}var speed=[0,0];if(pointer[0]<(p[0]+this.options.scrollSensitivity)){speed[0]=pointer[0]-(p[0]+this.options.scrollSensitivity)}if(pointer[1]<(p[1]+this.options.scrollSensitivity)){speed[1]=pointer[1]-(p[1]+this.options.scrollSensitivity)}if(pointer[0]>(p[2]-this.options.scrollSensitivity)){speed[0]=pointer[0]-(p[2]-this.options.scrollSensitivity)}if(pointer[1]>(p[3]-this.options.scrollSensitivity)){speed[1]=pointer[1]-(p[3]-this.options.scrollSensitivity)}this.startScrolling(speed)}if(Prototype.Browser.WebKit){window.scrollBy(0,0)}Event.stop(event)},finishDrag:function(B,E){this.dragging=false;if(this.options.quiet){Position.prepare();var D=[Event.pointerX(B),Event.pointerY(B)];Droppables.show(D,this.element)}if(this.options.ghosting){if(!this._originallyAbsolute){Position.relativize(this.element)}delete this._originallyAbsolute;Element.remove(this._clone);this._clone=null}var F=false;if(E){F=Droppables.fire(B,this.element);if(!F){F=false}}if(F&&this.options.onDropped){this.options.onDropped(this.element)}Draggables.notify("onEnd",this,B);var A=this.options.revert;if(A&&Object.isFunction(A)){A=A(this.element)}var C=this.currentDelta();if(A&&this.options.reverteffect){if(F==0||A!="failure"){this.options.reverteffect(this.element,C[1]-this.delta[1],C[0]-this.delta[0])}}else{this.delta=C}if(this.options.zindex){this.element.style.zIndex=this.originalZ}if(this.options.endeffect){this.options.endeffect(this.element)}Draggables.deactivate(this);Droppables.reset()},keyPress:function(A){if(A.keyCode!=Event.KEY_ESC){return }this.finishDrag(A,false);Event.stop(A)},endDrag:function(A){if(!this.dragging){return }this.stopScrolling();this.finishDrag(A,true);Event.stop(A)},draw:function(A){var F=this.element.cumulativeOffset();if(this.options.ghosting){var C=Position.realOffset(this.element);F[0]+=C[0]-Position.deltaX;F[1]+=C[1]-Position.deltaY}var E=this.currentDelta();F[0]-=E[0];F[1]-=E[1];if(this.options.scroll&&(this.options.scroll!=window&&this._isScrollChild)){F[0]-=this.options.scroll.scrollLeft-this.originalScrollLeft;F[1]-=this.options.scroll.scrollTop-this.originalScrollTop}var D=[0,1].map(function(G){return(A[G]-F[G]-this.offset[G])}.bind(this));if(this.options.snap){if(Object.isFunction(this.options.snap)){D=this.options.snap(D[0],D[1],this)}else{if(Object.isArray(this.options.snap)){D=D.map(function(G,H){return(G/this.options.snap[H]).round()*this.options.snap[H]}.bind(this))}else{D=D.map(function(G){return(G/this.options.snap).round()*this.options.snap}.bind(this))}}}var B=this.element.style;if((!this.options.constraint)||(this.options.constraint=="horizontal")){B.left=D[0]+"px"}if((!this.options.constraint)||(this.options.constraint=="vertical")){B.top=D[1]+"px"}if(B.visibility=="hidden"){B.visibility=""}},stopScrolling:function(){if(this.scrollInterval){clearInterval(this.scrollInterval);this.scrollInterval=null;Draggables._lastScrollPointer=null}},startScrolling:function(A){if(!(A[0]||A[1])){return }this.scrollSpeed=[A[0]*this.options.scrollSpeed,A[1]*this.options.scrollSpeed];this.lastScrolled=new Date();this.scrollInterval=setInterval(this.scroll.bind(this),10)},scroll:function(){var current=new Date();var delta=current-this.lastScrolled;this.lastScrolled=current;if(this.options.scroll==window){with(this._getWindowScroll(this.options.scroll)){if(this.scrollSpeed[0]||this.scrollSpeed[1]){var d=delta/1000;this.options.scroll.scrollTo(left+d*this.scrollSpeed[0],top+d*this.scrollSpeed[1])}}}else{this.options.scroll.scrollLeft+=this.scrollSpeed[0]*delta/1000;this.options.scroll.scrollTop+=this.scrollSpeed[1]*delta/1000}Position.prepare();Droppables.show(Draggables._lastPointer,this.element);Draggables.notify("onDrag",this);if(this._isScrollChild){Draggables._lastScrollPointer=Draggables._lastScrollPointer||$A(Draggables._lastPointer);Draggables._lastScrollPointer[0]+=this.scrollSpeed[0]*delta/1000;Draggables._lastScrollPointer[1]+=this.scrollSpeed[1]*delta/1000;if(Draggables._lastScrollPointer[0]<0){Draggables._lastScrollPointer[0]=0}if(Draggables._lastScrollPointer[1]<0){Draggables._lastScrollPointer[1]=0}this.draw(Draggables._lastScrollPointer)}if(this.options.change){this.options.change(this)}},_getWindowScroll:function(w){var T,L,W,H;with(w.document){if(w.document.documentElement&&documentElement.scrollTop){T=documentElement.scrollTop;L=documentElement.scrollLeft}else{if(w.document.body){T=body.scrollTop;L=body.scrollLeft}}if(w.innerWidth){W=w.innerWidth;H=w.innerHeight}else{if(w.document.documentElement&&documentElement.clientWidth){W=documentElement.clientWidth;H=documentElement.clientHeight}else{W=body.offsetWidth;H=body.offsetHeight}}}return{top:T,left:L,width:W,height:H}}});Draggable._dragging={};var SortableObserver=Class.create({initialize:function(B,A){this.element=$(B);this.observer=A;this.lastValue=Sortable.serialize(this.element)},onStart:function(){this.lastValue=Sortable.serialize(this.element)},onEnd:function(){Sortable.unmark();if(this.lastValue!=Sortable.serialize(this.element)){this.observer(this.element)}}});var Sortable={SERIALIZE_RULE:/^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,sortables:{},_findRootElement:function(A){while(A.tagName.toUpperCase()!="BODY"){if(A.id&&Sortable.sortables[A.id]){return A}A=A.parentNode}},options:function(A){A=Sortable._findRootElement($(A));if(!A){return }return Sortable.sortables[A.id]},destroy:function(A){A=$(A);var B=Sortable.sortables[A.id];if(B){Draggables.removeObserver(B.element);B.droppables.each(function(C){Droppables.remove(C)});B.draggables.invoke("destroy");delete Sortable.sortables[B.element.id]}},create:function(C){C=$(C);var B=Object.extend({element:C,tag:"li",dropOnEmpty:false,tree:false,treeTag:"ul",overlap:"vertical",constraint:"vertical",containment:C,handle:false,only:false,delay:0,hoverclass:null,ghosting:false,quiet:false,scroll:false,scrollSensitivity:20,scrollSpeed:15,format:this.SERIALIZE_RULE,elements:false,handles:false,onChange:Prototype.emptyFunction,onUpdate:Prototype.emptyFunction},arguments[1]||{});this.destroy(C);var A={revert:true,quiet:B.quiet,scroll:B.scroll,scrollSpeed:B.scrollSpeed,scrollSensitivity:B.scrollSensitivity,delay:B.delay,ghosting:B.ghosting,constraint:B.constraint,handle:B.handle};if(B.starteffect){A.starteffect=B.starteffect}if(B.reverteffect){A.reverteffect=B.reverteffect}else{if(B.ghosting){A.reverteffect=function(F){F.style.top=0;F.style.left=0}}}if(B.endeffect){A.endeffect=B.endeffect}if(B.zindex){A.zindex=B.zindex}var D={overlap:B.overlap,containment:B.containment,tree:B.tree,hoverclass:B.hoverclass,onHover:Sortable.onHover};var E={onHover:Sortable.onEmptyHover,overlap:B.overlap,containment:B.containment,hoverclass:B.hoverclass};Element.cleanWhitespace(C);B.draggables=[];B.droppables=[];if(B.dropOnEmpty||B.tree){Droppables.add(C,E);B.droppables.push(C)}(B.elements||this.findElements(C,B)||[]).each(function(H,F){var G=B.handles?$(B.handles[F]):(B.handle?$(H).select("."+B.handle)[0]:H);B.draggables.push(new Draggable(H,Object.extend(A,{handle:G})));Droppables.add(H,D);if(B.tree){H.treeNode=C}B.droppables.push(H)});if(B.tree){(Sortable.findTreeElements(C,B)||[]).each(function(F){Droppables.add(F,E);F.treeNode=C;B.droppables.push(F)})}this.sortables[C.identify()]=B;Draggables.addObserver(new SortableObserver(C,B.onUpdate))},findElements:function(B,A){return Element.findChildren(B,A.only,A.tree?true:false,A.tag)},findTreeElements:function(B,A){return Element.findChildren(B,A.only,A.tree?true:false,A.treeTag)},onHover:function(E,D,A){if(Element.isParent(D,E)){return }if(A>0.33&&A<0.66&&Sortable.options(D).tree){return }else{if(A>0.5){Sortable.mark(D,"before");if(D.previousSibling!=E){var B=E.parentNode;E.style.visibility="hidden";D.parentNode.insertBefore(E,D);if(D.parentNode!=B){Sortable.options(B).onChange(E)}Sortable.options(D.parentNode).onChange(E)}}else{Sortable.mark(D,"after");var C=D.nextSibling||null;if(C!=E){var B=E.parentNode;E.style.visibility="hidden";D.parentNode.insertBefore(E,C);if(D.parentNode!=B){Sortable.options(B).onChange(E)}Sortable.options(D.parentNode).onChange(E)}}}},onEmptyHover:function(E,G,H){var I=E.parentNode;var A=Sortable.options(G);if(!Element.isParent(G,E)){var F;var C=Sortable.findElements(G,{tag:A.tag,only:A.only});var B=null;if(C){var D=Element.offsetSize(G,A.overlap)*(1-H);for(F=0;F<C.length;F+=1){if(D-Element.offsetSize(C[F],A.overlap)>=0){D-=Element.offsetSize(C[F],A.overlap)}else{if(D-(Element.offsetSize(C[F],A.overlap)/2)>=0){B=F+1<C.length?C[F+1]:null;break}else{B=C[F];break}}}}G.insertBefore(E,B);Sortable.options(I).onChange(E);A.onChange(E)}},unmark:function(){if(Sortable._marker){Sortable._marker.hide()}},mark:function(B,A){var D=Sortable.options(B.parentNode);if(D&&!D.ghosting){return }if(!Sortable._marker){Sortable._marker=($("dropmarker")||Element.extend(document.createElement("DIV"))).hide().addClassName("dropmarker").setStyle({position:"absolute"});document.getElementsByTagName("body").item(0).appendChild(Sortable._marker)}var C=B.cumulativeOffset();Sortable._marker.setStyle({left:C[0]+"px",top:C[1]+"px"});if(A=="after"){if(D.overlap=="horizontal"){Sortable._marker.setStyle({left:(C[0]+B.clientWidth)+"px"})}else{Sortable._marker.setStyle({top:(C[1]+B.clientHeight)+"px"})}}Sortable._marker.show()},_tree:function(E,B,F){var D=Sortable.findElements(E,B)||[];for(var C=0;C<D.length;++C){var A=D[C].id.match(B.format);if(!A){continue}var G={id:encodeURIComponent(A?A[1]:null),element:E,parent:F,children:[],position:F.children.length,container:$(D[C]).down(B.treeTag)};if(G.container){this._tree(G.container,B,G)}F.children.push(G)}return F},tree:function(D){D=$(D);var C=this.options(D);var B=Object.extend({tag:C.tag,treeTag:C.treeTag,only:C.only,name:D.id,format:C.format},arguments[1]||{});var A={id:null,parent:null,children:[],container:D,position:0};return Sortable._tree(D,B,A)},_constructIndex:function(B){var A="";do{if(B.id){A="["+B.position+"]"+A}}while((B=B.parent)!=null);return A},sequence:function(B){B=$(B);var A=Object.extend(this.options(B),arguments[1]||{});return $(this.findElements(B,A)||[]).map(function(C){return C.id.match(A.format)?C.id.match(A.format)[1]:""})},setSequence:function(B,C){B=$(B);var A=Object.extend(this.options(B),arguments[2]||{});var D={};this.findElements(B,A).each(function(E){if(E.id.match(A.format)){D[E.id.match(A.format)[1]]=[E,E.parentNode]}E.parentNode.removeChild(E)});C.each(function(E){var F=D[E];if(F){F[1].appendChild(F[0]);delete D[E]}})},serialize:function(C){C=$(C);var B=Object.extend(Sortable.options(C),arguments[1]||{});var A=encodeURIComponent((arguments[1]&&arguments[1].name)?arguments[1].name:C.id);if(B.tree){return Sortable.tree(C,arguments[1]).children.map(function(D){return[A+Sortable._constructIndex(D)+"[id]="+encodeURIComponent(D.id)].concat(D.children.map(arguments.callee))}).flatten().join("&")}else{return Sortable.sequence(C,arguments[1]).map(function(D){return A+"[]="+encodeURIComponent(D)}).join("&")}}};Element.isParent=function(B,A){if(!B.parentNode||B==A){return false}if(B.parentNode==A){return true}return Element.isParent(B.parentNode,A)};Element.findChildren=function(D,B,A,C){if(!D.hasChildNodes()){return null}C=C.toUpperCase();if(B){B=[B].flatten()}var E=[];$A(D.childNodes).each(function(G){if(G.tagName&&G.tagName.toUpperCase()==C&&(!B||(Element.classNames(G).detect(function(H){return B.include(H)})))){E.push(G)}if(A){var F=Element.findChildren(G,B,A,C);if(F){E.push(F)}}});return(E.length>0?E.flatten():[])};Element.offsetSize=function(A,B){return A["offset"+((B=="vertical"||B=="height")?"Height":"Width")]};if(typeof Effect=="undefined"){throw ("controls.js requires including script.aculo.us' effects.js library")}var Autocompleter={};Autocompleter.Base=Class.create({baseInitialize:function(B,C,A){B=$(B);this.element=B;this.update=$(C);this.hasFocus=false;this.changed=false;this.active=false;this.index=0;this.entryCount=0;this.oldElementValue=this.element.value;if(this.setOptions){this.setOptions(A)}else{this.options=A||{}}this.options.paramName=this.options.paramName||this.element.name;this.options.tokens=this.options.tokens||[];this.options.frequency=this.options.frequency||0.4;this.options.minChars=this.options.minChars||1;this.options.onShow=this.options.onShow||function(D,E){if(!E.style.position||E.style.position=="absolute"){E.style.position="absolute";Position.clone(D,E,{setHeight:false,offsetTop:D.offsetHeight})}Effect.Appear(E,{duration:0.15})};this.options.onHide=this.options.onHide||function(D,E){new Effect.Fade(E,{duration:0.15})};if(typeof (this.options.tokens)=="string"){this.options.tokens=new Array(this.options.tokens)}if(!this.options.tokens.include("\n")){this.options.tokens.push("\n")}this.observer=null;this.element.setAttribute("autocomplete","off");Element.hide(this.update);Event.observe(this.element,"blur",this.onBlur.bindAsEventListener(this));Event.observe(this.element,"keydown",this.onKeyPress.bindAsEventListener(this))},show:function(){if(Element.getStyle(this.update,"display")=="none"){this.options.onShow(this.element,this.update)}if(!this.iefix&&(Prototype.Browser.IE)&&(Element.getStyle(this.update,"position")=="absolute")){new Insertion.After(this.update,'<iframe id="'+this.update.id+'_iefix" style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" src="javascript:false;" frameborder="0" scrolling="no"></iframe>');this.iefix=$(this.update.id+"_iefix")}if(this.iefix){setTimeout(this.fixIEOverlapping.bind(this),50)}},fixIEOverlapping:function(){Position.clone(this.update,this.iefix,{setTop:(!this.update.style.height)});this.iefix.style.zIndex=1;this.update.style.zIndex=2;Element.show(this.iefix)},hide:function(){this.stopIndicator();if(Element.getStyle(this.update,"display")!="none"){this.options.onHide(this.element,this.update)}if(this.iefix){Element.hide(this.iefix)}},startIndicator:function(){if(this.options.indicator){Element.show(this.options.indicator)}},stopIndicator:function(){if(this.options.indicator){Element.hide(this.options.indicator)}},onKeyPress:function(A){if(this.active){switch(A.keyCode){case Event.KEY_TAB:case Event.KEY_RETURN:this.selectEntry();Event.stop(A);case Event.KEY_ESC:this.hide();this.active=false;Event.stop(A);return ;case Event.KEY_LEFT:case Event.KEY_RIGHT:return ;case Event.KEY_UP:this.markPrevious();this.render();Event.stop(A);return ;case Event.KEY_DOWN:this.markNext();this.render();Event.stop(A);return }}else{if(A.keyCode==Event.KEY_TAB||A.keyCode==Event.KEY_RETURN||(Prototype.Browser.WebKit>0&&A.keyCode==0)){return }}this.changed=true;this.hasFocus=true;if(this.observer){clearTimeout(this.observer)}this.observer=setTimeout(this.onObserverEvent.bind(this),this.options.frequency*1000)},activate:function(){this.changed=false;this.hasFocus=true;this.getUpdatedChoices()},onHover:function(B){var A=Event.findElement(B,"LI");if(this.index!=A.autocompleteIndex){this.index=A.autocompleteIndex;this.render()}Event.stop(B)},onClick:function(B){var A=Event.findElement(B,"LI");this.index=A.autocompleteIndex;this.selectEntry();this.hide()},onBlur:function(A){setTimeout(this.hide.bind(this),250);this.hasFocus=false;this.active=false},render:function(){if(this.entryCount>0){for(var A=0;A<this.entryCount;A++){this.index==A?Element.addClassName(this.getEntry(A),"selected"):Element.removeClassName(this.getEntry(A),"selected")}if(this.hasFocus){this.show();this.active=true}}else{this.active=false;this.hide()}},markPrevious:function(){if(this.index>0){this.index--}else{this.index=this.entryCount-1}this.getEntry(this.index).scrollIntoView(true)},markNext:function(){if(this.index<this.entryCount-1){this.index++}else{this.index=0}this.getEntry(this.index).scrollIntoView(false)},getEntry:function(A){return this.update.firstChild.childNodes[A]},getCurrentEntry:function(){return this.getEntry(this.index)},selectEntry:function(){this.active=false;this.updateElement(this.getCurrentEntry())},updateElement:function(F){if(this.options.updateElement){this.options.updateElement(F);return }var D="";if(this.options.select){var A=$(F).select("."+this.options.select)||[];if(A.length>0){D=Element.collectTextNodes(A[0],this.options.select)}}else{D=Element.collectTextNodesIgnoreClass(F,"informal")}var C=this.getTokenBounds();if(C[0]!=-1){var E=this.element.value.substr(0,C[0]);var B=this.element.value.substr(C[0]).match(/^\s+/);if(B){E+=B[0]}this.element.value=E+D+this.element.value.substr(C[1])}else{this.element.value=D}this.oldElementValue=this.element.value;this.element.focus();if(this.options.afterUpdateElement){this.options.afterUpdateElement(this.element,F)}},updateChoices:function(C){if(!this.changed&&this.hasFocus){this.update.innerHTML=C;Element.cleanWhitespace(this.update);Element.cleanWhitespace(this.update.down());if(this.update.firstChild&&this.update.down().childNodes){this.entryCount=this.update.down().childNodes.length;for(var A=0;A<this.entryCount;A++){var B=this.getEntry(A);B.autocompleteIndex=A;this.addObservers(B)}}else{this.entryCount=0}this.stopIndicator();this.index=0;if(this.entryCount==1&&this.options.autoSelect){this.selectEntry();this.hide()}else{this.render()}}},addObservers:function(A){Event.observe(A,"mouseover",this.onHover.bindAsEventListener(this));Event.observe(A,"click",this.onClick.bindAsEventListener(this))},onObserverEvent:function(){this.changed=false;this.tokenBounds=null;if(this.getToken().length>=this.options.minChars){this.getUpdatedChoices()}else{this.active=false;this.hide()}this.oldElementValue=this.element.value},getToken:function(){var A=this.getTokenBounds();return this.element.value.substring(A[0],A[1]).strip()},getTokenBounds:function(){if(null!=this.tokenBounds){return this.tokenBounds}var E=this.element.value;if(E.strip().empty()){return[-1,0]}var F=arguments.callee.getFirstDifferencePos(E,this.oldElementValue);var H=(F==this.oldElementValue.length?1:0);var D=-1,C=E.length;var G;for(var B=0,A=this.options.tokens.length;B<A;++B){G=E.lastIndexOf(this.options.tokens[B],F+H-1);if(G>D){D=G}G=E.indexOf(this.options.tokens[B],F+H);if(-1!=G&&G<C){C=G}}return(this.tokenBounds=[D+1,C])}});Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos=function(C,A){var D=Math.min(C.length,A.length);for(var B=0;B<D;++B){if(C[B]!=A[B]){return B}}return D};Ajax.Autocompleter=Class.create(Autocompleter.Base,{initialize:function(C,D,B,A){this.baseInitialize(C,D,A);this.options.asynchronous=true;this.options.onComplete=this.onComplete.bind(this);this.options.defaultParams=this.options.parameters||null;this.url=B},getUpdatedChoices:function(){this.startIndicator();var A=encodeURIComponent(this.options.paramName)+"="+encodeURIComponent(this.getToken());this.options.parameters=this.options.callback?this.options.callback(this.element,A):A;if(this.options.defaultParams){this.options.parameters+="&"+this.options.defaultParams}new Ajax.Request(this.url,this.options)},onComplete:function(A){this.updateChoices(A.responseText)}});Autocompleter.Local=Class.create(Autocompleter.Base,{initialize:function(B,D,C,A){this.baseInitialize(B,D,A);this.options.array=C},getUpdatedChoices:function(){this.updateChoices(this.options.selector(this))},setOptions:function(A){this.options=Object.extend({choices:10,partialSearch:true,partialChars:2,ignoreCase:true,fullSearch:false,selector:function(B){var D=[];var C=[];var H=B.getToken();var G=0;for(var E=0;E<B.options.array.length&&D.length<B.options.choices;E++){var F=B.options.array[E];var I=B.options.ignoreCase?F.toLowerCase().indexOf(H.toLowerCase()):F.indexOf(H);while(I!=-1){if(I==0&&F.length!=H.length){D.push("<li><strong>"+F.substr(0,H.length)+"</strong>"+F.substr(H.length)+"</li>");break}else{if(H.length>=B.options.partialChars&&B.options.partialSearch&&I!=-1){if(B.options.fullSearch||/\s/.test(F.substr(I-1,1))){C.push("<li>"+F.substr(0,I)+"<strong>"+F.substr(I,H.length)+"</strong>"+F.substr(I+H.length)+"</li>");break}}}I=B.options.ignoreCase?F.toLowerCase().indexOf(H.toLowerCase(),I+1):F.indexOf(H,I+1)}}if(C.length){D=D.concat(C.slice(0,B.options.choices-D.length))}return"<ul>"+D.join("")+"</ul>"}},A||{})}});Field.scrollFreeActivate=function(A){setTimeout(function(){Field.activate(A)},1)};Ajax.InPlaceEditor=Class.create({initialize:function(C,B,A){this.url=B;this.element=C=$(C);this.prepareOptions();this._controls={};arguments.callee.dealWithDeprecatedOptions(A);Object.extend(this.options,A||{});if(!this.options.formId&&this.element.id){this.options.formId=this.element.id+"-inplaceeditor";if($(this.options.formId)){this.options.formId=""}}if(this.options.externalControl){this.options.externalControl=$(this.options.externalControl)}if(!this.options.externalControl){this.options.externalControlOnly=false}this._originalBackground=this.element.getStyle("background-color")||"transparent";this.element.title=this.options.clickToEditText;this._boundCancelHandler=this.handleFormCancellation.bind(this);this._boundComplete=(this.options.onComplete||Prototype.emptyFunction).bind(this);this._boundFailureHandler=this.handleAJAXFailure.bind(this);this._boundSubmitHandler=this.handleFormSubmission.bind(this);this._boundWrapperHandler=this.wrapUp.bind(this);this.registerListeners()},checkForEscapeOrReturn:function(A){if(!this._editing||A.ctrlKey||A.altKey||A.shiftKey){return }if(Event.KEY_ESC==A.keyCode){this.handleFormCancellation(A)}else{if(Event.KEY_RETURN==A.keyCode){this.handleFormSubmission(A)}}},createControl:function(G,C,B){var E=this.options[G+"Control"];var F=this.options[G+"Text"];if("button"==E){var A=document.createElement("input");A.type="submit";A.value=F;A.className="editor_"+G+"_button";if("cancel"==G){A.onclick=this._boundCancelHandler}this._form.appendChild(A);this._controls[G]=A}else{if("link"==E){var D=document.createElement("a");D.href="#";D.appendChild(document.createTextNode(F));D.onclick="cancel"==G?this._boundCancelHandler:this._boundSubmitHandler;D.className="editor_"+G+"_link";if(B){D.className+=" "+B}this._form.appendChild(D);this._controls[G]=D}}},createEditField:function(){var C=(this.options.loadTextURL?this.options.loadingText:this.getText());var B;if(1>=this.options.rows&&!/\r|\n/.test(this.getText())){B=document.createElement("input");B.type="text";var A=this.options.size||this.options.cols||0;if(0<A){B.size=A}}else{B=document.createElement("textarea");B.rows=(1>=this.options.rows?this.options.autoRows:this.options.rows);B.cols=this.options.cols||40}B.name=this.options.paramName;B.value=C;B.className="editor_field";if(this.options.submitOnBlur){B.onblur=this._boundSubmitHandler}this._controls.editor=B;if(this.options.loadTextURL){this.loadExternalText()}this._form.appendChild(this._controls.editor)},createForm:function(){var B=this;function A(D,E){var C=B.options["text"+D+"Controls"];if(!C||E===false){return }B._form.appendChild(document.createTextNode(C))}this._form=$(document.createElement("form"));this._form.id=this.options.formId;this._form.addClassName(this.options.formClassName);this._form.onsubmit=this._boundSubmitHandler;this.createEditField();if("textarea"==this._controls.editor.tagName.toLowerCase()){this._form.appendChild(document.createElement("br"))}if(this.options.onFormCustomization){this.options.onFormCustomization(this,this._form)}A("Before",this.options.okControl||this.options.cancelControl);this.createControl("ok",this._boundSubmitHandler);A("Between",this.options.okControl&&this.options.cancelControl);this.createControl("cancel",this._boundCancelHandler,"editor_cancel");A("After",this.options.okControl||this.options.cancelControl)},destroy:function(){if(this._oldInnerHTML){this.element.innerHTML=this._oldInnerHTML}this.leaveEditMode();this.unregisterListeners()},enterEditMode:function(A){if(this._saving||this._editing){return }this._editing=true;this.triggerCallback("onEnterEditMode");if(this.options.externalControl){this.options.externalControl.hide()}this.element.hide();this.createForm();this.element.parentNode.insertBefore(this._form,this.element);if(!this.options.loadTextURL){this.postProcessEditField()}if(A){Event.stop(A)}},enterHover:function(A){if(this.options.hoverClassName){this.element.addClassName(this.options.hoverClassName)}if(this._saving){return }this.triggerCallback("onEnterHover")},getText:function(){return this.element.innerHTML.unescapeHTML()},handleAJAXFailure:function(A){this.triggerCallback("onFailure",A);if(this._oldInnerHTML){this.element.innerHTML=this._oldInnerHTML;this._oldInnerHTML=null}},handleFormCancellation:function(A){this.wrapUp();if(A){Event.stop(A)}},handleFormSubmission:function(D){var B=this._form;var C=$F(this._controls.editor);this.prepareSubmission();var E=this.options.callback(B,C)||"";if(Object.isString(E)){E=E.toQueryParams()}E.editorId=this.element.id;if(this.options.htmlResponse){var A=Object.extend({evalScripts:true},this.options.ajaxOptions);Object.extend(A,{parameters:E,onComplete:this._boundWrapperHandler,onFailure:this._boundFailureHandler});new Ajax.Updater({success:this.element},this.url,A)}else{var A=Object.extend({method:"get"},this.options.ajaxOptions);Object.extend(A,{parameters:E,onComplete:this._boundWrapperHandler,onFailure:this._boundFailureHandler});new Ajax.Request(this.url,A)}if(D){Event.stop(D)}},leaveEditMode:function(){this.element.removeClassName(this.options.savingClassName);this.removeForm();this.leaveHover();this.element.style.backgroundColor=this._originalBackground;this.element.show();if(this.options.externalControl){this.options.externalControl.show()}this._saving=false;this._editing=false;this._oldInnerHTML=null;this.triggerCallback("onLeaveEditMode")},leaveHover:function(A){if(this.options.hoverClassName){this.element.removeClassName(this.options.hoverClassName)}if(this._saving){return }this.triggerCallback("onLeaveHover")},loadExternalText:function(){this._form.addClassName(this.options.loadingClassName);this._controls.editor.disabled=true;var A=Object.extend({method:"get"},this.options.ajaxOptions);Object.extend(A,{parameters:"editorId="+encodeURIComponent(this.element.id),onComplete:Prototype.emptyFunction,onSuccess:function(C){this._form.removeClassName(this.options.loadingClassName);var B=C.responseText;if(this.options.stripLoadedTextTags){B=B.stripTags()}this._controls.editor.value=B;this._controls.editor.disabled=false;this.postProcessEditField()}.bind(this),onFailure:this._boundFailureHandler});new Ajax.Request(this.options.loadTextURL,A)},postProcessEditField:function(){var A=this.options.fieldPostCreation;if(A){$(this._controls.editor)["focus"==A?"focus":"activate"]()}},prepareOptions:function(){this.options=Object.clone(Ajax.InPlaceEditor.DefaultOptions);Object.extend(this.options,Ajax.InPlaceEditor.DefaultCallbacks);[this._extraDefaultOptions].flatten().compact().each(function(A){Object.extend(this.options,A)}.bind(this))},prepareSubmission:function(){this._saving=true;this.removeForm();this.leaveHover();this.showSaving()},registerListeners:function(){this._listeners={};var A;$H(Ajax.InPlaceEditor.Listeners).each(function(B){A=this[B.value].bind(this);this._listeners[B.key]=A;if(!this.options.externalControlOnly){this.element.observe(B.key,A)}if(this.options.externalControl){this.options.externalControl.observe(B.key,A)}}.bind(this))},removeForm:function(){if(!this._form){return }this._form.remove();this._form=null;this._controls={}},showSaving:function(){this._oldInnerHTML=this.element.innerHTML;this.element.innerHTML=this.options.savingText;this.element.addClassName(this.options.savingClassName);this.element.style.backgroundColor=this._originalBackground;this.element.show()},triggerCallback:function(B,A){if("function"==typeof this.options[B]){this.options[B](this,A)}},unregisterListeners:function(){$H(this._listeners).each(function(A){if(!this.options.externalControlOnly){this.element.stopObserving(A.key,A.value)}if(this.options.externalControl){this.options.externalControl.stopObserving(A.key,A.value)}}.bind(this))},wrapUp:function(A){this.leaveEditMode();this._boundComplete(A,this.element)}});Object.extend(Ajax.InPlaceEditor.prototype,{dispose:Ajax.InPlaceEditor.prototype.destroy});Ajax.InPlaceCollectionEditor=Class.create(Ajax.InPlaceEditor,{initialize:function($super,C,B,A){this._extraDefaultOptions=Ajax.InPlaceCollectionEditor.DefaultOptions;$super(C,B,A)},createEditField:function(){var A=document.createElement("select");A.name=this.options.paramName;A.size=1;this._controls.editor=A;this._collection=this.options.collection||[];if(this.options.loadCollectionURL){this.loadCollection()}else{this.checkForExternalText()}this._form.appendChild(this._controls.editor)},loadCollection:function(){this._form.addClassName(this.options.loadingClassName);this.showLoadingText(this.options.loadingCollectionText);var options=Object.extend({method:"get"},this.options.ajaxOptions);Object.extend(options,{parameters:"editorId="+encodeURIComponent(this.element.id),onComplete:Prototype.emptyFunction,onSuccess:function(transport){var js=transport.responseText.strip();if(!/^\[.*\]$/.test(js)){throw ("Server returned an invalid collection representation.")}this._collection=eval(js);this.checkForExternalText()}.bind(this),onFailure:this.onFailure});new Ajax.Request(this.options.loadCollectionURL,options)},showLoadingText:function(B){this._controls.editor.disabled=true;var A=this._controls.editor.firstChild;if(!A){A=document.createElement("option");A.value="";this._controls.editor.appendChild(A);A.selected=true}A.update((B||"").stripScripts().stripTags())},checkForExternalText:function(){this._text=this.getText();if(this.options.loadTextURL){this.loadExternalText()}else{this.buildOptionList()}},loadExternalText:function(){this.showLoadingText(this.options.loadingText);var A=Object.extend({method:"get"},this.options.ajaxOptions);Object.extend(A,{parameters:"editorId="+encodeURIComponent(this.element.id),onComplete:Prototype.emptyFunction,onSuccess:function(B){this._text=B.responseText.strip();this.buildOptionList()}.bind(this),onFailure:this.onFailure});new Ajax.Request(this.options.loadTextURL,A)},buildOptionList:function(){this._form.removeClassName(this.options.loadingClassName);this._collection=this._collection.map(function(D){return 2===D.length?D:[D,D].flatten()});var B=("value" in this.options)?this.options.value:this._text;var A=this._collection.any(function(D){return D[0]==B}.bind(this));this._controls.editor.update("");var C;this._collection.each(function(E,D){C=document.createElement("option");C.value=E[0];C.selected=A?E[0]==B:0==D;C.appendChild(document.createTextNode(E[1]));this._controls.editor.appendChild(C)}.bind(this));this._controls.editor.disabled=false;Field.scrollFreeActivate(this._controls.editor)}});Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions=function(A){if(!A){return }function B(C,D){if(C in A||D===undefined){return }A[C]=D}B("cancelControl",(A.cancelLink?"link":(A.cancelButton?"button":A.cancelLink==A.cancelButton==false?false:undefined)));B("okControl",(A.okLink?"link":(A.okButton?"button":A.okLink==A.okButton==false?false:undefined)));B("highlightColor",A.highlightcolor);B("highlightEndColor",A.highlightendcolor)};Object.extend(Ajax.InPlaceEditor,{DefaultOptions:{ajaxOptions:{},autoRows:3,cancelControl:"link",cancelText:"cancel",clickToEditText:"Click to edit",externalControl:null,externalControlOnly:false,fieldPostCreation:"activate",formClassName:"inplaceeditor-form",formId:null,highlightColor:"#ffff99",highlightEndColor:"#ffffff",hoverClassName:"",htmlResponse:true,loadingClassName:"inplaceeditor-loading",loadingText:"Loading...",okControl:"button",okText:"ok",paramName:"value",rows:1,savingClassName:"inplaceeditor-saving",savingText:"Saving...",size:0,stripLoadedTextTags:false,submitOnBlur:false,textAfterControls:"",textBeforeControls:"",textBetweenControls:""},DefaultCallbacks:{callback:function(A){return Form.serialize(A)},onComplete:function(B,A){new Effect.Highlight(A,{startcolor:this.options.highlightColor,keepBackgroundImage:true})},onEnterEditMode:null,onEnterHover:function(A){A.element.style.backgroundColor=A.options.highlightColor;if(A._effect){A._effect.cancel()}},onFailure:function(B,A){alert("Error communication with the server: "+B.responseText.stripTags())},onFormCustomization:null,onLeaveEditMode:null,onLeaveHover:function(A){A._effect=new Effect.Highlight(A.element,{startcolor:A.options.highlightColor,endcolor:A.options.highlightEndColor,restorecolor:A._originalBackground,keepBackgroundImage:true})}},Listeners:{click:"enterEditMode",keydown:"checkForEscapeOrReturn",mouseover:"enterHover",mouseout:"leaveHover"}});Ajax.InPlaceCollectionEditor.DefaultOptions={loadingCollectionText:"Loading options..."};Form.Element.DelayedObserver=Class.create({initialize:function(B,A,C){this.delay=A||0.5;this.element=$(B);this.callback=C;this.timer=null;this.lastValue=$F(this.element);Event.observe(this.element,"keyup",this.delayedListener.bindAsEventListener(this))},delayedListener:function(A){if(this.lastValue==$F(this.element)){return }if(this.timer){clearTimeout(this.timer)}this.timer=setTimeout(this.onTimerEvent.bind(this),this.delay*1000);this.lastValue=$F(this.element)},onTimerEvent:function(){this.timer=null;this.callback(this.element,$F(this.element))}});if(!Control){var Control={}}Control.Slider=Class.create({initialize:function(D,A,B){var C=this;if(Object.isArray(D)){this.handles=D.collect(function(E){return $(E)})}else{this.handles=[$(D)]}this.track=$(A);this.options=B||{};this.axis=this.options.axis||"horizontal";this.increment=this.options.increment||1;this.step=parseInt(this.options.step||"1");this.range=this.options.range||$R(0,1);this.value=0;this.values=this.handles.map(function(){return 0});this.spans=this.options.spans?this.options.spans.map(function(E){return $(E)}):false;this.options.startSpan=$(this.options.startSpan||null);this.options.endSpan=$(this.options.endSpan||null);this.restricted=this.options.restricted||false;this.maximum=this.options.maximum||this.range.end;this.minimum=this.options.minimum||this.range.start;this.alignX=parseInt(this.options.alignX||"0");this.alignY=parseInt(this.options.alignY||"0");this.trackLength=this.maximumOffset()-this.minimumOffset();this.handleLength=this.isVertical()?(this.handles[0].offsetHeight!=0?this.handles[0].offsetHeight:this.handles[0].style.height.replace(/px$/,"")):(this.handles[0].offsetWidth!=0?this.handles[0].offsetWidth:this.handles[0].style.width.replace(/px$/,""));this.active=false;this.dragging=false;this.disabled=false;if(this.options.disabled){this.setDisabled()}this.allowedValues=this.options.values?this.options.values.sortBy(Prototype.K):false;if(this.allowedValues){this.minimum=this.allowedValues.min();this.maximum=this.allowedValues.max()}this.eventMouseDown=this.startDrag.bindAsEventListener(this);this.eventMouseUp=this.endDrag.bindAsEventListener(this);this.eventMouseMove=this.update.bindAsEventListener(this);this.handles.each(function(F,E){E=C.handles.length-1-E;C.setValue(parseFloat((Object.isArray(C.options.sliderValue)?C.options.sliderValue[E]:C.options.sliderValue)||C.range.start),E);F.makePositioned().observe("mousedown",C.eventMouseDown)});this.track.observe("mousedown",this.eventMouseDown);document.observe("mouseup",this.eventMouseUp);document.observe("mousemove",this.eventMouseMove);this.initialized=true},dispose:function(){var A=this;Event.stopObserving(this.track,"mousedown",this.eventMouseDown);Event.stopObserving(document,"mouseup",this.eventMouseUp);Event.stopObserving(document,"mousemove",this.eventMouseMove);this.handles.each(function(B){Event.stopObserving(B,"mousedown",A.eventMouseDown)})},setDisabled:function(){this.disabled=true},setEnabled:function(){this.disabled=false},getNearestValue:function(A){if(this.allowedValues){if(A>=this.allowedValues.max()){return(this.allowedValues.max())}if(A<=this.allowedValues.min()){return(this.allowedValues.min())}var C=Math.abs(this.allowedValues[0]-A);var B=this.allowedValues[0];this.allowedValues.each(function(D){var E=Math.abs(D-A);if(E<=C){B=D;C=E}});return B}if(A>this.range.end){return this.range.end}if(A<this.range.start){return this.range.start}return A},setValue:function(B,A){if(!this.active){this.activeHandleIdx=A||0;this.activeHandle=this.handles[this.activeHandleIdx];this.updateStyles()}A=A||this.activeHandleIdx||0;if(this.initialized&&this.restricted){if((A>0)&&(B<this.values[A-1])){B=this.values[A-1]}if((A<(this.handles.length-1))&&(B>this.values[A+1])){B=this.values[A+1]}}B=this.getNearestValue(B);this.values[A]=B;this.value=this.values[0];this.handles[A].style[this.isVertical()?"top":"left"]=this.translateToPx(B);this.drawSpans();if(!this.dragging||!this.event){this.updateFinished()}},setValueBy:function(B,A){this.setValue(this.values[A||this.activeHandleIdx||0]+B,A||this.activeHandleIdx||0)},translateToPx:function(A){return Math.round(((this.trackLength-this.handleLength)/(this.range.end-this.range.start))*(A-this.range.start))+"px"},translateToValue:function(A){return((A/(this.trackLength-this.handleLength)*(this.range.end-this.range.start))+this.range.start)},getRange:function(B){var A=this.values.sortBy(Prototype.K);B=B||0;return $R(A[B],A[B+1])},minimumOffset:function(){return(this.isVertical()?this.alignY:this.alignX)},maximumOffset:function(){return(this.isVertical()?(this.track.offsetHeight!=0?this.track.offsetHeight:this.track.style.height.replace(/px$/,""))-this.alignY:(this.track.offsetWidth!=0?this.track.offsetWidth:this.track.style.width.replace(/px$/,""))-this.alignX)},isVertical:function(){return(this.axis=="vertical")},drawSpans:function(){var A=this;if(this.spans){$R(0,this.spans.length-1).each(function(B){A.setSpan(A.spans[B],A.getRange(B))})}if(this.options.startSpan){this.setSpan(this.options.startSpan,$R(0,this.values.length>1?this.getRange(0).min():this.value))}if(this.options.endSpan){this.setSpan(this.options.endSpan,$R(this.values.length>1?this.getRange(this.spans.length-1).max():this.value,this.maximum))}},setSpan:function(B,A){if(this.isVertical()){B.style.top=this.translateToPx(A.start);B.style.height=this.translateToPx(A.end-A.start+this.range.start)}else{B.style.left=this.translateToPx(A.start);B.style.width=this.translateToPx(A.end-A.start+this.range.start)}},updateStyles:function(){this.handles.each(function(A){Element.removeClassName(A,"selected")});Element.addClassName(this.activeHandle,"selected")},startDrag:function(C){if(Event.isLeftClick(C)){if(!this.disabled){this.active=true;var D=Event.element(C);var E=[Event.pointerX(C),Event.pointerY(C)];var A=D;if(A==this.track){var B=this.track.cumulativeOffset();this.event=C;this.setValue(this.translateToValue((this.isVertical()?E[1]-B[1]:E[0]-B[0])-(this.handleLength/2)));var B=this.activeHandle.cumulativeOffset();this.offsetX=(E[0]-B[0]);this.offsetY=(E[1]-B[1])}else{while((this.handles.indexOf(D)==-1)&&D.parentNode){D=D.parentNode}if(this.handles.indexOf(D)!=-1){this.activeHandle=D;this.activeHandleIdx=this.handles.indexOf(this.activeHandle);this.updateStyles();var B=this.activeHandle.cumulativeOffset();this.offsetX=(E[0]-B[0]);this.offsetY=(E[1]-B[1])}}}Event.stop(C)}},update:function(A){if(this.active){if(!this.dragging){this.dragging=true}this.draw(A);if(Prototype.Browser.WebKit){window.scrollBy(0,0)}Event.stop(A)}},draw:function(B){var C=[Event.pointerX(B),Event.pointerY(B)];var A=this.track.cumulativeOffset();C[0]-=this.offsetX+A[0];C[1]-=this.offsetY+A[1];this.event=B;this.setValue(this.translateToValue(this.isVertical()?C[1]:C[0]));if(this.initialized&&this.options.onSlide){this.options.onSlide(this.values.length>1?this.values:this.value,this)}},endDrag:function(A){if(this.active&&this.dragging){this.finishDrag(A,true);Event.stop(A)}this.active=false;this.dragging=false},finishDrag:function(A,B){this.active=false;this.dragging=false;this.updateFinished()},updateFinished:function(){if(this.initialized&&this.options.onChange){this.options.onChange(this.values.length>1?this.values:this.value,this)}this.event=null}});Sound={tracks:{},_enabled:true,template:new Template('<embed style="height:0" id="sound_#{track}_#{id}" src="#{url}" loop="false" autostart="true" hidden="true"/>'),enable:function(){Sound._enabled=true},disable:function(){Sound._enabled=false},play:function(B){if(!Sound._enabled){return }var A=Object.extend({track:"global",url:B,replace:false},arguments[1]||{});if(A.replace&&this.tracks[A.track]){$R(0,this.tracks[A.track].id).each(function(D){var C=$("sound_"+A.track+"_"+D);C.Stop&&C.Stop();C.remove()});this.tracks[A.track]=null}if(!this.tracks[A.track]){this.tracks[A.track]={id:0}}else{this.tracks[A.track].id++}A.id=this.tracks[A.track].id;$$("body")[0].insert(Prototype.Browser.IE?new Element("bgsound",{id:"sound_"+A.track+"_"+A.id,src:A.url,loop:1,autostart:true}):Sound.template.evaluate(A))}};if(Prototype.Browser.Gecko&&navigator.userAgent.indexOf("Win")>0){if(navigator.plugins&&$A(navigator.plugins).detect(function(A){return A.name.indexOf("QuickTime")!=-1})){Sound.template=new Template('<object id="sound_#{track}_#{id}" width="0" height="0" type="audio/mpeg" data="#{url}"/>')}else{if(navigator.plugins&&$A(navigator.plugins).detect(function(A){return A.name.indexOf("Windows Media")!=-1})){Sound.template=new Template('<object id="sound_#{track}_#{id}" type="application/x-mplayer2" data="#{url}"></object>')}else{if(navigator.plugins&&$A(navigator.plugins).detect(function(A){return A.name.indexOf("RealPlayer")!=-1})){Sound.template=new Template('<embed type="audio/x-pn-realaudio-plugin" style="height:0" id="sound_#{track}_#{id}" src="#{url}" loop="false" autostart="true" hidden="true"/>')}else{Sound.play=function(){}}}}};
/*
Package: Utilities
	Utility functions and extensions to native javascript functionality.
*/

// create empty console.log function when console object is not available
if (typeof window.console == "undefined") window.console = {log : function(){}};

IPE.require = function(url) {
	if (!this.loadedScripts) this.loadedScripts = [];
	if (!this.loadedScripts[url]){
		this.loadedScripts[url] = 1;
		document.write('<script type="text/javascript" src="' + IPE.appPath + IPE.IPEPath + url+ '"></script>');
	}
};

// set IE flags
(function(){
	if (Prototype.Browser.IE){
		var ua = navigator.userAgent;
		
		/*
		Property: Prototype.Browser.IE6
			Boolean indicating whether browser is IE 6 or not.
		*/
		Prototype.Browser.IE6 = !!(window.ActiveXObject && !window.XMLHttpRequest);
		
		/*
		Property: Prototype.Browser.ltIE8
			Boolean indicating whether browser is IE version lower than 8.
		*/
		Prototype.Browser.ltIE8 = (ua.include("MSIE 7") || ua.include("MSIE 6"));
	}
})();

/*
Section: Element
	Extensions to the Element class of prototype.js
*/
Element.addMethods({
	
	/*
	Property: Element.wrapContent
		Wraps provided element with provided tag or span.
		
	Arguments:
		element - (Object) Elements that needs to be wrapped.
		tag - (string) Tag to wrap element with, defaults to span.
	*/
	wrapContent: function(element,tag){
		element = $(element);
		tag = tag || "span";
		var span = new Element(tag);
		$A(element.childNodes).each(function(node){
			span.appendChild(node);
		});
		element.appendChild(span);
		return element;
	},
	
	/*
	Property: Element.replaceTag
		Replaces tag name of an element. Optionally adds class name.
		
	Arguments:
		element - (Object) Elementsof which tag needs to be replaced.
		tag - (string) New tag name.
		className - (string) Optional class name to add to the element.
		
	Usage:
		:$("elementId").replaceTag("p","myClass")
	*/
	replaceTag: function(element,tag,className){
		var replacement = new Element(tag);
		var id = element.id;
		if (className){
			replacement.addClassName(className);
		}
		element.insert({before: replacement});
		$A(element.childNodes).each(function(node){
			replacement.appendChild(node);
		});
		element.remove();
		if (id) replacement.id = id;
		return replacement;
	},
	
	/*
	Property: Element.eliminate
		Delete custom property from an element. Uses <Element.Storage>.
		
	Arguments:
		key - (string) Name of property to delete.
	*/
	eliminate: function(element, key){
		if (!(element = $(element))) return;
		var hash = Element.getStorage(element);
		var value = hash.get(key);
		if (!Object.isUndefined(value)) {
			hash.unset(key);
		}
		return element;
	},
	
	/*
	Property: Element.getMetrics
		Return width, height, top and left properties of element
	*/
	getMetrics: function(element){
		var pos = element.cumulativeOffset();
		var dim = element.getDimensions();
		return {x:pos.left, y:pos.top, w:dim.width, h:dim.height};
	}
});

/* 
Section: Input
	Add check and uncheck methods to checkboxes and radiobuttons
*/
Element.addMethods("input", {
	/*
	Property: Element.check
		Checks a radio button or checkbox.
	*/
	check : function(element){
		if (element.type == "checkbox" || element.type == "radio"){
			element.checked = true;
			element.simulate("change");
		}
		return element;
	},
	/*
	Property: Element.uncheck
		Unchecks a radio button or checkbox.
	*/
	uncheck : function(element){
		if (element.type == "checkbox" || element.type == "radio"){
			element.checked = false;
			element.simulate("change");
		}
		return element;
	}
});

/* 
Section: Button
	Button methods
*/
Element.addMethods("button", {
	/*
	Property: Element.getValue
		Returns value of a button.
	*/
	getValue : function(element){
		var value = element.readAttribute("value");
		
		// fix for IE that can't read value attribute properly
		if (element.outerHTML && element.innerHTML && value == element.innerHTML){
			value = element.outerHTML.match(/value\=(?:\"|\')(.+?)(?:\"|\')/);
			value = value ? value[1] : null;
		}
		
		return value;
	}
});

/*
Section: String
	Extensions to String class
*/
Object.extend(String.prototype, {
	/*
	Method: String.toObject
		Return object with provided string reference
		
	Arguments:
		object - (object) Optional object to find reference in. Default is window object.
	*/
	toObject : function(object){
		var ctx = object || window;
		var expr = this;
		var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/, match = pattern.exec(expr);
		if (match == null) return '';
		
		while (match != null) {
			var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1];
			ctx = ctx[comp];
			if (null == ctx || '' == match[3]) break;
			expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
			match = pattern.exec(expr);
		}
		return ctx;
	},
	
	/*
	Method: String.toSeconds
		Return seconds float from string "123s" or "123ms".
	*/
	toSeconds : function(){
		var time = parseFloat(this);
		var multiplier = this.match(/ms$/) ? .001 : 1;
		return multiplier * time;
	},
	
	/*
	Method: String.toMilliseconds
		Return milliseconds float from string "123s" or "123ms".
	*/
	toMilliseconds : function(){
		var time = parseFloat(this);
		var multiplier = this.match(/\ds$/) ? 1000 : 1;
		return multiplier * time;
	}
});

/*
Package: Cookie
	Cookie functions, based on cookie script from Peter-Paul Koch (http://www.quirksmode.org).
*/
var Cookie = {
	/*
	Property: path
		Default path for cookies
	*/
	path : "/",
	
	/*
	Property: extension
		Extension to add to cookie names. Usefull for local use to prevent different prototypes from reading eachothers cookies.
	*/
	extension : "",
	
	/*
	Method: create
		Create a cookie.
	
	Arguments:
		name - (string) Name of the cookie.
		value - (string) Value to store in the cookie.
		days - (number) Number of days to keep the cookie.
		path - (string) Cookie path.
	*/
	create : function (name,value,days,path){
		if (days){
			var date = new Date();
			date.setTime(date.getTime()+(days*24*60*60*1000));
			var expires = "; expires="+date.toGMTString();
		}
		else var expires = "";
		document.cookie = name+Cookie.extension+"="+value+expires+"; path="+Cookie.path;
	},
	
	/*
	Method: create
		Retrieve a cookie.
	
	Arguments:
		name - (string) Name of the cookie to retrieve.
	*/
	read : function (name)	{
		var nameEQ = name + Cookie.extension + "=";
		var ca = document.cookie.split(';');
		for(var i=0;i < ca.length;i++)	{
			var c = ca[i];
			while (c.charAt(0)==' ') c = c.substring(1,c.length);
			if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
		}
		return null;
	},
	
	/*
	Method: erase
		Erase a cookie.
	
	Arguments:
		name - (string) Name of the cookie to erase.
	*/
	erase : function (name){
		Cookie.create(name,"",-1);
	}
};

/*
Package: Event
	Event extensions.
	From: http://github.com/kangax/protolicious/

Method: Event.simulate
	Simulates an event.

Arguments:
	element - (object) Element to fire event on.
	eventName - (string) Name of event to fire (only MouseEvents and HTMLEvents interfaces are supported).
	options - (object) Optional object to fine-tune event properties - pointerX, pointerY, ctrlKey, etc.
	
Usage:
	$('foo').simulate('click'); // => fires "click" event on an element with id=foo
*/

(function(){
  
  	var eventMatchers = {
		'HTMLEvents': /^(?:load|unload|abort|error|select|change|submit|reset|focus|blur|resize|scroll)$/,
		'MouseEvents': /^(?:click|mouse(?:down|up|over|move|out))$/
	};
	var defaultOptions = {
		pointerX: 0,
		pointerY: 0,
		button: 0,
		ctrlKey: false,
		altKey: false,
		shiftKey: false,
		metaKey: false,
		bubbles: true,
		cancelable: true
	};

	Event.simulate = function(element, eventName) {
		var options = Object.extend(defaultOptions, arguments[2] || { });
		var oEvent, eventType = null;

		element = $(element);

		for (var name in eventMatchers) {
			if (eventMatchers[name].test(eventName)) { eventType = name; break; }
		}

		if (!eventType){
			throw new SyntaxError('Only HTMLEvents and MouseEvents interfaces are supported');
		}
		
		if (document.createEvent) {
			oEvent = document.createEvent(eventType);
			if (eventType == 'HTMLEvents') {
				oEvent.initEvent(eventName, options.bubbles, options.cancelable);
			} else {
				oEvent.initMouseEvent(eventName, options.bubbles, options.cancelable, document.defaultView, 
				options.button, options.pointerX, options.pointerY, options.pointerX, options.pointerY,
				options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, element);
			}
			element.dispatchEvent(oEvent);
		} else {
			if (eventName == 'submit') {
				element.submit();
			} else {
				options.clientX = options.pointerX;
				options.clientY = options.pointerY;
				oEvent = Object.extend(document.createEventObject(), options);
				element.fireEvent('on' + eventName, oEvent);
			}
		}
		return element;
	};

	Element.addMethods({ simulate: Event.simulate });
})();

/*
* Orginal: http://adomas.org/javascript-mouse-wheel/
* prototype extension by "Frank Monnerjahn" themonnie @gmail.com
*/
Object.extend(Event, {
	wheel:function (event){
		var delta = 0;
		if (!event) event = window.event;
		if (event.wheelDelta) {
			delta = event.wheelDelta/120;
			if (window.opera) delta = -delta;
		} else if (event.detail) {
			delta = -event.detail/3;
		}
		return Math.round(delta); //Safari Round
	}
});
/*
File: Storage
	The storage module allows creating storage containers that components can use to store data. These storage containers have an option for persistent storage.
*/

if (typeof IPE == "undefined") IPE = {};

/*
Package: IPE.storage
	Storage container for used data.
*/
IPE.storage = {
	/*
	Property: ipe
		IPE configuration data.
	*/
	ipe : {
		version : IPE.version || "",
		appPath : IPE.appPath || "/",
		prototypingMode : IPE.prototypingMode || null,
		debugMode : IPE.debugMode || null
	},
	
	/*
	Property: config
		Prototype configuration data.
	*/
	config : window.$config || {},
	
	/*
	Method: createContainer
		Creates a new storage object.
	
	Arguments:
		name - (string) Name of the storage object.
		persistent - (boolean) Makes the storage object persistent through a browser session in the current window.
		data - (object) Data to store in the container (optional).
	*/
	createContainer : function(name, persistent, data){
		if (!this[name]) this[name] = persistent ? document.session.createItem(name, {}) : {};
		if (data) Object.extend(this[name], data);
		return this[name];
	}
};

/*
Package: IPE.Session
	Session object that store data during a browser session.
	
Usage:
	Just save a property in IPE.Storage.session.
	
	Set session variable:
	:IPE.Session.setItem(key, value);
	
	Get session variable:
	:IPE.Session.getItem(key);
	
	Create new variable or return existing one:
	:IPE.Session.createItem(key, initialValue);
	
	The method createItem() can be used to create a persistent storage container that other scripts can use to store data in. 
	By providing an empty object as initial value, a direct reference to the session variable is created.
	
	:var myStorageContainer = IPE.Session("myStorage", {});
	:myStorageContainer.myValue = "value";
*/

document.session = (function(){
	var appId = IPE.appId || "session";
	var fromWindowName = {};
	
	// read session data from window name
	if (window.name){
		try {
			fromWindowName = window.name.evalJSON();
		} catch(e){
			//console.log("Retrieving session data failed");
		}
	}
	
	var data = fromWindowName[appId] || {};
	
	// private store function
	var store = function(){
		// delete methods before saving
		for(var key in data){
			if(typeof data[key] == "function") delete data[key];
		}
		var toWindowName = {};
		toWindowName[appId] = data;
		
		// convert to JSON string
		var dataString = Object.toJSON(toWindowName);
		
		// don't save more than 2Mb
		if(dataString.length/1024 > 2000){
			console.log("Too much session data");
			return false;
		}
		
		// save dataString in window.name
		window.name = dataString;
	};
	
	// set onunload handler
	Event.observe(window,"unload",store);
	
	var storageInterface = {
		/*
		Method: removeItem
			Removes a session variable.
			
		Arguments:
			key - (string) Name of session variable to delete.
		*/
		removeItem : function(key){
			if (data[key]) delete data[key];
		},
		
		/*
		Method: getItem
			Retrieve value of a session variable.
			
		Arguments:
			key - (string) Name of session variable to retrieve.
		*/
		getItem : function(key){
			return data[key] || null;
		},
		
		/*
		Method: setItem
			Set value of a session variable.
			
		Arguments:
			key - (string) Name of session variable to set.
			value - () Value to set.
		*/
		setItem : function(key, value){
			data[key] = value;
			return data[key];
		},
		
		/*
		Method: clearItem
			Clear all data in an session object. This leaves the session variable intact
			
		Arguments:
			key - (string) Name of session variable to clear.
		*/
		clearItem : function(key){
			var item = data[key];
			for (var key in item){
				delete item[key];
			}
			return item;
		},
		
		/*
		Method: createItem
			Create a new session variable value and return it. If the session variable with the provided name exists, this variable is returned.
			
		Arguments:
			key - (string) Name of session variable to create.
			value - Value to set.
		*/
		createItem : function(key, initialValue){
			return data[key] || this.setItem(key, initialValue);
		},
		
		/*
		Method: clear
			Clear all session data.
			
		Arguments:
			userAction - (boolean) Indicates that session clearing is intiated by the user, so a confirmation alert is shown.
		*/
		clear : function(userAction){
			for(var key in data){
				if(typeof data[key] != "function") this.removeItem(key);
				window.name = "";
			}
			if (userAction) alert("All session data has been deleted");
		},
		
		setClearEvent : function(eventName, userAction){
			document.observe(eventName, document.session.clear.bind(this, userAction));
		}
	};
	
	return storageInterface;
	
})();
/*
Package: IPE.Component
	This modules handles component initialization. There is a difference between global components and "normal" components.
	Global components are attached to the document and can be instantiated only once in an HTML document. Normal components are attached to HTML elements through a css selector.

Events fired:
	ipe:startComponentInitialization - Fired just before initialization of components starts. This is fired right after DOM has loaded.
	ipe:globalComponentsInitialized - Fired when all global components have been initialized.
	ipe:beforeComponentInitialization - Fired just before components inside a certain scope (element or document.body) are initialized.
		This can happen multiple times on a page, for example after when content of an element has been updated.
		The event contains a memo object with an element property which contains a reference to the element in which components are initialized.
	ipe:afterComponentInitialization - Fired when components inside a certain scope (element or document.body) have been initialized.
		It has the same memo object as the event "ipe:beforeComponentInitialization".

Events observed:
	element:afterUpdate - When this event is fired, component inside the elements which fired this event will be initialized.
*/

if (typeof IPE == "undefined") IPE = {};

IPE.Component = {
	options : {
		initializationEvents : ["element:afterUpdate"],
		components : typeof $config != "undefined" ? $config.components || [] : [],
		loadComponentFiles : IPE.debugMode || null,
		appPath : IPE.appPath || "/"
	},
	
	componentDefinitions : {},
	usedComponents : [],
	globalComponents : [],
	loadedScripts : [],
	
	/*
	Method: loadComponents
		Load required js files for components in debug mode.
	*/
	loadComponents : function(){
		this.options.components.each(function(component){
			// load files in debug mode
			if (component.jsFile){
				if (!this.loadedScripts[component.jsFile]){
					this.loadedScripts[component.jsFile] = 1;
					document.write('<script type="text/javascript" src="' + this.options.appPath + component.jsFile + '"></script>');
				}
			}
		},this);
	},
	
	/*
	Method: initialize
		Initialize Component module.
	*/
	initialize : function(options){
		Object.extend(this.options, options || {});
		
		if (this.options.loadComponentFiles) {
			this.loadComponents();
		}
		document.observe("dom:loaded", function(){
			document.fire("ipe:startComponentInitialization");
			
			// override component definitions
			if (this.customComponentDefinitions){
				for (var component in this.customComponentDefinitions){
					if (this.componentDefinitions[component]){
						Object.extend(this.componentDefinitions[component],this.customComponentDefinitions[component]);
					}
				}
			}
			
			// fill this.usedComponents with all available js components
			this.usedComponents = Object.keys(this.componentDefinitions);
			
			// register global components
			this.initGlobalComponents();
			
			// initialize scope on events
			this.options.initializationEvents.each(function(eventName){
				document.observe(eventName, function(event){
					this.initScope(event.memo.element || null);
				}.bind(this));
			}, this);
			
			// initialize body element
			this.initScope(document.body);
		}.bind(this));
	},
	
	/*
	Method: register
		Attach component behaviour to element.
	
	Arguments:
		element - (object) Element to attach behaviour to
		componentName - (string) Name of Component to create instance of
	
	Returns:
		Reference to component instance
	*/
	register : function(element,componentName,options){
		
		// prevent multiple initializion
		if (element[componentName]) return;
		if (element.retrieve) {
			var instance = element.retrieve(componentName);
			if (instance && instance.element == element) return;
		};
		
		// get component Class and definition
		var componentDefinition = this.componentDefinitions[componentName] || {};
		var componentClass = componentDefinition.componentClass || this[componentName];
		if (!componentClass) return;
		
		// get default options from componentDefinition and extend provided options onto it.
		var defaultOptions = componentDefinition.options || {};
		Object.extend(defaultOptions,options || {});
		
		// create component instance
		try {
			var instance = new componentClass(element,defaultOptions || null);
		}
		catch (e){
			console.log("Error initializing component " + componentName);
			console.log(e);
		}
		
		// store component instance
		if (element == document) {
			element[componentName] = instance;
		} else if (element.store) {
			element.store(componentName,instance);
		}
		
		// store name in component class
		if (!componentClass.prototype.name){
			componentClass.prototype.name = componentName;
		}
		
		// register global events
		var events = componentClass.prototype._globalEvents;
		if (events){
			events.each(function(event){
				event = "element" + ":" + event;
				element.observe(event,this.dispatchEvent.bindAsEventListener(this,element,instance));
			},this);
		}
		
		return instance;
	},
	
	/*
	Method: dispatchEvent
		Fire a global event.
	
	Arguments:
		element - Element from which event originated
		event - Event name
		instance - Component instance
	*/
	dispatchEvent : function(event,element,instance){
		var memo = Object.extend({
			event:event, 
			element:element, 
			component:instance
		}, event.memo || {});
		document.fire(element.identify() + "." + event.eventName,memo);
	},
	
	/*
	Method: initScope
		Initialize components in scope.
	
	Arguments
		scope - Scope (element) in which components must be initialized
	*/
	initScope : function(scope){
		scope = $(scope) || null;
		
		document.fire("ipe:beforeComponentInitialization", {element: scope || document.body});
		
		this.usedComponents.each(function(name){
			var component = this.componentDefinitions[name];
			if (!component || component.selector == "document"){return;}
			var componentScope = component.scope ? $(component.scope) : null;
			if (component.selector && !(componentScope && scope != componentScope && !scope.descendantOf(componentScope))){
				var componentInstances = scope ? scope.select(component.selector) : $$(component.selector);
				componentInstances.each(function(element){
					this.register(element,name);
				},this);
			}
		},this);
		
		document.fire("ipe:afterComponentInitialization", {element: scope || document.body});
	},
	
	/*
	Method: initGlobalComponents
		Initialize global components (these components are tied to document object).
	*/
	initGlobalComponents : function(){
		// get list of global components
		this.globalComponents = this.usedComponents.findAll(function(componentName){
			return this.componentDefinitions[componentName] ? this.componentDefinitions[componentName].selector == "document" : false;
		},this);
		
		// initialize all global components
		this.globalComponents.each(function(name){
			this.register(document,name);
		},this);
		
		document.fire("ipe:globalComponentsInitialized");
	}
};

/*
File: Wrapper.js
*/

/*
Class: IPE.Component.Wrapper
	Base class for IPE components containin generic functionality.
*/
IPE.Component.Wrapper = Class.create({
	initialize : function(element,options){
		this.options = Object.extend(Object.clone(this.defaultOptions),options);
		this.element = element;
		this._getOptions();
		this._setCallbacks();
	},
	
	/*
	Method: _setCallbacks
		For each defined callback in the property _callbacks it adds a function with the callback name to the options attribute. 
		The options attribute then can be passed on during instantiation of the wrapped class.
		This is used when an IPE component is used as a wrapper around another class that uses callback funtions instead of custom events. 
	
	Usage:
		(start code)
		IPE.Component.MyComponent = Class.create({
			_callBacks : {
				onChange : "change",
				[callbackName] : [eventName]
			},
			
			initialize : function($super, element, options){
				$super(element, options);
				this.component = new MyWrappedClass(element, options);
			...
		});
		
		var MyWrappedClass = Class.create({	
			...
		});
		(end)
	*/
	_setCallbacks : function(){
		if (this._callbacks){
			$H(this._callbacks).each(function(callback){
				this.options[callback.key] = function(){
					if (this._setState){
						this._setState();
					}
					this.element.fire("element:" + callback.value,this);
				}.bind(this);
			},this);
		}
	},
	
	/*
	Method: _createEventBindings
		Passes through events from a wrapped object. The binding are defined in the _eventBindings of the IPE component.
		
	Usage:
		(start code)
		IPE.Component.MyComponent = Class.create({
			_eventBindings : {
				"change : "change",
				[eventOfWrappedClass] : [eventToFire]
			},

			initialize : function($super, element, options){
				$super(element, options);
				this.component = new MyWrappedClass(element, options);
			...
		});

		var MyWrappedClass = Class.create({	
			...
		});
		(end)
	 */
	_createEventBindings : function(){
		if (this._eventBindings){
			this._eventBindings.each(function(events){
				this.component.observe(events[0], function(){
					if (this._setState){
						this._setState();
					}
					this.element.fire("element:" + events[1]);
				});
			},this);
		}
	},
	
	/*
	Method: _getOptions
		Reads options from class attribute of element and saves it into this.options. It searches for all options that are defined in the defaultOptions property of the IPE component.
	*/
	_getOptions : function(){
		// get options from class attribute
		if (!this.element.classNames) return;
		var options = {};
		
		// get possible options from defaultOptions attribute.
		var possibleOptions;
		if (this.defaultOptions){
			possibleOptions = Object.keys(this.defaultOptions);
		}
		possibleOptions = possibleOptions ? "|"+possibleOptions.join("|") : "";
		
		// retrieve values form class name
		var re = new RegExp("^((?:ipe-)\\w+"+ possibleOptions +")?(?:-([\\w\\.#-_]+))?$");
		this.element.classNames().each(function(name){
			var s = name.match(re);
			if (s){
				var property = s[1].replace("ipe-","");
				var value = s[2] || true;
				if (Object.isString(value)){
					if (value.match(/^\d+(\.\d+)?$/)){
						value = Number(value); // number
					} else if (value == "true" || value == "enabled"){
						value = true; // boolean true
					} else if (value == "false" || value == "disabled"){
						value = false; // boolean false
					}
				}
				if (!options[property]){
					options[property] = value;
				} else {
					if (!Object.isArray(options[property])){
						options[property] = [options[property]];
					}
					options[property].push(value);
				}
			}
			Object.extend(this.options,options);
		},this);
	},
	
	/*
	Method: queueAction
		Places a function in the effect queue of an element
	*/
	queueAction : function(fn, element){
		if (Effect && Effect.Event){
			new Effect.Event({
				queue : {
					scope: (element || this.element).identify(),
					position: "end"
				},
				afterFinish: fn
			});
		} else {
			fn();
		}
	}
});

IPE.Component.initialize();



/*
File: Dispatcher
*/

IPE.Component.componentDefinitions.Dispatcher = {
	selector : ["a","form"],
	options: {
		appendBeforAbsoluteUrls : IPE.appPath || ""
	}
};

/*
Class: IPE.Component.Dispatcher
	Dispatches click and submit events as global events on which other components can act upon and stop the default action if necessary. 
	
Options: 
	appendBeforAbsoluteUrls - (string) Path that should be appended before absolute url's.
	
Events:
	element:dispatch - This event is fired right after the click or submit event of the element is captured, before the event is send to the dispatcher function.
	
Usage:
	This component attaches to links and forms and captures the submit or click events. 
	It then analyses the form or link and fires a special execution event upon which other components can act. The
	components that use these events can stop the original click or submit event.
	With the execution event an object is sent with the following properties:
	
	element - (object) Form or link element.
	destination - (string) Href or action attribute of element.
	event - (object) Original click or submit event. This event can be stopped by the receiving component.
	targets - (array) Array with target element that should receive the content from destination page.
	relNames : (array) Array with all names from the rel attribute.
	anchor : (string) Anchor id, this link is pointing to.
	
	Before the executions events a special click or submit event is fired: "Form:submit" or "Link:click". Other components can 
	observe these events and stop further execution by setting a stopExecution flag on the form or link element.
	
	Setting the stopExecution flag is done like this:
	
	> element.store("stopExecution", 1);
	
*/
IPE.Component.Dispatcher = Class.create(IPE.Component.Wrapper, {
	defaultOptions : {
		appendBeforAbsoluteUrls : null
	},
	
	initialize : function($super,element,options){
		$super(element,options);
		
		this.isForm = element.match("form");
		this.destinationAttribute = this.isForm ? "action" : "href";
		this.destination = element.readAttribute(this.destinationAttribute);
		if (!this.destination) return;
		this.executionEvent = this.isForm ? "submit" : "click";
		this.eventNamespace = this.isForm ? "form:" : "link:";
		
		// observe click or submit event
		this.element.observe(this.executionEvent, this.dispatch.bind(this));
		
		// observe button clicks inside a form
		if (this.isForm) {
			this.element.observe("click", this.handleButtonClick.bind(this));
		}
		
		// rewrite absolute url
		this.rewriteAbsolutePath(this.element);
		
		// execute auto loading links
		if (this.element.hasClassName("autoLoading")){
			this.dispatch();
			this.element.removeClassName("autoLoading");
		}
	},
	
	/*
	Method: stop
		Stop execution of the request and prevent the default action of the event.
		
	Arguments:
		event - (object) event to stop.
	*/
	stop : function(event){
		this.element.eliminate("stopExecution");
		if (event) event.preventDefault();
		return false;
	},
	
	/*
	Method: dispatch
		Calls the dispatch class method.
		
	Arguments:
		event - (object) Event object.
	*/
	dispatch : function(event){
		this.element.fire("element:dispatch", {element: this.element, attribute: this.destinationAttribute});
		this.constructor.dispatch.apply(this, [this.element, event || null]);
	},
	
	/*
	Method: handleButtonClick
		Process a click on a button. The name and value are added to a data object stored on the form element.
		
	Arguments:
		event - (object) Event object.
	*/
	handleButtonClick : function(event){
		var clicked = event.element();
		var button = (clicked.type && clicked.type == "submit") ? clicked : clicked.up("button");

		if (button){
			var name = button.readAttribute("name");
			var value = button.getValue();

			if (name) {
				var data = {};
				data[name] = value || true;
				this.element.store("data", data);
			}

			button.fire("button:click", {element: button});
		}
	},
	
	/*
	Method: rewriteAbsolutePath
		Convert absolute path starting with "/" to complete path.
	*/
	rewriteAbsolutePath : function(element){
		var destination = element.readAttribute(this.destinationAttribute);
		if (this.options.appendBeforAbsoluteUrls && destination.startsWith("/")) element.setAttribute(this.destinationAttribute, this.options.appendBeforAbsoluteUrls + destination.substring(1));
	}
});

Object.extend( IPE.Component.Dispatcher, {
	
	/*
	Method: dispatch
		Function executed after click on link or button. This function decides what to do with the event.
	
	Arguments:
		event - (object) Event object.
		lnk - (object) Link that is clicked on.
		options - (object) extra options, used for form parameters and method.
		
	Events (on document)
		"linkId:click" - fired on click on link.
		"form:submit" - fired on form submit.
		"anchor:elementId" - fired when link with href "#elementId" is clicked.
		link:execute - Dispatch event for links.
		form:execute - Dispatch event for forms.
	*/
	
	dispatch : function(element, event, options){
		if (element.hasClassName("nofollow")) return this.stop(event);
		
		// make sure element does not have absolute path
		this.rewriteAbsolutePath(element);
		
		// read properties
		var destination = element.readAttribute(this.destinationAttribute);
		var rel = element.readAttribute("rel");
		var anchor = destination.match(/(?:^#)([\w-]+)/);
		
		// accomodate for form submit resulting in multiple modules injections
		if (this.isForm){
			var targetsDefined = destination.split(/\s|\|/);
			if (targetsDefined[1]){
				destination = targetsDefined.shift();
				rel = targetsDefined.join(" ");
			}
		}
		
		// make array of rel values
		rel = rel ? rel.split(/\s+/) : [];
		
		// create properties object
		var properties = Object.extend({
			element : element,
			destination : destination,
			event: event || null,
			targets : rel.grep(/^#.+/),
			relNames : rel.grep(/^[\w-]+/),
			anchor : anchor ? anchor[1] : null
		}, options || {});
		
		// get form parameters and method
		if (this.isForm) {
			var storedData = element.retrieve("data");
			var serialized = this.constructor.serializeForm(element, storedData);
			properties.parameters = serialized.data;
			properties.querystring = serialized.querystring;
			properties.method = element.readAttribute("method") || "get";
		}
		
		// delete anchor property if targets are defined
		if (anchor && properties.targets.length > 0){
			properties.anchor = null;
		}
		
		// For deprecated target use
		var target = element.readAttribute("target");
		if (target){
			// check if target is a window name
			if (target.match(/^[\w-]+/)) properties.relNames.push(target);
			// Check if target is id selector
			if (target.match(/^#.+/)) properties.targets.push(target);
		}
		
		// Check if href is methodCall
		var methodCall = destination.match(/(?:^#)([\w-]+)(((\.|\/)[\w-]+)+)/);
		
		// Fire events
		/*
			Events are fired before execution proceeds to give components that listen 
			to these events the opportunity to stop further execution 
		*/
		element.fire(this.eventNamespace + this.executionEvent, properties);
		if (element.id) document.fire( element.id + ":" + this.executionEvent, properties);
		if (anchor && !properties.targets.length) document.fire("anchor:" + anchor[1], properties);
		
		// check if execution can proceed. Other components can set element.stopExecution to stop the event
		if (element.retrieve("stopExecution")) return this.stop(event);
		
		// fire execution event
		element.fire(this.eventNamespace + "execute", properties);
		
		// another execution check for IE 6 that can't stop the event in a listener to the execute event
		if (element.retrieve("stopExecution")) return this.stop(event);
		
		// stop event if destination is "#"
		if (!destination || destination == "#") return this.stop(event);
	},

	/*
	Method: serializeForm
		Serialize a form into javascript object and querystring. The querystring has same order as form fields in the FOM.

	Arguments:
		form - (object) Form to serialize.
		storedData - (object) Additional data that has to be added to data object and querystirng..
	*/
	serializeForm : function(form, storedData){
		var elements = Form.getElements(form);
		var key, value, pairs = [];

		var data = elements.inject({ }, function(result, element) {
			if (!element.disabled && element.name) {
				key = element.name; value = $(element).getValue();
				if (value != null && element.type != 'file' && element.type != 'submit') {
					if (key in result) {
						if (!Object.isArray(result[key])) result[key] = [result[key]];
						result[key].push(value);
					} else {
						result[key] = value;
					}
					
					// add to pairs array
					if (Object.isArray(value)){
						value.each(function(value){
							pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(String.interpret(value)));
						});
					} else {
						pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(String.interpret(value)));
					}
				}
			}
			return result;
		});
		
		if (storedData){
			for (var key in storedData){
				data[key] = storedData[key];
				pairs.push(key + '=' + encodeURIComponent(String.interpret(storedData[key])));
			}
		}

		return {
			data: data,
			querystring: pairs.join("&")
		};
	}
});

// fire an anchor event if location contains a hash with an element id.
(function(){
	var hash = location.hash;
	if (hash.match(/#[\w-]+/)){
		document.observe("ipe:afterComponentInitialization", function(event){
			var scope = event.memo.element || null;
			if (scope == document.body){
				document.fire("anchor:" + hash.substring(1));
			}
		});
	}
})();
/*
File: Module Injection
*/

IPE.Component.componentDefinitions.ModuleInjection = {
	selector : "document",
	options : {
		rootPath : IPE.appPath || null
	}
};

/*
Class: IPE.Component.ModuleInjection

Options:
	events : (Array) Array of events, to apply module injection on.

Dependencies:
	IPE.Component.Dispatcher - This components dispatches the events that are captured by this component.
*/
IPE.Component.ModuleInjection = Class.create(IPE.Component.Wrapper, {
	defaultOptions : {
		events : ["link:execute", "form:execute", "document:load"],
		rootPath : null
	},

	initialize : function($super,element,options){
		$super(element,options);
		// observe events
		this.options.events.each(function(eventName){
			document.observe(eventName, this.execute.bindAsEventListener(this));
		}, this);
	},
	
	execute : function(event){
		var properties = event.memo;
		
		if (properties.destination && properties.targets && properties.targets.length){
			Object.extend(properties, {
				trigger: properties.element || null
			});
			
			// stop the original event
			if (properties.event) properties.event.preventDefault();
			
			// IE can't stop the event here, so set "no execution" flag on the element
			if (properties.element) properties.element.store("stopExecution", 1);
			
			// start module injection
			this.loadContent(properties.destination, properties.targets, properties);
		}
	},
	
	/*
	Method: loadContent
		Load content for specified targets.
		
	Arguments:
		url - (string) Url of document or id of element containing the new content.
		targets - (array) Array with id's of elements that should be updated with new content.
		options - (object) Object with additional parameters for the request.
	*/
	loadContent : function(url, targets, options){
		if (!options) options = {};
		
		// get source element id's
		var sources = url.match(/#[\w-]+/g);
		if (targets.length > 1 && !sources) sources = targets;
		
		// rewrite absolute urls
		if (this.options.rootPath && url.startsWith("/")) url = this.options.rootPath + url.substring(1);
		
		// check if in-page link
		var inPageLink = (url.startsWith("#") && sources && sources.length > 0);
		
		// fire before load events in each target
		targets.each(function(target, i){
			var targetId = target.split(".")[0].replace(/^#/,"");
			var element = $(targetId);
			
			if (element) element.fire("element:beforeUpdate",{url:url, element:element, event:options.event || null, target: target});
			document.fire(targetId + ".element:beforeUpdate",{url:url, element:element, event:options.event || null, target: target});
			
			// If in page link get content of linked element
			if (inPageLink) this.injectNewContent($(sources[i].substring(1)).innerHTML, [target], url, [sources[i]], options);
		}.bind(this));
		
		if (inPageLink) return;
		
		// check if url is an image
		var image = url.match(/\.(gif|png|jpg)$/);
		if (url.match(/\.(gif|png|jpg)$/)){
			var html = "<img src='"+ url +"' />";
			this.injectNewContent(html, targets, url, sources, options);
			return;
		}

		// load resource (strip of hash form url for IE so it doesn't get encoded)
		new Ajax.Request(url.replace(/#.*$/,""), {
			method: options.method || "get",
			parameters: options.querystring || options.parameters || null,
			onComplete: function (transport) {
				this.handleAjaxResponse(transport, targets, url, sources, options);
			}.bind(this)
		});
	},
	
	/*
	Method: handleAjaxResponse
		Handles the response from a AJAX request.
	*/
	handleAjaxResponse : function(transport, targets, url, sources, options){
		// this event can be used by components that need to execute operations on the returned HTML.
		document.fire("document:onAJAXResponse", transport);
		
		// check if response wants to reset targets or sources
		var resetTargets = transport.getHeader("ipe-targets");
		var resetSources = transport.getHeader("ipe-sources");
		if (resetTargets) targets = resetTargets.replace(/\s/g,"").split(",");
		if (resetSources) sources = resetSources.replace(/\s/g,"").split(",");
		
		if (transport.delay){
			this.injectNewContent.bind(this).delay(transport.delay, transport.responseText, targets, url, sources, options);
		} else {
			this.injectNewContent(transport.responseText, targets, url, sources, options);
		}
	},
	
	/*
	Method: injectNewContent
		Injects new content into specified targets.
	
	Arguments:
		html - (string) HTML to inject.
		targets - (array) Array with id's of elements that should be updated with new content.
		url - (string) Url from which content has been loaded.
		sources - (array) Id's of element in loaded html from which content should be extracted.
		options - (object) Object with additional parameters that were sent with the request.
	*/
	injectNewContent: function(html, targets, url, sources, options){
		
		if (!this.idCount) this.idCount = 0;
		
		// get body outer and inner html
		var body = html.match(/<body[^>]*>((.|\s)*)<\/body>/);
		if (body) html = body[1];
		var raw = body ? body[0] : html;
		
		// add appPath before absolute urls
		if (this.options.rootPath) html = html.replace(/(href|action|src)=("|')\//gi, "$1=\"" + this.options.rootPath);
		
		// get injection ID
		if (!this.injectionId) this.injectionId = 0;
		this.injectionId++;
		
		// replace {count} with injectionId
		html = html.replace(/\{count\}/g, this.injectionId.toString());
		
		// paste html in temporary element if sources are defined
		if (sources){
			var tempElement = new Element('div',{"style":"position:absolute;width:0px;height:0px;display:none;overflow:hidden","id":"tempData"});
			$(document.body).insert(tempElement);
			
			// prevent double id's
			sources.each(function(sourceId){
				sourceId = sourceId.substring(1);
				html = html.replace(new RegExp("id=(\"|')"+sourceId+"(\"|')"), "id=\""+sourceId+"_temp\"");
			});
			tempElement.update(this.prepareHTML(html));
		}
		
		// update each target
		targets.each(function(targetId,i){
			var initialTarget = targetId;
			var position, newElement, insertionScope, sourceElement;
			targetId = targetId.replace(/^#/,"");
			
			// get html from source element if defined
			if (sources && sources[i]){
				var sourceElement = tempElement.down(sources[i] + "_temp");
				if (sourceElement) html = sourceElement.innerHTML;
			}
			
			// retrieve element and optional position
			var targetArray = targetId.split(/\.|\:/);
			targetId = targetArray[0];
			var element = $(targetId);
			
			// create target element if it does not exist yet
			if (!element){
				element = element = new Element("div",{"id": targetId});
				document.body.insert(element);
			}
			
			var insertionScope = element;
			
			// handle position
			if (targetArray.length > 1){
				// get position to insert new content
				position = targetArray[1];
				
				// translate position to understandable positions for Prototype.js's Element.insert
				if (position == "first") position = "top";
				if (position == "last") position = "bottom";
				
				// check if an extra container is specified
				var className, l;
				if (!["top","bottom","before","after","replace"].include(position)){
					className = position;
					position = null;
					l = 2;
				} else {
					className = targetArray[2] || null;
					l = 3;
				}
				
				// check if container element has to be inserted or reused
				if (className){
					if (!this.nr) this.nr = 0;
					newId = targetId + "-" + className + (className == "new" ? ("-" + this.nr++) : "");
					var newElement = $(newId);
					
					if (targetArray.length > l){
						for (var i=l; i<targetArray.length; i++){
							className += " " + targetArray[i];
						}
					}
					
					if (!newElement) {
						newElement = new Element("div",{"id": newId, "class": className});
						var insertion = {};
						if (!position) element.update("");
						insertion[position || "top"] = newElement;
						element.insert(insertion);
					}
					insertionScope = newElement;
				}
			}
			
			// store src and trigger on element
			if (!position) element.store("src",url);
			if (options.trigger && !position) element.store("trigger", options.trigger);
			
			// wrap injection in injection comments
			html = "<!-- @startInjection:" + this.injectionId + " -->" + html + "<!-- @endInjection:" + this.injectionId + " -->";
			html = this.prepareHTML(html);
			
			// set element to parent for passing with events if position is before or after
			var eventElement = element;
			if (position == "before" || position == "after" || position == "replace"){
				eventElement = element.up();
			}
			
			// insert new content (place in effectqueue)
			this.queueAction(function(html, injectionId){
				if (position) {
					if (position == "replace" && sourceElement){
						// replace element
						insertionScope.parentNode.replaceChild(sourceElement, insertionScope);
						sourceElement.id = sourceElement.id.replace("_temp", "");
					} else {
						insertion = {};
						insertion[position] = html;
						insertionScope.insert(insertion);
					}
				} else {
					insertionScope.update(html);
				}
				
				// strip comment protection element in IE
				if (Prototype.Browser.IE){
					eventElement.select(".commentProtection").invoke("remove");
				}
				
				// fire afterLoad events
				var memo = {
					url: url,
					element: eventElement,
					injectionId: this.injectionId,
					event: options.event || null,
					raw: raw,
					target: initialTarget
				};
				
				eventElement.fire("element:afterUpdate", memo);
				document.fire(targetId + ".element:afterUpdate", memo);
			}.bind(this, html, this.injectionId), eventElement);
		}.bind(this));
		
		// remove temporary element
		if (tempElement) tempElement.remove();
	},
	
	/*
	Method: prepareHTML
		Makes some fixes to the HTML that has to be injected
	*/
	prepareHTML : function(html){
		var startsWithComment = html.match(/\s*<\!--/);
		
		// add comment protection element before HTML to prevent loosing first comment nodes in IE
		if (Prototype.Browser.IE && startsWithComment){
			html = "<br style=\"display:none\" class=\"commentProtection\" />" + html;
		}
		
		return html;
	}
});

Element.addMethods({
	/*
	Method: loadContent
		Load content from another html file into an element
	*/
	loadContent : function(element, url, position){
		document.fire("document:load", {
			destination: url,
			targets: [element.identify() + (position ? ("." + position) : "")]
		});
		return element;
	},
	
	/*
	Property: Element.refresh
		Refresh loaded AJAX content in an element
		
	Arguments:
		element - (object) Element to refresh content of.
	*/
	refresh: function(element){
		element = $(element);
		var src = element.retrieve("src");
		if (src){
			element.loadContent(src);
		}
		return element;
	}
});
/*
File: Keyboard
*/

IPE.Component.componentDefinitions.Keyboard = {
	selector : "document"
};

/*
Class: IPE.Component.Keyboard
	Keyboard shortcut manager. Used for applying keyboard shortcuts to IPE functions. Each shortcut is fired as an event on the document.
	
Example:
	You can observe these events like this.
	
	:document.observe("keyboard:ctrl-alt-s",function(){});
*/
IPE.Component.Keyboard = Class.create({
	initialize: function(){
		this.keysPressed = {};
		this.shortcut = null;
		document.observe("keydown",this.keyDown.bindAsEventListener(this));
		document.observe("keyup",this.keyUp.bindAsEventListener(this));
	},
	
	/*
	Property: modifierKeys
		Modifier keys.
	*/
	modifierKeys : {
		17 : "ctrl",
		18 : "alt",
		16 : "shift"
	},
	
	/*
	Property: keyCodes
		Mapping of keycodes to key names.
	*/
	keyCodes : {
		8 : "backspace",
		17 : "ctrl",
		18 : "alt",
		16 : "shift",
		27 : "esc",
		13 : "enter",
		32 : "space",
		37 : "left",
		38 : "up",
		39 : "right",
		40 : "down"
	},
	
	/*
	Method: keyDown
		Fires an event on the document element for each pressed key combination
	*/
	keyDown: function(e) {
		this.keysPressed[this.modifierKeys[e.keyCode] || "key"] = this.keyCodes[e.keyCode] || String.fromCharCode(e.charCode || e.keyCode).toLowerCase();
		var keys = [];
		["ctrl","alt","shift","key"].each(function(key){
			if (this.keysPressed[key]){
				keys.push(this.keysPressed[key]);
			}
		},this);
		var shortcut = keys.join("-");
		if (shortcut != this.shortcut){
			document.fire("keyboard:"+shortcut);
			this.shortcut = shortcut;
		}
	},
	
	/*
	Method: keyUp
		Executed on keyup.
	*/
	keyUp: function (e) {
		this.keysPressed[this.modifierKeys[e.keyCode] || "key"] = null;
		this.shortcut = null;
	}
});

IPE.Component.componentDefinitions.TagReplacer = {
	selector : "legend",
	options : {
		replacements : {
			"legend" : ["p","question"]
		}
	}
};

/*
Class: IPE.Component.TagReplacer
	Replaces a HTML tag with another one and add an optional class name.
	
Options:
	replacements - (object) Object with all replacements.
*/
IPE.Component.TagReplacer = Class.create({
	initialize : function(element,options){
		var tag = element.tagName.toLowerCase();
		var replacement = options.replacements[tag];
		if (replacement){
			element.replaceTag(replacement[0],replacement[1]);
		}
	}
});
/*
File: MultipleFormActions
*/

IPE.Component.componentDefinitions.MultipleFormActions = {
	selector : "document",
	options : {
		events: ["button:click"]
	}
};

/*
Class: IPE.Component.MultipleFormActions
	This component adds extra functionality to form buttons. A "cancel" class name makes the button work as a cancel button that deletes all saved form values.
	By adding a location in the value attribute you can change the form action for different submit buttons.
	
Events:
	Form:cancel - Fired when cancel button is clicked.
*/
IPE.Component.MultipleFormActions = Class.create(IPE.Component.Wrapper,{
	defaultOptions : {
		events : []
	},
	
	initialize : function($super, element, options){
		$super(element, options);
		
		this.options.events.each(function(eventName){
			document.observe(eventName, this.resetAction.bind(this));
		}.bind(this));
	},
	
	/*
	Method: resetAction
		Uses button value as action
	*/
	resetAction : function(event){
		var button = event.memo.element;
		
		var form = button.up("form");
		if (!form) return;
		var defaultAction = form.retrieve("defaultAction");
		if (!defaultAction) {
			defaultAction = form.readAttribute("action") || "#";
			form.store("defaultAction", defaultAction);
		}
		
		var value = button.getValue();
		// change form action
		if (value && (value.match(/(^\.{0,2}\/|https?\:|\w+\.[\w]{1,6})/) || button.hasClassName("formAction"))){
			form.action = value;
			form.store("actionReplaced", 1);
		} else if (form.retrieve("actionReplaced")){
			form.action = defaultAction;
			form.eliminate("actionReplaced");
		}
	}
});

/*
File: Accordion
*/

IPE.Component.componentDefinitions.Accordion = {
	selector : ".accordion"
};

/*
Class: IPE.Component.Accordion
	Accordion component.
	
Options:
	firstOpen - (number) indexNumber of first panel to open.
	allClosable - (boolean) All panels are closable or one always stays open.
	direction - (string) Specifies whether this is a horizontal or vertical accordion. Defaults to vertical.
	height - (number) Fixed height of sections of vertical accordion.
	width - (number) Fixed width of sections of horizontal accordion.
	duration - (string) Duration of the open and close animations.
	mouseOver - (boolean) Option for opening accordion panels on mouseover.
	
Events:
	element:open - Fires when an accordion section opens.
	element:close - Fires when an accordion section closes.
*/

IPE.Component.Accordion = Class.create(IPE.Component.Wrapper,{
	defaultOptions : {
		firstOpen : null,
		allClosable : true,
		direction : "vertical",
		height: null,
		width: null,
		duration: "300ms",
		mouseOver: false
	},
	
	initialize : function($super,element,options){
		$super(element,options);
		
		// set instance properties
		this.headers = [];
		this.sections = [];
		this.activeSection = null;
		this.sectionIdToIndex = {};
		var firstAlreadyOpen;
		
		// disable allClosable option for accordion that works on mouseover
		if (this.options.mouseOver){
			this.options.allClosable = false;
		}
		
		// set initial states of headers and sections
		this.element.immediateDescendants().each(function(element,index){
			var sectionIndex = Math.floor(index/2);
			if (index%2){
				// this is a panel
				this.sections.push(element);
				element.addClassName("content");
				
				// react on anchor link events
				if (element.id){
					document.observe("anchor:" + element.id,this.openSection.bind(this,sectionIndex, 1));
					this.sectionIdToIndex[element.id] = sectionIndex;
				}
				
				// set initial styling
				var options = {
					display: 'none',
					overflow: 'hidden'
				};
				if (this.options.direction == 'horizontal') {
					options.width = '0px';
				} else {
					options.height = '0px';
				}
				element.setStyle(options);
			} else {
				// this is a header
				this.headers.push(element);
				element.addClassName("header");
				
				// set event handler
				var openEvent = this.options.mouseOver ? "mouseover" : "click";
				element.observe(openEvent, this.openSection.bind(this, sectionIndex, null));
			}
			
			// set initial state
			if (element.hasClassName("open")){
				this.options.firstOpen = sectionIndex + 1;
			} else {
				element.addClassName("closed");
			}
		}.bind(this));
		
		// set scaling direction
		if (this.options.direction == 'horizontal') {
			this.scaling = {
				scaleX: true,
				scaleY: false
			};
		} else {
			this.scaling = {
				scaleX: false,
				scaleY: true
			};
		}
		
		// open a section
		if (!this.options.firstOpen) return;
		if (typeof this.options.firstOpen == "number" || this.options.firstOpen.match(/^\d+$/)){
			this.openSection(this.options.firstOpen-1);
		} else {
			this.openSectionById(this.options.firstOpen);
		}
	},
	
	/*
	Method: showPanel
		Open specific panel.
	
	Arguments
		index - (String) index number (1-based) of panel that has to be opened.
	*/
	showPanel : function(index){
		this.openSection(index-1, 1);
	},
	
	/*
	Method: openSectionById
		Open an accordion panel by its id.
	
	Arguments:
		id - (String) id of section to open.
	*/
	openSectionById : function(id){
		var index = this.sectionIdToIndex[id] || null;
		if (typeof index == "number") this.openSection(index, 1);
	},
	
	/*
	Method: openSection
		Open an accordion panel.
	
	Arguments:
		evt - (Event) Event object of the click on a header.
		index - (Element) Header that is clicked on.
	*/
	openSection : function(index, anchor){
		
		// stop if animation is running
		if (this.animating) return false;
		
		var effects = [];
		
		// get header and section element
		var header = this.headers[index];
		var section = this.sections[index];
		
		// effect options
		var effectOptions = Object.extend({
			sync: true,
			scaleContent: false,
			transition: Effect.Transitions.sinoidal
		}, this.scaling);
		
		// open effect
		if (section != this.activeSection){
			// set initial styling
			section.setStyle({
				display: 'block',
				overflow: 'hidden'
			});
			
			// add effect object
			effects.push(
				new Effect.Scale(section, 100, Object.extend({
					scaleFrom: 0,
					scaleMode: {
						originalHeight: this.options.height || section.scrollHeight,
						originalWidth: this.options.width || section.scrollWidth
					}
				}, effectOptions))
			);
			
			// set class names
			section.addClassName("open").removeClassName("closed");
			header.addClassName("open").removeClassName("closed");
		} else if (!this.options.allClosable) {
			return;
		}
		
		// close effect
		if (this.activeSection){
			this.activeSection.setStyle({
				overflow: 'hidden'
			});
			effects.push(
				new Effect.Scale(this.activeSection, 0, effectOptions)
			);
		}
		
		// play effects
		new Effect.Parallel(effects, {
			duration: this.options.duration.toSeconds(),
			queue: {
				scope: this.element.identify()
			},
			beforeStart: function() {
				this.animating = true;
			}.bind(this),
			afterFinish: function() {
				// set styling and class names of closed section
				if (this.activeSection) {
					this.activeSection.setStyle({
						display: 'none'
					});
					this.activeSection.previous().addClassName("closed").removeClassName("open");
					this.activeSection.addClassName("closed").removeClassName("open");
					this.activeSection.fire("element:close", {element: this.activeSection});
				}
				// remove styling from opened section
				if (section != this.activeSection){
					section.removeAttribute("style");
					section.fire("element:open", {element: section});
				}
				this.activeSection = (section != this.activeSection) ? section : null;
				this.animating = false;
			}.bind(this)
		});
	}
});

/*
File: AutoCalculate
*/

IPE.Component.componentDefinitions.AutoCalculate = {
	selector : ".autoCalculate"
};


/*
Class: IPE.Component.AutoCalculate
	Auto-calculates a result from one or more inputs or html elements
*/
IPE.Component.AutoCalculate = Class.create(IPE.Component.Wrapper,{
	initialize : function($super,element,options) {
		$super(element,options);
		
		this.result = this.element.down(".result");
		this.fields = this.element.select(".addUp",".add",".subtract");
		
		this.fields.each(function(field){
			if (field.hasClassName("add") || field.hasClassName("addUp")) field.store("sum","add");
			if (field.hasClassName("subtract")) field.store("sum","subtract");
			if (field.match("input")) field.observe("keyup",this.sum.bind(this));
		},this);
		this.sum();
	},
	
	/*
	Method: setValue
		populates a value to result element
		
	Arguments:
		value - (number) Value to populate in result element
	*/
	setValue : function(value){
		if (this.result.match("input")){
			this.result.value = value;
		} else {
			this.result.update(value);
		}
	},
	
	getNumber : function(string){
		string = string.replace(/^[\D]+(\d)/,"$1");
		string = string.replace(/(\d)[\D]+$/,"$1");
		string = string.replace(",",".");
		return Number(string);
	},
	
	/* 
	Method: sum
		calculates sum of all fields
	*/
	sum : function() {
		this.total = 0;
		this.fields.each(function(field){
			var amount = this.getNumber(field.match("input") ? field.value : field.innerHTML) || 0;
			var action = field.retrieve("sum");
			if (action == "add") this.total += amount;
			if (action == "subtract") this.total -= amount;
		},this);
		this.setValue((this.total ? this.total : ""));
	},
	
	/* 
	Method: addField
		Add an extra field to this component
	
	Arguments:
		field - (element) element to add
	*/
	addField : function(field) {
		this.fields.push($(field));
		if (field.match("input")) field.observe("keyup",this.sum.bind(this));
	}
});

/*
File: AutoComplete
*/

IPE.Component.componentDefinitions.AutoComplete = {
	selector : ".autoComplete",
	options : {
		data: IPE.storage
	} 
};

/*
Class: IPE.Component.AutoComplete
	Auto complete on input fields. Uses Scriptaculous Auto Completer.
	
Dependecies:
	- Scriptaculous 1.8
	
Options:
	choices - (number) Number of choices in auto complete list
	partialSearch - (boolean) Boolean option that enables or disables matching at the beginning of words embedded within the completion strings.
	fullSearch - (boolean) Boolean option that enables or disables matching anywhere within the completion strings.
	partialChars - (number) The number of characters that must be typed before any partial matching is attempted.
	data - (object) Data object in which collections can be found.
	collection - (string) string representation of object in data object.
*/
IPE.Component.AutoComplete = Class.create(IPE.Component.Wrapper,{
	defaultOptions : {
		choices : 10,
		partialSearch : true,
		fullSearch : false,
		partialChars : 2,
		data : null,
		collection : null
	},
	
	initialize: function($super,element,options) {
		$super(element,options);
		this.element.identify();
		
		// get auto complete data
		this.options.collection = this.options.collection ? this.options.collection.toObject(this.options.data) : [];
		// stop if no data is found
		if (!this.options.collection) return;
		
		// insert container for showing autocomplete suggestions
		this.element.insert({after:"<span id=\""+this.element.id+"autocomplete\" class=\"autoComplete\"></span>"});
		
		// create instance of Scriptaculous Autocompleter
		var autoCompleter = new Autocompleter.Local(this.element.id,this.element.id+"autocomplete",this.options.collection,this.options);
	}
});
/*
File: AutoHelp
*/

IPE.Component.componentDefinitions.AutoHelp = {
	selector : [
		".autoHelp",
		
		// for backwards compatibililty
		"div.infoPanel",
		"em.infoPanel"
	]
};

/*
Class: IPE.Component.AutoHelp
	Shows an element when another element is focussed or hovered over. Mostly used for providing extra information about input fields.
	
Options:
	master - (string) Optional id of element that is focussed or hovered over.
	showOn - (string) Event to use for showing the element.
	exclusive - (boolean) Specifies whether help message can be open next to other help messages.
*/
IPE.Component.AutoHelp = Class.create(IPE.Component.Wrapper,{
	defaultOptions : {
		master: null,
		showOn: "focus",
		exclusive: true
	},
	eventMap : {
		focus : {hideEvent : "blur"},
		mouseover : {hideEvent : "mouseout"}
	},
	initialize : function($super,element,options){
		$super(element,options);
		
		var triggers = [];
		var masters = this.options.master;
		var showEvent = $(this.options.showOn);
		
		if (masters && !Object.isArray(masters)) masters = [masters];
		
		// find elements that show the help message
		this.group = this.element.up("label") || this.element.up("fieldset") || null;
		if (masters) {
			masters.each(function(masterId){
				var masterElement = $(masterId);
				if (masterElement) triggers.push(masterElement);
			});
		} else {
			if (this.group) {
				triggers = this.group.select("input","select","textarea");
			}
		}
		
		// set initial class name
		this.element.addClassName('idle');
		
		// observe anchor event
		document.observe("anchor:" + this.element.identify(), function(event){
			this.onShowEvent(event, event.memo.element);
			triggers[0].focus();
			event.memo.element.store("stopExecution", 1);
		}.bind(this));
		
		// observe show and hide events
		if (this.options.showOn){
			triggers.each(function(trigger){
				trigger.observe(this.options.showOn, this.onShowEvent.bind(this));
				trigger.observe(this.eventMap[this.options.showOn].hideEvent, this.onHideEvent.bind(this));
			}, this);
		}
		
		// hide other autoHelp instances
		if (this.options.exclusive){
			document.observe("autoHelp:show", function(event){
				var element = event.memo.element;
				if (element != this.element){
					this.onHideEvent();
				}
			}.bind(this));
		}
		
		// set close on click handler
		this.element.observe("click", this.onHideEvent.bind(this));
	},
	
	/*
	Method: onShowEvent
		Shows the element.
	*/
	onShowEvent : function () {
		this.element.fire("element:show", {element: this.element});
		
		// place show actions in effect queue
		this.queueAction(function(){
			this.element.removeClassName('idle');
			this.element.addClassName('active');
			if (this.group) this.group.addClassName("autoHelp");
		}.bind(this));
		
		// fire event that other instances can observe
		document.fire("autoHelp:show", {element: this.element});
	},
	
	/*
	Method: onHideEvent
		Hides the element.
	*/
	onHideEvent : function () {
		this.element.fire("element:hide", {element: this.element});
		
		// place hide actions in effect queue
		this.queueAction(function(){
			this.element.removeClassName('active');
			this.element.addClassName('idle');
			if (this.group) this.group.removeClassName("autoHelp");
		}.bind(this));
	}
});

/*
File: AutoSubmit
*/

IPE.Component.componentDefinitions.AutoSubmit = {
	selector : ".autoSubmit"
};

/*
Class: IPE.Component.AutoSubmit
	
	Automatically submits a form on a chosen event
	
Options
	eventSubmit - (string) event on wich to submit the form
*/
IPE.Component.AutoSubmit = Class.create(IPE.Component.Wrapper,{
	defaultOptions : {
		submitEvent : "change"
	},
	
	initialize : function($super, element, options){
		$super(element,options);
		
		// get the form element
		this.form = this.element.match("form") ? this.element : this.element.up("form");
		
		// submit the form on chosen event
		this.element.observe(this.options.submitEvent, this.submit.bind(this));
		
		//also submit the form on reset
		this.resetters = this.form.select("*[type='reset']");
		this.resetters.each(function(btn){
			btn.observe("click", this.reset.bindAsEventListener(this));
		}.bind(this));
		
		// change event doesn't bubble in IE
		if (Prototype.Browser.IE && this.options.submitEvent == "change"){
			this.form.getElements().each(function(input){
				input.observe("change", this.submit.bind(this));
			}.bind(this));
		}
	},
	
	submit : function(){
		this.form.simulate("submit");
	},
	
	reset : function(event){
		event.stop();
		this.form.reset();
		this.submit();
	}
});
/*
File: AutoTOC
*/

IPE.Component.componentDefinitions.AutoTOC = {
	selector : [
		".autoToc",
		".autoTOC"
	]
};

/*
Class: IPE.Component.AutoTOC
	
	Creates a TOC from headers found in an element
	
Options:
	levelStart: int, the highest heading level
	levelEnd: int, the lowest heading level

*/

IPE.Component.AutoTOC = Class.create(IPE.Component.Wrapper,{
	defaultOptions : {
		levelStart : 1,
		levelEnd : 50
	},
	
	initialize : function($super,element,options){
		$super(element,options);
		
		//get the element to replace with the generated toc
		//if no placeholder is present, insert the toc as the first element in this.element's parent
		var id = this.element.className.match(/autoToc-(\S+)/) || [];
		this.tocPlaceholder = id[1] ? $(id[1]) : this.element;

		//get the element containing the headers	
		this.contentElement = this.element.up();
		
		//insert the toc in the document and remove placeholder
		this.toc = new Element("ol", {"class":"toc"}).hide();
		this.tocPlaceholder.insert({before:this.toc});
		//remove the tocPlaceholder element(s)
		if(this.tocPlaceholder!=this.element) this.element.remove();
		this.tocPlaceholder.remove();
		
		//get the heading levels to process
		var levels = this.element.className.match(/level-(\d+)-?(\d?)/);
		if(levels){
			this.options.levelStart = parseInt(levels[1]);
			this.options.levelEnd = levels[2] && levels[2]!="" ? parseInt(levels[2]) : this.options.levelStart;
		}

		//build the selector for the headings that will be included in the toc
		this.selector = "";
		for(var i=this.options.levelStart; i<=this.options.levelEnd; i++){
			this.selector+="h"+i+",";
		}
		this.selector = this.selector.replace(/,$/, "");

		//get all the headers in the proper (document) order
		this.headers = this.sortByDocOrder(this.contentElement.select(this.selector));		
		
		//find the highest level
		var topLevel = this.options.levelEnd;
		this.headers.each(function(header){
			var headerLevel = parseInt(header.tagName.replace(/h/i, ""));
			topLevel = headerLevel < topLevel ? headerLevel : topLevel;
		});
	
		var lastLevel = topLevel;
		var lastItem = this.toc;
		
		//process all the headers to build the toc
		this.headers.each(function(header, i){
			
			//get the current level heading											 
			var currentLevel = parseInt(header.tagName.replace(/h/i, ""));
			
			//if the level get's higher/stays equal -> start a new item in the last current level			
			if(currentLevel<=lastLevel){
				var isp = lastItem.up("ol.level"+currentLevel) || this.toc;
				lastItem = new Element("li",{"class":"level"+currentLevel});
				isp.insert({bottom:lastItem});
				//get to the correct level nesting
				for(var i=lastLevel; i<currentLevel; i++){
					var subL = new Element("ol",{"class":"level"+(i+1)});
					lastItem.insert(subL);
					lastItem = new Element("li",{"class":"level"+(i+1)});
					subL.insert(lastItem);
				}
			}
			
			//if the level get's lower ->  add a new item in the last item
			else if(currentLevel>lastLevel){
				if(lastItem==this.toc){
					lastItem = new Element("li",{"class":"level"+lastLevel});
					this.toc.insert({bottom:lastItem});
				}
				//get to the correct level nesting
				for(var i=lastLevel; i<currentLevel; i++){
					var subL = new Element("ol",{"class":"level"+(i+1)});
					lastItem.insert(subL);
					lastItem = new Element("li",{"class":"level"+(i+1)});
					subL.insert(lastItem);
				}
			}
			//insert the link to the header
			var headerId = header.identify();
			var lnk = new Element("a", {"href":"#"+headerId});
			new IPE.Component.Dispatcher(lnk); //initialize IPE behaviour on link

			lastItem.update(lnk.update(header.innerHTML));
			//update the current level
			lastLevel = currentLevel;
		
		}.bind(this));
		
		//show the toc
		this.toc.show();
	
	},
	
	/*
	Method: sortByDocOrder
		sort a collection of elements like they appear in the document
	
	Arguments:
		collection - (array) Array containing all elements to be sorted.
	*/
	sortByDocOrder : function(collection){
		var testNode = collection[0];
		// IE doc-order
		if (testNode.sourceIndex) {
			collection.sort(function (a,b) {
				return a.sourceIndex - b.sourceIndex;
			});
		}
		// W3C doc-order
		else if (testNode.compareDocumentPosition) {
			collection.sort(function (a,b) {
				return 3 - (a.compareDocumentPosition(b) & 6);
			});
		}
		return collection;
	}
	
});

/*
File: Autofocus
*/

IPE.Component.componentDefinitions.Autofocus = {
	selector : [
		".autofocus",
		".focusInitial"
	]
};

/*
Class: IPE.Component.Autofocus
	Focus form field automatically.
*/
IPE.Component.Autofocus = Class.create({
	initialize : function(element,options) {
		// focus after delay to prevent that other components remove te focus again
		(function(){
			element.focus();
		}).delay(.1);
		element.hasFocus = true;
		element.observe("blur", function(){element.hasFocus = false;});
	}
});
/*
File: Formatter
*/

IPE.Component.componentDefinitions.Formatter = {
	selector : [
		".format",
		".currency-eu",
		".bankAccount-nl",
		"input.url"
	]
};

/*
Class: IPE.Component.Formatter
	Formats a string according provided type. Currently available types:
	currencyEU - formats a number to european currency format. "1500" becomes "1.500".
	bankAccountNl - formats a number to dutch bank account format. "123456789" becomes "1.34.56.789".
	url - "http://" is added in front of string if not present already.

Options:
	type - (string) Type of formatter
*/
IPE.Component.Formatter = Class.create(IPE.Component.Wrapper,{
	types : [
		"currency-eu",
		"bankAccount-nl",
		"url"
	],
	
	defaultOptions : {
		type : null
	},
	
	initialize : function($super,element,options) {
		$super(element,options);
		this.isInput = this.element.match("input");
		if (!this.options.type){
			this.options.type = this.element.classNames().find(function(className){
				return (this.types.indexOf(className) != -1);
			},this).camelize();
		}
		if (this.isInput) {
			element.observe("blur", this.formatField.bind(this));
			element.observe("focus", this.applyUnFormatted.bind(this));
		}
		this.formatField();
	},
	
	/*
	Method: formatField
		formats the value or innerHTML of the element.
	*/
	formatField : function(){
		this.unFormatted = this.isInput ? this.element.value : this.element.innerHTML;
		var type = this.options.type;
		this.formatted = "";
		if (this.unFormatted!=""){
			var value = this.unFormat[type] ? this.unFormat[type](this.unFormatted) : this.unFormatted;
			this.formatted = this.format[type] ? this.format[type](value) : value;
			this.applyFormatted();
		}
	},
	
	/*
	Method: applyFormatted
		Applies the formatted string to the element
	*/
	applyFormatted : function(){
		if (!this.formatted) return;
		if (this.isInput) {
			this.element.value = this.formatted;
		} else {
			this.element.update(this.formatted);
		}
		
	},
	
	/*
	Method: applyFormatted
		Applies the unformatted string to the element on focus of the element
	*/
	applyUnFormatted : function(){
		if (this.unFormatted) this.element.value = this.unFormatted;
	},
	
	/*
	Property: format
		Formatter functions for each type.
	*/
	format : {
		currencyEu: function(value){
			return value.split("").reverse().join("").replace(/(\d{3})/g,"$1\.").replace(/\.$/,"").split("").reverse().join("");
		},
		url : function(value){
			if (!value.match(/^[http:\/\/|https:\/\/]/)){
				value = "http://" + value;
			}
			return value;
		},
		bankAccountNl : function(value){
			return value.replace(/^(\d{2})(\d{2})(\d{2,3})(\d{3})$/,"$1\.$2\.$3\.$4");
		}
	},
	
	/*
	Property: unFormat
		Unformatter functions for each type.
	*/
	unFormat : {
		currencyEu : function(value){
			return value.replace(/\./g,"");
		},
		bankAccountNl : function(value){
			return value.replace(/\./g,"");
		}
	}
});

/*
File: Carousel
*/

IPE.Component.componentDefinitions.Carousel = {
	selector : [
		".carousel-buttons",
		".carousel-slider",
		".carousel-buttons-slider"
	],
	options : {
		data : IPE.storage.content || {}
	}
};

/*
Class: IPE.Component.Carousel
	Carousel component. The carousel can be controlled by a slider or buttons or both.

Options:
	rows - (number) amount of rows to be displayed.
	filters - (string) id of form element that contains filter inputs.
	metadata - (string) reference to tag in content.xml containing metadata for carousel items.
	jumpToNav - (string) id of jumpto navigation.
	slide - (string) Duration of slide animation.
	fade - (string) Duration of fade animation after auto play.
	autoPlay - (string) Auto plays carousel at startup and defines the time each slide is shown (example: "3s" for 3 seconds).
	autoPlayDelay - (string) Delay before auto play starts.
	autoPlayRepeat - (boolean) Defines whether auto play should repeat or play only one time. 
	
Events:
	element:scroll - Fires after scrolling of the carousel.
*/
IPE.Component.Carousel = Class.create(IPE.Component.Wrapper, {
	defaultOptions : {
		data: {},
		rows: 1,
		filters: null,
		jumpToNav : null,
		slide: "300ms",
		fade: "750ms",
		autoPlay: false,
		autoPlayDelay: false,
		autoPlayRepeat: false
	},
	
	initialize:	function ($super,element,options) {
		$super(element,options);
		this.createChildren();
		if(this.options.autoPlay){
			this.autoPlay();
		}
	},
	
	/*
	Method: createChildren
		Create and initialize carousel controls.
	*/
	createChildren: function () {
		this.selection = [];
		this.tagsearch = false;
		this.groupedItems = false;
		this.showButtons = this.element.hasClassName('carousel-buttons') || this.element.hasClassName('carousel-buttons-slider');
		this.showSlider = this.element.hasClassName('carousel-slider') || this.element.hasClassName('carousel-buttons-slider');
		this.metaData = this.options.metadata ? this.options.data[this.options.metadata] : null;
		this.applyFilters = this.options.filters ? true : false;
		
		// convert slide and fade options to seconds
		if (typeof this.options.slide == "string") this.options.slide = this.options.slide.toSeconds();
		if (typeof this.options.fade == "string") this.options.slide = this.options.fade.toSeconds();
		
		// create carousel elements
		this.carouselStrip = this.element.down();
		var isList = this.carouselStrip.match("ul");
		if (!isList){
			this.element.wrapContent("div");
			this.carouselStrip = this.element.down();
		}
		this.carouselStrip.addClassName("carouselStrip");
		this.clippingDIV = new Element('div', {"class":"carouselClipping"});
		this.element.insert({top:this.clippingDIV});
		this.clippingDIV.insert(this.carouselStrip);
		
		// get all carouselitems from DOM
		this.allItems = this.carouselStrip.childElements();
		if (this.carouselStrip.match("ul")){
			var nestedUls = this.carouselStrip.select("ul");
			if (nestedUls.length > 0){
				this.groupedItems = true;
				nestedUls.each(function(ul){
					ul.addClassName("carouselGroup");
					ul.style.display = "block";
					ul.style.cssFloat = "left";
					this.allItems = this.allItems.concat(ul.childElements());
				}, this);
				this.groups = nestedUls;
			}
		}
		
		var itemOffset = 0;
		this.allItems.each(function(item){
			item.addClassName("carouselItem");
			item.style.cssFloat = "left";
			
			// store item position
			item.offset = itemOffset;
			itemOffset += this.getItemWidth(item);
			
			if (item.id){
				document.observe("anchor:" + item.id, function(event){
					var properties = event.memo;
					
					// stop the original event
					if (properties.event) properties.event.preventDefault();
					// IE can't stop the event here, so set "no execution" flag on the element
					if (properties.element) properties.element.store("stopExecution", 1);
					
					this.autoPlayStopped = true;
					this.jumpTo(item.id);
				}.bind(this));
			}
		},this);
		
		// get component-width from css and apply to injected html
		this.componentWidth = parseInt(this.element.getWidth() || this.getElementWidth(this.element) || 150);
		this.visibleWidth = parseInt(this.clippingDIV.getWidth() || this.getElementWidth(this.clippingDIV) || this.componentWidth);
		this.contentWidth = 0;
	
		this.calculateMetrics();
		
		// initialze jumpTo navigation if applicable
		if (this.options.jumpToNav) {
			this.navs = $(this.options.jumpToNav).select('a');
			
			this.navs.each(function(jump){
				var id;
				
				// retrieve id of linked carousel item
				// backwards compatibility: convert jumpTo classes to anchor links
				var c = jump.className.match(/jumpTo-([\w-]+)/);
				if (c){
					id = c[1];
					jump.writeAttribute("href","#"+id);
				} else {
					id = jump.readAttribute("href").split("#")[1] || null;
				}
				
				// store x position of linked carousel item on jump element
				var item = $(id);
				if (item){
					jump.xpos = item.positionedOffset()[0] || item.offset;
				}
			}, this);
		}
		
		this.componentHeight = this.clippingDIV.getStyle('height');
	
		// create scroll-buttons
		if (this.showButtons) {
			this.buttonPrev = this.element.insert({top: "<div class='carouselPrev'><button type='button'></button></div>"}).firstDescendant();
			this.buttonNext = this.element.insert("<div class='carouselNext'><button type='button'></button></div>").childElements()[2];
			
			// prevent focussed border on buttons
			this.buttonNext.observe("focus",this.buttonNext.blur);
			this.buttonPrev.observe("focus",this.buttonPrev.blur);
			
			// bind scrollNextPrev method to buttons
			this.buttonNext.observe("click", this.scrollNextPrev.bind(this,1));
			this.buttonPrev.observe("click", this.scrollNextPrev.bind(this,-1));
		}
		
		// create scroll-slider
		if (this.showSlider) {
			var scrollBar = new Element('div', {"class":"carouselSlider"});
			this.element.insert(scrollBar);
			if (this.contentWidth > this.componentWidth) {
				this.handle = new Element('div', {"class":"carouselSliderHandle"});
				this.handle.innerHTML = "<div/>";
				scrollBar.insert({top:this.handle});
				this.slider = new Control.Slider(this.handle,scrollBar);
				this.slider.options.onSlide = this.scroll.bind(this);
				this.slider.options.onChange = this.scroll.bind(this);
				this.sliderVisible = true;
			
				/* decorate slider */
				this.slider.clickValue = function(targetSliderValue){
				    targetSliderValue = this.slider.getNearestValue(targetSliderValue);    
					var hh = this.slider.handles[0];
					var targetx = this.slider.translateToPxPxLess(targetSliderValue);
					var ob = this.slider;
					var myCB = function(){
						var str = hh.getStyle('left');
						var hhx = Number(str.substr(0,str.indexOf('px')));
						ob.values[0] = ob.translateToValue(hhx);
					    ob.value = ob.values[0]; // assure backwards compat
						ob.updateFinished();
					};
					new Effect.Move (hh, {duration: this.options.slide, x: targetx, mode: 'absolute', afterFinish:myCB});
				  }.bind(this);
			
				this.slider.translateToPxPxLess = function(value) {
					return Math.round(
						((this.slider.trackLength-this.slider.handleLength)/(this.slider.range.end-this.slider.range.start)) * 
						(value - this.slider.range.start)
					);
				}.bind(this);
			}
		}
		
		this.updateControlsState(0,true);
	},
	
	/*
	Method: jumpTo
		Scroll carousel to item with provided id.
		
	Arguments:
		itemId - (String) Id of item to scroll to.
	*/
	jumpTo: function (itemId) {
		var element = $(itemId);
		var position = element.positionedOffset()[0];
		this.move(-position);
	},
	
	/*
	Method: setNavHighlight
		Highlight active jumpTo link.
		
	Arguments:
		newLeft - (Number) X-position of carousel strip.
		jumpTo - (Object) Jump button to highlight.
	*/
	setNavHighlight: function (newLeft, jumpTo) {
		var jump;
		if (!jumpTo){
			var xpos = Math.abs((newLeft || newLeft === 0) ? newLeft : parseInt(this.carouselStrip.getStyle('left')));
			this.navs.each(function(nav){
				if (nav.xpos < xpos+5){
					jump = nav;
				};
			},this);
		} else {
			jump = jumpTo;
		}
		if (jump && jump != this.currentJump){
			if (this.currentJump) this.currentJump.up().removeClassName('emphasise');
			this.currentJump = jump;
			var li = this.currentJump.up();
			li.addClassName('emphasise');
			li.fire("carouselButton:emphasise");
		}
	},
	
	/*
	Method: calculateMetrics
		Calculate and apply width of carousel. Filters and rowifies before calculation.
	*/
	calculateMetrics: function () {
		// reset inSelection-state of all items
		this.allItems.invoke("addClassName","inSelection");
		
		// apply filters
		this.filter();
		
		// check for childUL's and, if any, "rowify" them ...
		if (this.groups){
			this.groups.each(this.rowify.bind(this));
			
			// add width of group titles to total width if they float before the groups
			this.carouselStrip.select(".title").each(function(title){
				if (title.getStyle("float") == "left"){
					this.contentWidth += title.getWidth() || this.getElementWidth(title);
				}
			},this);
		} else {
			this.rowify(this.carouselStrip);
		}
		
		if (this.contentWidth <= this.componentWidth) this.contentWidth = this.componentWidth;
		this.carouselStrip.style.width = this.contentWidth + "px";
		this.diff = this.contentWidth-this.visibleWidth;
	},
	
	/*
	Method: scrollNextPrev
		Scroll left or right
	
	Arguments
		direction - (Number) Direction to scroll, 1 for right and -1 for left.
	*/
	scrollNextPrev: function(direction){
		// define new position
		var scrollWidth = Math.floor(this.visibleWidth/this.itemWidth) * this.itemWidth;
		var scrollpos = parseInt(this.carouselStrip.getStyle('left') || 0);
		var x = scrollpos + -direction*scrollWidth;
		
		// move carousel
		this.move(x);
	},
	
	/*
	Method: move
		moves the carousel strip.
	
	Arguments
		x - (Number) X-coordinate to move the carousel to.
	*/
	move: function(x){
		// prevent scrolling too far
		var diff = this.diff;
		if (x > 0 ){
			x = 0;
		} else if (x < -diff) {
			x = -diff;
		}
		
		// move the carousel
		new Effect.Move(this.carouselStrip,{
			duration: this.options.slide,
			x: x,
			y: 0,
			mode: 'absolute',
			afterFinish: function(){
				this.element.fire("element:scroll", {element: this.clippingDIV});
			}.bind(this)
		});
		
		this.updateControlsState(x);
	},
	
	/*
	Method: updateControlsState
		Updates the state of all controls.
	
	Arguments
		x - (Number) x-coordinate of carousel strip.
		slide - (Boolean) Defines whether carousel is scrolled through the slider too prevent recursion.
	*/
	updateControlsState : function(x, slide){
		// scroll buttons
		if (this.showButtons) {
			this.buttonPrev.className = (x < 0) ? "carouselPrev enabled" : "carouselPrev";
			this.buttonNext.className = (x > -this.diff) ? "carouselNext enabled" : "carouselNext";
		}
		
		// jump to buttons
		if (this.navs) this.setNavHighlight(x);
		
		// slider
		if (this.slider && !slide){
			var ratio = -x/this.diff;
			this.slider.clickValue(ratio);
		}
	},
	
	/*
	Method: scroll
		Move accordion strip as result of slider action.
	
	Arguments
		v - (Number) Percentage of carousel length to scroll.
	*/
	scroll: function (v) {
		var difference = this.contentWidth - this.visibleWidth;
		var x = 0 - v * difference;
		this.carouselStrip.style.left = x + "px";
		this.updateControlsState(x,1);
	},
	
	/*
	Method: rowify
		Make rows in the slider.
	
	Arguments
		element - (Object) Element in which children should be rowified.
	*/
	rowify: function (element) {
		var children = $(element).childElements();
		
		// get width of carousel item
		var childWidth = children[0].getWidth();
		if (!childWidth){
			childWidth += this.getElementWidth(children[0]);
		}
		childWidth += parseInt(children[0].getStyle("margin-left"));
		childWidth += parseInt(children[0].getStyle("margin-right"));
		
		// set item container width
		var childrenInRow = Math.ceil(children.length/this.options.rows);
		element.style.width = childrenInRow * childWidth + "px";
		this.contentWidth += childrenInRow * childWidth;
		
		// add margin of group to total width
		if (element != this.carouselStrip){
			this.contentWidth += parseInt(element.getStyle("margin-left"));
			this.contentWidth += parseInt(element.getStyle("margin-right"));
		}
		
		// store item width
		this.itemWidth = childWidth;
	},
	
	getItemWidth : function(element){
		var width = element.getWidth() || this.getElementWidth(element);
		width += parseInt(element.getStyle("margin-left"));
		width += parseInt(element.getStyle("margin-right"));
		return width;
	},
	
	getElementWidth : function(element){
		var width = 0;
		if (element.getStyle("width") == "100%") return 0;
		width += parseInt(element.getStyle("width"));
		width += parseInt(element.getStyle("padding-left"));
		width += parseInt(element.getStyle("padding-right"));
		width += parseInt(element.getStyle("border-left-width"));
		width += parseInt(element.getStyle("border-right-width"));
		return width;
	},
	
	/*
	Method: autoPlay
		Scroll carousel automatically on load.
	*/
	autoPlay: function(){
		var obj = this;
		var jumpTime = this.options.autoPlay || 2;	//how long to show each section
		var startDelay = this.options.autoPlayDelay ? this.options.autoPlayDelay.toSeconds() : jumpTime; //how long to wait before starting autoPlay
		var currentItem = 0;

		var next = function(){
			if (!this.autoPlayStopped && !this.autoPlayPaused){
				if (currentItem >= this.allItems.length - 1){
					currentItem = -1;
					if (!this.options.autoPlayRepeat){
						new Effect.Opacity(obj.carouselStrip, {
							duration: obj.options.fade.toSeconds(),
							from:1,
							to:0,
							afterFinish : function(){
								obj.scroll(0);
								new Effect.Opacity(obj.carouselStrip, {
									duration: obj.options.fade.toSeconds(),
									from:0,
									to:1
								});
							}
						});
						return;
					}
				}
				currentItem++;
				this.jumpTo(this.allItems[currentItem]);
			}
			if (!this.autoPlayStopped) next.delay(jumpTime);
		}.bind(this);

		next.delay(startDelay);

		// pause autoPlay onmouseover
		this.element.observe("mouseover", function(){
			this.autoPlayPaused = true;
		}.bind(this));
		this.element.observe("mouseout", function(){
			this.autoPlayPaused = false;
		}.bind(this));
	},
	
	// Filter code
	
	filter: function () {
		// filtercode here ...
	},
	textSearch: function (txt) {
		this.reset();
		var ob = this;
		var selectedItems = [];
		this.allItems.each (function(i) {
			var expr = new RegExp(txt);
			if(i.tags.toString().match(expr)){
				selectedItems.push(i);
				if (!i.inSelection) {
					i.changed = true;
				} else {
					i.changed = false;
				}
				i.inSelection = true;
			} else {
				if (i.inSelection) {
					i.changed = true;
				} else {
					i.changed = false;
				}
				i.inSelection = false;
			}
		});
		this.selection = selectedItems.clone();
		this.renderItems();
	},
	reset: function () {
		var checkboxes = $(this.options.filters).getInputs('checkbox');
		checkboxes.each(function(c){c.checked = "checked";});
		for (var f in this.filters){this.filters[f].active = true;}
		this.filter();
		this.renderItems();
	},
	renderItems: function () {
		
		for (var i=0; i<this.allItems.length; i++) {
			var c = this.allItems[i];
			if (c.inSelection && c.changed) {
				new Effect.Appear(c.element, {duration: 0.4});
			} else if (!c.inSelection && c.changed) {
				new Effect.Fade(c.element, {duration: 0.4});
			}
		}
				
		this.calculateMetrics();
		this.clippingDIV.style.height = this.componentHeight;
		
		if (this.showSlider){
			if (this.diff > 10) {
				if (!this.sliderVisible){
					this.sliderVisible = true;
					new Effect.Appear(this.handle);
				}
				var len = Math.floor((this.visibleWidth/this.contentWidth) * this.visibleWidth);
				
				if (this.handle.getStyle('left') != "0px"){
					new Effect.Parallel( [
						new Effect.Morph(this.handle, {style:{width: len + 'px' }}), 
						new Effect.Move(this.handle, { x: 0, y: 0, mode: 'absolute'}),
						new Effect.Move(this.carouselStrip, { x: 0, y: 0, mode: 'absolute'}),],
						{duration: 0.4 }
					);
				} else {
					new Effect.Morph(this.handle, {style:{width: len + 'px' }, duration: 0.4});
				}
				
				this.slider.setHandleWidth(len + 2); // +2 = componsate for border ..
				
			} else {
				
				if (this.sliderVisible){
					this.sliderVisible = false;
					new Effect.Fade(this.handle);
				}
				
			}
		}
		
		if ($('themesFound')) $('themesFound').innerHTML = this.selection.length;
	}
});

/*
File: CoverFlow
*/

IPE.Component.componentDefinitions.CoverFlow = {
	selector : ".carousel-flow"
};

/*
Class: IPE.Component.CoverFlow
	 CoverFlow component.
	
Options:
	slider - (boolean) Show scrollbar below cover flow element.
*/
IPE.Component.CoverFlow = Class.create(IPE.Component.Wrapper,{
	defaultOptions: {
		slider: true,
		reflection: 0.0,
		startIndex: 5,
		buttons: false
	},
	
	initialize : function($super,element,options){
		$super(element,options);
		this.container = this.element;
		var lis = "";
		var uls = this.element.down('ul').getElementsByTagName("ul");
		if (uls.length > 1){
			$A(uls).each(function(ul){
				lis += ul.innerHTML;
			});
			this.element.innerHTML = "<ul>" + lis + "</ul>";
		}
		
		this.imgContainer = this.element.down('ul');
		this.imgContainer.setStyle({'position': 'relative'});
		this.imgContainer.setStyle({'height': '180px'});
		this.foc = 150;
		this.cur = 0;
		this.tar = 0;
		this.curI = this.options.startIndex;
		this.isRun = false;
		this.sli = null;
		this.checker = null;
		this.images = this.imgContainer.childElements();
		
		var obj = this;
		this.images.each(function(image, i){
			var im = image.down('img');
			im.height = im.readAttribute("height") || im.getHeight();
			im.width = im.readAttribute("width") || im.getWidth();
			im.aspectRatio = im.height/im.width;
			
			im.setStyle({
				'position':'absolute',
				'border':'none'
			});
			image.setStyle({
				'display':'block',
				'position':'absolute'
			});
			image.observe('click', obj.clickTo.bind(obj, i));			
			image.down('span').style.display = "none";
		});
			
		this.iL = this.images.length;
		this.update();
		if(this.options.buttons){
			this.initButtons(this.options.buttons);
		}
	},
	update: function(){
		this.oW = parseInt(this.container.getStyle('width'));
		this.oT = parseInt(this.container.getStyle('height'));
		this.imgContainer.setStyle({'height': this.oW*0.2 + 'px'});
		this.container.setStyle({'height': this.oW*0.66 + 'px'});
		this.container.setStyle({'visibility': 'visible'});
		
		if(this.options.slider){
			var obj = this;
			this.container.insert("<div class='carouselSlider'><div class='carouselSliderHandle' /><div/></div>");
			this.scrollBar = (this.container.down(".carouselSlider"));
			this.handle = this.scrollBar.childElements()[0];
			this.sli = new Control.Slider(this.handle,this.scrollBar);
			this.sli.options.onSlide = function (v) {
				obj.glideTo(Math.round(v*(obj.iL-1)));
			};
			this.sli.options.onChange = function (v) {
				obj.glideTo(Math.round(v*(obj.iL-1)));
			};
		}
		
		this.sz = this.oW * 0.5;
		this.process(this.curI*-this.foc);
		this.glideTo(0);
	},
	initButtons: function(btns){
		if($chk(btns.prev)){ $(btns.prev).observe('click', this.prev.bind(this)); };
		if($chk(btns.stop)){ $(btns.stop).observe('click', this.stop.bind(this)); };
		if($chk(btns.play)){ $(btns.play).observe('click', this.play.bind(this)); };
		if($chk(btns.next)){ $(btns.next).observe('click', this.next.bind(this)); };
	},
	prev: function(){
		if(this.curI > 0)
			this.curI--;this.clickTo(this.curI);
	},
	next: function(){
		if(this.curI < this.iL-1)
			this.curI++;this.clickTo(this.curI);
	},
	start: function(){
		this.isRun = true;
		this.checker = new PeriodicalExecuter(this.check.bind(this), 0.05);
	},
	end: function(){
		this.checker.stop();
	},
	clickTo: function(i){
		this.glideTo(i);		
		this.sli.setValue(i/(this.iL-1));
	},
	glideTo: function(i){
		if(!this.isRun){this.start();}
		
		if(this.currentSpan) this.currentSpan.style.display = "none";
		
		this.currentSpan = this.images[i].down('span');
		this.currentSpan.style.display = 'block';
		this.curI = i;
		this.tar = i*-this.foc;
	},
	check: function(){
		switch(this.tar < this.cur-1 || this.tar > this.cur+1){
			case true:
				this.process(this.cur + (this.tar-this.cur)/3);
				break;
			default:
				this.isRun = false;
				this.end();
				break;
		}
	},
	process: function(x){		
		x = Math.round(x);
		this.cur = x;
		var zI = this.iL;
		var z, xs, imgH, imgW, percent;
		this.images.each(function(li){
			
			var img = li.down('img');
			
			if(x<-this.foc*4||x>this.foc*4)	{
				img.setStyle({'display':'none'});
			} else {
				img.setStyle({'display':'block'});
			}
			
			z = Math.sqrt(10000 + x * x) + 100;
			xs = x / z * this.sz + this.sz;
			
			imgH = img.getHeight();
			imgW = imgH/img.aspectRatio;
			
			percent = 100;
			if ((imgW + 1) > (imgW / (this.options.reflection + 1))){
				percent = 115;
			}
			
			var newL = xs - (percent / 2) / z * this.sz;
			var newH = (imgH / imgW * percent) / z * this.sz;
			var newT = (this.oW * 0.2 - newH) + ((newH / (this.options.reflection + 1)) * this.options.reflection);
			var newW = newH/img.aspectRatio;
			
			if (x < 0) {
				zI++;
			} else {
				zI--;
			}
							
			img.setStyle({'left': newL + 'px'});
			img.setStyle({'height': newH + 'px'});
			img.setStyle({'width': newW + 'px'});
			img.setStyle({'top': newT + 'px'});
			li.setStyle({'zIndex': zI});		
			
			x += this.foc;
		}, this);
	}
});
/*
File: ClickZone
*/
IPE.Component.componentDefinitions.ClickZone = {
	selector : "*[class*=clickZone]"
};

/*
Class: IPE.Component.ClickZone
	Make an area clickable. The provided element can either be the hyperlink that should be followed or the element that should
	act as the click zone. In the first situation, the parent element or the element with the provided id is treated as the click zone.
	In the second situation, the first link in the provided element or the link with the provided id is followed when the element is clicked.
	
Options:
	clickZone - (string) Id of element that should be the click zone or the link that should be executed.
*/
IPE.Component.ClickZone = Class.create(IPE.Component.Wrapper, {
	defaultOptions : {
		clickZone: null
	},
	
	initialize : function($super, element, options){
		$super(element, options);
		
		// find referenced element
		var reference = typeof this.options.clickZone == "string" ? $(this.options.clickZone) : null;
		
		// define hyperlink and click zone
		if (element.match("a")){
			this.hyperlink = element;
			this.clickZone = reference || element.up();
		} else {
			this.hyperlink = reference || element.down("a");
			if (!this.hyperlink) return;
			this.clickZone = element;
		}
		
		// observe click event
		this.clickZone.observe("click", this.handleClick.bind(this));
	},
	
	/*
	Method: handleClick
		Handles the click event on the click zone.
		
	Arguments:
		event - (event) Event object.
	*/
	handleClick : function(event){
		// stop if target is a hyperlink
		if (event.element().match("a")){
			return;
		}
		
		// read link properties
		var url = this.hyperlink.readAttribute("href");
		var rel = this.hyperlink.readAttribute("rel");
		
		// execute link action
		if (url.startsWith("#") || (rel && rel.match(/#\w+/))){
			this.hyperlink.simulate("click");
		} else {
			location.href = url;
		}
	}
});
/*
File: CollapsibleTable
*/

IPE.Component.componentDefinitions.CollapsibleTable = {
	selector : ["table.collapsibleTable",
				"table.collapsibleTable-exclusive"]
};

/*
Class: IPE.Component.CollapsibleTable
	 Table with collapsible sections (each tbody).

Events:
	element:open - Fires when a section opens.
	element:close - Fires when a section closes.
*/
IPE.Component.CollapsibleTable = Class.create(IPE.Component.Wrapper,{
	initialize : function($super,element){
		$super(element);
		this.exclusive = this.element.hasClassName('collapsibleTable-exclusive');
		this.activeSection = -1;
		this.rows = [];
		this.headers = [];
		
		// loop through all tbodies
		this.tbodies = this.element.select("tbody");
		this.tbodies.each(function(tbody,i){
			this.rows[i] = tbody.immediateDescendants();
			this.headers[i] = this.rows[i].shift();
			
			// add toggle button
			var btn = new Element("span",{"class":"toggle"});
			tbody.down("th").insert({top:btn});			
			btn.observe("click",this.toggleSection.bind(this,i,0));			
			
			// set initial state of section
			this.toggleSection(i,1);
			
			// observe click of links with class "toggle"
			tbody.select('a.toggle').each(function(sw){
				sw.observe("click",this.toggleSection.bind(this,i,0));
			}, this);
		}.bind(this));
	},
	
	/*
	Method: toggleSection
		Toggles a section.
		
	Arguments:
		i - (number) Index number of section to toggle.
		initial - (boolean) first initialization
	*/
	toggleSection : function(i,initial){		
		var classToCheck = initial ? "expanded" : "collapsed";
		var state = this.tbodies[i].hasClassName(classToCheck) ? ["expanded","show"] : ["collapsed","hide"];
		
		// collapse active section if exclusive flag is true
		if (this.activeSection >= 0 && this.activeSection != i && this.exclusive){
			this.setState(this.activeSection, ["collapsed","hide"]);
		}
		
		// set state of section
		this.setState(i, state);
		this.activeSection = (state[1] == "show") ? i : -1;
	},
	
	/*
	Method: setState
		Set display state of section.
		
	Arguments:
		i - (number) Index number of section to set state of.
		state - (array) State array with class name and show or hide action.
	*/
	setState : function(i, state){
		// show or hide rows in section
		this.rows[i].each(function(row){
			row[state[1]]();
		});
		
		var tbody = this.tbodies[i];
		
		// set class name on section
		tbody.removeClassName("collapsed").removeClassName("expanded").addClassName(state[0]);
		
		// fire event
		tbody.fire("element:" + (state[0] == "expanded" ? "open" : "close"), {element: tbody});
	}
});


/*
File: Collapsible
*/

IPE.Component.componentDefinitions.Collapsible = {
	selector : ".collapsible"
};

/*
Class: IPE.Component.Collabsible
	Makes an element collapsible. A twistie is added automatically. By default a collapsible element is displayed open. 
	Add a class name "closed" to display the element collapsed.
	
Events:
	element:open - fires when element is expanded.
	element:close - fires when element is collapsed.
*/

IPE.Component.Collapsible = Class.create(IPE.Component.Wrapper,{
	_globalEvents : ["beforeOpen","beforeClose","open","close"],
	defaultOptions : {
		duration : "300ms",
		remember : false
	},
	
	initialize : function ($super,element,options){
		$super(element,options);
		this.header = this.element.down();
		
		// wrap content
		this.content = new Element("div",{"class":"panelContent"});
		this.header.insert({after:this.content});
		var contentNodes = this.content.nextSiblings();
		contentNodes.each(function(node){
			this.content.insert(node);
		},this);
		this.element.loadContent = this.content.loadContent;
		
		// inject twistie
		var toggle = new Element("span",{"class":"toggle"});
		this.element.insert({
			top: toggle
		});
		toggle.observe("click", this.toggle.bind(this));
		
		// retrieve state
		if (this.options.remember){
			//Event.observe(window, 'beforeunload', this._storeState.bind(this));
			/*** can't do it on beforeonload because of webkit (chrome) bug ***/
			this.element.observe("element:open", this._storeState.bind(this));
			this.element.observe("element:close", this._storeState.bind(this));
			
			var retrievedState = this._retrieveState();
			if(retrievedState=="closed") this.element.addClassName("closed");
		}
		
		// set initial state
		if (!this.element.hasClassName("closed")){
			this.element.addClassName("open");
			this.currentState = "open";
		} else {
			this.content.hide();
			this.currentState = "closed";
		}
		
	},
	
	/*
	Method: _storeState
		Store state of tree in cookie
	*/
	_storeState : function(){
		var duration = (this.options.remember == "session") ? null : 365;
		Cookie.create(this.element.identify() + "State", this.currentState, duration);
	},
	
	/*
	Method: _retrieveState
		retrieve state of tree from cookie
	*/
	_retrieveState :  function(){
		state = Cookie.read(this.element.identify() + "State");
		return state;
	},
	
	/*
	Method: toggle
		Toggles the element.
		
	Arguments:
		state - (string) New state of element (open or closed).
	*/
	toggle : function(state){
		if (state && state == this.currentState){
			return;
		}
		this.element.fire("element:before" + ((this.currentState=="open") ? "Close" : "Open"), {element: this.element});
		var effect = (this.currentState=="open") ? "BlindUp" : "BlindDown";
		new Effect[effect](this.content, {
			duration:this.options.duration.toSeconds(),
			queue: {
				position: "end",
				scope: this.element.identify()
			},
			afterFinish: function(){
				this.currentState = this.currentState=="open" ? "closed" : "open";
				this.element.removeClassName((this.currentState=="open") ? "closed" : "open");
				this.element.addClassName((this.currentState=="open") ? "open" : "closed");
				this.element.fire("element:" + ((this.currentState=="open") ? "close" : "open"), {element: this.element});
			}.bind(this)
		});
		
	}
});

/*
File: DatePicker
*/

IPE.Component.componentDefinitions.DatePicker = {
	selector : ".datePicker"
};

/*
Class: IPE.Component.DatePicker
	DatePicker component. Shows a little icon next to a date field that opens a date selection widget on click.
	
Options:
	language - (string) Language code.
	keepFieldEmpty - (boolean) Doesn't apply current date to date field.
	topOffset - (number) Vertical offset in pixels for date selection popup.
	leftOffset - (number) Horizontal offset in pixels.
	zindex - (number) Z-index for datepicker popup.
*/
IPE.Component.DatePicker = Class.create(IPE.Component.Wrapper,{
	defaultOptions : {
		language: "nl",
		keepFieldEmpty: false,
		topOffset: 0,
		leftOffset: 4,
		zindex: 5000
	},
	
	initialize : function($super,element,options){
		$super(element,options);
		this.component = new DatePicker(element,this.options);
	}
});


/*
Class: DatePicker
	DatePicker widget using Prototype and Scriptaculous.
	(c) 2007 Mathieu Jondet <mathieu@eulerian.com>
	Eulerian Technologies
	DatePicker is freely distributable under the same terms as Prototype.

Modifications:	
	25-04-2007, Added dutch language, several other changes
	23-10-2007, changes to hash value retrieval (because of prototype 1.6)
*/

var DatePicker	= Class.create({
	Version : '0.9.2',
	_relative : null,
	_div : null,
	_zindex : 5000,
	_open : false,
	_keepFieldEmpty : false,
	_daysInMonth : [31,28,31,30,31,30,31,31,30,31,30,31],
	/* language */
	_language : 'nl',
	_language_month : $H({
		'nl' : [ 'Januari', 'Februari', 'Maart', 'April', 'Mei', 'Juni', 'Juli', 'Augustus', 'September', 'October', 'November', 'December' ],
		'fr' : [ 'Janvier', 'F&#233;vrier', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Aout', 'Septembre', 'Octobre', 'Novembre', 'D&#233;cembre' ],
		'en' : [ 'January', 'February', 'March', 'April', 'May','June', 'July', 'August', 'September', 'October', 'November', 'December' ],
		'sp' : [ 'Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre' ],
		'it' : [ 'Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno','Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre' ],
		'de' : [ 'Januar', 'Februar', 'M&#228;rz', 'April', 'Mai', 'Juni','Juli', 'August', 'September', 'Oktober', 'November', 'Dezember' ],
		'pt' : [ 'Janeiro', 'Fevereiro', 'Mar&#231;o', 'Abril', 'Maio', 'Junho','Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro' ]
	}),
 	_language_day : $H({
		'nl' : [ 'Ma', 'Di', 'Wo', 'Do', 'Vrij', 'Za', 'Zo' ],
		'fr' : [ 'Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam', 'Dim' ],
		'en' : [ 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun' ],
		'sp' : [ 'Lun', 'Mar', 'Mie', 'Jue', 'Vie', 'S&#224;b', 'Dom' ],
		'it' : [ 'Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab', 'Dom' ],
		'de' : [ 'Mon', 'Die', 'Mit', 'Don', 'Fre', 'Sam', 'Son' ],
		'pt' : [ 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'S&#225;', 'Dom' ]
	}),
	_language_close : $H({
		'nl' : 'sluiten',
		'fr' : 'fermer',
		'en' : 'close',
		'sp' : 'cierre',
		'it' : 'fine',
		'de' : 'schliessen',
		'pt' : 'fim'
	}),
	/* date manipulation */
	_todayDate : new Date(),
	_date_regexp : /^(\d{1,2})(\/|\.|\-)(\d{1,2})(?:\/|\.|\-)(\d{4})$/,
	_current_date : null,
	_clickCallback : Prototype.emptyFunction,
	_date_separator: '-',
	_id_datepicker : null,
	/* positionning */
	_topOffset : 0,
	_leftOffset : 4,
	_isPositionned : false,
	_relativePosition : true,
	/* return the name of current month in appropriate language */
	getMonthLocale : function ( month ) {
		return this._language_month.get(this._language)[month];
	},
	getLocaleClose : function () {
		return this._language_close.get(this._language);
	},
	_initCurrentDate : function () {
		/* check if value in field is proper, set to today */
		this._current_date = $F(this._relative);
		if ( !this._date_regexp.test(this._current_date) ) {
			var now = new Date();
			var day = this._leftpadZero(now.getDate(), 2);
			var mon = this._leftpadZero(now.getMonth() + 1, 2);
			/* depending on language not presented the same way */
			if (this._language == 'en') {
				this._current_date = mon + this._date_separator + day + this._date_separator + now.getFullYear();
			}
			else {
				this._current_date = day + this._date_separator + mon + this._date_separator + now.getFullYear();
			}

		}
		var a_date_regexp = this._current_date.match(this._date_regexp);
		/* fetch date separator as specified in option or via value */
		this._date_separator = String(a_date_regexp[2]);
		/* check language */
		if ( this._language == 'en' ) {
			this._current_mon = Number(a_date_regexp[1]) - 1;
			this._current_day = Number(a_date_regexp[3]);
		} else {
			this._current_day = Number(a_date_regexp[1]);
			this._current_mon = Number(a_date_regexp[3]) - 1;
		}
		this._current_year = Number(a_date_regexp[4]);
	},
	/* init */
	initialize : function (element, options) {
		/* arguments */
		var options = options || {};
		this._relative= element;
		if ( options["language"] ){
			this._language = options["language"];
		}
		this._zindex = ( options["zindex"] ) ? parseInt(Number(options["zindex"])) : this._zindex;
		if ( typeof(options["keepFieldEmpty"]) != 'undefined' ){
			this._keepFieldEmpty = options["keepFieldEmpty"];
		}
		if ( typeof(options["clickCallback"]) == 'function' ){
			this._clickCallback = options["clickCallback"];
		}
		if ( typeof(options["leftOffset"]) != 'undefined' ){
			this._leftOffset = parseInt(options["leftOffset"]);
		}
		if ( typeof(options["topOffset"]) != 'undefined' ){
			this._topOffset = parseInt(options["topOffset"]);
		}
		if ( typeof(options["relativePosition"]) != 'undefined' ){
			this._relativePosition = options["relativePosition"];
		}
		this._id_datepicker = 'datepicker-'+this._relative.id;
		this._id_datepicker_prev = this._id_datepicker+'-prev';
		this._id_datepicker_next = this._id_datepicker+'-next';
		this._id_datepicker_hdr = this._id_datepicker+'-header';
		this._id_datepicker_ftr = this._id_datepicker+'-footer';
		
		/* Add calendar button */
		this.buttonElement = $(document.createElement("SPAN"));
		this.buttonElement.addClassName("datepicker-button");
		this.buttonElement.innerHTML = " ";
		this._relative.parentNode.insertBefore(this.buttonElement,(this._relative.nextSibling || null));
		this.buttonElement.observe('click', this.click.bindAsEventListener(this));
		
		/* build up calendar skel */
		this._div = Builder.node('div', { id  : this._id_datepicker, className : 'datepicker',  style : 'display: none; z-index: '+this._zindex }, [
			/* header */
			Builder.node('div', { className : 'datepicker-header' }, [
				Builder.node('span', { id : this._id_datepicker_prev, style : 'cursor: pointer;', className : 'datepicker-prev' }, ' << '),
				Builder.node('span', { id : this._id_datepicker_hdr }),
				Builder.node('span', { id : this._id_datepicker_next, style : 'cursor: pointer;', className : 'datepicker-next' }, ' >> ')
			]),
			/* calendar */
			Builder.node('div', { className : 'datepicker-calendar' }, [
				Builder.node('table', { id : this._id_datepicker+'-table' }) ])
			/* footer */
			/*Builder.node('div', { 
			 id  : this._id_datepicker_ftr,
			 className: 'datepicker-footer' }, this.getLocaleClose() )*/
		]);
		this._div = $(this._div);
		/* finally declare the event listener on input field */
		//Event.observe(this._relative, 'click', this.click.bindAsEventListener(this), false);
		/* need to append on body when doc is loaded for IE */
		//Event.observe(window, 'load', this.load.bindAsEventListener(this), false);
		this.load();
	},
	/**
	* load : called when document is fully-loaded to append datepicker
	*   to main object.
	*/
	load : function () {
		/* append to body */
		var body = document.getElementsByTagName("body").item(0);
		if ( body ){
			body.appendChild( this._div );
		}
		/* init the date in field if needed */
		this._initCurrentDate();
		/* declare the observers for UI control */
		this.nextButton = $(this._id_datepicker_next);
		this.prevButton = $(this._id_datepicker_prev);
		this.prevButton.observe('click', this.prevMonth.bindAsEventListener(this), false);
		this.nextButton.observe('click', this.nextMonth.bindAsEventListener(this), false);
		//Event.observe($(this._id_datepicker_ftr), 'click', this.close.bindAsEventListener(this), false);
	},
	/**
	* click : called when input element is clicked
	*/
	click : function (evt) {
		this._position(evt);
		Event.stop(evt);
		if (this._open){
			this.close();
		} else {
			this._initCurrentDate();
			this._redrawCalendar();
			this.open();
			Event.observe(document,"click",this.handleDocumentClick.bind(this));
		}
	},
	/**
	* open : called when the datepicker is opened
	*/
	open : function () {
		if (Effect){
			new Effect.Appear(this._div, { duration : 0.2 });
		} else {
			this._div.show();
		}
		this._open = true;
		//$(this._id_datepicker).hide();
	},
	/**
	* close : called when the datepicker is closed
	*/
	close : function () {
		if (Effect){
			new Effect.Fade(this._div, { duration : 0.2 });
		} else {
			this._div.hide();
		}
		this._open = false;
		Event.stopObserving(document,"click",this.handleDocumentClick.bind(this));
		//$(this._id_datepicker).hide();
	},
	
	handleDocumentClick : function(evt){
		if (!Position.within(this._div,Event.pointerX(evt),Event.pointerY(evt))){
			this.close();
		}
	},
	/**
	* _position : set the position of the datepicker.
	*/
	_position : function (evt) {
		if ( !this._isPositionned && this._relativePosition ) {
			/* position the datepicker relatively to element */
			//var fieldPosition = Position.cumulativeOffset($(this._relative));
			//var fieldDimensions = $(this._relative).getDimensions();
			//var left = Number(fieldPosition[0]+fieldDimensions.width+this._leftOffset)+'px';
			//var top = Number(fieldPosition[1]+this._topOffset)+'px'
			var left = Event.pointerX(evt)+"px";
			var top = Event.pointerY(evt)+"px";
			this._div.setStyle({
				'left' : left,
				'top' : top
			});
			this._isPositionned = true;
		}
	},


	_leftpadZero : function ( str, padToLength ) {
		var result = '';
		for ( var i = 0; i < (padToLength - String(str).length); i++ ){
			result += '0';
		}
		return result + str;
	},
	/**
		* _getMonthDays : given the year and month find the number of days.
		*/
	_getMonthDays : function ( year, month ) {
		if (((0 == (year%4)) && 
			( (0 != (year%100)) || (0 == (year%400)))) && (month == 1)){
			return 29;
		}
		return this._daysInMonth[month];
	},
	/**
		* _buildCalendar : draw the days array for current date
		*/
	_buildCalendar : function () {
		var _self = this;
		var tbody = document.createElement('tbody');
		/* generate day headers */
		var trDay = document.createElement('tr');
		this._language_day.get(this._language).each( function ( item ) {
			var td = document.createElement('td');
			td.innerHTML = item;
			td.className = 'wday';
			trDay.appendChild( td );
		});
		tbody.appendChild( trDay );
		/* generate the content of days */
		
		/* build-up days matrix */
		var a_d = [
			[ 0, 0, 0, 0, 0, 0, 0 ],
			[ 0, 0, 0, 0, 0, 0, 0 ],
			[ 0, 0, 0, 0, 0, 0, 0 ],
			[ 0, 0, 0, 0, 0, 0, 0 ],
			[ 0, 0, 0, 0, 0, 0, 0 ],
			[ 0, 0, 0, 0, 0, 0, 0 ]
		];
		/* set date at beginning of month to display */
		var d = new Date(this._current_year, this._current_mon, 1, 12);
		/* start the day list on monday */
		var startIndex = ( !d.getDay() ) ? 6 : d.getDay() - 1;
		var nbDaysInMonth = this._getMonthDays(this._current_year, this._current_mon);
		var daysIndex = 1;
		for ( var j = startIndex; j < 7; j++ ) {
			a_d[0][j] = { 
				d : daysIndex,
				m : this._current_mon,
				y : this._current_year 
			};
			daysIndex++;
		}
		var a_prevMY = this._prevMonthYear();
		var nbDaysInMonthPrev = this._getMonthDays(a_prevMY[1], a_prevMY[0]);
		for ( var j = 0; j < startIndex; j++ ) {
			a_d[0][j] = { 
				d : Number(nbDaysInMonthPrev - startIndex + j + 1) ,
				m : Number(a_prevMY[0]),
				y : a_prevMY[1],
				c : 'outbound'
			};
		}
		var switchNextMonth = false;
		var currentMonth = this._current_mon;
		var currentYear = this._current_year;
		for ( var i = 1; i < 6; i++ ) {
			for ( var j = 0; j < 7; j++ ) {
				a_d[i][j] = { 
					d : daysIndex,
					m : currentMonth,
					y : currentYear,
					c : ( switchNextMonth ) ? 'outbound' : ( 
						((daysIndex == this._todayDate.getDate()) &&
						(this._current_mon  == this._todayDate.getMonth()) &&
						(this._current_year == this._todayDate.getFullYear())) ? 'today' : null)
				};
				daysIndex++;
				/* if at the end of the month : reset counter */
				if ( daysIndex > nbDaysInMonth ) {
					daysIndex = 1;
					switchNextMonth = true;
					if ( this._current_mon + 1 > 11 ) {
						currentMonth = 0;
						currentYear += 1;
					} else {
						currentMonth += 1;
					}
				}
			}
		}

		/* generate days for current date */
		for ( var i = 0; i < 6; i++ ) {
			var tr = document.createElement('tr');
			for ( var j = 0; j < 7; j++ ) {
				var h_ij = a_d[i][j];
				var td = document.createElement('td');
				/* id is : datepicker-day-mon-year or depending on language other way */
				/* don't forget to add 1 on month for proper formmatting */
				if ( this._language == 'en' ) {
					var id = $A([ this._relative.id, this._leftpadZero((h_ij["m"] +1), 2),
					this._leftpadZero(h_ij["d"], 2), h_ij["y"] ]).join('-');
				} else { 
					var id = $A([ this._relative.id, this._leftpadZero(h_ij["d"], 2),
					this._leftpadZero((h_ij["m"] + 1), 2), h_ij["y"] ]).join('-');
				}
				/* set id and classname for cell if exists */
				td.setAttribute('id', id);
				if ( h_ij["c"] ){
					td.className = h_ij["c"];
				}
				/* on onclick : rebuild date value from id of current cell */
				Event.observe(td,"click",this._selectDate.bindAsEventListener(this,td));
				td.innerHTML= h_ij["d"];
				tr.appendChild( td );
			}
			tbody.appendChild( tr );
		}
		return tbody;
	},
	
	_selectDate : function(evt,cell){
		this._relative.value = cell.id.replace(this._relative.id+'-','').replace(/-/g,this._date_separator); 
		this.close();
	},
	
	/**
		* nextMonth : redraw the calendar content for next month.
		*/
	_nextMonthYear : function () {
		var c_mon = this._current_mon;
		var c_year = this._current_year;
		if ( c_mon + 1 > 11 ) {
			c_mon = 0;
			c_year += 1;
		} else {
			c_mon += 1;
		}
		return [ c_mon, c_year ];
	},
	nextMonth : function () {
		var a_next = this._nextMonthYear();
		this._current_mon = a_next[0];
		this._current_year  = a_next[1];
		this._redrawCalendar();
	},
	/**
		* prevMonth : redraw the calendar content for previous month.
		*/
	_prevMonthYear : function () {
		var c_mon = this._current_mon;
		var c_year = this._current_year;
		if ( c_mon - 1 < 0 ) {
			c_mon = 11;
			c_year -= 1;
		} else {
			c_mon -= 1;
		}
		return [ c_mon, c_year ];
	},
	prevMonth : function () {
		var a_prev = this._prevMonthYear();
		this._current_mon = a_prev[0];
		this._current_year  = a_prev[1];
		this._redrawCalendar();
	},
	_redrawCalendar : function () {
		this._setLocaleHdr();
		var table = $(this._id_datepicker+'-table');
		try {
			while (table.hasChildNodes()) {
				table.removeChild(table.childNodes[0]);
			}
		} catch ( e ) {}
		table.appendChild( this._buildCalendar() );
	},
	_setLocaleHdr : function () {
		/* next link */
		var a_next = this._nextMonthYear();
		this.nextButton.setAttribute('title',
			this.getMonthLocale(a_next[0])+' '+a_next[1]);
		/* prev link */
		var a_prev = this._prevMonthYear();
		this.prevButton.setAttribute('title',
			this.getMonthLocale(a_prev[0])+' '+a_prev[1]);
		/* header */
		$(this._id_datepicker_hdr).update('&nbsp;&nbsp;&nbsp;'+this.getMonthLocale(this._current_mon)+'&nbsp;'+this._current_year+'&nbsp;&nbsp;&nbsp;');
	}
});

/*
File: DependentBlock
*/

IPE.Component.componentDefinitions.Dependency = {
	selector : "*[class*='dependency-']",
	options : {
		connectionEvents: ["element:afterUpdate"]
	}
};

/*
Class: IPE.Component.DependentBlock
	Makes an element dependent of one or more form fields. Different type of expressions are allowed, see dependency types below. 
	By default the display state of the element is dependent. It is also possible to toggle a class name, the disabled property or the checked property.

Options:
	dependency - (string) name of dependent form element.
	toggle - (string) Property that should be toggled. Three types are supported: "display", "className-myClassName", "attribute-optionalValue" and "effect-effectName".
	operator - (string) Boolean operator that should be used when element is dependent of multiple form elements.
	not - (boolean) Reverses the dependency.
	
Dependency types:
	on - (boolean) Show if filled in.
	off - (boolean) Don't show if filled in.
	equals - (string,array) Value(s) for which element should be displayed.
	notEquals - (string,array) Value(s) for which element should be hidden.
	min - (number) Maximum value.
	max - (number) Minimum value.
	
Toggle types:
	display - Toggles the display property.
	className - Toggles the provided class name.
	disabled - Toggles the disabled attribute.
	checked - Toggles the checked attribute.
*/

IPE.Component.Dependency = Class.create(IPE.Component.Wrapper,{
	/*
	Note: The options off, on, equals, notEquals, min, max and disable are for compatibility with the ipe 2.0 dependency component.
	*/
	defaultOptions : {
		dependency : null,
		toggle : "display",
		relation : "and",
		connectionEvents: [],
		off: null,
		on: null,
		equals: null,
		notEquals: null,
		min: null,
		max: null,
		disable: false
	},
	
	initialize : function($super,element,options){
		$super(element,options);
		this.dependencies = {};
		
		// fix disabled toggle (IPE turns "disabled" into false)
		if (this.options.toggle === false) this.options.toggle = "disabled";
		
		this.toggle = this.options.toggle.split("-");
		
		// compatibility with ipe 2.0 dependency component
		if (this.options.disable) this.toggle = ["disabled"];
		if (!Object.isArray(this.options.dependency)){
			if (this.options.on) this.options.dependency += "-on";
			if (this.options.off) this.options.dependency += "-off";
			if (this.options.equals) this.options.dependency += ("-equals-" + this.options.equals.toString().replace(/\,/g,"-"));
			if (this.options.notEquals) this.options.dependency += ("-notEquals-" + this.options.notEquals.toString().replace(/\,/g,"-"));
			if (this.options.min) this.options.dependency += ("-gt-" + this.options.min);
			if (this.options.max) this.options.dependency += ("-lt-" + this.options.max);
		}
		
		// set effects
		if (this.toggle[0] == "effect" || this.toggle[0] == "fade" || this.toggle[0] == "appear" || this.toggle[0] == "blind" || this.toggle[0] == "slide"){
			var effects = {
				effect : ["Appear","Fade"],
				fade : ["Appear","Fade"],
				appear : ["Appear","Fade"],
				blind : ["BlindDown","BlindUp"],
				slide : ["SlideDown","SlideUp"]
			};
			if (this.toggle[0] == "effect") this.toggle.shift();
			this.effect = {};
			var duration = this.toggle[1] || null;
			if (duration) this.effect.duration = duration.toSeconds();
			this.effect.name = effects[this.toggle[0]];
			this.toggle[0] = "effect";
		}
		
		/*
		Structure of this.dependencies:
		
		{
			"inputName" : {
				equals : [valueToEqual, otherValueToEqual],
				notEquals : [valueNotToEqual, otherValueNotToEqual],
				lt : minValue,
				gt : maxValue,
				on: true,
				off: true,
				element : [
					elementReference,
					elementReference
				],
				tag : "tagName",
				type : "radio",
				value : valueOfInput
			}
		}
		
		*/
		
		var operatorMappings = {
			"min" : "gte",
			"max" : "lte",
			"lessThan" : "lt",
			"greaterThan" : "gt",
			"lessThanOrEquals" : "lte",
			"greaterThanOrEquals" : "gte"
		};
		
		// group dependencies by input in object
		if (!Object.isArray(this.options.dependency)) this.options.dependency = [this.options.dependency];
		this.options.dependency.each(function(dependency){
			dependency = dependency.split("-");
			var inputName = dependency[0];
			var operator = dependency[1];
			if (operatorMappings[operator]) operator = operatorMappings[operator];
			var value = dependency[2] || true;
			//console.log(dependency.length)
			if (dependency.length > 3){
				// multple values are provided, so store array with these values
				value = dependency.splice(2, dependency.length-2);
			}
			if (!this.dependencies[inputName]) this.dependencies[inputName] = {};
			
			dependency = this.dependencies[inputName];
			
			// store data
			if (dependency[operator]){
				// already value stored for this operator
				
				// make array if only one value is stored
				if (!Object.isArray(dependency[operator])){
					dependency[operator] = [dependency[operator]];
				}
				
				// add values
				if (Object.isArray(value)){
					dependency[operator] = dependency[operator].concat(value);
				} else {
					dependency[operator].push(value);
				}
			} else {
				dependency[operator] = value;
			}
		}, this);
		
		// process all dependency data
		for (var inputName in this.dependencies){
			var dependency = this.dependencies[inputName];
			
			// if no operator is specified, use "on"
			if (
				!dependency.on &&
				!dependency.off &&
				!dependency.equals &&
				!dependency.notEquals &&
				!dependency.lt &&
				!dependency.lte &&
				!dependency.gt &&
				!dependency.gte
			){
				dependency.on = true;
			}
			
			// create arrays of equals and notEquals value and convert number values to string (necessary because input values are also strings)
			["equals","notEquals"].each(function(option){
				if (dependency[option]){
					if (!Object.isArray(dependency[option])){
						dependency[option] = [dependency[option]];
					}
					dependency[option].each(function(value,i){
						if (Object.isNumber(value)){
							dependency[option][i]  = value.toString();
						}
					},this);
				}
			},this);
			
			// set max to infinity if not defined
			if ((dependency.gt || dependency.gte) && !(dependency.lt || dependency.lte)){
				dependency.lt = Infinity;
			}
			// set min to -infinity if not defined
			if ((dependency.lt || dependency.lte) && !(dependency.gt || dependency.gte)){
				dependency.gt = -Infinity;
			}
			
			// connect to input
			this.connect(inputName, 1);
		};
		
		// toggle on initialization
		this._toggleBlock(true);
	},
	
	/*
	Method: connect
		Creates the connection between the dependent element and the form element it is dependent of.
		
	Arguments:
		inputName - (string) Name of input to connect.
		afterLoad - (boolean) Boolean flag that indicates whether connection is initiated by a content update of an element.
	*/
	connect : function(inputName, afterLoad){
		var dependency = this.dependencies[inputName];
		
		// store presence of input
		var inputPresent = dependency.element || null;
		
		// find elements with name="inputName"
		element = $$("*[name='"+inputName+"']");
		
		// dependent form element not found by name
		if (element.length == 0){
			element = $(inputName);
			if (!element) {
				// observe connection events
				if (!this.eventObserver){
					this.options.connectionEvents.each(function(eventName){
						document.observe(eventName,this.connect.bind(this, inputName));
					}, this);
					this.eventObserver = true;
				}
				element = null;
			}
			element = element ? [element] : null;
		}
		
		// store element reference(s) in element
		dependency.element = element;
		
		// stop if input was already present
		if (!element || (inputPresent && element)) return;
		
		if (element.length && element.length>1 && element[0].type == "radio") {
			// radio button group
			for (var i=0;i<element.length;i++){
				var input = element[i];
				dependency.tag = "input";
				dependency.type = "radio";
				input.observe("click", function(){this._toggleBlock();}.bind(this));
				input.observe("element:restore", function(){this._toggleBlock(1);}.bind(this));
			}
		} else {
			var input = element[0];
			dependency.tag = input.tagName.toLowerCase();
			dependency.type = (dependency.tag == "select" || dependency.tag == "textarea") ? dependency.tag : input.type;
			var e = (dependency.type == "checkbox") ? "click" : "change";
			input.observe(e, function(){this._toggleBlock();}.bind(this));
			input.observe("element:restore", function(){this._toggleBlock(1);}.bind(this));
		}
		
		if (afterLoad) this._toggleBlock(1);
	},
	
	/*
	Method: evaluate
		Evalutate conditions for a connected form element
		
	Arguments:
		inputName - (string) Name of input to check.
	*/
	evaluate : function(inputName){
		var dependency = this.dependencies[inputName];
		
		// get value of input
		var value = dependency.value || null;
		if (dependency && dependency.element){
			value = this.getValue(inputName);
			// store value
			dependency.value = value;
		}
		
		// evaluate
		var result = (
			(dependency.on && value) ||
			(dependency.off && !value) ||
			(dependency.equals && dependency.equals.include(value)) ||
			(dependency.notEquals && !dependency.notEquals.include(value)) ||
			(
				value &&
				(dependency.gt || dependency.gte) && 
				(dependency.lt || dependency.lte) && 
				(dependency.gt ? 
					Number(value.replace(",",".")) > Number(dependency.gt) :
					Number(value.replace(",",".")) >= Number(dependency.gte)
				) && 
				(dependency.lt ? 
					Number(value.replace(",",".")) < Number(dependency.lt) :
					Number(value.replace(",",".")) <= Number(dependency.lte)
				)
			)
		);

		// return result of evaluation
		return result ? true : false;
	},
	
	/*
	Method: getValue
		Get value of form element.
		
	Arguments:
		inputName - (string) inputName to get value of.
	*/
	getValue : function(inputName){
		var value = null;
		var dependency = this.dependencies[inputName];
		
		if (!dependency.element) return null;
		
		if (dependency.type=="radio"){// radio button group
			var selectedOption = dependency.element.find(function(opt){
				return opt.getValue();
			});
			value = selectedOption ? selectedOption.value : null;
		} else {
			value = dependency.element[0].getValue();
		}
		// remove whitespace
		if (Object.isString(value)) value = value.replace(/\s/g,"");
		return value;
	},
	
	/*
	Method: _toggleBlock
		Sets the display state of the element based on the value of the input field.
		
	Arguments:
		initial - (boolean) Is set when toggling is performed on initialization. This flag prevents execution of effects at initialization, instead uses display property.
	*/
	_toggleBlock : function(initial) {
		var result = [];
		var hasFalse = false;
		var hasTrue = false;
		
		// evaluate condition for each form element
		for (var inputName in this.dependencies){
			var dependency = this.dependencies[inputName];
			
			var result = this.evaluate(inputName);
			if (result) {
				hasTrue = true;
			} else {
				hasFalse = true;
			}
		}
		
		// check if all conditions combined evaluate true or false
		var isTrue = (this.options.relation == "and") ? !hasFalse : hasTrue;
		
		// stop if nothing changed
		if(isTrue === this.isTrue) return;
		
		// set corresponding classname
		this.element.removeClassName(!isTrue ? "dependencyState-on" : "dependencyState-off");
		this.element.addClassName(isTrue ? "dependencyState-on" : "dependencyState-off");
		
		// toggle the dependent property
		if (this.toggle[0] == "display" || (this.toggle[0] == "effect" && initial)){
			this.element[isTrue ? "show" : "hide"]();
			this.element.fire("element:" + (isTrue ? "show" : "hide"), {element: this.element});
		} else if (this.toggle[0] == "disabled"){
			if (this.element.enable && this.element.disable){
				this.element[isTrue ? "enable" : "disable"]();
			} else {
				this.element.disabled = !isTrue;
			}
			this.element.fire("element:" + (isTrue ? "enable" : "disable"), {element: this.element});
		} else if (this.toggle[0] == "checked"){
			this.element.checked = isTrue;
		} else if (this.toggle[0] == "className"){
			this.element[ (isTrue ? "add" : "remove") + "ClassName"](this.toggle[1]);
		} else if (this.toggle[0] == "effect"){
			var effectName = this.effect.name[isTrue ? 0 : 1];
			new Effect[effectName](this.element, {
				duration : this.effect.duration || 0.5,
				afterFinish : function(){
					this.element.fire("element:" + (isTrue ? "show" : "hide"), {element: this.element});
				}.bind(this)
			});
		}
		
		this.element.fire("dependency:" + (isTrue ? "true" : "false"));
		
		// store current state
		this.isTrue = isTrue;
	}
});

/*
File: Navigation
*/

IPE.Component.componentDefinitions.Navigation = {
	selector : "*[class*=navigation]"
};

/*
Class: IPE.Component.Navigation
	Basic navigation component. Automatic highlights active menu item by comparing it with loaded page url.
	Also opens the current branch in case of a nested menu.

Options:
	url - (string) url of item to highlight.
	menuIds - (array) array of link id's in the menu that should be highlighted
	target - (string) target that should be used for all hyperlinks in the menu
*/
IPE.Component.Navigation = Class.create(IPE.Component.Wrapper,{
	globalEvents : ["updateState"],
	defaultOptions : {
		url : location.href,
		target : null
	},

	initialize : function($super,element,options) {
		$super(element,options);
		this.activeLink = null;
		this.activeLinkNr = null;
		this.rotating = true;
		this.listeners = [];

		// strip hash and query string from url
		this.options.url = this.options.url.replace(/(\?|#).*$/,"");

		if (this.options.target){
			var target = $(this.options.target.replace(/^#/,""));
			if (target) this.options.url = target.retrieve("src") || location.href;
		}

		// get all navigation links
		this.lnks = this.element.select("a");

		this.lnks.each(function(lnk){
			if (this.options.target) {
				lnk.setAttribute("rel","#" + this.options.target);
			}
			if (lnk.readAttribute("target")){
				this.setListener(lnk.readAttribute("target"));
			}
			var rel = lnk.readAttribute("rel");
			var targets = rel ? rel.match(/#[\w\.-]+/g) : null;
			if (targets && targets[0]){
				this.setListener(targets[0]);
			}
			lnk.wrapContent();
		},this);
		this.showActive(this.options.url);
	},

	/*
	Property: setListener
		Set event listener for afterload event of target element in case of AJAX loading.

	Arguments:
	 	targetId - (string) Id of the target element.
	*/
	setListener : function(targetId){
		targetId = targetId.replace(/^#/,"");
		if (this.listeners[targetId]){
			return;
		}
		this.listeners[targetId] = true;
		var targetElement = $(targetId);
		if (!targetElement) return;
		targetElement.observe("element:afterUpdate",function(event){
			var elementId = event.element().identify();
			if (event && targetId == elementId) {
				var url = event.memo.url;
				this.showActive(url, 1);
			}
		}.bindAsEventListener(this));
	},

	/*
	Property: showActive
		Highlight active menu item

	Arguments:
	 	url - (string) Url of item to highlight.
	 	menuIds - (array) Array of menu item ids to highlight.
	*/
	showActive : function(url, moduleInjection){
		this._deactivate();
		this.lnks.each(function(lnk,i){
			var node = lnk.up("li") || lnk;
			var match = this._checkIfActive(lnk, url, moduleInjection);

			if (match[0]){
				lnk.addClassName("inPath");
				node.addClassName("inPath");
				this._openBranch(lnk);
			}
			if (match[1] || lnk.hasClassName("current")){
				this._deactivate();
				lnk.addClassName("current");
				node.addClassName("current");
				this.activeLink = lnk;
				this.activeLinkNr = i;
				this._openBranch(lnk);
				this.element.store("activeLink",lnk);
				this.element.store("activeLinkNr",this.activeLinkNr);
				node.fire("element:activate", {element: node});
				this.element.fire("Navigation:updateState",{
					activeLink : lnk,
					activeLinkNr : this.activeLinkNr
				});
			}
		}.bind(this));
	},

	/*
	Property: deactivate
		Deactivate current active item.
	*/
	_deactivate : function(){
		if (this.activeLink){
			var node = this.activeLink.up("li") || this.activeLink;
			this.activeLink.removeClassName("current");
			this._openBranch(this.activeLink, 1);
			node.removeClassName("current");
			node.fire("element:deactivate", {element: node});
		}
	},

	/*
	Property: _checkIfActive
		Check if a menu item should be activated.

	Arguments:
		lnk - (object) Menu item elemement.
		url - (string) Url to compare href of menu item to.
		menuIds - (array) Menu item ids to compare id of menu item to.
	*/
	_checkIfActive : function(lnk, url, moduleInjection){
		var isInPath = false;
		var isPageMatch = false;
		var href = lnk.readAttribute("href");
		if (href == "#") return false;
		href = this._normalizeUrl(href, moduleInjection);
		url = this._normalizeUrl(url, moduleInjection);

		// check if link href is same as page url
		isPageMatch = (href == url);

		// check if 'section' matches on links pointing to folder roots
		if (!isPageMatch && href.match(/\/(index.html)?$/)){
			href = href.replace("index.html", "");
			isInPath = url.include(href) ? true : false;
		}

		return [isInPath, isPageMatch];
	},

	/*
	Property: _openBranch
		Open menu nodes above active node

	Arguments:
		node - (object) Menu item from where up the branch should be opened.
	*/
	_openBranch : function(node, close){
		while (node = node.up("li")){
			node[close ? "removeClassName" : "addClassName"]("active");
		}
	},

	/*
	Property: _normalizeUrl
		Returns a url in a consistent format across browsers.

	Arguments:
		url - (string) Url to normalize
	*/
	_normalizeUrl : function(url, moduleInjection) {
		var l = window.location;
		if (url.startsWith("#")) {
			// reconstruct url for "in page" links
			url = location.pathname + url;
		} else if (!moduleInjection) {
			url = url.replace(/#.*$/,"");
		}
		if (url.match(/(http|https|file)\:/)) return url;// immediately return explicit protocols
		if (url.charAt(0) == '/') return l.protocol + '//' + l.host + url;// handle absolute references
		l = l.href.replace(/\/[^\/]+$/,"");// strip off the filename (if any) of the location to leave the folder we're in
		while (url.substring(0, 3) == '../') {// handle any relative directory references, i.e. '../'
			url = url.substring(3);
			l = l.replace(/\/[^\/]+$/,"");
		}
		return l + "/" + url;
	}
});

/*
File: Draggable
*/

IPE.Component.componentDefinitions.Draggable = {
	selector : ".draggable"
};

/*
Class: IPE.Component.Draggable
	Makes an element draggable.
	
Options:
	zindex - (number) z-index while dragging
	constraint - (string) A setting that constrains movement to the horizontal or vertical axis. By default, no 
		constraint is applied.
	snap - (number) A dimension (in pixels) used to specify whether movement during a drag operation 
		â€œsnapsâ€� to a grid of that size. Defaults to no snapping.
	ghosting - (boolean) 	A Boolean value specifying whether ghosting is enabled during a drag operation. 
		Defaults to false (no ghosting).
	revert - (boolean) 	A Boolean value that specifies whether the reverteffect callback will be 
		invoked when the drag operation stops. Defaults to false.
		
Events:
	element:startDrag - Fires when a drag is initiated.
	element:drag - Fires repeatedly during a drag.
	element:endDrag - Fires when a drag is ended.
*/
IPE.Component.Draggable = Class.create(IPE.Component.Wrapper,{
	_globalEvents : ["startDrag","drag","endDrag"],
	_callBacks : {
		onStart: "startDrag",
		onDrag: "drag",
		onEnd: "endDrag"
	},
	defaultOptions : {
		zindex: 100000,
		constraint: false,
		snap: false,
		ghosting: false,
		revert: true
	},
	
	initialize : function($super,element,options){
		$super(element,options);
		var handle = element.down(".handle");
		if (handle){
			this.options.handle = handle;
		}
		new Draggable(element,this.options);
	}
});

/*
File: Droppable
*/

IPE.Component.componentDefinitions.Droppable = {
	selector : ".droppable"
};

/*
Class: IPE.Component.Droppable
	Allows draggable elements to be dropped in the element.
	
Options
	hoverclass - (string) A class name that will be applied to the drop target when a draggable hovers 
		over it.
	containment - (string,array) An element id, or array of element id's, that must be a parent of a draggable item in 
		order for it to be accepted by the drop target. By default, no containment con- 
		straints are applied.

Events:
	Droppable:hover - fires when draggable element is dragged over.
	Droppable:drop - fires when draggable element is dropped in the droppable element.
*/
IPE.Component.Droppable = Class.create(IPE.Component.Wrapper,{
	_globalEvents : ["hover","drop"],
	_callBacks : {
		onHover : "hover",
		onDrop : "drop"
	},
	defaultOptions : {
		hoverclass: "hover",
		containment: null
	},
	
	initialize : function($super,element,options){
		$super(element,options);
		this.cleared = false;
		this.options.onDrop = this.moveElement.bind(this);
		Droppables.add(element,this.options);
	},
	
	moveElement : function(draggable,droppable){
		if (!this.cleared){
			droppable.innerHTML = "";
			this.cleared = true;
		}
		draggable.remove();
		droppable.insert(draggable);
	}
});
/*
File: Drum
*/

IPE.Component.componentDefinitions.Drum = {
	selector : ".drum"
};

/*
Class: IPE.Component.Drum
	Drum scroller.
	
Options:
	rows - (number) Number of rows to display.
	image - (string) Id of element containing background image that should change for each focussed element in the drum.
*/
IPE.Component.Drum = Class.create(IPE.Component.Wrapper, {
	defaultOptions : {
		rows : 5,
		image : null
	},
	
	initialize: function ($super,element,options) {
		$super(element,options);
		this.visibleItems = this.options.rows;	
		this.list = element.down('ul');
		this.pic = this.options.image ? $(this.options.image) : null;
		
		// insert up and down buttons
		this.buttonUp = new Element("button",{"class":"up"}).update("<strong>up</strong>");
		this.buttonDown = new Element("button",{"class":"down"}).update("<strong>down</strong>");
		this.list.insert({
			before: this.buttonUp,
			after: this.buttonDown
		});
		
		// set click handlers on buttons
		this.buttonUp.observe("click", this.shiftItemUp.bind(this));
		this.buttonDown.observe("click", this.shiftItemDown.bind(this));
		
		// initial magnify
		this.magnify();
	},
	
	/*
	Method: shiftItemUp
		Scroll upwards.
	*/
	shiftItemUp: function () {
		var items = this.list.childElements();
		this.list.insert({top:items[items.length-1]});
		this.magnify();
	},
	
	/*
	Method: shiftItemDown
		Scroll downwards.
	*/
	shiftItemDown: function () {
		var items = this.list.childElements();
		this.list.insert({bottom:items[0]});
		this.magnify();
	},
	
	/*
	Method: magnify
		Magnify focussed element.
	*/
	magnify: function () {
		var items = this.list.childElements();			
		var peak = Math.ceil(this.visibleItems/2);
		
		//this.pic.src = items[peak].childElements()[1].src;
		if (this.pic) {
			this.pic.style.backgroundImage = "url(" + items[peak - 1].down("img").getAttribute('src') + ")";
		}
		
		items.each(function(item, index){
			item.style.display = index > this.visibleItems-1 ? "none" : "block";				
			if (index+1 < peak){
				var pos = "down-"+Math.abs(index+1-peak);
			} else if (index+1 > peak){
				var pos = "up-"+Math.abs(index+1-peak);
			} else {
				var pos = "peak";
			}
			item.className = pos;
		},this);
	}
});
/*
File: Effect
*/

IPE.Component.componentDefinitions.Effect = {
	selector : "*[class*=effect]"
};

/*
Class: IPE.Component.Effect
	This component can play effects on events 
	
Options:
	effect - (array) Effects that should be connected to the element.
	Effect declarations (string) must have the following structure:
	> "nameOfEffect-eventToPlayEffectOn-option1-option2"
	
Supported effects:
	All Scriptaculous effects are supported. In the effect declaration, the uncapitalized name of the effect has to be used ("highlight" instead of "Highlight"). Only a few support providing options through the effect declarations mentioned above. The effects are pulsate, highlight, shake and flash.
	
Effect options:
	duration - Duration of the effect. Examples: "2000ms" or "2s".
	from - Value to use for start of animation. Examples: "from#fff" or "from0".
	to - Value to use for end of animation. Examples: "to#fff" or "to1".
	restore - Value to restore to after animation. Examples: "restore#fff" or "restore0".
	loop - Times to loop animation. Example: "5x".
	
Supported custom events:
	load - When element is loaded.
	update - After content update of element.
	show - On show event. 
	hide - On hide event.
	enable - On enable event.
	disable - on disable event.
	
	Above events are fired by other IPE components.
		
Providing default effect option:
	It is possible to provide default effect options through the options object. This can be done like this:
	
	(start code)
	{
		highlight : {
			startcolor: "#ffff00",
			endcolor: "#cccccc"
		}
	}
	(end)
	
*/
IPE.Component.Effect = Class.create(IPE.Component.Wrapper,{
	defaultOptions : {
		effect: []
	},
	
	initialize: function($super,element,options) {
		$super(element,options);
		this.effects = [];
		if (!Object.isArray(this.options.effect)){
			this.options.effect = [this.options.effect];
		}
		this.options.effect.each(function(effect, i){
			// get effect object
			effect = this.configureEffect(effect.split("-"), i);
			
			if (effect){
				// if event is load play effect directly
				if (effect.event == "load"){
					this.play(this.element, effect);
				} else {
					// listen to event
					this.element.observe(effect.event, this.onEvent.bindAsEventListener(this, effect));
				}
			}
		}, this);
	},
	
	/*
	Property: eventMappings
		event mappings, to allow shorter event names in options
	*/
	eventMappings : {
		"beforeUpdate" : "element:beforeUpdate",
		"update" : "element:afterUpdate",
		"show" : "element:show",
		"beforeOpen" : "element:beforeOpen",
		"beforeClose" : "element:beforeClose",
		"open" : "element:open",
		"close" : "element:close",
		"hide" : "element:hide",
		"enable" : "element:enable",
		"disable" : "element:disable",
		"scroll" : "element:scroll",
		"activate" : "element:activate",
		"deactivate" : "element:deactivate",
		"startDrag" : "element:startDrag",
		"endDrag" : "element:endDrag",
		"drag" : "element:drag",
		"remove" : "element:remove"
	},
	
	/*
	Method: configureEffect
		Create an effect object from specified effect parameters.
	
	Arguments:
		effectDef - (array) Array containing all effect parameters.
	*/
	configureEffect : function(effectDef, i){
		
		// get effect properties
		
		// duration of the effect
		var duration = effectDef.grep(/^\d+m?s$/)[0];
		if (duration && typeof duration == "string") duration = duration.toSeconds();
		
		// times to loop the effect
		var loop = effectDef.grep(/^\d+x$/, function(value){
			return value.replace("x","");
		})[0];
		
		// value to animate a property from
		var from = effectDef.grep(/^from[^-]+$/, function(value){
			return value.replace("from","");
		})[0];
		if (from && from.match(/^[\d\.]+$/)){
			from = Number(from);
		}
		if (from && from.match(/^\d+%$/)){
			from = parseInt(from)/100;
		}
		
		// value to animate a property to
		var to = effectDef.grep(/^to[^-]+$/, function(value){
			return value.replace("to","");
		})[0];
		if (to && to.match(/^[\d\.]+$/)){
			to = Number(to);
		}
		if (to && to.match(/^\d+%$/)){
			to = parseInt(to)/100;
		}
		
		// value to restore a property to after the animation has finished
		var restore = effectDef.grep(/^restore[^-]+$/, function(value){
			return value.replace("restore","");
		})[0];
		if (restore && restore.match(/^[\d\.]+$/)){
			restore = Number(restore);
		}
		
		// dimension (pixel value) to use for animation
		var dimension = effectDef.grep(/^\d+px$/, function(value){
			return value.replace("px","");
		})[0];
		
		// queue or not?
		var queue = effectDef.grep(/^queue$/)[0];
		
		var effectName = effectDef[0];
		var defaultOptions = this.options[effectName] || {};
		
		// create effect object
		var effect = {
			name : effectName.charAt(0).toUpperCase() + effectName.substring(1),
			options : defaultOptions || {}
		};
		
		if (effectName == "pulse"){
			effect.name = "Pulsate";
			if (to) effect.options.from = to;
			if (loop) effect.options.pulses = loop;
		}
		
		else if (effectName == "shake"){
			if (dimension) effect.options.distance = dimension;
		}
		
		else if (effectName == "highlight"){
			if (to) effect.options.startcolor = to;
			if (from) effect.options.endcolor = from;
			if (restore) effect.options.restorecolor = restore;
		}
		
		else if (effectName == "flash"){
			if (loop) effect.options.pulses = loop;
			if (to) effect.options.startcolor = to;
			if (from) effect.options.endcolor = from;
		}
		
		else if (effectName.startsWith("morph")){
			if (!to && to != 0) return null;
			effectName= effectName.replace("morph","");
			var property = effectName.charAt(0).toLowerCase() + effectName.substring(1);
			effect.name = "Morph";
			effect.options.style = {};
			effect.options.style[property] = to;
		}
		
		else {
			if (from || from == 0) effect.options.from = from;
			if (to || to == 0) effect.options.to = to;
		}
		
		effect.options.duration = duration || 0.5;
		
		// set queue
		if (queue || !i) {
			effect.options.queue = {
				position: "end",
				scope: this.element.identify()
			};
		}
		
		// event
		effect.event = effectDef[1];
		if (this.eventMappings[effect.event]){
			effect.event = this.eventMappings[effect.event];
		}
		
		// return effect
		return effect;
	},
	
	/*
	Method: onEvent
		Event handler that play effect on an event.
	
	Arguments:
		event - (object) Event object.
		effect - (object) Effect specification.
	*/
	onEvent: function(event, effect){
		var eventMemo = event.memo;
		var element = (eventMemo && eventMemo.element) ? eventMemo.element : this.element;
		this.play(element, effect);
	},
	
	/*
	Method: play
		Plays the effect on the provided element
		
	Arguments:
		element - (object) Element to play the effect upon.
		effect - (object) Object containing all effect parameters.
	*/
	play: function(element, effect){
		if (!element) element = this.element;
		new Effect[effect.name](element, effect.options);
	}
});



// Effects:

/*
Class: 
*/
Effect.Flash = function(element) {
	element = $(element);
	var options    = arguments[1] || { },
		oldColor = element.getStyle("background-color"),
		from = options.from || (oldColor == "transparent" ? "#ffffff" : oldColor),
		to = options.to || "#ffffcc",
		transition = options.transition || Effect.Transitions.linear,
		duration = options.duration || options.pulses || 3.0,
		pulses = options.pulses || 3,
		reverser = function(pos){
			return 1 - transition((Math.cos((pos*pulses*2)*Math.PI)/2) + .5);
		};
	
	return new Effect.Morph(element,
		Object.extend({
			duration: options.duration || options.pulses || 3.0, 
			style: "background-color:"+to,
			afterFinish: function(effect) { 
				element.setStyle({backgroundColor: oldColor});
			},
			transition: reverser
		}, options)
	);
};
/*
File: FloatingPanel
*/

IPE.Component.componentDefinitions.FloatingPanel = {
	selector : "*[class*=floatingPanel]"
};

/*
Class: IPE.Component.FloatingPanel
	Floating Panel object. Four types: normal, modal, callout and context

Options:
	addCloseButton - (boolean) add a close button or not. Defaults to true.
	closeOnClick - (boolean) Closes the panel on the first mouse click.
	escape - (boolean) Closes the panel after pressing the escape key.
	
Events:
	element:close - Fires when panel is closed.
	element:open - Fires when panel is opened.
	
	Both events are fired on the panel element itself and the document (through bubbling). The panel element is sent as element property of event.memo.
*/
IPE.Component.FloatingPanel = Class.create(IPE.Component.Wrapper,{
	_globalEvents : ["open","close"],
	defaultOptions : {
		addCloseButton : true,
		closeOnClick : false,
		escape : "enabled"
	},
	
	initialize : function($super,element,options) {
		$super(element,options);
		this.inner = element;
		this.element = element.up();
		
		// create reference to highest z-index, that is stored on FloatingPanel class
		this.topZIndex = IPE.Component.FloatingPanel.topZIndex;
		
		// add outer container with class "panel" if not available
		if (this.element.match("body") || (!this.element.up().match("body") && !this.element.hasClassName("panel"))){
			var panel = new Element("div",{"class":"panel"}).hide();
			this.element.insert(panel.insert(this.inner));
			this.element = panel;
		}
		
		// add panel class name
		this.element.addClassName("panel");
		
		// hide element
		this.element.hide();
		this.inner.setStyle({display: "block"});
		
		// get panel type
		var panelClassName = this.inner.classNames().find(function(className){
			return className.startsWith("floatingPanel");
		});
		this.panelType = panelClassName.split("-")[1] || "base";
		this.element.addClassName('panel'+ this.panelType.capitalize());
		
		//See if panel should act as a confirm dialog
		this.trigger = this.element.retrieve("trigger");
		if(this.trigger && (this.trigger.hasClassName("confirm") || this.trigger.retrieve("confirm"))) this.processConfirmAction();

		// get visible state of panel
		this.visible = (this.element.getStyle("display")!="none");
		
		this.closer = this.close.bind(this);
		
		// enable closeOnClick for callout panels
		if (this.panelType == "callout") this.options.closeOnClick = true;
		
		// show panel
		this.show();
		
		// add close method to element.
		this.element.close = this.closer;
	},
	
	/*
	Method: processConfirmAction
		Fires confirm event when the confirm button is clicked
		Closes the panel when confirm is clicked
	*/
	processConfirmAction : function(){
		
		//get confirm button
		this.btnConfirm = this.element.down(".confirm");
		if(!this.btnConfirm) return;
		
		//when confirm is clicked, execute the delayed action(s) by fireing the event
		this.btnConfirm.observe("click", function(e){
			this.trigger.fire("action:confirmed", {element:this.trigger});
			this.close();
		}.bind(this));
	},
	
	showFunctions : {
		base : function(){
			this.element.appear({
				duration: .2,
				afterFinish: function(){
					if (this.panelType == "modal") {
						this._setOverlayDimensions();
					}
				}.bind(this)
			});
		},
				
		sheet : function(){
			this.inner.hide();
			this.element.show();
			this.inner.blindDown({
				duration: .3
			});
		}
	},
	
	closeFunctions : {
		base : function(){
			this.element.fade({
				duration:.5,
				afterFinish: this._remove.bind(this)
			});
		},

		sheet : function(){
			this.inner.blindUp({
				duration: .3,
				afterFinish: this._remove.bind(this)
			});
		}
	},
	
	positionFunctions : {
		base : function(positionTop) {

			var centeredVertical = positionTop ? false : true;
			var innerWidth = this.inner.getStyle("width");
			if (innerWidth && !parseInt(innerWidth)) innerWidth = null;

			// calculate total width of panel
			if (innerWidth){
				var paddingLeft = this.inner.getStyle("padding-left") || 0;
				var paddingRight = this.inner.getStyle("padding-right") || 0;
				var borderLeft = this.inner.getStyle("border-left-width") || 0;
				var borderRight = this.inner.getStyle("border-right-width") || 0;
				innerWidth = parseInt(innerWidth) + parseInt(paddingLeft) + parseInt(paddingRight) + parseInt(borderLeft) + parseInt(borderRight);
			}
			
			if (!this.element.getStyle("width")){
				this.element.setStyle({
					width: innerWidth ? "auto" : "50%"
				});
			}
			var context = (this.element.up() != document.body);
			var dimensions = this.element.getDimensions();
			var viewport = document.viewport.getDimensions();
			var top = 0;
			if (centeredVertical && !context){
				var top = 20;
				if (dimensions.height < viewport.height){
					top = (viewport.height-dimensions.height)/2;
				}
				if (this.element.select('object')) {
					top = 34;
				}
				top += document.viewport.getScrollOffsets().top;
			}
			
			this.element.setStyle({
				top: top + "px",
				marginLeft: innerWidth ? -1*parseInt(innerWidth)/2 + "px" : "-25%"
			});
		},
		
		callout : function() {
			var trigger = this.element.retrieve("trigger");
			if (!trigger) return;
			var position = trigger.viewportOffset();
			var offsets = trigger.getDimensions();
			var dimensions = this.element.getDimensions();
			var scrolled = document.viewport.getScrollOffsets();
			if (position.top + dimensions.height > document.viewport.getHeight()){
				position.top = document.viewport.getHeight() - dimensions.height;
			}
			if (position.left + dimensions.width > document.viewport.getWidth()){
				position.left = position.left - dimensions.width - 10;
			} else {
				position.left = position.left + offsets.width + 10;
			}
			this.element.setStyle({
				top: position.top + scrolled.top + "px",
				left: position.left + scrolled.left + "px",
				marginLeft: 0
			});
		},
		
		sheet : function() {
			this.positionFunctions.base.call(this, true);
		}
	},
	
	/*
	Method: close
		Closes the panel.
	*/
	close : function(event) {
		// don't close panel if click is on trigger that opened panel 
		if (event){
			var origin = event.element ? event.element() : event.target;
			var trigger = this.element.retrieve("trigger");
			if (trigger && (trigger == origin || (trigger.rel == origin.rel && trigger.rel))) return;
		}
		
		(this.closeFunctions[this.panelType] || this.closeFunctions.base).call(this);
		if (this.panelType == "modal"){
			this._hideOverlay();
		}
		this.visible = false;
		this.element.fire("element:close", {element: this.element});
		
		// stop observing clicks
		if (this.options.closeOnClick){
			document.stopObserving("click", this.closer);
		}
		
		// stop observing esc key
		if (this.options.escape){
			document.stopObserving("keyboard:esc", this.closer);
		}
	},
	
	/*
	Method: show
		Shows the panel.
	*/
	show : function(event) {
		if (event && event.target != this.element){
			return;
		}
		
		if (!this.visible) {
			this.element.hide();
		}
		if (this.options.addCloseButton){
			this._addCloseButton();
		}
		
		if (this.panelType == "modal") this.element.addClassName('panelModal');
		
		this._position();
		this.overlay = $("overlay") || null;
		
		if (!this.visible) {
			if (this.panelType == "modal") {
				this._showOverlay();
			}
			// perform show animation
			(this.showFunctions[this.panelType] || this.showFunctions.base).call(this);
		}
		this.element.select(".closePanel").each(function(closer){
			if (closer.type && closer.type == "submit") closer = closer.up("form") || closer;
			var eventName = closer.nodeName.toLowerCase() == "form" ? "form:execute" : "click";
			closer.observe(eventName, this.closer);
		},this);
		this.visible = true;
		this.element.fire("element:open", {element: this.element});
		
		// set close on click handler
		if (this.options.closeOnClick){
			document.observe("click", this.closer);
		}
		
		// set close on esc handler
		if (this.options.escape){
			document.observe("keyboard:esc", this.closer);
		}
	},
	
	/*
	Method: _addCloseButton
		Add a close button to the panel
	*/
	_addCloseButton : function(){
		// add close button                                     
		var closeButton = new Element("span");
		closeButton.addClassName('close');
		this.inner.insert({
			top: closeButton
		});
		closeButton.observe("click", this.close.bind(this));
	},
	
	/*
	Method: _position
		Position the panel. Each panel type has it's own position function.
	*/
	_position : function() {
		
		// set z-index
		this.topZIndex.value += 100;
		this.element.setStyle({zIndex:this.topZIndex.value});
		
		// execute positioning function
		(this.positionFunctions[this.panelType] || this.positionFunctions.base).call(this);
	},
	
	/*
	Method: _remove
		Removes the panel
	*/
	_remove : function() {
		var parent = this.element.up();
		if (parent){
			this.element.remove();
		}
	},
	
	/*
	Method: _showOverlay
		Shows overlay behind the panel and above rest of the screen.
	*/
	_showOverlay : function() {
		// create overlay if not available
		this.overlay = $("overlay");
		if (!this.overlay) {
			this.overlay = new Element("div",{id:"overlay"}).hide();
			$(document.body).insert(this.overlay);
		}
		
		// set styles
		if (this.overlay.getStyle("display")!="none") return;
		this.overlay.setStyle({
			opacity: 0,
			zIndex:(this.topZIndex.value-100),
			position: Prototype.Browser.IE6 ? "absolute" : "fixed"
		});
		this._setOverlayDimensions();
		this.overlay.show();
		new Effect.Opacity(this.overlay,{
			duration: 1,
			from: 0,
			to: .6
		});
	},
	
	/*
	Method: _setOverlayHeight
		Set dimensions of overlay to hide the whole page.
	*/
	_setOverlayDimensions : function(){
		// set height
		var bodyHeight = document.body.scrollHeight;
		var htmlHeight = document.body.parentNode.scrollHeight;
		var documentHeight = (bodyHeight > htmlHeight) ? bodyHeight : htmlHeight;
		var viewportHeight = document.viewport.getHeight();
		var height = (documentHeight >= viewportHeight) ? documentHeight : viewportHeight;
		this.overlay.setStyle({height: height + "px"});
		
		// set width for IE6
		if (Prototype.Browser.IE6){
			var bodyWidth = document.body.getWidth() || 0;
			var viewportWidth = document.viewport.getWidth();
			if (viewportWidth > bodyWidth) this.overlay.setStyle({width: viewportWidth + "px"});
		}
	},
	
	/*
	Method: _hideOverlay
		Hide overlay.
	*/
	_hideOverlay : function(){
		this.overlay.fade();
	}
});

/*
Property: topZIndex
	Property that stores the top z-index. This property is used to position a floating panel above all other panels and elements.
*/
IPE.Component.FloatingPanel.topZIndex = {value: 1000};
/*
File: NavigationFlyout
*/

IPE.Component.componentDefinitions.NavigationFlyout = {
	selector : [
		".navigationFlyout-horizontal",
		".navigationFlyout-vertical"
	]
};

/*
Class: IPE.Component.NavigationFlyout
	Flyout navigation menu

Options:
	openClassName - (string) Class name for open branch
	closedClassName - (string) Class name for closed class name
	delay - (float) Delay for closing a node
	click - (boolean) Makes flyout open on click.
 */
IPE.Component.NavigationFlyout = Class.create(IPE.Component.Wrapper,{
	defaultOptions : {
		openClassName : "open",
		closedClassName : "closed",
		delay: "1s",
		click : false
	},
	
	initialize : function($super,element,options) {
		// call super constructor
		$super(element,options);
		this.options.delay = this.options.delay.toSeconds();
		this.closed = true;
		this.horizontal = this.element.hasClassName("navigationFlyout-horizontal");
		
		// collect all li's and attach behaviour
		this.element.select('li').each(function(li){
			if (li.down("ul")){
				li.addClassName("hasChildren");
				li.observe("mouseover", this._onMouseOver.bindAsEventListener(this,li));
				if (this.options.click) li.observe("click", this._onClick.bindAsEventListener(this, li));
				li.observe("mouseout", this._onMouseOut.bindAsEventListener(this,li));
			}
		},this);
		
		this.element.observe("mouseout", function(){
			if (this.openDelay) window.clearTimeout(this.openDelay);
		}.bind(this));
	},
	
	/*
	Method: _onMouseOver
		Mouseover event handler.
	*/
	_onMouseOver : function (evt, node) {
		if (this.options.click && this.closed) return;
		if (this.closed || this.horizontal){
			this.openDelay = this._openNode.bind(this).delay(0.3, node);
		} else {
			this._openNode(node);
		}
	},
	
	/*
	Method: _onClick
		Click event handler.
	*/
	_onClick : function (evt, node){
		this.closed = false;
		this._onMouseOver(evt, node);
	},
	
	/*
	Method: _onMouseOut
		Mouseout event handler.
	*/
	_onMouseOut : function (evt, node){
		window.clearTimeout(this.closeDelay);
		this.closeDelay = this._closeNode.bind(this).delay(this.options.delay, node, 1);
	},
	
	/*
	Method: _closeNode
		Opens a node of the menu.
	*/
	_closeNode : function(node, leave) {
 		if (leave) this.closed = true;
		node.removeClassName(this.options.openClassName);
		node.addClassName(this.options.closedClassName);
		node.open = false;
	},
	
	/*
	Method: _openNode
		Closes a node of the menu.
	*/
	_openNode : function(node) {
		window.clearTimeout(this.closeDelay);
		this.closed = false;
		if(!node.open){
			node.adjacent("li").each(function(li){
				this._closeNode(li);
			},this);
			node.open = true;	
			node.removeClassName(this.options.closedClassName);	
			node.addClassName(this.options.openClassName);
		}
	}
});
/*
File: Form.js
*/


IPE.Component.componentDefinitions.FormState = {
	selector : ["label", "fieldset","*[class*='state']"]
};

/*
Class: IPE.Component.FormState
	Add classes to elements based on the live value of form elements.
*/
IPE.Component.FormState = Class.create(IPE.Component.Wrapper,{

	defaultOptions : {
		state : null
	},

	initialize : function($super,element,options){
		$super(element,options);
		
		this.nodeName = element.nodeName.toLowerCase();
		var forAttribute = element.readAttribute("for");
		var labelOrFieldset = (this.nodeName == "label" || this.nodeName == "fieldset");
		
		if (forAttribute){
			element = $(forAttribute);
			if (!element) return;
		}

		if (this.options.state){
			document.observe("element:change-"+this.options.state, this.onChange.bind(this));
		} else {
			element.observe("element:change", this.onChange.bind(this));
			element.observe("element:focus", this.onFocus.bind(this));
			element.observe("element:blur", this.onBlur.bind(this));
		}
	},

	onFocus : function(event){
		this.element.addClassName("field-focus");
	},

	onBlur : function(event){
		this.element.removeClassName("field-focus");
	},

	onChange : function(event){
		var target = event.element();
		var init = event.memo.init;
		
		if (this.nodeName == "label" && !this.typeSet) {
			if (target.type && target.type != "text") this.element.addClassName(target.type);

			if (Prototype.Browser.ltIE8 && !this.element.readAttribute("for") && (target.type == "radio" || target.type == "checkbox")){
				this.element.observe('click', function(e) {
					if (e.target != target && !target.disabled){
						e.stop();
						target.checked = !target.checked;
						target.simulate("change");
					}
				});
			}
		}
		
		var value = this.getValue(target);
		
		if (this.nodeName == "label"){
			if (target.type == "radio" || target.type == "checkbox"){
				this.element.addClassName(target.checked ? "on" : "off");
				this.element.removeClassName(target.checked ? "off" : "on");
			}
			this.element[target.disabled ? "addClassName" : "removeClassName"]("disabled");
		}

		if (target.type != "textarea" && target.type != "text"){
			if (!init) this.removeValueClass();
			if (value){
				if (Object.isArray(value)) {
					value.each(function(value){
						this.element.addClassName("value-" + value.replace(/[^\w-]/g, ""));
					}.bind(this));
				} else {
					this.element.addClassName("value-" + value.replace(/[^\w-]/g, ""));
				}
			}
		}

		this.typeSet = true;
	},

	getValue : function(element){
		if ((element.tagName.toLowerCase() == "button" || element.type == "button") && !element.hasClassName("on")) return null;
		if (element.type == "checkbox" && !element.checked) return "off";
		return element.getValue();
	},

	removeValueClass : function(){
		this.element.classNames().each(function(name){
			if (name.startsWith("value")) this.element.removeClassName(name);
		}.bind(this));
	}
});


IPE.Component.componentDefinitions.FormElement = {
	selector: [
		"input",
		"select",
		"textarea",
		"button"
	]
};

/*
Class: IPE.Component.FormElement
	Add classes to elements based on the live value of form elements.
	Add focus class to form elements when element has focus.
*/
IPE.Component.FormElement = Class.create(IPE.Component.Wrapper,{

	initialize : function($super,element,options){
		$super(element,options);
		// stop if element has no name or type is hidden
		if (!element.name || element.type == "hidden") return;

		var nodeName = element.nodeName.toLowerCase();
		this.name = element.name;
		this.isButton = (nodeName == "button" || element.type == "button");
		this.isCheckbox = element.type == "checkbox";
		this.isRadio = element.type == "radio";

		// set type class name
		if (element.type) element.addClassName(element.type);

		// simulate change event in IE on checkbox or radiobutton
		if (Prototype.Browser.IE && (this.isCheckbox || this.isRadio)){
			element.observe("click", function(){
				this.element.simulate("change");
			}.bind(this));
		}
		
		element.observe("focus", this.onFocus.bind(this));
		element.observe("blur", this.onBlur.bind(this));

		// set change or click events
		var changeEvent = this.isButton ? "click" : "change";
		element.observe(changeEvent, this.onChange.bind(this));
		// observe restore event from components that set value programmatically
		element.observe("element:restore", this.onChange.bind(this));

		// apply initial state
		this.onChange(true);

		// extend disable and enable methods
		if (element.disable) element.disable = element.disable.wrap(function(proceed){
			proceed();
			this.onChange();
		}.bind(this));

		if (element.enable) element.enable = element.enable.wrap(function(proceed){
			proceed();
			this.onChange();
		}.bind(this));
	},

	onFocus : function(){
		this.element.fire("element:focus");
		if (this.element.id) this.element.fire("element:focus-"+this.element.id);
	},

	onBlur : function(){
		this.element.fire("element:blur");
		if (this.element.id) this.element.fire("element:blur-"+this.element.id);
	},
	
	onChange : function(){
		var init = arguments[0] && typeof arguments[0] == "boolean";
		if (!init && (this.isButton || this.isCheckbox || this.isRadio)) this.element.addClassName("on").removeClassName("off");
		var value = this.getValue(this.element);
		this.element[this.element.disabled ? "addClassName" : "removeClassName"]("disabled");
		if (!init && (this.isRadio || this.isButton)) {
			document.fire("element:sameNameChange", {element: this.element, value:value});
		}
		this.element.fire("element:change", {element: this.element, value:value, init:init});
		this.element.fire("element:change-"+this.name, {element: this.element, value:value, init:init});
	},

	getValue : function(element){
		if ((element.tagName.toLowerCase() == "button" || element.type == "button") && !element.hasClassName("on")) return null;
		if (element.type == "checkbox" && !element.checked) return "off";
		return element.getValue();
	}
});

IPE.Component.componentDefinitions.HandleSameNameChange = {
	selector: "document"
};

IPE.Component.HandleSameNameChange = Class.create({

	initialize : function($super,element,options){
		document.observe("element:sameNameChange", this.handleChange.bind(this));
	},

	handleChange : function(event){
		var target = event.memo.element;
		if (!this.isButtonOrRadio(target)) return;
		var sameNameElements = $$("*[name="+target.name+"]");
		sameNameElements.each(function(element){
			if (element == target || !this.isButtonOrRadio(element)) return;
			element.addClassName("off").removeClassName("on");
			var value = this.getValue(element);
			element.fire("element:change", {element: element, value:value});
		}.bind(this));
	},

	isButtonOrRadio : function(element){
		var nodeName = element.nodeName.toLowerCase();
		var isButton = (nodeName == "button" || element.type == "button");
		var isRadio = element.type == "radio";
		return (isButton || isRadio);
	},

	getValue : function(element){
		if ((element.tagName.toLowerCase() == "button" || element.type == "button") && !element.hasClassName("on")) return null;
		if (element.type == "checkbox" && !element.checked) return "off";
		return element.getValue();
	}
});

/*
File: Validation
*/

IPE.Component.componentDefinitions.Validation = {
	selector : ".validate"
};

/*
Class: IPE.Component.Validation
	Form validation.

	Events:
		form:valid - fires when all validations in the form pass
		element:invalid - fires when validation fails
		element:invalid-load - fires when validation fails after load
		element:invalid-keyup - fires when validation fails after keyup
		element:invalid-change - fires when validation fails after change
		element:invalid-submit - fires when validation fails after submit
		element:valid - fires when validation passes
		element:valid-load - fires when validation passes after load
		element:valid-keyup - fires when validation passes after keyup
		element:valid-change - fires when validation passes after change
		element:valid-submit - fires when validation passes after submit
*/
IPE.Component.Validation = Class.create(IPE.Component.Wrapper,{
	initialize : function($super,element,options){
		$super(element,options);

		this.validateTimeout = null;

		//set initial state to invalid
		this.invalid = null;

		// store form element
		this.form = this.element.up("form");
		this.fieldset = this.element.up("fieldset");
		this.validators = this.constructor.validators;

		// get element's requirements and events
		this.parseValidations(this.element);

		// validate element on form submit
		this.form.observe("form:submit", function(event){
			if(!this.form.retrieve("validated")) this.validate(event, "submit");
		}.bind(this));

		// up the form's validations property by 1
		if (!this.form.validations) this.form.validations = [];
		this.form.validations.push(false);
		this.index = this.form.validations.length - 1;

		// if direct feedback, add eventlistener to input(s)
		// always add keyup validation, change for checkboxes, radiobuttons & selects
		var initialValue = false;

		this.nrChanged = 0;
		this.inputs.each(function(input){
			var changeEvent = (Prototype.Browser.IE && (input.type=="radio" || input.type=="checkbox")) ? "click" : "change";

			input.observe(changeEvent, function(event){
				this.setChanged(event.element());
			}.bind(this));

			if (!(input.type=="radio" || input.type=="checkbox" || input.tagName=="SELECT")){
				input.observe("keyup", function(event){
					var target = event.element();
					var valid = this.element.retrieve("valid");
					if (this.checkChanged(target) || (valid === false)){
						this.validate(event, "keyup");
					}
				}.bind(this));
			}
			if(!!input.getValue()) {
				this.setChanged(input, 1);
			}
		}.bind(this));

		if (this.nrChanged == this.inputs.length) this.validate(null, "load");
	},

	setChanged : function(input, initial){
		if (!input.retrieve("changed") && !!input.getValue()){
			input.store("changed", true);
			this.nrChanged++;
		}

		if (this.checkChanged(input)){
			this.validate(null, initial ? "load" : "change");
		}
	},

	/*
	Method: checkChanged
		Checks or sets changed state to an input element.

	Arguments:
		event - (object) Event object of keyup event.
	*/
	checkChanged : function(element){
		return (element.retrieve("changed") && ((element.type && element.type == "radio") || this.nrChanged == this.inputs.length));
	},

	/*
	Method: getValue
		Get the value(s) of the input element(s).

	Returns:
		Single value or array of values.
	*/
	getValue : function(){
		var value = [];

		if (this.inputs[0].type == "radio"){
			// group of radio buttons
			var selectedOption = this.inputs.find(function(opt){
				return opt.getValue();
			});
			value.push(selectedOption ? selectedOption.value : null);
		} else {
			this.inputs.each(function(input){
				value.push(input.getValue());
			});
		}
		if (value.length==1) value = value[0];
		return value;
	},

	/*
	Method: parseValidations
		Initialize validators.
	*/
	parseValidations : function(){
		var element = this.element;

		// function to check if element is a form input element
		function isInput(element){
			return (element.match("input") || element.match("select") || element.match("textarea"));
		}

		// store if element is input
		this.isInput = isInput(element);

		// find inputs in correct order (that's why no select("input, select, textarea") is used)
		this.inputs = this.isInput ? [element] : element.select("*").findAll(function(el){return isInput(el);});

		// get requirements
		this.requirements = [];
		element.classNames().each(function(className){
			var requirement = className.match(/^(\w+)-(.*)$/);
			if (requirement && this.validators[requirement[1]]){
				this.requirements.push([requirement[1], requirement[2]]);
			} else if (this.validators[className.camelize()]){
				this.requirements.push([className.camelize()]);
			}

			// store required state
			if (className == "required") this.required = true;
		}.bind(this));
	},

	/*
	Method: validate
		Execute validation.
	*/
	validate : function(event, eventName){
		var element = this.element;
		var value = this.getValue(element);
		var error = null;
		// if the element still exists in the DOM and is not 'disabled' by a dependency
		if(element.up("body") && !element.hasClassName("dependencyState-off") && !element.up(".dependencyState-off")){
			// run validators
			this.requirements.each(function(requirement){
				var validationType = requirement[0];
				var validationValue = requirement[1] || null;
				if (!this.required && (!value || value == [])) return;
				if(!this.validators[validationType].call(this, value, validationValue, element)){
					error = [validationType, validationValue];
					throw $break;
				}
			}.bind(this));
		}
		// broadcast the validation outcome if valid state is not set or changed
		//if (this.invalid != null && ((this.invalid && error) || (!this.invalid && !error))) return;
		var outcome = {};

		if (error){
			// add invalid flag to element & form
			this.form.validations[this.index] = false;
			this.form.store("validated", false);
			Object.extend(outcome, {
				flag : "invalid",
				type: error[0],
				requirement : error[1]
			});
			this.form.store("stopExecution", true);
		} else {
			// add valid flag to element & form
			this.form.validations[this.index] = true;
			outcome.flag = "valid";
		}

		if (!(error && (eventName == "load" || eventName == "keyup"))) this.setState(!error);

		this.broadcastEvent.bind(this).defer(outcome, eventName);

		// if all validations in the form passed, fire form:valid event
		if(!this.form.validations.include(false)){
			this.form.store("validated", true);
			this.form.eliminate("stopExecution");
			this.form.fire("form:valid");
		}
	},

	/*
	Method: broadcastEvent
		Broadcast outcome of validation.
	*/
	broadcastEvent : function(eventData, eventName){
		var element = this.element;
		
		// create event memo
		var memo = Object.extend(eventData, {
			label : this.getLabelText(),
			title : element.title || "",
			id : element.identify(),
			name : element.name || "",
			element : element,
			value : this.getValue(),
			eventName: eventName
		});
		
		// fire event
		element.fire("element:" + eventData.flag + "-" + eventName, memo);
		element.fire("element:" + eventData.flag, memo);
	},

	/*
	Method: markInvalid
		Add or remove invalid class names to element itself and corresponding label or fieldset.
	*/
	setState : function(valid){
		this.invalid = !valid;
		var element = this.element;
		element.store("valid", valid);
		/*var action = valid ? "removeClassName" : "addClassName";
		element[action]("invalid");
		if (this.isInput){
			label = element.up("label") || element.previous("label") || element.next("label") || element.up("fieldset");
			if (label) label[action]("invalid");
		}*/
	},

	/*
	Method: getLabelText
		Returns text in corresponding label or legend.
	*/
	getLabelText : function(){
		if (this.labelText) return this.labelText;
		var element = this.element;
		this.labelText = "";
		var label = this.isInput ? (element.up("label") || element.previous("label")) : element.down("p.question");
		if (!label){
			var fieldset = element.up("fieldset");
			if (fieldset) label = fieldset.down("p.question");
		}
		if (label){
			var nodes = $A(label.childNodes);
			var textNode = nodes.find(function(node){return (node.nodeType===3 && node.nodeValue != "");});
			if (textNode) return textNode.nodeValue;
		}
		return "";
	}
});

Object.extend(IPE.Component.Validation,{
	/*
	Property: validators
		Object containing all validator functions.
	*/
	validators : {

		equals : function(value, equal){
			return (value == equal);
		},
		notEquals : function(value, equal){
			return (value != equal);
		},
		equalsOther : function(value, equalId){
			var element = $(equalId);
			if (!element) return false;
			if(element.type=="checkbox" || element.type=="radio"){
				return ((value && element.checked) || (!value && !element.checked));
			} else {
				return (value==element.getValue());
			}
		},
		notEqualsOther : function(value, equalId){
			var element = $(equalId);
			if (!element) return false;
			if(element.type=="checkbox" || element.type=="radio"){
				return ((!value && element.checked) || (value && !element.checked));
			} else {
				return (value!=element.getValue());
			}
		},
		lt : function(value, max){
			return (parseFloat(value) < parseFloat(max));
		},
		lte : function(value, max){
			return (parseFloat(value) <= parseFloat(max));
		},
		gt : function(value, min){
			return (parseFloat(value) > parseFloat(min));
		},
		gte : function(value, min){
			return (parseFloat(value) >= parseFloat(min));
		},
		range : function(value, range, element){
			var minMax = range.match(/(-?\d+)-(-?\d+)/);
			return (value >= parseFloat(minMax[1]) && value <= parseFloat(minMax[2]));
		},
		required : function(value, required){
			if (Object.isArray(value)){
				return value.find(function(val){
					return val ? true : false;
				}) ? true : false;
			} else {
				return value ? true : false;
			}
		},
		minChars : function(value, minChars){
			return (value.length >= minChars);
		},
		noSpaces : function(value){
			var noSpaces = /\s/;
			return !noSpaces.test(value);
		},
		email: function(value){
			// regex found here: http://fightingforalostcause.net/misc/2006/compare-email-regex.php
			var email = /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+)*\.(aero|arpa|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org|pro|travel|mobi|[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i;
			return email.test(value);
		},
		alphaNumeric : function(value){
			var alphaNumeric = new RegExp( "^[" +
				// Numbers
				"\u0030-\u0039" +
				// English alpha characters
				"\u0041-\u005a\u0061-\u007a" +
				// Underscore and hyphen
				"\u002d\u005f" +
				// Quote, apostrophe, punctuation
				"\u0022\u0027\u00b4" +
				// Whitespace
				"\\s" +
				// Latin-1 Supplement - With excess removed, only leaving characters
				// Suitable for; German, Italian, Portuguese, Spanish.
				"\u00c0-\u00ff" +
				// Latin-1 Supplement - Extra characters
				// Suitable for Dutch as per http://en.wikipedia.org/wiki/ISO/IEC_8859-1
				"\u0131-\u0132" +
				// Latin-1 Supplement - Extra characters
				// Suitable for French as per http://en.wikipedia.org/wiki/ISO/IEC_8859-1
				"\u0152\u0178" +
				// Chinese Characters
				"\u4e00-\ud7a3" +
			"]+$");
			return alphaNumeric.test(value);
		},
		numeric : function(value){
			var numeric = /^-?\d+$/;
			return numeric.test(value);
		},
		alphabetic : function(value){
			if (this.validators.alphaNumeric(value)){
				return !(/[\d]/).test(value);
			} else {
				return false;
			}
		},
		url : function(value){// ******** to strict ********* //
			var url = /^(ht|f)tps?\:\/\/[A-Za-z0-9-_]+\.[A-Za-z0-9-_%&\?\/.=\{\}\:]+$/;
			return url.test(value);
		},
		postalCodeNl : function(value, requirement){
			if ((Object.isArray(value)) && value.length == 2){
				return ((/^[0-9]{4}$/).test(value[0]) && (/^[a-zA-Z]{2}$/).test(value[1]));
			} else {
				return (/^[0-9]{4}\s?[a-zA-Z]{2}$/).test(value);
			}
		},
		minSelected : function(value, min, element){
			//expect collection
			var selected = 0;
			this.inputs.each(function(input){
				if(input.checked){selected++;}
			});
			return (selected >= min);
		},
		maxSelected : function(value, max, element){
			//expect collection
			var selected = 0;
			this.inputs.each(function(input){
				if(input.checked){selected++;}
			});
			return (selected <= max);
		},
		dateEu : function(value){
			var validDate = true;
			if (!Object.isArray(value)){
				value = value.split(/\/|-/);
			}

			// test days
			var day = parseInt(value[0], 10);
			if (isNaN(day) || day < 1 || day > 31){
				validDate = false;
			}

			// test month
			var month = parseInt(value[1], 10);
			if (isNaN(month) || month < 1 || month > 12){
				validDate = false;
			}

			// test year
			var year = parseInt(value[2], 10);
			if (isNaN(year) || year < 1 || year > 9999){
				validDate = false;
			}

			return validDate;
		},
		before : function(value, date){
			var inputDate = IPE.Component.Validation.getDate(value);

			if(date.match(/.*-|\+\d+[d|m|y]+$/)){
				var parsedCheck = date.match(/(.*)(-|\+)(\d+)(\w+)/);
				if(parsedCheck.length==5){
					date = parsedCheck[1];
					var operator = parsedCheck[2];
					var amount = parsedCheck[3];
					var units = parsedCheck[4]=="d" ? "Date" : (parsedCheck[4]=="m" ? "Month" : "Year");
				}
			}
			if(date=="today"){
				var checkDate = new Date();
			} else {
				checkDate = IPE.Component.Validation.getDate(date);
			}
			if(operator && units){
				var newUnit = eval("checkDate.get"+units+"()");
				newUnit = eval("newUnit"+operator+amount);
				checkDate = eval("checkDate.set"+units+"("+newUnit+")");
			}

			return (inputDate < checkDate);
		},
		after : function(value, date, element){
			var inputDate = IPE.Component.Validation.getDate(value);
			if(date.match(/.*-|\+\d+[d|m|y]+$/)){
				var parsedCheck = date.match(/(.*)(-|\+)(\d+)(\w+)/);
				if(parsedCheck.length==5){
					date = parsedCheck[1];
					var operator = parsedCheck[2];
					var amount = parsedCheck[3];
					var units = parsedCheck[4]=="d" ? "Date" : (parsedCheck[4]=="m" ? "Month" : "Year");
				}
			}
			if(date=="today"){
				var checkDate = new Date();
			} else {
				checkDate = IPE.Component.Validation.getDate(date);
			}
			if(operator && units){
				var newUnit = eval("checkDate.get"+units+"()");
				newUnit = eval("newUnit"+operator+amount);
				checkDate = eval("checkDate.set"+units+"("+newUnit+")");
			}

			return (inputDate > checkDate);
		},
		bankAccountNl : function(value){
			// remove spaces and dots
			value = value.replace(/[\s\.]/g,"");

			// check pattern
			var pattern = /^([pP]?)([\d]{1,10})$/;
			var match = value.match(pattern);

			// return false if value doesn't match pattern
			if (!match) return false;
			
			value = match[2];
			var multiplier = value.length;
			var result = 0;
			if (multiplier < 8){
				// no 'elfproef' for numbers shorter than 8 digits (girorekeningnummer)
				return true;
			} else if ((multiplier == 9 || multiplier == 10) && !match[1] ){
				// elfproef
				for (var i=0; i<value.length; i++, multiplier--){
					result += parseInt(value.charAt(i), 10) * multiplier;
				}
				return (result%11 == 0);
			} else {
				return false;
			}
		},
		telephoneNumberNl : function(value){
			var telephone = /^(((00|\+)\d{2}|0)[\s-]*((\d{2}[\s-]*\d{7})|(\d{3}[\s-]*\d{6})|(6[\s-]*\d{8})))$/;
			return telephone.test(value);
		},
		cellphoneNumberNl : function(value){
			var telephone = /^(((00|\+)\d{2}|0)[\s-]*((6[\s-]*\d{8})))$/;
			return telephone.test(value);
		},
		licensePlateNumberNl : function(value){
			// supports side codes 1,2,3,4,5,6,7,8 (cars and commercial vehicles only, no mopeds)
			// there's no check on valid alpha characters
			var pattern = /([a-zA-Z]{2}-?[a-zA-Z]{2}-?[\d]{2})|([a-zA-Z]{2}-?[\d]{2}-?[a-zA-Z]{2})|([\d]{2}-?[a-zA-Z]{2}-?[a-zA-Z]{2})|([a-zA-Z]{2}-?[\d]{2}-?[\d]{2})|([\d]{2}-?[a-zA-Z]{2}-?[\d]{2})|([\d]{2}-?[\d]{2}-?[a-zA-Z]{2})|([\d]{2}-?[a-zA-Z]{3}-?[\d]{1})|([\d]{1}-?[a-zA-Z]{3}-?[\d]{2})/;
			return pattern.test(value);
		}
	},

	/*
	Method: getDate
		Returns a data object from provided day, month and year.

	Arguments:
		date - (array) Array containing day, month and year.
	*/
	getDate : function(date){
		if (!Object.isArray(date)) date = date.split(/\/|-/);

		var rDate = new Date(parseInt(date[2], 10), parseInt(date[1], 10) - 1, parseInt(date[0], 10));
		rDate.setFullYear(parseInt(date[2], 10)); // prevents prefixing 1900
		return rDate;
	}
});




IPE.Component.componentDefinitions.Message = {
	selector : "form",
	options : {
		messageFile : (IPE.appPath || "/") + "Modules/messages.html",
		events : ["validation:error", "validation:success"]
	}
};

/*
Class: IPE.Component.Message
	Shows messages in a element after certain events have taken place.

Options:
	messageFile - (string) Path of message file.
	events - (array) Array of events to show messages on.
*/
IPE.Component.Message = Class.create(IPE.Component.Wrapper,{
	defaultOptions : {
		messageFile : "/",
		events : []
	},

	/*
	Property: messageCount
		Tracks number of messages in form.
	*/
	messageCount : 0,

	initialize : function($super, element, options){
		$super(element, options);

		//determine global feedbacktype
		this.globalFeedbackType = this.getFeedbackType(this.element) || {type: "form"};

		this.direct = this.globalFeedbackType.direct;

		// capture valid event
		this.element.observe("element:valid", function(event){
			var element = event.memo.element;
			this.removeMessage(element);
			this.setState(element, true);
		}.bind(this));

		// capture invalid event
		this.element.observe("element:invalid", function(event){
			var eventName = event.memo.eventName;
			var element = event.memo.element;
			var direct = this.getFeedbackType(element).direct || this.direct;

			if ((eventName == "change" && direct) || eventName == "submit"){
				this.showMessage(event.memo);
				this.setState(element, false);
			}
		}.bind(this));

		// load messagefiles
		this.constructor.getMessageFile(this.getMessageFileLocation() || this.options.messageFile);
	},

	/*
	Method: setState
		Add or remove invalid class names to element itself and corresponding label, fieldset, form and inputs.
	*/
	setState : function(element, valid){
		var action = valid ? "removeClassName" : "addClassName";
		element[action]("invalid");

		var isInput = (element.match("input") || element.match("select") || element.match("textarea"));

		// set class name of containing fieldset element
		var fieldset = element.up("fieldset");
		if (fieldset) fieldset[action]("invalid");

		// set class name of containing form element
		var form = element.up("form");

		var formValid = form.retrieve("validated");
		formValid = formValid || typeof formValid == "undefined";
		if (form) form[formValid ? "removeClassName" : "addClassName"]("invalid");

		// set class name of label
		var label = element.up("label");
		if (!label && isInput && element.id) {
			label = (fieldset || form).select("label[for='"+element.id+"']")[0];
		}
		if (label) label[action]("invalid");

		// set class name of containing form elements
		if (!isInput) {
			element.select("input", "select", "textarea").each(function(input){
				input[action]("invalid");
			});
		}
	},

	/*
	Method: getMessageFileLocation
		Get messages file location from hidden input in the form.
	*/
	getMessageFileLocation : function(){
		var messageFileInput = this.element.getInputs("hidden", "validationMessages")[0];
		return messageFileInput ? IPE.appPath + messageFileInput.value.substring(1) : null;
	},

	/*
	Method: getMessage
		Returns a feedback type for the provided element.

	Arguments:
		element - (object) Element to get feedback type for.
	*/
	getFeedbackType : function(element){
		var feedbackType = false;
		var className = element.classNames().find(function(name){
			return name.startsWith("feedback");
		});
		if (!className) return false;

		var direct = className.include("-direct");
		className = className.replace("-direct","");
		className = className.match(/feedback(?:-?)([a-zA-Z]+)(?:-?)([a-zA-Z]+)?/);
		feedbackType = className[1].toLowerCase();

		if (feedbackType == "before" || feedbackType == "after" || feedbackType == "first" || feedbackType == "last"){
			var reference = className[2] ? ($(className[2]) || element) : element;
		} else {
			var reference = className[2] || null;
		}
		feedbackType = {
			type: feedbackType,
			reference: reference,
			direct: direct
		};
		return feedbackType;
	},

	/*
	Method: getMessage
		Returns a data object from provided day, month and year.

	Arguments:
		data - (object) Object containing data to find the right message and data that has to be inserted in the message.
	*/
	getMessage : function(data){
		data.x = data.requirement || "";
		if (data.x.split("-").length > 1){
			data.x.split("-").each(function(r,i){data["x"+(i+1)]=r;});
		}
		var formId = this.element.readAttribute("name") || this.element.identify();
		var messageType = (data.flag == "invalid") ? "error" : "success";

		// find message
		var content;
		[
			formId+"-"+data.name+"-"+data.type+"-"+messageType,
			data.name+"-"+data.type+"-"+messageType,
			formId+"-"+data.name+"-"+messageType,
			data.name+"-"+messageType,
			formId+"-"+data.type+"-"+messageType,
			data.type + "-" + data.requirement +"-"+messageType,
			data.type+"-"+messageType,
			data.type+"-"+messageType+"-"+data.eventName
		].each(function(key){
			if (this.constructor.messages[key]){
				content = this.constructor.messages[key];
				throw $break;
			}
		}.bind(this));

		if(!content) return false;

		//replace messagevariables with actual values
		data.content = content.interpolate(data);

		return data;
	},

	/*
	Method: showMessage
		Show a message

	Arguments:
		data - (object) Object containing data to find the right message and data that has to be inserted in the message (data comes from the event that triggered the message).
	*/
	showMessage : function(data){
		// get validation message
		var message = this.getMessage(data);

		if(!message) return;

		var element = message.element;
		var previousMessage = element.retrieve("messageData") || {};

		// stop if error is the same as existing error on element
		if (element == previousMessage.element && message.type == previousMessage.type) return;

		// get generic form message
		var genericFormMessage = this.getMessage({
			type : "form",
			flag : data.flag,
			eventName : data.eventName
		});

		// remove old message
		if(element.retrieve("message")){
			this.removeMessage(element);
		}
		element.store("messageData", message);

		// create temporary element
		var tempElement = new Element("div").hide();
		document.body.insert(tempElement);
		tempElement.update(message.content);
		messageElement = tempElement.down().hide();

		var feedbackType = this.getFeedbackType(element) || this.globalFeedbackType;

		// create global message box at top of form
		if((feedbackType.type=="form" || genericFormMessage) && !this.messageBox){
			this.messageBox = new Element("div", {"class":"messageBox " + data.flag}).hide();
			this.element.insert({top : this.messageBox});
			this.messageBoxList = new Element("ul");
			if (genericFormMessage) this.messageBox.insert(genericFormMessage.content);
			this.messageBox.insert(this.messageBoxList);

			this.element.fire("element:show", {element: this.messageBox});
			this.queueAction(function(){
				this.messageBox.show();
			}.bind(this));
		}

		var getInsertionPosition = function(position){
			position = position || "bottom";

			// translate first and last into top and bottom
			if (position == "first") position = "top";
			if (position == "last") position = "bottom";

			return position;
		};

		// insert message
		if(feedbackType.type=="form"){
			//insert all messages in this element
			var listItem = new Element("li").insert(messageElement);
			this.messageBoxList.insert({bottom : listItem});
			element.store("message", listItem);
		}
		else if(feedbackType.type=="label"){
			//insert message into label belonging to the input that fired the message
			var label = this.element.select("label[for='"+element.name+"']")[0] || element.up("label");
			if(label){
				var position = getInsertionPosition(feedbackType.reference);
				var insertion = {};
				insertion[position] = messageElement;
				label.insert(insertion);
				element.store("message", messageElement);
			}
		}
		else if(feedbackType.type=="legend"){
			//insert message after the legend belonging to the fieldset the input that fired the message is in
			var legend = (element.match("fieldset") ? element : element.up("fieldset")).down("p.question");
			if(legend){
				var position = getInsertionPosition(feedbackType.reference);
				var insertion = {};
				insertion[position] = messageElement;
				legend.insert(insertion);
				element.store("message", messageElement);
			}
		}
		else if(feedbackType.type == "before" || feedbackType.type == "after" || feedbackType.type == "first" || feedbackType.type == "last"){
			//insert message before or after the given element
			if (feedbackType.reference){
				var insertion = {};
				insertion[getInsertionPosition(feedbackType.type)] = messageElement;
				feedbackType.reference.insert(insertion);
				message.element.store("message", messageElement);
			}
		}

		this.messageCount++;

		// show message (in effect queue)
		this.element.fire("element:show", {element: messageElement});
		this.queueAction(function(messageElement){
			messageElement.show();
		}.bind(this, messageElement));

		// remove temporary element
		tempElement.remove();
	},

	/*
	Method: removeMessage
		Remove a message

	Arguments:
		element - (object) Element for which message has to be removed (messages are always connected to an element).
	*/
	removeMessage : function(element){
		if(element){
			var message = element.retrieve("message");
			//remove message from DOM
			if(message){
				this.element.fire("element:remove", {element: message});
				this.queueAction(function(){
					message.remove();
				}.bind(this));

				//remove reference to message from element
				element.eliminate("message");
				element.eliminate("messageData");

				this.messageCount--;

				// remove global message box if no messages are present
				if (this.messageCount == 0 && this.messageBox){
					this.messageBox.remove();
					this.messageBox = null;
				}
			}
		}
	}
});


Object.extend(IPE.Component.Message,{
	messageFile : {},
	// storage container for all messages
	messages : {},

	/*
	Method: getMessageFile
		Load messages from file.

	Arguments:
		file - (string) Location of file to load.
	*/
	getMessageFile : function(file){
		var marker = file.replace(/[^\w]/g, "");
		if(!this.messageFile[marker]){
			//set a state to prevent multiple requests for the same file
			this.messageFile[marker] = "loading";
			new Ajax.Request(file,{
				onFailure : function(){this.messageFile = null;},
				method: "get",
				onSuccess : function(transport){
					document.fire("ipe:messagesLoaded");
					this.storeMessages(transport.responseText);
				}.bind(this)
			});
		}
	},

	/*
	Method: storeMessages
		Store loaded messages

	Arguments:
		html - (string) HTML of loaded messages.
	*/
	storeMessages : function(html){
		// get html inside body element
		var body = html.match(/<body[^>]*>((.|\n|\r)*)<\/body>/);
		var messages = body ? body[1] : html;

		// store messages
		var messageContainer = new Element("div", {"id" : "messageContainer"});
		document.body.insert(messageContainer);
		messageContainer.update(messages).hide();
		messageContainer.childElements().each(function(message){
			this.messages[message.id] = message.innerHTML;
		}.bind(this));
		messageContainer.remove();
	}
});
/*
File: History
*/

IPE.Component.componentDefinitions.HistoryManager = {
	selector : "document"
};

/*
Class: HistoryManager
	Javascript class for restoring use of the back/forward buttons on web pages that are completely
	dynamic and therefore don't actually navigate to different pages.

Author:
	Neil Jenkins
	
Version:
	1.2 (2007-10-24)
	
Version history:
	1.2 Port to prototype.js
	1.1 Update to allow IE to keep its history even when navigating to a different site and back.
	1.0 Initial release
	
Usage:
	Calling new HistoryManager() returns an instance of the History Manager
	e.g. var h = new HistoryManager();

Public interfaces:

	addState(String: hash) - This method creates a new history state in the browser (as though a link has been clicked)
		and also sets the location hash to the supplied argument to allow for bookmarking.

		The hash is expected to be a vaild URI hash component; the global function encodeURI() is useful for
		this. Encoding the state of a javascript program into a string is very much specific to each program therefore
		no processing is done by this module; it is left to the subscribing functions to encode and parse the state.
		
		e.g. h.addState('tab3');
	
	addEvent(String: event, Function: callbackFunction) - This method subscribes functions to be called when the history state changes.
		NB The only event currently available is 'onHistoryChange'.
		   Functions subscribed to this event will be called with the hash of the new state as their argument.
		e.g. h.addEvent('onHistoryChange', functionToCall);
	
	removeEvent(String: event, Function: callbackFunction) - This method removes functions subscribed to the HistoryManager by the addEvent method		
		e.g. h.removeEvent('onHistoryChange', functionToRemove);

	getCurrentLocation() - Returns the current hash.
		e.g. var state = h.getCurrentLocation();

Dependencies:
	prototype: http://www.prototypejs.org

Notes:
	This is a singleton; there can only ever be one instance of the class. Calling new HistoryManger() for a second time
	will simply return a reference to the current instance.
	Supports Gecko, Safari, Opera and IE
*/

IPE.Component.HistoryManager = (function() {

	var HistoryManagerSingleton = Class.create({
		
		initialize: function() {
			this._currentLocation = this._getHash();
			
			var historyManager = this;		
			
			if (Prototype.Browser.IE) {
				this.addState = this._addStateIE;
				var iframe = new Element('iframe', {
					src: "javascript:'<html></html>'"
				});
				iframe.setStyle({
					position: 'absolute',
					top: '-1000px'
				});
				$(document.body).insert(iframe);
				this._iframe = iframe.contentWindow;
				
				$justForIE = function(hash) {
					historyManager._getHash = function() { return hash; };
					historyManager._monitorDefault.call(historyManager);
					location.hash = hash;
				};
				
				function waitForIframeLoad() {
					if (historyManager._iframe && historyManager._iframe.document && historyManager._iframe.document.body) {
						if (!historyManager._iframe.document.body.innerHTML) {
							historyManager.addState(historyManager._currentLocation, true);
						}
					}
					else setTimeout(waitForIframeLoad, 50);
				}
				
				waitForIframeLoad();
			}
			else if (Prototype.Browser.WebKit) {
				this._form = new Element("form", {method: 'get'});
				$(document.body).insert(this._form);
				this._historyCounter = history.length;
				this._stateHistory = [];
				this._stateHistory[history.length] = this._getHash();
				
				this.addState = this._addStateSafari;
				setInterval(this._monitorSafari.bind(this),250);
			}
			else if (window.opera) {
				this.addState = this._addStateDefault;
	
				$justForOpera = function() {
					historyManager._monitorDefault.call(historyManager);
				};
				var image = new Element('img', {
					src: "javascript:location.href='javascript:$justForOpera();';",
					style: "position: absolute; top: -1000px;"
				});
				$(document.body).insert(image);
			}
			else {
				this.addState = this._addStateDefault;
				setInterval(this._monitorDefault.bind(this),250);
			}
		},
		
		getCurrentLocation: function() {
			return this._currentLocation;
		},
		
		_getHash: function() {
			return location.href.split('#')[1] || '';
		},
		
		_addStateIE: function(hash, override) {
			if (this._currentLocation == hash && !override) return;

			this._currentLocation = hash;
			this._iframe.document.write('<html><body onload="top.$justForIE(\'', hash ,'\');">Loaded</body></html>');
			// IE gives an error on document.close when page is loaded in iframe
			if (window == top) this._iframe.document.close();
		},
		
		_addStateSafari: function(hash) {
			if (this._currentLocation == hash) return;
	
			this._form.setAttribute('action', '#' + hash);
			this._form.submit();
			this._currentLocation = hash;
			this._stateHistory[history.length] = this._getHash();
			this._historyCounter = history.length;
		},
	
		_monitorSafari: function() {	
			if (history.length != this._historyCounter) {
				this._historyCounter = history.length;
				this._currentLocation = this._stateHistory[history.length];
				document.fire('History:onHistoryChange', [this._stateHistory[history.length]]);
			}
		},
	
		_addStateDefault: function(hash) {
			if (this._currentLocation == hash) return;
			location.hash = '#' + hash;
			this._currentLocation = hash;
		},
	
		_monitorDefault: function() {
			var hash = this._getHash();
	
			if (hash != this._currentLocation) {
				this._currentLocation = hash;
				document.fire('History:onHistoryChange', [hash]);
			}
		}
	});
	
	var singleton;

	return function() {
		return singleton ? singleton : singleton = new HistoryManagerSingleton();
	}
	
})();
/*
File: HotSpots
*/

IPE.Component.componentDefinitions.HotSpots = {
	selector : ".hotSpots"
};

/*
Class: IPE.Component.HotSpots
	

Options:
	
*/
IPE.Component.HotSpots = Class.create(IPE.Component.Wrapper,{
	defaultOptions : {
		hotspots : null
	},
	
	initialize : function($super,element,options){
		$super(element,options);
		var elementId = this.element.identify();
		var wrapper = new Element("div",{"class":"hotSpotsLayer",id:elementId+"-hotSpots"});
		this.element.insert({before:wrapper});
		wrapper.insert(this.element);
		var classes = this.element.className.split(' ');
		var hotspotUL = $(this.options.hotspots);
		var hotspotDIV = $(elementId+"-hotSpots");
		var hotspotLIs = hotspotUL.childElements();	
		var hotSpotList = [];
		hotspotLIs.each(function(li) {			
			var link = li.down("a");										   
			var obj = {};
			
			var spot = new Element("a",{"class":"hotSpot",id:li.id+"-hotspot"});
			hotspotDIV.insert({top:spot});
			
			var mouseOver = function () { 
				spot.addClassName("emphasis");
				li.addClassName("emphasis");
			};
			var mouseOut = function () { 
				spot.removeClassName("emphasis");
				li.removeClassName("emphasis");
			};
			spot.observe("mouseover",mouseOver);
			li.observe("mouseover",mouseOver);
			spot.observe("mouseout",mouseOut);
			li.observe("mouseout",mouseOut);
			
			spot.href = li.down('a').href;
			spot.target = li.down('a').target;
			hotSpotList.push(obj);	
		});
	}
});

/*
File: InfoPanel
*/

IPE.Component.componentDefinitions.InfoPanel = {
	selector : "dfn.infoPanel"
};

/*
Class: IPE.Component.InfoPanel
	Info Panel
*/
IPE.Component.InfoPanel = Class.create(IPE.Component.Wrapper, {
	initialize : function($super, element, options) {
		$super(element, options);
		
		this.label = this.element.up("label");
		
		// set initial state
		this.toggle(null, true);
		
		// toggle on click
		this.element.observe("click", this.toggle.bind(this));
		
		// close on click
		document.observe("click", this.toggle.bindAsEventListener(this, true));
	},
	
	/*
	Method: toggle
		Toggles the open/closed class names.
		
	Arguments:
		event - (object) event object from the click on the info panel or document
		forceClose - (boolean) when set to true the info panel will close
	*/
	toggle : function(event, forceClose){
		// don't toggle if click event captured on document originates from the info panel itself or an element inside the same label
		// the problem with labels is, that click on the label also generates an extra click event on the form element inside the label
		if (event){
			var clicked = event.element();
			if ((clicked == this.element || (this.label && clicked.descendantOf(this.label))) && forceClose) return;
		}
		
		// remove extisting class names
		this.element.removeClassName("open").removeClassName("closed");
		
		// add new class name
		var className = (this.open || forceClose) ?  "closed" : "open";
		this.element.addClassName(className);
		
		// toggle open flag
		this.open = forceClose ? false : !this.open;
	}
});
/*
File: ModuleNavigation

Dependencies:
	IPE.Component.Navigation (Navigation.js)
*/

IPE.Component.componentDefinitions.ModuleNavigation = {
	selector : [
		".moduleNavigation",
		".moduleNavigation-tabs",
		".moduleNavigation-fingerTabs"
	]
};

/*
Class: IPE.Component.ModuleNavigation
	Combination of a content container and a navigation menu. The links of the navigation menu load content in the content container through AJAX.
	The first item of the menu is automatically loaded.

Options:
	autoRotate - (Number) Interval in milliseconds
	delay - (Number) Initial delay
	mouseover - (boolean) Navigate on mouseover
	rotateOnce - (boolean) autorotates only once and stops rotating when mouseover
*/
IPE.Component.ModuleNavigation = Class.create(IPE.Component.Wrapper,{
	defaultOptions : {
		autoRotate : false,
		delay : false,
		mouseover : false,
		rotateOnce : false
	},
	
	initialize : function($super,element,options){
		$super(element,options);
		this.navigation = this.element.down("ul,ol");
		if (!this.navigation) return;
		this.links = this.navigation.select("a");
		this.container = this.element.down("div");
		if (this.container == this.navigation.up("div")){
			this.container = this.container.next("div");
		}
		this.containerId = this.container.identify();
		
		var navComponent = this.navigation.retrieve("Navigation");
		if (navComponent){
			navComponent.setListener(this.containerId);
		}
		
		this.links.each(function(a){
			var rel = a.readAttribute("rel");
			if (!rel || !rel.match(/#\w+/)) a.setAttribute("rel", "#"+this.containerId);
			
			// navigate on mouseover
			if (this.options.mouseover){
				a.observe("mouseover",function(){
					this.container.loadContent(a.readAttribute("href"));
				}.bind(this));
			}
		},this);
		
		// find first module to open
		var current = this.element.down("li.current");
		var firstLink = current ? (current.down("a") || this.links[0]) : this.links[0];
		
		// load first module
		this.container.loadContent(firstLink.readAttribute("href"));
		
		// auto rotation
		if (this.options.autoRotate) {
			this.options.autoRotate = (typeof this.options.autoRotate == "string") ? this.options.autoRotate.toSeconds() : parseInt(this.options.autoRotate);
			this.rotating = true;
			this.activeLinkNr = 0;
			if (this.options.delay){
				this.options.delay = (typeof this.options.delay == "string") ? this.options.delay.toSeconds() : parseInt(this.options.delay);
				this.startRotation.bind(this).delay(this.options.delay);
			} else {
				this.startRotation();
			}
		}
	},
	
	/*
	Method: startRotation
		Start auto rotate behaviour.
	*/
	startRotation : function () {
		this.rotator = new PeriodicalExecuter(this.activateNextMenuItem.bind(this), this.options.autoRotate);
		this.element.observe('mouseover', function () {
			this.rotating = false;
		}.bind(this));
		this.element.observe('mouseout', function () {
			if(this.options.rotateOnce == true) {
				this.rotating = false;
			}
			else {
				this.rotating = true;
			}
		}.bind(this));
	},
	
	/*
	Method: activateNextMenuItem
		Load next menu item.
	*/
	activateNextMenuItem : function () {
		if (this.rotating){
			this.activeLinkNr ++;
			if (this.activeLinkNr > this.links.length-1) {
				this.activeLinkNr = 0;
				if(this.options.rotateOnce == true) {
					this.rotating = false;
				}
			}
			if (this.links[this.activeLinkNr].up('li').getStyle("display") == "none"){
				this.activateNextMenuItem();
			}
			this.container.loadContent(this.links[this.activeLinkNr].readAttribute("href"));
		}
	}
});

/*
File: NavigationTreeFolderish
*/

IPE.Component.componentDefinitions.NavigationTreeFolderish = {
	selector : [".navigationTree-folderish",
				".navigationTreeView-folderish"]
};

/*
Class: IPE.Component.NavigationTreeFolderish
	Foldable navigation tree

Options:
	openClassName - (string) Class name for open branch
	closedClassName - (string) Class name for closed class name
	hoverClassName - (string) Class name for hover
 */
IPE.Component.NavigationTreeFolderish = Class.create(IPE.Component.Wrapper,{
	defaultOptions : {
		openClassName : "open",
		closedClassName : "closed",
		hoverClassName : "over",
		exclusive : false,
		remember : false
	},
	
	activeNodes : {},
	
	initialize : function($super,element,options) {
		$super(element,options);
		
		// retrieve state
		if (this.options.remember){
			Event.observe(window, 'beforeunload', this._storeState.bind(this));
			this._retrieveState();
		}
		
		this.element.select("li").each(function(node, i){
			var subs = node.down("ul");
			if (subs){
				// insert toggle button
				var toggleBtn = new Element("span",{"class":"toggle"});
				var nodeLabel = node.down();
				node.insert({top: toggleBtn});
				toggleBtn.observe("click",this._toggleNode.bind(this));
				toggleBtn.observe("mouseover",this._hoverToggle.bind(this,toggleBtn));
				toggleBtn.observe("mouseout",this._hoverToggle.bind(this,toggleBtn));
				
				// set initial node state
				if (
					node.hasClassName("open") ||
					node.hasClassName("active") ||
					(this.state && this.state["n"+i])
				) {
					this.openNode(node);
				} else {
					node.addClassName("closed");
				}
				
				// make empty links work as node toggler
				if (nodeLabel.match("a") && nodeLabel.getAttribute("href").match(/.*#$/)){
					nodeLabel.observe("click",this._toggleNode.bind(this));
				}
			}
		},this);
	},
	
	/*
	Method: _hoverToggle
		Function to execute on hover over item in navigation tree
	 */
	_hoverToggle : function(element){
		element.toggleClassName(this.options.hoverClassName);
	},
	
	/*
	Method: _toggleNode
		Collapse or expand a node of the tree
	
	Arguments
		evt - (object) Event object of event that triggered collapse or expand
	*/
	_toggleNode : function(evt) {
		var node = evt.findElement("LI");
		if(node.hasClassName(this.options.openClassName)) {
			this.closeNode(node);
		} else if(node.hasClassName(this.options.closedClassName)) {	
			this.openNode(node);
		}
	},
	
	/*
	Method: _findLevel
		Find the level of the provided node.
		
	Arguments:
		node - (object) Node to find the level of.
	*/
	_findLevel : function(node){
		var level = node.retrieve("level") || null;
		if (!level){
			level = 1;
			var element = node;
			while(element = element.up("li")){
				level++;
			}
			node.store("level", level);
		}
		return level;
	},
	
	/*
	Method: _storeState
		Store state of tree in cookie
	*/
	_storeState : function(){
		var state = [];
		this.element.select("li").each(function(node, i){
			if (node.hasClassName(this.options.openClassName)){
				state.push(i);
			}
		}.bind(this));
		state = state.join(",");
		
		var duration = (this.options.remember == "session") ? null : 365;
		Cookie.create(this.element.identify() + "TreeState", state, duration);
	},
	
	/*
	Method: _retrieveState
		retrieve state of tree from cookie
	*/
	_retrieveState :  function(){
		var state = {};
		var retrievedState = Cookie.read(this.element.identify() + "TreeState");
		if (retrievedState){
			retrievedState.split(",").each(function(i){
				state["n"+i] = true;
			});
		}
		this.state = state;
	},
	
	/*
	Method: openNode
		Open a node.
		
	Arguments:
		node - (object) Node to open.
	*/
	openNode : function(node){
		// close active node on this level
		if (this.options.exclusive){
			var level = this._findLevel(node);
			if (this.activeNodes["level" + level]){
				this.closeNode(this.activeNodes["level" + level]);
			}
			this.activeNodes["level" + level] = node;
		}
		
		// open node
		node.fire("element:beforeOpen", {element: node});
		this.queueAction(function(){
			node.addClassName(this.options.openClassName);
			node.removeClassName(this.options.closedClassName);
			node.fire("element:open", {element: node});
		}.bind(this));
	},
	
	/*
	Method: closeNode
		Close a node.
		
	Arguments:
		node - (object) Node to close.
	*/
	closeNode : function(node){
		node.fire("element:beforeClose", {element: node});
		this.queueAction(function(){
			node.removeClassName(this.options.openClassName);
			node.addClassName(this.options.closedClassName);
			node.fire("element:close", {element: node});
		}.bind(this));
		
		// clear active node on this level
		if (this.options.exclusive){
			var level = this._findLevel(node);
			this.activeNodes["level" + level] = null;
		}
	}
});


/*
File: OmniPlayer
*/

IPE.Component.componentDefinitions.OmniPlayer = {
	selector : ".omniPlayer",
	options : {
		resourcePath : IPE.appPath + IPE.IPEPath + "Resources/"
	}
};

/*	SWFObject v2.2 <http://code.google.com/p/swfobject/> 
	is released under the MIT License <http://www.opensource.org/licenses/mit-license.php> 
*/
var swfobject=function(){var D="undefined",r="object",S="Shockwave Flash",W="ShockwaveFlash.ShockwaveFlash",q="application/x-shockwave-flash",R="SWFObjectExprInst",x="onreadystatechange",O=window,j=document,t=navigator,T=false,U=[h],o=[],N=[],I=[],l,Q,E,B,J=false,a=false,n,G,m=true,M=function(){var aa=typeof j.getElementById!=D&&typeof j.getElementsByTagName!=D&&typeof j.createElement!=D,ah=t.userAgent.toLowerCase(),Y=t.platform.toLowerCase(),ae=Y?/win/.test(Y):/win/.test(ah),ac=Y?/mac/.test(Y):/mac/.test(ah),af=/webkit/.test(ah)?parseFloat(ah.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,X=!+"\v1",ag=[0,0,0],ab=null;if(typeof t.plugins!=D&&typeof t.plugins[S]==r){ab=t.plugins[S].description;if(ab&&!(typeof t.mimeTypes!=D&&t.mimeTypes[q]&&!t.mimeTypes[q].enabledPlugin)){T=true;X=false;ab=ab.replace(/^.*\s+(\S+\s+\S+$)/,"$1");ag[0]=parseInt(ab.replace(/^(.*)\..*$/,"$1"),10);ag[1]=parseInt(ab.replace(/^.*\.(.*)\s.*$/,"$1"),10);ag[2]=/[a-zA-Z]/.test(ab)?parseInt(ab.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof O.ActiveXObject!=D){try{var ad=new ActiveXObject(W);if(ad){ab=ad.GetVariable("$version");if(ab){X=true;ab=ab.split(" ")[1].split(",");ag=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}}catch(Z){}}}return{w3:aa,pv:ag,wk:af,ie:X,win:ae,mac:ac}}(),k=function(){if(!M.w3){return}if((typeof j.readyState!=D&&j.readyState=="complete")||(typeof j.readyState==D&&(j.getElementsByTagName("body")[0]||j.body))){f()}if(!J){if(typeof j.addEventListener!=D){j.addEventListener("DOMContentLoaded",f,false)}if(M.ie&&M.win){j.attachEvent(x,function(){if(j.readyState=="complete"){j.detachEvent(x,arguments.callee);f()}});if(O==top){(function(){if(J){return}try{j.documentElement.doScroll("left")}catch(X){setTimeout(arguments.callee,0);return}f()})()}}if(M.wk){(function(){if(J){return}if(!/loaded|complete/.test(j.readyState)){setTimeout(arguments.callee,0);return}f()})()}s(f)}}();function f(){if(J){return}try{var Z=j.getElementsByTagName("body")[0].appendChild(C("span"));Z.parentNode.removeChild(Z)}catch(aa){return}J=true;var X=U.length;for(var Y=0;Y<X;Y++){U[Y]()}}function K(X){if(J){X()}else{U[U.length]=X}}function s(Y){if(typeof O.addEventListener!=D){O.addEventListener("load",Y,false)}else{if(typeof j.addEventListener!=D){j.addEventListener("load",Y,false)}else{if(typeof O.attachEvent!=D){i(O,"onload",Y)}else{if(typeof O.onload=="function"){var X=O.onload;O.onload=function(){X();Y()}}else{O.onload=Y}}}}}function h(){if(T){V()}else{H()}}function V(){var X=j.getElementsByTagName("body")[0];var aa=C(r);aa.setAttribute("type",q);var Z=X.appendChild(aa);if(Z){var Y=0;(function(){if(typeof Z.GetVariable!=D){var ab=Z.GetVariable("$version");if(ab){ab=ab.split(" ")[1].split(",");M.pv=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}else{if(Y<10){Y++;setTimeout(arguments.callee,10);return}}X.removeChild(aa);Z=null;H()})()}else{H()}}function H(){var ag=o.length;if(ag>0){for(var af=0;af<ag;af++){var Y=o[af].id;var ab=o[af].callbackFn;var aa={success:false,id:Y};if(M.pv[0]>0){var ae=c(Y);if(ae){if(F(o[af].swfVersion)&&!(M.wk&&M.wk<312)){w(Y,true);if(ab){aa.success=true;aa.ref=z(Y);ab(aa)}}else{if(o[af].expressInstall&&A()){var ai={};ai.data=o[af].expressInstall;ai.width=ae.getAttribute("width")||"0";ai.height=ae.getAttribute("height")||"0";if(ae.getAttribute("class")){ai.styleclass=ae.getAttribute("class")}if(ae.getAttribute("align")){ai.align=ae.getAttribute("align")}var ah={};var X=ae.getElementsByTagName("param");var ac=X.length;for(var ad=0;ad<ac;ad++){if(X[ad].getAttribute("name").toLowerCase()!="movie"){ah[X[ad].getAttribute("name")]=X[ad].getAttribute("value")}}P(ai,ah,Y,ab)}else{p(ae);if(ab){ab(aa)}}}}}else{w(Y,true);if(ab){var Z=z(Y);if(Z&&typeof Z.SetVariable!=D){aa.success=true;aa.ref=Z}ab(aa)}}}}}function z(aa){var X=null;var Y=c(aa);if(Y&&Y.nodeName=="OBJECT"){if(typeof Y.SetVariable!=D){X=Y}else{var Z=Y.getElementsByTagName(r)[0];if(Z){X=Z}}}return X}function A(){return !a&&F("6.0.65")&&(M.win||M.mac)&&!(M.wk&&M.wk<312)}function P(aa,ab,X,Z){a=true;E=Z||null;B={success:false,id:X};var ae=c(X);if(ae){if(ae.nodeName=="OBJECT"){l=g(ae);Q=null}else{l=ae;Q=X}aa.id=R;if(typeof aa.width==D||(!/%$/.test(aa.width)&&parseInt(aa.width,10)<310)){aa.width="310"}if(typeof aa.height==D||(!/%$/.test(aa.height)&&parseInt(aa.height,10)<137)){aa.height="137"}j.title=j.title.slice(0,47)+" - Flash Player Installation";var ad=M.ie&&M.win?"ActiveX":"PlugIn",ac="MMredirectURL="+O.location.toString().replace(/&/g,"%26")+"&MMplayerType="+ad+"&MMdoctitle="+j.title;if(typeof ab.flashvars!=D){ab.flashvars+="&"+ac}else{ab.flashvars=ac}if(M.ie&&M.win&&ae.readyState!=4){var Y=C("div");X+="SWFObjectNew";Y.setAttribute("id",X);ae.parentNode.insertBefore(Y,ae);ae.style.display="none";(function(){if(ae.readyState==4){ae.parentNode.removeChild(ae)}else{setTimeout(arguments.callee,10)}})()}u(aa,ab,X)}}function p(Y){if(M.ie&&M.win&&Y.readyState!=4){var X=C("div");Y.parentNode.insertBefore(X,Y);X.parentNode.replaceChild(g(Y),X);Y.style.display="none";(function(){if(Y.readyState==4){Y.parentNode.removeChild(Y)}else{setTimeout(arguments.callee,10)}})()}else{Y.parentNode.replaceChild(g(Y),Y)}}function g(ab){var aa=C("div");if(M.win&&M.ie){aa.innerHTML=ab.innerHTML}else{var Y=ab.getElementsByTagName(r)[0];if(Y){var ad=Y.childNodes;if(ad){var X=ad.length;for(var Z=0;Z<X;Z++){if(!(ad[Z].nodeType==1&&ad[Z].nodeName=="PARAM")&&!(ad[Z].nodeType==8)){aa.appendChild(ad[Z].cloneNode(true))}}}}}return aa}function u(ai,ag,Y){var X,aa=c(Y);if(M.wk&&M.wk<312){return X}if(aa){if(typeof ai.id==D){ai.id=Y}if(M.ie&&M.win){var ah="";for(var ae in ai){if(ai[ae]!=Object.prototype[ae]){if(ae.toLowerCase()=="data"){ag.movie=ai[ae]}else{if(ae.toLowerCase()=="styleclass"){ah+=' class="'+ai[ae]+'"'}else{if(ae.toLowerCase()!="classid"){ah+=" "+ae+'="'+ai[ae]+'"'}}}}}var af="";for(var ad in ag){if(ag[ad]!=Object.prototype[ad]){af+='<param name="'+ad+'" value="'+ag[ad]+'" />'}}aa.outerHTML='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'+ah+">"+af+"</object>";N[N.length]=ai.id;X=c(ai.id)}else{var Z=C(r);Z.setAttribute("type",q);for(var ac in ai){if(ai[ac]!=Object.prototype[ac]){if(ac.toLowerCase()=="styleclass"){Z.setAttribute("class",ai[ac])}else{if(ac.toLowerCase()!="classid"){Z.setAttribute(ac,ai[ac])}}}}for(var ab in ag){if(ag[ab]!=Object.prototype[ab]&&ab.toLowerCase()!="movie"){e(Z,ab,ag[ab])}}aa.parentNode.replaceChild(Z,aa);X=Z}}return X}function e(Z,X,Y){var aa=C("param");aa.setAttribute("name",X);aa.setAttribute("value",Y);Z.appendChild(aa)}function y(Y){var X=c(Y);if(X&&X.nodeName=="OBJECT"){if(M.ie&&M.win){X.style.display="none";(function(){if(X.readyState==4){b(Y)}else{setTimeout(arguments.callee,10)}})()}else{X.parentNode.removeChild(X)}}}function b(Z){var Y=c(Z);if(Y){for(var X in Y){if(typeof Y[X]=="function"){Y[X]=null}}Y.parentNode.removeChild(Y)}}function c(Z){var X=null;try{X=j.getElementById(Z)}catch(Y){}return X}function C(X){return j.createElement(X)}function i(Z,X,Y){Z.attachEvent(X,Y);I[I.length]=[Z,X,Y]}function F(Z){var Y=M.pv,X=Z.split(".");X[0]=parseInt(X[0],10);X[1]=parseInt(X[1],10)||0;X[2]=parseInt(X[2],10)||0;return(Y[0]>X[0]||(Y[0]==X[0]&&Y[1]>X[1])||(Y[0]==X[0]&&Y[1]==X[1]&&Y[2]>=X[2]))?true:false}function v(ac,Y,ad,ab){if(M.ie&&M.mac){return}var aa=j.getElementsByTagName("head")[0];if(!aa){return}var X=(ad&&typeof ad=="string")?ad:"screen";if(ab){n=null;G=null}if(!n||G!=X){var Z=C("style");Z.setAttribute("type","text/css");Z.setAttribute("media",X);n=aa.appendChild(Z);if(M.ie&&M.win&&typeof j.styleSheets!=D&&j.styleSheets.length>0){n=j.styleSheets[j.styleSheets.length-1]}G=X}if(M.ie&&M.win){if(n&&typeof n.addRule==r){n.addRule(ac,Y)}}else{if(n&&typeof j.createTextNode!=D){n.appendChild(j.createTextNode(ac+" {"+Y+"}"))}}}function w(Z,X){if(!m){return}var Y=X?"visible":"hidden";if(J&&c(Z)){c(Z).style.visibility=Y}else{v("#"+Z,"visibility:"+Y)}}function L(Y){var Z=/[\\\"<>\.;]/;var X=Z.exec(Y)!=null;return X&&typeof encodeURIComponent!=D?encodeURIComponent(Y):Y}var d=function(){if(M.ie&&M.win){window.attachEvent("onunload",function(){var ac=I.length;for(var ab=0;ab<ac;ab++){I[ab][0].detachEvent(I[ab][1],I[ab][2])}var Z=N.length;for(var aa=0;aa<Z;aa++){y(N[aa])}for(var Y in M){M[Y]=null}M=null;for(var X in swfobject){swfobject[X]=null}swfobject=null})}}();return{registerObject:function(ab,X,aa,Z){if(M.w3&&ab&&X){var Y={};Y.id=ab;Y.swfVersion=X;Y.expressInstall=aa;Y.callbackFn=Z;o[o.length]=Y;w(ab,false)}else{if(Z){Z({success:false,id:ab})}}},getObjectById:function(X){if(M.w3){return z(X)}},embedSWF:function(ab,ah,ae,ag,Y,aa,Z,ad,af,ac){var X={success:false,id:ah};if(M.w3&&!(M.wk&&M.wk<312)&&ab&&ah&&ae&&ag&&Y){w(ah,false);K(function(){ae+="";ag+="";var aj={};if(af&&typeof af===r){for(var al in af){aj[al]=af[al]}}aj.data=ab;aj.width=ae;aj.height=ag;var am={};if(ad&&typeof ad===r){for(var ak in ad){am[ak]=ad[ak]}}if(Z&&typeof Z===r){for(var ai in Z){if(typeof am.flashvars!=D){am.flashvars+="&"+ai+"="+Z[ai]}else{am.flashvars=ai+"="+Z[ai]}}}if(F(Y)){var an=u(aj,am,ah);if(aj.id==ah){w(ah,true)}X.success=true;X.ref=an}else{if(aa&&A()){aj.data=aa;P(aj,am,ah,ac);return}else{w(ah,true)}}if(ac){ac(X)}})}else{if(ac){ac(X)}}},switchOffAutoHideShow:function(){m=false},ua:M,getFlashPlayerVersion:function(){return{major:M.pv[0],minor:M.pv[1],release:M.pv[2]}},hasFlashPlayerVersion:F,createSWF:function(Z,Y,X){if(M.w3){return u(Z,Y,X)}else{return undefined}},showExpressInstall:function(Z,aa,X,Y){if(M.w3&&A()){P(Z,aa,X,Y)}},removeSWF:function(X){if(M.w3){y(X)}},createCSS:function(aa,Z,Y,X){if(M.w3){v(aa,Z,Y,X)}},addDomLoadEvent:K,addLoadEvent:s,getQueryParamValue:function(aa){var Z=j.location.search||j.location.hash;if(Z){if(/\?/.test(Z)){Z=Z.split("?")[1]}if(aa==null){return L(Z)}var Y=Z.split("&");for(var X=0;X<Y.length;X++){if(Y[X].substring(0,Y[X].indexOf("="))==aa){return L(Y[X].substring((Y[X].indexOf("=")+1)))}}}return""},expressInstallCallback:function(){if(a){var X=c(R);if(X&&l){X.parentNode.replaceChild(l,X);if(Q){w(Q,true);if(M.ie&&M.win){l.style.display="block"}}if(E){E(B)}}a=false}}}}();

/*
Class: IPE.Component.OmniPlayer
	

Options:
	
*/

function playerReady(obj){$(obj.id).fire("omni:playerready", obj);}

IPE.Component.OmniPlayer = Class.create(IPE.Component.Wrapper,{
	defaultOptions : {},
	
	initialize : function($super,element,options){
		$super(element,options);

		this.element.id = this.element.id!="" ? this.element.id : "player-"+Math.floor(Math.random()*999999);

		//get object parameters
		this.getParams(this.element);
		//get object flashvars
		this.flashVars = this.params;
		//this.flashVars.id = this.flashVars.name = this.playerID;
		
		document.observe("omni:playerready", function(event){
			this.JWplayer = event.memo;
		});
		
		//this.element.wrap("div", {"class":"omniPlayer"});

		//embed the player with SWFObject
		swfobject.embedSWF(
			this.options.resourcePath + "JWplayer/player.swf",
			this.element.id,
			this.params.width || "400",
			this.params.height || "300",
			this.params.flashVersion || "9.0.0",
			this.options.resourcePath + "JWplayer/expressInstall.swf",
			this.flashVars,
			this.params);
	},
	
	getParams : function(){
		this.params = {};
		for (node in this.element.childNodes){
			if(this.element.childNodes[node].tagName=="PARAM"){
				//allow both 'src' and 'file' attribute names
				if(this.element.childNodes[node].name=="src") this.element.childNodes[node].name = "file";
				this.params[this.element.childNodes[node].name] = this.element.childNodes[node].value;
			}
		}
		//set some common parameters defaults
		this.params.allowscriptaccess = this.params.allowscriptaccess || "always";
		this.params.allowfullscreen = this.params.allowfullscreen || "true";
		this.params.wmode = this.params.wmode || "transparent";
		
		return this.params;
	}
	
});

/*
File: NavigationPagination
*/

IPE.Component.componentDefinitions.NavigationPagination = {
	selector : ".navigationPagination"
};

/*
Class: IPE.Component.NavigationPagination
	Pagination component. Previous and next links are added automatically.

Options:
	nextLabel - (string) label on the next link.
	prevLabel - (string) label on the previous link.
	target - (string) id of element in which content is loaded.
*/
IPE.Component.NavigationPagination = Class.create(IPE.Component.Wrapper,{
	defaultOptions : {
		prevLabel : "Vorige",
		nextLabel : "Volgende",
		target : null
	},
	
	initialize : function($super,element,options){
		this._updateState = function(node){this._updateBrowseButtonsState(node);};
		$super(element,options);
		this.lnks = this.element.select("a");
		
		// determine number of pages
		this.lnksNr = this.lnks.length;
		
		// get target from first link
		if (!this.options.target){
			var rel = this.lnks[0].readAttribute("rel");
			if (rel){
				var target = rel.match(/#([\w-\.\:]+)/);
				if (target) this.options.target = target[1];
			}
		}
		
		// add prev and next buttons 
		this.nextButton = new Element("li",{"class":"next"});
		this.nextButton.insert("<a href=\"#\">"+this.options.nextLabel+"</a>");
		this.element.insert(this.nextButton);
		this.nextLink = this.nextButton.down("a");
		if (this.options.target) this.nextLink.setAttribute("rel","#"+this.options.target);
		
		this.previousButton = new Element("li",{"class":"previous"});
		this.previousButton.insert("<a href=\"#\">"+this.options.prevLabel+"</a>");
		this.element.insert({top: this.previousButton});
		this.previousLink = this.previousButton.down("a");
		if (this.options.target) this.previousLink.setAttribute("rel","#"+this.options.target);
		
		// make sure added links get dispatcher behaviour by firing an update event
		this.element.fire("element:afterUpdate", {element: this.element});
		
		// set state of browse buttons
		this._updateBrowseButtonsState();
		
		// respond to update of navigation component
		this.element.observe("Navigation:updateState",function(event){
			this._updateBrowseButtonsState();
		}.bind(this));
	},
	
	/*
	Method: _updateBrowseButtonsState
		Enable or disable browse buttons.
		
	Arguments:
		activeLinkNr - (number) index number of active link.
	*/
	_updateBrowseButtonsState : function(activeLinkNr){
		if (this.nextButton && this.previousButton){
			if (!activeLinkNr) activeLinkNr = this.element.retrieve("activeLinkNr") || 0;
			if (activeLinkNr==0){
				this.previousButton.addClassName("disabled");
				this.previousLink.setAttribute("href","#");
			} else {
				this.previousButton.removeClassName("disabled");
				this.previousLink.setAttribute("href",this.lnks[activeLinkNr - 1].getAttribute("href"));
			}
			if (activeLinkNr==this.lnksNr - 1){
				this.nextButton.addClassName("disabled");
				this.nextLink.setAttribute("href","#");
			} else {
				this.nextButton.removeClassName("disabled");
				this.nextLink.setAttribute("href",this.lnks[activeLinkNr + 1].getAttribute("href"));
			}
		}
	}
});

/*
File: PersistentContent
*/

IPE.Component.componentDefinitions.PersistentContent = {
	selector : [
		".persistent",
		".persistent-location"
	],
	options : {
		storage : IPE.storage.createContainer("persistent", 1)
	}
};

/*
Class: IPE.Component.PersistentContent
	Stores element content loaded through AJAX for restoring state after coming back to the page.

Options:
	storage - (object) Object to store element content in. This data should be stored in during the session, but that is not handled by this container.
*/
IPE.Component.PersistentContent = Class.create(IPE.Component.Wrapper, {
	defaultOptions: {
		persistent : "content",
		storage : {},
		session : false
	},
	
	initialize : function ($super,element,options) {
		$super(element,options);
		if (this.options.persistent == "location"){
			var location = this.options.session ? this.options.storage[this.element.id] : Cookie.read("persistent" + this.element.id);
			if (location) {
				this.element.loadContent(location);
			}
			this.element.observe("element:afterUpdate", this.storeLocation.bind(this));
		} else {
			Event.observe(window, 'beforeunload', this.storeContent.bind(this));
			if (this.options.storage[this.element.id]) this.loadContent();
		}
	},
	
	/*
	Method: storeContent
		Store content of element.
	*/
	storeContent : function () {
		this.options.storage[this.element.id] = this.element.innerHTML;
	},
	
	/*
	Method: storeLocation
		Store location of current loaded content inside the element.
	*/
	storeLocation : function (event) {
		if (this.options.session){
			this.options.storage[this.element.id] = event.memo.url;
		} else {
			Cookie.create("persistent" + this.element.id, event.memo.url, 365);
		}
	},
	
	/*
	Method: loadContent
		Load stored content in element.
	*/
	loadContent : function () {
		this.element.innerHTML = this.options.storage[this.element.id];
		this.element.fire("element:afterUpdate", {element: this.element});
	}
});
/*
File: NavigationProgress
*/

IPE.Component.componentDefinitions.NavigationProgress = {
	selector : [
		".navigationProgress",
		".navigationProgress-horizontal",
		".navigationProgress-vertical"
	]
};

/*
Class: IPE.Component.NavigationProgress
	Progress navigation component. Used for navigating through a sequence of pages.
 */
IPE.Component.NavigationProgress = Class.create(IPE.Component.Wrapper,{
	initialize : function($super,element,options){
		$super(element,options);
		this.lnks = this.element.select("a");
		this.lnks.each(function(lnk){
			lnk.observe("click",this.clickItem.bindAsEventListener(this,lnk));
		}.bind(this));
		this.element.observe("Navigation:updateState",function(event){
			this._markPreviousSteps();
		}.bind(this));
		this._markPreviousSteps();
	},
	
	/*
	Method: clickItem
		Prevent execution of links that lead to future steps of the process.
		
	Arguments
		event - (object) Event object
		lnk - (object) Link that has been clicked upon.
	*/
	clickItem : function(event,lnk){
		var li = lnk.up();
		if (!li.hasClassName("done")) event.preventDefault(); 
	},
	
	/*
	Method: _markPreviousSteps
		Marks visited pages as completed.
		
	Arguments:
		activeLinkNr - (number) Index number of active link (optional).
	*/
	_markPreviousSteps : function(activeLinkNr){
		if (!activeLinkNr) activeLinkNr = this.element.retrieve("activeLinkNr") || 0;
		this.lnks.each(function(lnk,i){
			if(this.element.hasClassName("noReturn")){
				lnk.addClassName("nofollow");
				return;
			}
			if (i<activeLinkNr){
				lnk.up("li").addClassName("done");
				lnk.removeClassName("nofollow");
			} else {
				lnk.up("li").removeClassName("done");
				lnk.addClassName("nofollow");
			}
		}.bind(this));
	}
});
/*
File: Removable
*/

IPE.Component.componentDefinitions.Removable = {
	selector : [
		".removable",
		".remove-this",
		".remove-parent"
	]
};

/*
Class: IPE.Component.Removable
	Removable element. A remove button is added automatically.
*/
IPE.Component.Removable = Class.create(IPE.Component.Wrapper, {
	initialize : function ($super,element,options) {
		$super(element,options);
		if (this.element.hasClassName("remove-parent")){
			this.removableElement = this.element.up();
		} else {
			this.removableElement = this.element;
		}
		
		//check for confirm
		if(this.removableElement.hasClassName("confirm")) this.confirm = true;
		
		var removeButtons = this.element.select(".remove", ".buttonRemove");
		removeButtons.each(function(button){
			var parentRemovable = button.up([".removable", ".remove-this", ".remove-parent"]);
			if (parentRemovable != this.element) return;
			
			if (this.confirm) button.addClassName("confirm");
			
			if (button.hasClassName("confirm")){
				button.store("confirm",1);
				button.observe("action:confirmed", function(event){
					if (event.memo.element == button) this.removeElement();
				}.bind(this));
			} else {
				button.observe("click", this.removeElement.bind(this));
			}
		}.bind(this));
	},
	
	/*
	Method: removeElement
		Removes the element.
	*/
	removeElement : function () {
		this.element.fire("element:remove", {element: this.removableElement});
		this.queueAction(function(){
			this.removableElement.remove();
		}.bind(this));
	}
});
/*
File: SelfHealingMessage
*/

IPE.Component.componentDefinitions.SelfHealingMessage = {
	selector : ".selfHealing"
};

/*
Class: IPE.Component.SelfHealingMessage

	Displays a message and hides it after some time

	options: displayTime - (int) seconds to display the message
*/
IPE.Component.SelfHealingMessage = Class.create(IPE.Component.Wrapper,{
	defaultOptions : {
		displayTime : 2,
		removeOnClick : true
	},
	
	initialize : function($super, element, options){
		$super(element,options);

		//check for message container element
		this.container = $("selfhealing-messages");
		//if not available, insert messagecontainer in body
		if(!this.container){
			this.container = new Element("div", {"id":"selfhealing-messages"});
			document.body.insert(this.container);
		}
		
		//create message element
		this.message = this.element.cloneNode(true).removeClassName("selfHealing").addClassName("message-selfHealing");
		this.wrapper = this.message.wrap("div", {"class":"messageWrapper"}).hide();
		
		//get all elements and their events that trigger a message
		this.getInitiators();
		 //if there is no initiator, show message immediately
		if(!this.initiators[0]){
			this.show(); }
		 //else, add the appropriate eventlistener to the initiator element
		else {
			this.initiators.each(function(initiator){
				switch(initiator.event){
					case "update" : initiator.event = "element:afterUpdate"; break;
					case "drop" : initiator.event = "element:drop"; break;
					case "show" : initiator.event = "element:show"; break;
					case "hide" : initiator.event = "element:hide"; break;
					case "close" : initiator.event = "element:close"; break;
					case "open" : initiator.event = "element:open"; break;
					case "remove" : initiator.event = "element:remove"; break;
				}
				initiator.element.observe(initiator.event, this.show.bind(this));
			}.bind(this));
		}

	},
	//get all elements and events that trigger the message
	getInitiators : function(){
		this.initiators = [];
		$w(this.message.className).each(function(cName){
			var elementEvent = cName.match(/(showOn)-(\S+)-([^-]+)/);
			if(elementEvent && elementEvent.length==4){
				var initiator = {
					element : $(elementEvent[2]),
					event : elementEvent[3]
				};
				//only add initiator when trigger element exists
				if(initiator.element) this.initiators.push(initiator);
			}
		}.bind(this));

		return this.initiators;
	},
	
	createMessage : function(){
		//create a fresh copy of the message
		var message = this.wrapper.cloneNode(true);
		//stop removing message when hovered
		var doMouseEnter = function(){message.store("persistent", true);}.bind(this);
		message.observe("mouseenter", doMouseEnter);
		//resume removing on mouseout
		var doMouseLeave = function(){message.eliminate("persistent"); this.remove(message);}.bind(this);
		message.observe("mouseleave", doMouseLeave);
		//add removeOnClick behaviour
		if(this.options.removeOnClick){message.observe("click", function(){
			message.stopObserving("mouseleave", doMouseLeave);
			message.stopObserving("mouseenter", doMouseEnter);
			doMouseLeave();
		}.bind(this));}

		return message;
	},
	
	//shows the message and removes it after some time
	show : function(){
		//create a fresh copy of the message
		var message = this.createMessage();
		this.container.insert(message);
		message.appear({
			duration : .3,
			afterFinish : function(){
				message.store("timer", setTimeout(function(){
					this.remove(message);																				
				}.bind(this), this.options.displayTime*1000));
			}.bind(this)
		});
	},
	/*removes the message
		stops hiding and restores message when hovered
		message only responds to hover while opacity effect is running
	*/
	remove : function(message){
		if(message.retrieve("persistent") || message.retrieve("inFx")) return;
		var _this = this;
		message.store("fxFade", new Effect.Opacity(message,{
			from: 1,
			to: 0,
			duration : .3,
			beforeUpdate : function(){
				if(message.retrieve("persistent")){_this.restore(message);}
			},
			afterFinish : function(){
				message.eliminate("fxFade");
				new Effect.BlindUp(message,{
					beforeStart : function(){
						message.store("inFx", true);
					},
					afterFinish : function(){
						message.eliminate("inFx");
						message.remove();
					}
				});
			}
		}));
	},
	
	//restores message opacity & cancels effects
	restore : function(message){
		//clear the remove timer
		window.clearTimeout(message.retrieve("timer"));
		//cancel hiding effects
		var fxFade = message.retrieve("fxFade");		
		if(fxFade) fxFade.cancel();
		message.eliminate("inFx");
		//restore message's opacity
		new Effect.Opacity(message, {
			from: message.getStyle("opacity") || .2,
			to: 1,
			duration: .3
		});
	}
	
});
/*
File: SkinnableSelect
*/

IPE.Component.componentDefinitions.SkinnableSelect = {
	selector : "select"
};

/*
Class: IPE.Component.SkinnableSelect
	Skinnable checkbox
*/
IPE.Component.SkinnableSelect = Class.create(IPE.Component.Wrapper,{
	defaultOptions : {
		speed : 0.5,
		keySupport : true,
		closeOnOutsideClick : true,
		keepInViewport : true,
		clearAtFirst : true
	},
	
	initialize : function($super, element, options){
		$super(element,options);
		
		//sla values en options op
		this.selectOptions = [];
		this.element.select("option").each(function(option, index){
			this.selectOptions[index] = [option.value, option.innerHTML];
			if(this.element.selectedIndex == index){
				this.selected = [option, index]; 
			}
		}.bind(this));
		this.opened = false;
		
		// voeg 'select wrapper' toe
		this.selectWrapper = new Element("div", {"class":"selectWrapper", "id":this.element.id+"-wrapper"});
		this.element.insert({after : this.selectWrapper});
		
		// klapt naar boven uit als class 'top' op de select zit
		if(this.element.hasClassName("top")){
			this.selectWrapper.addClassName("top");
		}

		// voeg 'optielijst wrapper' toe
		this.listWrapper = new Element("div", {"class":"selectListWrapper", "id":this.element.id+"-options-wrapper"});
		this.selectWrapper.insert({top : this.listWrapper});
		
		// voeg 'optielijst' toe
		this.list = new Element("ul", {"class":"selectList", "id":this.element.id+"-options"});
		this.listWrapper.insert(this.list);
		
		// voeg de opties toe aan de lijst
		this.selectOptions.each(function(option, index){
			var li = new Element("li", {"class":"selectOption",	"id":option[0]});
			// set selected
			if(index==this.element.selectedIndex){
				li.addClassName("selected");
				this.selectedLI = li;
			}
			this.list.insert(li);
			li.insert({top : new Element("span").update(option[1])});
		}.bind(this));
		
		this.listItems = this.list.childElements();
		
		// voeg 'selector' toe
		this.selectWrapper.insert({top : new Element("div", {"id":this.element.id+"-selector", "class":"selector"}) });
		this.selector = $(this.element.id+"-selector");
		this.selector.insert({top : new Element("span", {"id":"selectorCont-"+this.element.id}) });
		if(this.selected || !this.options.clearAtFirst){
			this.selector.firstDescendant().update(this.selectedLI.firstDescendant().innerHTML);		
		} else if(this.element.readAttribute("title")){
			this.selector.firstDescendant().update(this.element.readAttribute("title"));
			this.selected = [this.element.select("option")[0], 0];
			//this.selected[0].writeAttribute("value", "");
			this.cleared = true;			
		} else if(!this.selected) {
			// show lege selector & clear value
			this.selected = [this.element.select("option")[0], 0];
			//this.selected[0].writeAttribute("value", "");
			this.cleared = true;
		}
		
		//set afmetingen van de wrapper naar afmetingen van de lijst
		this.selectWrapper.setStyle({"width":this.selectWrapper.getWidth()+"px"});
		this.listWrapper.setStyle({"position":"absolute"});//-> pas na 'getwidth'

		// eventlisteners voor de select
		this.element.observe("change", function(){
			var index = parseInt(this.element.selectedIndex);
			this.setSelected(this.list.childElements()[index], index);
		}.bind(this));
		
		this.element.observe("focus", function(){this.hasFocus = true;
			Event.observe(document, "keydown", this.keyControl);
			this.selectWrapper.addClassName("focus");
		}.bind(this));

		this.element.observe("blur", function(){
			Event.stopObserving(document, "keydown", this.keyControl);
			this.selectWrapper.removeClassName("focus");
			var index = parseInt(this.element.selectedIndex);
			if(this.element.value!=""){
				this.setSelected(this.list.childElements()[index], index);
			}
		}.bind(this));

		// eventlistener voor de selector
		document.observe("click", function(e){
			if(!this.opened && (e.target.descendantOf(this.selector) || e.target==this.selector)){
				this.toggleUpDown();
				return;
			}
			if(this.opened){
				this.toggleUpDown();
			}
		}.bind(this));
	
		this.list.childElements().each(function(li,index){
			li.observe("click", function(e){
				this.setSelected(li, index);
				this.element.focus();
			}.bind(this));
		}.bind(this));

		// keyboard support voor selects
		this.keyControl = function(e){
			if(this.options.keySupport){
				var first = this.list.down("li");
				var nrOfChoices = this.list.childElements();
				var last = nrOfChoices[nrOfChoices.length-1];
				var index = parseInt(this.element.selectedIndex);
				
				if(!this.preSelectedLI){
					this.preSelectedLI = this.selectedLI;
				}
				//down-arrow of left arrow
				if(e.keyCode=="40" || e.keyCode=="39"){
					if(this.preSelectedLI!=last){
						this.preSelect(this.preSelectedLI.next(), index+1);
						this.preSelectedIndex++;
					}					
				}
				//up-arrow of right arrow
				if(e.keyCode=="38" || e.keyCode=="37"){
					if(this.preSelectedLI!=first){
						this.preSelect(this.preSelectedLI.previous(), index-1);
						this.preSelectedIndex--;
					}
				}
				// enter of esc
				if(e.keyCode=="13" || e.keyCode=="27"){
					if(this.opened){
						this.setSelected(this.preSelectedLI, this.preSelectedIndex);
						this.preSelect(this.preSelectedLI, this.preSelectedIndex);
						this.toggleUpDown();
					}
				}
				// Autoselect function, springt naar eerstvolgende optie die begint met de getypte letter
				var typed = String.fromCharCode(e.keyCode);				
				var startAt = parseInt(this.element.selectedIndex);				
				var looped = false;
				
				for(i=0; i<nrOfChoices.length; i++){
					if(!looped){
						i = startAt; }//begin bij selected
					if(this.selectOptions[i][1].startsWith(typed) && i!=startAt){
						this.preSelect(this.listItems[i], i) ;
						this.preSelectedIndex = i;
						break; }
					if(i==startAt && looped){//alle opties doorlopen
						break; }
					if(i==nrOfChoices.length-1){
						i=-1;	} //naar eerste optie
					looped = true; 
				}
			}
		}.bind(this);

	},
	
	// restore original values of select options
	restoreValues: function(){
		this.selected[0].writeAttribute("value", this.selectOptions[this.selected[1]][0]);
		this.cleared = false;
	},

	setSelected : function(li, index){
		if(this.cleared){
			this.restoreValues();
		}
		this.element.selectedIndex = index; //set selectie in 'echte' select
		this.selectedLI.removeClassName("selected");
		this.selectedLI = li.addClassName("selected");
		this.selector.firstDescendant().update(li.firstDescendant().innerHTML);
	},
	
	preSelect : function(li, index){
		this.preSelectedLI.removeClassName("preselected");
		this.preSelectedLI = li.addClassName("preselected");
		this.selector.firstDescendant().update(li.firstDescendant().innerHTML);
	},	
	
	//Show/hide optielijst
	toggleUpDown : function(o) {
		var targetHeight = this.list.getHeight();
		if(this.opened){//klap dicht
			new Effect.Scale(this.listWrapper, 0,{
				duration: this.options.speed,
				scaleFrom: 100,
				scaleContent: false,
				scaleX: false,
				scaleMode: { originalHeight: targetHeight }
			});
			this.opened = false;
		}
		else{//start listeners en klap open
			Event.observe(document, "keydown", this.keyControl);	
			this.element.focus();
			this.preSelectedIndex = parseInt(this.element.selectedIndex);
			new Effect.Scale(this.listWrapper, 100,{
					duration: this.options.speed,
					scaleFrom: 0,
					scaleContent: false,
					scaleX: false,
					scaleMode: { originalHeight: targetHeight },
					beforeSetup: function(){
						this.listWrapper.setStyle({	height: "0" });
					}.bind(this)
				});
			this.opened = true;
	 	}
	}

});

/*
File: Slider
*/

IPE.Component.componentDefinitions.Slider = {
	selector : ".slider"
};

/*
Class: IPE.Component.Slider
	Slider Control
	
Options:
	min - (number) Minimum value
	max - (number) Maximum value
	step - (number) Interval between each possible value
	value - (number) Default value
	input - (string) Id of input element that should display the slider value
*/
IPE.Component.Slider = Class.create(IPE.Component.Wrapper,{
	_globalEvents : ["slide","change"],
	_callbacks : {
		onSlide : "slide",
		onChange : "change"
	},
	defaultOptions : {
		min : 0,
		max : 1,
		step: null,
		value: null,
		input: null
	},
	
	initialize : function($super,element,options){
		$super(element,options);
		this.element.identify();
		var handle = this.element.down(".sliderHandle");
		var fill = this.element.down(".sliderFill");
		if (!handle){
			var handle = new Element("span",{"class":"sliderHandle"});
			this.element.insert(handle);
		}
		if (!fill){
			var fill = new Element("span",{"class":"sliderFill"});
			this.element.insert(fill);
		}
		handle.id = this.element.id + "handle";
		
		var calculateSteps = function(step,min,max){
			var values = [];
			var v = min;
			while (v<=max){
				values.push(v);
				v = v + step;
			}
			return values;
		};

		var axis = element.hasClassName("slider-vertical") ? "vertical" : "horizontal";
		var min = Number(this.options.min);
		var max = Number(this.options.max);
		var range = $R(min,max);
		var step = Number(this.options.step);
		if (step) step = this._calculateSteps(step,min,max);
		
		// create object instance
		this.component = new Control.Slider(
			handle.id,
			this.element.id,
			{
				range: range,
				startSpan: this.element.down(".sliderFill") || null,
				sliderValue: Number(this.options.value),
				onSlide: this.options.onSlide || null,
				onChange: this.options.onChange || null,
				values: step
			}
		);
		this._setState();
		
		this.element.getValue = function(){
			return this.retrieve("value");
		}.bind(this.element);
		
		if (this.options.input){
			this.input = $(this.options.input);
			if (this.input){
				this.element.observe("element:slide",this.updateInput.bind(this));
				this.element.observe("element:change",this.updateInput.bind(this));
				this.input.observe("change",function(){
					this.setValue(this.input.getValue());
				}.bind(this));
				this.updateInput();
			}
		}
		
	},
	
	/*
	Method: updateInput
		Updates value of connected input field.
	*/
	updateInput : function(){
		this.input.value = this.element.getValue();
		this.input.simulate("change");
	},
	
	/*
	Method: _setState
		stores the current state
	*/
	_setState : function(){
		this.element.store("value",(this.component.values.length>1 ? this.component.values : this.component.value));
	},
	
	/*
	Method: _calculateSteps
		Calculates the available values from the min, max and step options. The Scriptaculous slider needs these values.
	*/
	_calculateSteps : function(step,min,max){
		var values = [];
		var v = min;
		while (v<=max){
			values.push(v);
			v = v + step;
		}
		return values;
	},
	
	/*
	Method: setValue
		Set the slider to the provide value
		
	Arguments:
		value - (number) Value to set slider to.
	*/
	setValue : function(value){
		value = Number(this.input.getValue());
		this.component.setValue(value);
	}
});
 
 
 /*
 Modifications to scriptaculous slider
 */
Object.extend(Control.Slider.prototype,{
	setHandleWidth : function (l) {
		this.handleLength = l;
	},
	maximumOffset: function(){
		return(this.isVertical() ? 
			(this.track.offsetHeight != 0 ? this.track.offsetHeight :
				this.track.style.height.replace(/px$/,"")) - this.alignY : 
			(this.track.offsetWidth != 0 ? this.track.offsetWidth : 
				this.track.style.width.replace(/px$/,"")) - this.alignX);
	}
});


/*
File: SmoothScroll
*/
IPE.Component.componentDefinitions.SmoothScroll = {
	selector : "document",
	options : {
		events : ["link:execute"]
	}
};

/*
Class: IPE.Component.SmoothScroll
	Adds smooth scroll behaviour to anchor links
	
Options:
	events - (array) Events the smoothscroll component should listen to.
	
Usage:
	This component can listen to "click on link" events that are fired by a dispatcher component. 
	The original click event is stopped by this component if the link points to an "anchor" in the same page and scrolls smoothly to the targeted element.
	
	The events should send a memo with the following properties:
	anchor - (string) The id of the element the link is pointing to.
	event - (object) Original click event.
*/
IPE.Component.SmoothScroll = Class.create(IPE.Component.Wrapper, {
	defaultOptions : {
		events : []
	},
	
	initialize : function($super, element, options) {
		$super(element,options);
		
		// observe events
		this.options.events.each(function(eventName){
			document.observe(eventName, this.execute.bindAsEventListener(this));
		}, this);
	},
	
	/*
	Method: execute
		Checks if the event comes from an anchor link. If so the scroll method is called, and the original event is stopped.
	*/
	execute : function(event){
		var properties = event.memo;
		if (properties.anchor){
			this.scroll(properties.anchor);
			properties.element.store("stopExecution", 1);
		}
	},
	
	/*
	Method: scroll
		Scrolls to the element with provided id.
		
	Arguments:
		elementId - (string) Id of element to scroll to.
	*/
	scroll : function(elementId){
		var element = $(elementId);
		if (!element) return;
		if (!Effect || !Effect.ScrollTo) return;
		Effect.ScrollTo(element, {
			afterFinish: function(){
				location.href = location.href.replace(/#[\w-]*/,"") + "#" + elementId;
				element.fire("element:scrollTo", {element: element});
			}
		});
	}
});

/*
File: Sortable
*/

IPE.Component.componentDefinitions.Sortable = {
	selector : ".sortable"
};

/*
Class: IPE.Component.Sortable
	Sortable component
	
Options:
	accept - (string,array) An element, or set of elements, whose sortable subelements can be 
		dragged into this Sortable, allowing for sortable elements to be moved 
		between lists. By default, only elements in the current Sortable can be 
		dropped.
	ghosting - (boolean) A Boolean value specifying whether ghosting is enabled during a drag operation. 
		Defaults to false (no ghosting).
	hoverclass - (string) A class name that will be applied to the drop target when a draggable hovers 
		over it.
	dropOnEmpty - (boolean) This option, if true, allows sortable elements to be dropped onto an 
		empty list. Defaults to true.
	tree - (boolean) This option, if true, enables sorting with subelements within the sort- 
		able element. The default is false.
	constraint - (string) A setting that constrains movement to the horizontal or vertical axis. By default, no 
		constraint is applied.
	remember - (boolean) Store order of elements in sortable.
		
Events:
	element:orderChange - Fires whenever the sort order changes while dragging. When dragging from one Sortable to another, the callback is called once on each Sortable.
	element:update - Fires when the drag ends and the Sortableâ€™s order is changed in any way. When dragging from one Sortable to another, the callback is called once on each Sortable. 
*/
IPE.Component.Sortable = Class.create(IPE.Component.Wrapper,{
	_globalEvents : ["orderChange","update"],
	_callBacks : {
		onUpdate : "update"
	},
	defaultOptions : {
		/*accept: null,
		ghosting: false,
		hoverclass: "hover",
		dropOnEmpty: true,
		constraint: null,
		tree: false,*/
		remember: false
	},
	
	initialize : function($super,element,options){
		$super(element,options);

		if (this.options.accept){
			if (!Object.isArray(this.options.accept)){
				this.options.accept = [this.options.accept];
			}
			this.options.accept.push(this.element.identify());
		}
		
		// set drag handle
		if (element.down(".handle")){
			var handle = "handle";
		}
		
		// set onUpdate handler if persistent option is set
		// onUpdate doesn't work in Firefox so we use onChange
		if (this.options.remember){
						
			var order = Cookie.read("order" + this.element.identify());
			this.order = order ? decodeURIComponent(order).split(",") : [];	
			var newItems = [];
		
			this.element.childElements().each(function(element){
				var id = element.identify();
				//collect items not yet in index
				if(this.order.indexOf(id)==-1){	newItems.push(id); 	}
			}.bind(this));
			
			//make complete new index, new items first
			this.order = newItems.concat(this.order);
			//restore the correct order
			this.restoreOrder();
		}
		
		// Scriptaculous Sortable needs an id on the element
		this.element.identify();
		
		// create sortable instance
		Sortable.create(this.element, Object.extend({
			tag : this.element.down().tagName,
			containment: this.options.accept || this.element,
			handle: handle || null,
			onChange : function(element){
				if (this.options.remember) this.storeOrder(element);
				this.element.fire("element:orderChange");
			}.bind(this)
		},this.options));
		
	},
	
	/*
	Method: storeOrder
		Stores order in a cookie.
	*/
	storeOrder : function(element){
		var id = element.identify();
		this.order = this.order.without(id);
		
		var next = element.next();
		var pos = next ? this.order.indexOf(next.identify()) : null;
		
		if(pos===null){
			var prev = 	element.previous();
			pos = prev ? this.order.indexOf(prev.identify())+1 : null;
		}
		if(pos===null) return;
		
		//insert the element back in the array on it's new position
		this.order.splice(pos, 0, id);
		// store cookie
		var order = encodeURIComponent(this.order.toString());
		Cookie.create("order" + this.element.identify(), order);
	},
	
	/*
	Method: restoreOrder
		Restore saved order.
	*/
	restoreOrder : function(){
		
		// put elements in stored order
		this.order.each(function(elementId){
			var element = $(elementId);
			if (element) this.element.appendChild(element);
		}.bind(this));
	}
});
/*
File: SpinBox
*/

IPE.Component.componentDefinitions.SpinBox = {
	selector : ".spinBox"
};

/*
Class: IPE.Component.SpinBox
	SpinBox component
	
Options
	min - (number) Minimum value for the spin box. Defaults to 0.
	max - (number) Maximum value. Defaults to infinity.
	step - (number) Incremental value for each step. Defaults to 1.
*/
IPE.Component.SpinBox = Class.create(IPE.Component.Wrapper,{
	defaultOptions : {
		min : 0,
		max : Infinity,
		step: null
	},
	
	initialize: function($super,element,options) {
		$super(element,options);
		this.wrapper = new Element("span",{"class":"spinBoxWrapper"});
		this.up = new Element("span",{"class":"up"});
		this.down = new Element("span",{"class":"down"});
		this.wrapper.insert(this.up);
		this.wrapper.insert(this.down);
		this.element.insert({
			before: this.wrapper
		});
		this.wrapper.insert({top:this.element});
		this.minValue = this.options.min;
		this.maxValue = this.options.max;
		this.step = parseInt(this.options.step) || 1;
		this.up.observe("click",this._up.bindAsEventListener(this));
		this.down.observe("click",this._down.bindAsEventListener(this));
		
		//set focussed flag on element
		this.element.observe("focus", function(){this.element.focussed = true;}.bind(this));
		this.element.observe("blur", function(){this.element.focussed = false;}.bind(this));
		
		//add eventlisteners to element
		document.observe("keyboard:up",function(event){
			if (this.element.focussed) this._up(event);
		}.bind(this));
		document.observe("keyboard:down",function(event){
			if (this.element.focussed) this._down(event);
		}.bind(this));
	},
	
	/*
	Method: _addValue
		Increments value of input field.
	
	Arguments:
		x - (number) value to add.
	*/
	_addValue: function(x) {
		var v = parseInt(this.element.value);
		if (isNaN(v)) v = 0;
		v = v + x;
		if (this.minValue <= v && v <= this.maxValue) {
			this.element.value = v.toString();
		}
	},
	
	/*
	Method: _up
		Up button click event handler.
	*/
	_up: function(event) {
		event.stop();
		this._addValue(this.step);
	},
	
	/*
	Method: _down
		Down button click event handler.
	*/
	_down: function(event) {
		event.stop();
		this._addValue(-this.step);
	},
	
	/*
	Method: _validateInput
		Converts value to an allowed value based on min, max and step.
	*/
	_validateInput : function(value){
		var value = parseInt(value);
		if(this.step && value%this.step){
			value = Math.round(value/this.step)*this.step;
		}
		if (value <= this.minValue) value = this.minValue;
		if (value >= this.maxValue) value = this.maxValue;
		return value;
	}
});
/*
File: SuperImposedLabel
*/

IPE.Component.componentDefinitions.SuperImposedLabel = {
	selector : ["label.superImpose","label em.instruction"]
};

/*
Class: IPE.Component.SuperImposedLabel
	Labels that are superimposed on top op the associated input field
*/
IPE.Component.SuperImposedLabel = Class.create({
	initialize : function(element){
		this.element = $(element);
		this.label = this.element.tagName == "LABEL" ? this.element : this.element.up("label");
		var forAttribute = this.label.readAttribute("for");
		this.input = forAttribute ? $(forAttribute) : this.label.down("input");
		if (!this.input){return;}
		this.input.observe("blur",this.onBlur.bind(this));
		this.input.observe("focus",this.onFocus.bind(this));
		this.input.observe("element:restore",this.onBlur.bind(this));
		//check if element already has focus
		//hasFocus property is set by autofocus component
		this.input.hasFocus ? this.onFocus() : this.onBlur();
	},
	
	/*
	Method: onBlur
		Onblur event handler. Shows the label.
	*/
	onBlur : function(){
		if (this.input.value != ""){
			this.element.hide();
			this.element.style.visibility = "hidden";
		} else {
			this.element.show();
			this.element.style.visibility = "visible";
		}
	},
	
	/*
	Method: onFocus
		Onfocus event handler. Hides the label.
	*/
	onFocus : function(){
		this.element.hide();
		this.element.style.visibility = "hidden";
	}
});

/*
File: NavigationTabsPreview
*/

IPE.Component.componentDefinitions.NavigationTabsPreview = {
	selector : ".navigationTabs-preview"
};

/*
Class: IPE.Component.NavigationTabsPreview
	Tab navigation with preview of second level navigation

Options:
	delay - (number) Delay for showing second level in milliseconds
*/
IPE.Component.NavigationTabsPreview = Class.create(IPE.Component.Wrapper,{
	defaultOptions : {
		delay : 300
	},
	
	initialize : function($super,element,options) {
		this.activeTab = null;
		this.hoverTab = null;
		this.tabTimer = null;
		this.openClassName = "open";
		this.closedClassName = "closed";
		$super(element,options);
		this.element.immediateDescendants().each(function(tab){
			tab.observe("mouseover",this._overTab.bindAsEventListener(this,tab));
			tab.observe("mouseout",this._outTab.bindAsEventListener(this));
			if (tab.hasClassName("active") || tab.hasClassName("current")) {
				this.activeTab=tab;
			}
		}.bind(this));
		this.element.observe("Navigation:updateState",function(event){
			var tab = this.element.down("li.active");
			if (tab){
				this.activeTab=tab;
				this._restoreToActive();
			}
			
		}.bind(this));
		this.element.observe("mouseout",this._outTab.bindAsEventListener(this));
		this._restoreToActive();
	},
	
	/*
	Method: _clearTimer
		Clears the delay timer.
	*/
	_clearTimer : function(){
		clearTimeout(this.tabTimer);
	},
	
	/*
	Method: _overTab
		Mouseover event handler.
	*/
	_overTab : function(evt,tab){
		clearTimeout(this.tabTimer);
		if (tab!=this.hoverTab) this.tabTimer = setTimeout(function() {this._tabOver(tab);}.bind(this), this.options.delay);
	},
	
	/*
	Method: _outTab
		Mouseout event handler.
	*/
	_outTab : function(evt){
		clearTimeout(this.tabTimer);
		this.tabTimer = setTimeout(this._restoreToActive.bind(this), this.options.delay);
	},
	
	/*
	Method: _tabOver
		Shows second level under a tab.
	*/
	_tabOver : function(tab) {
		if (this.hoverTab && this.hoverTab!=tab){
			this.hoverTab.removeClassName("open");
			this.hoverTab.addClassName("closed");
			this.hoverTab=null;
		}
		if (tab && tab!=this.hoverTab){
			tab.removeClassName("closed");
			tab.addClassName("open");
			this.hoverTab = tab;
		}
	},
	
	/*
	Method: _restoreToActive
		Show second level under active tab.
	*/
	_restoreToActive : function(){
		this._tabOver(this.activeTab || null);
	}
});
IPE.Component.componentDefinitions.Throbber = {
	selector : "document"
};

/*
Class: IPE.Component.Throbber
	Shows throbber in elements that are updated with new content
	
Options:
	optionName - (type) Description of option
*/
IPE.Component.Throbber = Class.create(IPE.Component.Wrapper,{
	defaultOptions : {
		position : "element",
		showEvents: [
			"element:beforeUpdate"
		],
		hideEvents: [
			"element:afterUpdate"
		]
	},
	
	initialize : function($super,element,options) {
		$super(element,options);

		this.throbbers = {};
		
		// show handler function
		var showHandler = function(event){
			var element = event.memo.element;
			var originalEvent = event.memo.event || event;
			this.show(element, event.memo.target, originalEvent);
		}.bind(this);
		
		// hide handler function
		var hideHandler = function(event){
			var element = event.memo.element;
			var originalEvent = event.memo.event || event;
			this.hide(element, event.memo.target, originalEvent);
		}.bind(this);
		
		// attacht show handler to events
		this.options.showEvents.each(function(eventName){
			document.observe(eventName, showHandler);
		});
		
		// attacht hide handler to events
		this.options.hideEvents.each(function(eventName){
			document.observe(eventName, hideHandler);
		});
	},
	
	/*
	Method: createThrobber
		Add throbber to specified scope or return existing one.
		
	Arguments:
		scope - (object) Element to create throbber in.
	*/
	createThrobber : function(scope){
		// find existing throbber
		var throbber = scope.down(".throbber");
		
		// create new throbber if no one exists yet
		if (!throbber){
			throbber = new Element("span",{"class":"throbber"}).update("<span></span>");
			var scopeNodeName = scope.nodeName.toLowerCase();
			if (this.scopeTranslations[scopeNodeName]) scope = scope.down(this.scopeTranslations[scopeNodeName]);
			if (!scope) return null;
			scope.insert({top: throbber});
		}
		
		// return throbber element
		return throbber;
	},
	
	/*
	Method: show
		Shows a throbber and adds class name "loading" to the element.
		
	Arguments:
		element - (object) Element in which a throbber needs to be shown.
	*/
	show: function(element, target, event){
		var position = this.options.position;
		
		// stop if loading is already in progress
		if (element && element.hasClassName("loading")) return;
		
		var scope = element;
		// if element does not exist or is hidden: show throbber in body
		if (!element || element.getStyle("display") == "none" || position == "cursor"){
			scope = document.body;
		}
		
		// find or create throbber element
		var throbber = this.createThrobber(scope);
		
		if (throbber) {
			// position and show throbber
			this.positionFunctions[position].call(this, throbber, scope, event);
			throbber.show();

			// store throbber reference
			if (scope == document.body) this.throbber = throbber;

			this.throbbers[target] = throbber;
		}
		
		// add class name "loading" for additional styling of loading elements
		if (element) element.addClassName("loading");
		
		//add class name "wait" to body
		document.body.addClassName("wait");
	},
	
	/*
	Method: position
		Keeps the throbber in the viewport if possible
		
	Arguments:
		element - (object) throbber element to position
	*/
	positionFunctions : {
		element: function(throbber, scope, event){
			if (scope == document.body) return;
			
			// get dimensions for calculations
			var throbberHeight = throbber.getHeight();
			var viewportOffset = scope.viewportOffset();
			var viewportHeight = document.viewport.getHeight();
			var scopeHeight = scope.getHeight();
			var newTop;
			
			// position throbber into view
			if (viewportOffset.top < 0){
				visibleHeight = scopeHeight + viewportOffset.top > viewportHeight ? viewportHeight : scopeHeight + viewportOffset.top;
				newTop = -viewportOffset.top + visibleHeight/2;
			} else if ((viewportOffset.top + scopeHeight) > viewportHeight){
				newTop = (viewportHeight - viewportOffset.top)/2;
			} else {
				newTop = scopeHeight/2;
			}
			// make sure throbber doesn't get positioned outside of its scope
			if (newTop < (throbberHeight/2)){
				newTop = (throbberHeight/2);
			} else if (newTop + (throbberHeight/2) > scopeHeight){
				newTop = scopeHeight - (throbberHeight/2);
			}
			
			// set top of throbber
			throbber.setStyle({"top": parseInt(newTop) + "px"});
		},
		
		cursor: function(throbber, scope, event){
			// get scrolled position
			var scrolled = document.viewport.getScrollOffsets();
			var x = scrolled.left;
			var y = scrolled.top;
			
			// calculate throbber position
			if (event.clientX || event.clientY){
				x += ((event.clientX || 0) + throbber.getHeight());
				y += ((event.clientY || 0) + throbber.getWidth());
			} else {
				// if event doesn't contain position information, position throbber in center of scope
				this.positionFunctions.element.call(this, throbber, scope);
				return;
			}
			
			// set position of throbber
			throbber.setStyle({
				left: x + "px",
				top: y + "px"
			});
		}
	},
	
	/*
	Method: hide
		Removes class name "loading". Removes throbber if it is not already removed with the content update.
		
	Arguments:
		element - (object) Element to hide loading indicator in.
	*/
	hide: function(element, target){
		// find throbber element
		if (!element) return;
		
		var throbber = this.throbbers[target] || element.down(".throbber") || this.throbber || element.next(".throbber") || element.previous(".throbber");
		
		if (throbber && throbber.parentNode) {
			// hide throbber
			throbber.hide();
			
			// don't know for sure that element is the element that was set to class name "loading", so also remove laoding class from parent of throbber
			var parent = throbber.up();
			parent.removeClassName("loading");
			
			// reset this.throbber if necessary
			if (throbber == this.throbber) this.throbber = null;

			// remove throbber
			throbber.remove();

			this.throbbers[target] = null;
		}
		
		// remove loading class from element
		element.removeClassName("loading");
		
		//remove class name "wait" from body
		document.body.removeClassName("wait");
	},

	/*
	Property: scopeTranslations
		Selectors for finding an allowed element in which throbber can be inserted.
	*/
	scopeTranslations : {
		"table" : "td, th",
		"tr" : "td, th",
		"tbody" : "td, th",
		"thead" : "th, td",
		"tfoot" : "td, th",
		"ul" : "li",
		"ol" : "li",
		"dl" : "dt, dd"
	}
});


