
eval(function(p,a,c,k,e,d){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)d[e(c)]=k[c]||e(c);k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('k 1m={6H:"1.5.0",6t:{6s:!!N.5h},5s:"(?:<4s.*?>)((\\n|\\r|.)*?)(?:</4s>)",3t:(c(){}),K:(c(x){f x})};k 1z={1y:(c(){f(c(){7.24.2y(7,T)})})};k 1K=P D();D.B=(c(3Q,1x){15(k 1n 29 1x){3Q[1n]=1x[1n]}f 3Q});D.B(D,{1f:(c(W){1Q{h(W===1b){f"1b"}h(W===12){f"12"}f W.1f?W.1f():W.2m()}1X(e){h(e cp co){f"..."}2B e}}),4y:(c(W){k 4y=[];15(k 1n 29 W){4y.M(1n)}f 4y}),1a:(c(W){k 1a=[];15(k 1n 29 W){1a.M(W[1n])}f 1a}),43:(c(W){f D.B({},W)})});5Q.I.1A=(c(){k 5u=7,1P=$A(T),W=1P.6o();f(c(){f 5u.2y(W,1P.1S($A(T)))})});5Q.I.cn=(c(W){k 5u=7,1P=$A(T),W=1P.6o();f(c(19){f 5u.2y(W,[(19||1r.19)].1S(1P).1S($A(T)))})});D.B(cm.I,{cl:(c(){k 70=7.2m(16);h(7<16){f"0"+70}f 70}),6L:(c(){f 7+1}),85:(c(L){$R(0,7,1j).U(L);f 7})});k 92={91:(c(){k 4H;15(k i=0,o=T.o;i<o;i++){k 9p=T[i];1Q{4H=9p();1i}1X(e){}}f 4H})};k 9o=1z.1y();9o.I={24:(c(1L,2k){7.1L=1L;7.2k=2k;7.5t=1d;7.3g()}),3g:(c(){7.3T=7v(7.3h.1A(7),7.2k*5H)}),5E:(c(){h(!7.3T){f}ck(7.3T);7.3T=12}),3h:(c(){h(!7.5t){1Q{7.5t=1j;7.1L(7)}cj{7.5t=1d}}})};2j.6T=(c(l){f l==12?"":2j(l)});D.B(2j.I,{2r:(c(2f,2g){k J="",1x=7,E;2g=T.ci.6V(2g);1u(1x.o>0){h(E=1x.E(2f)){J+=1x.2G(0,E.G);J+=2j.6T(2g(E));1x=1x.2G(E.G+E[0].o)}1k{J+=1x,1x=""}}f J}),ch:(c(2f,2g,4B){2g=7.2r.6V(2g);4B=4B===1b?1:4B;f 7.2r(2f,(c(E){h(--4B<0){f E[0]}f 2g(E)}))}),cg:(c(2f,L){7.2r(2f,L);f 7}),cf:(c(o,41){o=o||30;41=41===1b?"...":41;f 7.o>o?7.2G(0,o-41.o)+41:7}),4d:(c(){f 7.2q(/^\\s+/,"").2q(/\\s+$/,"")}),9i:(c(){f 7.2q(/<\\/?[^>]+>/5a,"")}),2C:(c(){f 7.2q(P 5d(1m.5s,"9n"),"")}),9j:(c(){k 9m=P 5d(1m.5s,"9n");k 9k=P 5d(1m.5s,"ce");f(7.E(9m)||[]).1F((c(9l){f(9l.E(9k)||["",""])[1]}))}),3P:(c(){f 7.9j().1F((c(4s){f 6A(4s)}))}),cd:(c(){k 1h=N.4f("1h");k 2V=N.cc(7);1h.63(2V);f 1h.2P}),cb:(c(){k 1h=N.4f("1h");1h.2P=7.9i();f 1h.2O[0]?(1h.2O.o>1?$A(1h.2O).2A("",(c(3Y,1G){f 3Y+1G.4j})):1h.2O[0].4j):""}),6J:(c(9h){k E=7.4d().E(/([^?#]*)(#.*)?$/);h(!E){f{}}f E[1].3m(9h||"&").2A({},(c(2E,Q){h((Q=Q.3m("="))[0]){k v=9g(Q[0]);k l=Q[1]?9g(Q[1]):1b;h(2E[v]!==1b){h(2E[v].3k!=1M){2E[v]=[2E[v]]}h(l){2E[v].M(l)}}1k{2E[v]=l}}f 2E}))}),2F:(c(){f 7.3m("")}),6L:(c(){f 7.2G(0,7.o-1)+2j.ca(7.c9(7.o-1)+1)}),6j:(c(){k 2e=7.3m("-"),6Z=2e.o;h(6Z==1){f 2e[0]}k 6Y=7.5r(0)=="-"?2e[0].5r(0).23()+2e[0].6X(1):2e[0];15(k i=1;i<6Z;i++){6Y+=2e[i].5r(0).23()+2e[i].6X(1)}f 6Y}),8o:(c(){f 7.5r(0).23()+7.6X(1).1Y()}),c8:(c(){f 7.2r(/::/,"/").2r(/([A-Z]+)([A-Z][a-z])/,"#{1}4u#{2}").2r(/([a-z\\d])([A-Z])/,"#{1}4u#{2}").2r(/-/,"4u").1Y()}),c7:(c(){f 7.2r(/4u/,"-")}),1f:(c(9f){k 6W=7.2q(/\\\\/g,"\\\\\\\\");h(9f){f"\\""+6W.2q(/"/g,"\\\\\\"")+"\\""}1k{f"\'"+6W.2q(/\'/g,"\\\\\'")+"\'"}})});2j.I.2r.6V=(c(2g){h(1w 2g=="c"){f 2g}k 3Z=P 4A(2g);f(c(E){f 3Z.5h(E)})});2j.I.c6=2j.I.6J;k 4A=1z.1y();4A.9e=/(^|.|\\r|\\n)(#\\{(.*?)\\})/;4A.I={24:(c(3Z,2f){7.3Z=3Z.2m();7.2f=2f||4A.9e}),5h:(c(W){f 7.3Z.2r(7.2f,(c(E){k 6U=E[1];h(6U=="\\\\"){f E[2]}f 6U+2j.6T(W[E[3]])}))})};k $1i=P D();k $4N=P D();k 20={U:(c(L){k G=0;1Q{7.2N((c(l){1Q{L(l,G++)}1X(e){h(e!=$4N){2B e}}}))}1X(e){h(e!=$1i){2B e}}f 7}),9c:(c(2Y,L){k G=-2Y,6S=[],1U=7.2F();1u((G+=2Y)<1U.o){6S.M(1U.2G(G,G+2Y))}f 6S.1F(L)}),6d:(c(L){k J=1j;7.U((c(l,G){J=J&&!!(L||1m.K)(l,G);h(!J){2B $1i}}));f J}),c5:(c(L){k J=1d;7.U((c(l,G){h(J=!!(L||1m.K)(l,G)){2B $1i}}));f J}),9a:(c(L){k 17=[];7.U((c(l,G){17.M((L||1m.K)(l,G))}));f 17}),99:(c(L){k J;7.U((c(l,G){h(L(l,G)){J=l;2B $1i}}));f J}),98:(c(L){k 17=[];7.U((c(l,G){h(L(l,G)){17.M(l)}}));f 17}),c4:(c(2f,L){k 17=[];7.U((c(l,G){k 9d=l.2m();h(9d.E(2f)){17.M((L||1m.K)(l,G))}}));f 17}),1o:(c(W){k 6R=1d;7.U((c(l){h(l==W){6R=1j;2B $1i}}));f 6R}),c3:(c(2Y,4z){4z=4z===1b?12:4z;f 7.9c(2Y,(c(2G){1u(2G.o<2Y){2G.M(4z)}f 2G}))}),2A:(c(3Y,L){7.U((c(l,G){3Y=L(3Y,l,G)}));f 3Y}),c2:(c(1E){k 1P=$A(T).2G(1);f 7.1F((c(l){f l[1E].2y(l,1P)}))}),c1:(c(L){k J;7.U((c(l,G){l=(L||1m.K)(l,G);h(J==1b||l>=J){J=l}}));f J}),c0:(c(L){k J;7.U((c(l,G){l=(L||1m.K)(l,G);h(J==1b||l<J){J=l}}));f J}),bZ:(c(L){k 6Q=[],6P=[];7.U((c(l,G){((L||1m.K)(l,G)?6Q:6P).M(l)}));f[6Q,6P]}),4x:(c(1n){k 17=[];7.U((c(l,G){17.M(l[1n])}));f 17}),bY:(c(L){k 17=[];7.U((c(l,G){h(!L(l,G)){17.M(l)}}));f 17}),bX:(c(L){f 7.1F((c(l,G){f{l:l,6O:L(l,G)}})).bW((c(1J,55){k a=1J.6O,b=55.6O;f a<b?-1:a>b?1:0})).4x("l")}),2F:(c(){f 7.1F()}),bV:(c(){k L=1m.K,1P=$A(T);h(1w 1P.6q()=="c"){L=1P.bU()}k 9b=[7].1S(1P).1F($A);f 7.1F((c(l,G){f L(9b.4x(G))}))}),96:(c(){f 7.2F().o}),1f:(c(){f"#<20:"+7.2F().1f()+">"})};D.B(20,{1F:20.9a,7G:20.99,25:20.98,bT:20.1o,bS:20.2F});k $A=1M.bR=(c(3X){h(!3X){f[]}h(3X.2F){f 3X.2F()}1k{k 17=[];15(k i=0,o=3X.o;i<o;i++){17.M(3X[i])}f 17}});D.B(1M.I,20);h(!1M.I.6N){1M.I.6N=1M.I.4X}D.B(1M.I,{2N:(c(L){15(k i=0,o=7.o;i<o;i++){L(7[i])}}),7E:(c(){7.o=0;f 7}),4b:(c(){f 7[0]}),6q:(c(){f 7[7.o-1]}),94:(c(){f 7.25((c(l){f l!=12}))}),5P:(c(){f 7.2A([],(c(1U,l){f 1U.1S(l&&l.3k==1M?l.5P():[l])}))}),5Y:(c(){k 1a=$A(T);f 7.25((c(l){f!1a.1o(l)}))}),8W:(c(W){15(k i=0,o=7.o;i<o;i++){h(7[i]==W){f i}}f-1}),4X:(c(97){f(97!==1d?7:7.2F()).6N()}),93:(c(){f 7.o>1?7:7[0]}),bQ:(c(){f 7.2A([],(c(1U,l){f 1U.1o(l)?1U:1U.1S([l])}))}),43:(c(){f[].1S(7)}),96:(c(){f 7.o}),1f:(c(){f"["+7.1F(D.1f).2M(", ")+"]"})});1M.I.2F=1M.I.43;c $w(2z){2z=2z.4d();f 2z?2z.3m(/\\s+/):[]}h(1r.2K){1M.I.1S=(c(){k 1U=[];15(k i=0,o=7.o;i<o;i++){1U.M(7[i])}15(k i=0,o=T.o;i<o;i++){h(T[i].3k==1M){15(k j=0,95=T[i].o;j<95;j++){1U.M(T[i][j])}}1k{1U.M(T[i])}}f 1U})}k 26=(c(5q){D.B(7,5q||{})});D.B(26,{3L:(c(5q){k 2e=[];7.I.2N.bP(5q,(c(Q){h(!Q.1q){f}h(Q.l&&Q.l.3k==1M){k 1a=Q.l.94();h(1a.o<2){Q.l=1a.93()}1k{1q=5p(Q.1q);1a.U((c(l){l=l!=1b?5p(l):"";2e.M(1q+"="+5p(l))}));f}}h(Q.l==1b){Q[1]=""}2e.M(Q.1F(5p).2M("="))}));f 2e.2M("&")})});D.B(26.I,20);D.B(26.I,{2N:(c(L){15(k 1q 29 7){k l=7[1q];h(l&&l==26.I[1q]){4N}k Q=[1q,l];Q.1q=1q;Q.l=l;L(Q)}}),4y:(c(){f 7.4x("1q")}),1a:(c(){f 7.4x("l")}),bO:(c(2E){f $H(2E).2A(7,(c(6M,Q){6M[Q.1q]=Q.l;f 6M}))}),4e:(c(){k J;15(k i=0,o=T.o;i<o;i++){k l=7[T[i]];h(l!==1b){h(J===1b){J=l}1k{h(J.3k!=1M){J=[J]}J.M(l)}}bN 7[T[i]]}f J}),3L:(c(){f 26.3L(7)}),1f:(c(){f"#<26:{"+7.1F((c(Q){f Q.1F(D.1f).2M(": ")})).2M(", ")+"}>"})});c $H(W){h(W&&W.3k==26){f W}f P 26(W)}5o=1z.1y();D.B(5o.I,20);D.B(5o.I,{24:(c(2Q,3w,3W){7.2Q=2Q;7.3w=3w;7.3W=3W}),2N:(c(L){k l=7.2Q;1u(7.1o(l)){L(l);l=l.6L()}}),1o:(c(l){h(l<7.2Q){f 1d}h(7.3W){f l<7.3w}f l<=7.3w})});k $R=(c(2Q,3w,3W){f P 5o(2Q,3w,3W)});k 1c={6y:(c(){f 92.91((c(){f P 8T()}),(c(){f P 90("bM.8Z")}),(c(){f P 90("bL.8Z")}))||1d}),6K:0};1c.3U={4w:[],2N:(c(L){7.4w.2N(L)}),8Y:(c(2S){h(!7.1o(2S)){7.4w.M(2S)}}),bK:(c(2S){7.4w=7.4w.5Y(2S)}),5l:(c(1L,3b,11,2R){7.U((c(2S){h(1w 2S[1L]=="c"){1Q{2S[1L].2y(2S,[3b,11,2R])}1X(e){}}}))})};D.B(1c.3U,20);1c.3U.8Y({8V:(c(){1c.6K++}),2d:(c(){1c.6K--})});1c.5k=(c(){});1c.5k.I={5j:(c(C){7.C={1E:"4t",5n:1j,8R:"6D/x-bJ-V-bI",6G:"bH-8",4v:""};D.B(7.C,C||{});7.C.1E=7.C.1E.1Y();h(1w 7.C.4v=="2z"){7.C.4v=7.C.4v.6J()}})};1c.4p=1z.1y();1c.4p.8N=["bG","bF","bE","bD","6C"];1c.4p.I=D.B(P 1c.5k(),{6E:1d,24:(c(1T,C){7.11=1c.6y();7.5j(C);7.3b(1T)}),3b:(c(1T){7.1T=1T;7.1E=7.C.1E;k Y=7.C.4v;h(!["8X","4t"].1o(7.1E)){Y.bC=7.1E;7.1E="4t"}Y=26.3L(Y);h(Y&&/3A|3z|3y/.2J(21.2I)){Y+="&4u="}h(7.1E=="8X"&&Y){7.1T+=(7.1T.8W("?")>-1?"&":"?")+Y}1Q{1c.3U.5l("8V",7,7.11);7.11.bB(7.1E.23(),7.1T,7.C.5n);h(7.C.5n){3p((c(){7.6F(1)}).1A(7),10)}7.11.8K=7.6I.1A(7);7.8U();k 1I=7.1E=="4t"?(7.C.bA||Y):12;7.11.bz(1I);h(!7.C.5n&&7.11.8Q){7.6I()}}1X(e){7.4q(e)}}),6I:(c(){k 3u=7.11.3u;h(3u>1&&!((3u==4)&&7.6E)){7.6F(7.11.3u)}}),8U:(c(){k 3v={\'X-by-bx\':"8T",\'X-1m-6H\':1m.6H,bw:"2V/8L, 2V/1l, 6D/8S, 2V/8S, */*"};h(7.1E=="4t"){3v[\'8M-2L\']=7.C.8R+(7.C.6G?"; bv="+7.C.6G:"");h(7.11.8Q&&(21.2I.E(/8n\\/(\\d{4})/)||[0,8P])[1]<8P){3v.bu="bt"}}h(1w 7.C.8O=="W"){k 3V=7.C.8O;h(1w 3V.M=="c"){15(k i=0,o=3V.o;i<o;i+=2){3v[3V[i]]=3V[i+1]}}1k{$H(3V).U((c(Q){3v[Q.1q]=Q.l}))}}15(k v 29 3v){7.11.bs(v,3v[v])}}),3c:(c(){f!7.11.5m||(7.11.5m>=br&&7.11.5m<bq)}),6F:(c(3u){k 4r=1c.4p.8N[3u];k 11=7.11,2R=7.8J();h(4r=="6C"){1Q{7.6E=1j;(7.C["3I"+7.11.5m]||7.C["3I"+(7.3c()?"bp":"bo")]||1m.3t)(11,2R)}1X(e){7.4q(e)}h((7.6B("8M-2L")||"2V/8L").4d().E(/^(2V|6D)\\/(x-)?(bn|bm)4s(;.*)?$/i)){7.8I()}}1Q{(7.C["3I"+4r]||1m.3t)(11,2R);1c.3U.5l("3I"+4r,7,11,2R)}1X(e){7.4q(e)}h(4r=="6C"){7.11.8K=1m.3t}}),6B:(c(v){1Q{f 7.11.bl(v)}1X(e){f 12}}),8J:(c(){1Q{k 2R=7.6B("X-bk");f 2R?6A("("+2R+")"):12}1X(e){f 12}}),8I:(c(){1Q{f 6A(7.11.5i)}1X(e){7.4q(e)}}),4q:(c(6z){(7.C.8H||1m.3t)(7,6z);1c.3U.5l("8H",7,6z)})});1c.6v=1z.1y();D.B(D.B(1c.6v.I,1c.4p.I),{24:(c(2c,1T,C){7.2c={3c:(2c.3c||2c),6x:(2c.6x||(2c.3c?12:2c))};7.11=1c.6y();7.5j(C);k 2d=7.C.2d||1m.3t;7.C.2d=(c(11,8G){7.8F();2d(11,8G)}).1A(7);7.3b(1T)}),8F:(c(){k 4o=7.2c[7.3c()?"3c":"6x"];k 4n=7.11.5i;h(!7.C.3P){4n=4n.2C()}h(4o=$(4o)){h(7.C.8E){P 7.C.8E(4o,4n)}1k{4o.6c(4n)}}h(7.3c()){h(7.2d){3p(7.2d.1A(7),10)}}})});1c.8D=1z.1y();1c.8D.I=D.B(P 1c.5k(),{24:(c(2c,1T,C){7.5j(C);7.2d=7.C.2d;7.2k=(7.C.2k||2);7.3s=(7.C.3s||1);7.6w={};7.2c=2c;7.1T=1T;7.2Q()}),2Q:(c(){7.C.2d=7.8C.1A(7);7.3h()}),5E:(c(){7.6w.C.2d=1b;bj(7.3T);(7.2d||1m.3t).2y(7,T)}),8C:(c(3b){h(7.C.3s){7.3s=(3b.5i==7.8B?7.3s*7.C.3s:1);7.8B=3b.5i}7.3T=3p(7.3h.1A(7),7.3s*7.2k*5H)}),3h:(c(){7.6w=P 1c.6v(7.2c,7.1T,7.C)})});c $(6){h(T.o>1){15(k i=0,1p=[],o=T.o;i<o;i++){1p.M($(T[i]))}f 1p}h(1w 6=="2z"){6=N.bi(6)}f m.B(6)}h(1m.6t.6s){N.8A=(c(18,4m){k 17=[];k 6u=N.5h(18,$(4m)||N,12,bh.bg,12);15(k i=0,o=6u.bf;i<o;i++){17.M(6u.be(i))}f 17})}N.6n=(c(1g,4m){h(1m.6t.6s){k q=".//*[bd(1S(\' \', @8v, \' \'), \' "+1g+" \')]";f N.8A(q,4m)}1k{k 6r=($(4m)||N.1I).4a("*");k 1p=[],3j;15(k i=0,o=6r.o;i<o;i++){3j=6r[i];h(m.4S(3j,1g)){1p.M(m.B(3j))}}f 1p}});h(!1r.m){k m=P D()}m.B=(c(6){h(!6||67||6.5c==3){f 6}h(!6.8z&&6.14&&6!=1r){k 2p=D.43(m.1t),3q=m.B.3q;h(6.14=="bc"){D.B(2p,O.1t)}h(["bb","ba","b9"].1o(6.14)){D.B(2p,O.m.1t)}D.B(2p,m.1t.69);15(k 1n 29 2p){k l=2p[1n];h(1w l=="c"&&!(1n 29 6)){6[1n]=3q.6a(l)}}}6.8z=1j;f 6});m.B.3q={6a:(c(l){f 7[l]=7[l]||(c(){f l.2y(12,[7].1S($A(T)))})})};m.1t={6f:(c(6){f $(6).u.2a!="56"}),83:(c(6){6=$(6);m[m.6f(6)?"8y":"8x"](6);f 6}),8y:(c(6){$(6).u.2a="56";f 6}),8x:(c(6){$(6).u.2a="";f 6}),4e:(c(6){6=$(6);6.2i.6b(6);f 6}),6c:(c(6,1l){1l=1w 1l=="1b"?"":1l.2m();$(6).2P=1l.2C();3p((c(){1l.3P()}),10);f 6}),2q:(c(6,1l){6=$(6);1l=1w 1l=="1b"?"":1l.2m();h(6.8w){6.8w=1l.2C()}1k{k 2o=6.81.80();2o.64(6);6.2i.b8(2o.7Z(1l.2C()),6)}3p((c(){1l.3P()}),10);f 6}),1f:(c(6){6=$(6);k J="<"+6.14.1Y();$H({3M:"3M",1g:"8v"}).U((c(Q){k 1n=Q.4b(),1e=Q.6q();k l=(6[1n]||"").2m();h(l){J+=" "+1e+"="+l.1f(1j)}}));f J+">"}),5g:(c(6,1n){6=$(6);k 1p=[];1u(6=6[1n]){h(6.5c==1){1p.M(m.B(6))}}f 1p}),8u:(c(6){f $(6).5g("2i")}),8t:(c(6){f $A($(6).4a("*"))}),b7:(c(6){h(!(6=$(6).4W)){f[]}1u(6&&6.5c!=1){6=6.4V}h(6){f[6].1S($(6).5f())}f[]}),6p:(c(6){f $(6).5g("b6")}),5f:(c(6){f $(6).5g("4V")}),b5:(c(6){6=$(6);f 6.6p().4X().1S(6.5f())}),E:(c(6,28){h(1w 28=="2z"){28=P 1Z(28)}f 28.E($(6))}),b4:(c(6,18,G){f 1Z.3J($(6).8u(),18,G)}),b3:(c(6,18,G){f 1Z.3J($(6).8t(),18,G)}),b2:(c(6,18,G){f 1Z.3J($(6).6p(),18,G)}),b1:(c(6,18,G){f 1Z.3J($(6).5f(),18,G)}),b0:(c(){k 1P=$A(T),6=$(1P.6o());f 1Z.5O(6,1P)}),6n:(c(6,1g){f N.6n(1g,6)}),5S:(c(6,v){6=$(6);h(N.6d&&!1r.2K){k t=m.2b;h(t.1a[v]){f t.1a[v](6,v)}h(t.53[v]){v=t.53[v]}k 1e=6.4c[v];h(1e){f 1e.4j}}f 6.8d(v)}),aZ:(c(6){f $(6).6i().2u}),aY:(c(6){f $(6).6i().2t}),3n:(c(6){f P m.4U(6)}),4S:(c(6,1g){h(!(6=$(6))){f}k 5e=6.1g;h(5e.o==0){f 1d}h(5e==1g||5e.E(P 5d("(^|\\\\s)"+1g+"(\\\\s|$)"))){f 1j}f 1d}),aX:(c(6,1g){h(!(6=$(6))){f}m.3n(6).61(1g);f 6}),aW:(c(6,1g){h(!(6=$(6))){f}m.3n(6).4e(1g);f 6}),aV:(c(6,1g){h(!(6=$(6))){f}m.3n(6)[6.4S(1g)?"4e":"61"](1g);f 6}),3H:(c(){1s.3H.2y(1s,T);f $A(T).4b()}),4G:(c(){1s.4G.2y(1s,T);f $A(T).4b()}),aU:(c(6){6=$(6);k 1G=6.4W;1u(1G){k 8s=1G.4V;h(1G.5c==3&&!/\\S/.2J(1G.4j)){6.6b(1G)}1G=8s}f 6}),7Q:(c(6){f $(6).2P.E(/^\\s*$/)}),8e:(c(6,5b){6=$(6),5b=$(5b);1u(6=6.2i){h(6==5b){f 1j}}f 1d}),8r:(c(6){6=$(6);k 3S=2H.42(6);1r.8r(3S[0],3S[1]);f 6}),1H:(c(6,u){6=$(6);h(["8l","57"].1o(u)){u=(1w 6.u.58!="1b"?"58":"57")}u=u.6j();k l=6.u[u];h(!l){h(N.6m&&N.6m.8q){k 6l=N.6m.8q(6,12);l=6l?6l[u]:12}1k{h(6.8p){l=6.8p[u]}}}h((l=="4k")&&["2t","2u"].1o(u)&&(6.1H("2a")!="56")){l=6["1W"+u.8o()]+"2h"}h(1r.2K&&["1J","1V","55","8f"].1o(u)){h(m.1H(6,"1D")=="5A"){l="4k"}}h(u=="59"){h(l){f 3B(l)}h(l=(6.1H("3r")||"").E(/4l\\(59=(.*)\\)/)){h(l[1]){f 3B(l[1])/ 8m;}}f 1;}f l == "4k" ? 12 : l;}), aT:(c (6, u) {6 = $(6);15 (k v 29 u) {k l = u[v];h (v == "59") {h (l == 1) {l = (/8n/.2J(21.2I)&&!/3A|3z|3y/.2J(21.2I))?0.aS:1;h(/6k/.2J(21.2I)&&!1r.2K){6.u.3r=6.1H("3r").2q(/4l\\([^\\)]*\\)/5a,"")}}1k{h(l==""){h(/6k/.2J(21.2I)&&!1r.2K){6.u.3r=6.1H("3r").2q(/4l\\([^\\)]*\\)/5a,"")}}1k{h(l<0.aR){l=0}h(/6k/.2J(21.2I)&&!1r.2K){6.u.3r=6.1H("3r").2q(/4l\\([^\\)]*\\)/5a,"")+"4l(59="+l*8m+")"}}}}1k{h(["8l","57"].1o(v)){v=(1w 6.u.58!="1b")?"58":"57"}}6.u[v.6j()]=l}f 6}),6i:(c(6){6=$(6);k 2a=$(6).1H("2a");h(2a!="56"&&2a!=12){f{2t:6.3D,2u:6.3C}}k 2D=6.u;k 8i=2D.6h;k 8j=2D.1D;k 8k=2D.2a;2D.6h="4M";2D.1D="3d";2D.2a="aQ";k 8h=6.77;k 8g=6.76;2D.2a=8k;2D.1D=8j;2D.6h=8i;f{2t:8h,2u:8g}}),aP:(c(6){6=$(6);k 3S=m.1H(6,"1D");h(3S=="5A"||!3S){6.6g=1j;6.u.1D="4C";h(1r.2K){6.u.1V=0;6.u.1J=0}}f 6}),aO:(c(6){6=$(6);h(6.6g){6.6g=1b;6.u.1D=6.u.1V=6.u.1J=6.u.8f=6.u.55=""}f 6}),aN:(c(6){6=$(6);h(6.3R){f 6}6.3R=6.u.54||"4k";h((m.1H(6,"54")||"6f")!="4M"){6.u.54="4M"}f 6}),aM:(c(6){6=$(6);h(!6.3R){f 6}6.u.54=6.3R=="4k"?"":6.3R;6.3R=12;f 6})};D.B(m.1t,{7L:m.1t.8e});m.2b={};m.2b.53={aL:"aK",aJ:"aI",aH:"aG",aF:"aE",aD:"aC",aB:"aA",az:"ay",ax:"aw",8b:"av",au:"at"};m.2b.1a={6e:(c(6,1e){f 6.8d(1e,2)}),4i:(c(6,1e){f $(6).4L(1e)?1e:12}),u:(c(6){f 6.u.as.1Y()}),8c:(c(6){k 1G=6.8a("8c");f 1G.89?1G.4j:12})};D.B(m.2b.1a,{ar:m.2b.1a.6e,aq:m.2b.1a.6e,2X:m.2b.1a.4i,7y:m.2b.1a.4i,8b:m.2b.1a.4i,ap:m.2b.1a.4i});m.1t.69={4L:(c(6,1e){k t=m.2b;1e=t.53[1e]||1e;f $(6).8a(1e).89})};h(N.6d&&!1r.2K){m.1t.6c=(c(6,1l){6=$(6);1l=1w 1l=="1b"?"":1l.2m();k 14=6.14.23();h(["88","66","65","87"].1o(14)){k 1h=N.4f("1h");48(14){1v"88":1v"66":1h.2P="<32><3a>"+1l.2C()+"</3a></32>";51=2;1i;1v"65":1h.2P="<32><3a><52>"+1l.2C()+"</52></3a></32>";51=3;1i;1v"87":1h.2P="<32><3a><52><86>"+1l.2C()+"</86></52></3a></32>";51=4;47:}$A(6.2O).U((c(1G){6.6b(1G)}));51.85((c(){1h=1h.4W}));$A(1h.2O).U((c(1G){6.63(1G)}))}1k{6.2P=1l.2C()}3p((c(){1l.3P()}),10);f 6})}D.B(m,m.1t);k 67=1d;h(/3A|3z|3y/.2J(21.2I)){["","O","ao","an","am"].U((c(50){k 1g="al"+50+"m";h(1r[1g]){f}k 4Y=1r[1g]={};4Y.I=N.4f(50?50.1Y():"1h").ak}))}m.71=(c(2p){D.B(m.1t,2p||{});c 4h(2p,3Q,4Z){4Z=4Z||1d;k 3q=m.B.3q;15(k 1n 29 2p){k l=2p[1n];h(!4Z||!(1n 29 3Q)){3Q[1n]=3q.6a(l)}}}h(1w 68!="1b"){4h(m.1t,68.I);4h(m.1t.69,68.I,1j);4h(O.1t,aj.I);[ai,ah,ag].U((c(4Y){4h(O.m.1t,4Y.I)}));67=1j}});k 84=P D();84.2a=m.83;1K.1O=(c(4g){7.4g=4g});1K.1O.I={24:(c(6,3o){7.6=$(6);7.3o=3o.2C();h(7.4g&&7.6.82){1Q{7.6.82(7.4g,7.3o)}1X(e){k 14=7.6.14.23();h(["66","65"].1o(14)){7.3N(7.7Y())}1k{2B e}}}1k{7.2o=7.6.81.80();h(7.3O){7.3O()}7.3N([7.2o.7Z(7.3o)])}3p((c(){3o.3P()}),10)}),7Y:(c(){k 1h=N.4f("1h");1h.2P="<32><3a>"+7.3o+"</3a></32>";f $A(1h.2O[0].2O[0].2O)})};k 1O=P D();1O.7X=1z.1y();1O.7X.I=D.B(P 1K.1O("af"),{3O:(c(){7.2o.ae(7.6)}),3N:(c(31){31.U((c(2Z){7.6.2i.62(2Z,7.6)}).1A(7))})});1O.7W=1z.1y();1O.7W.I=D.B(P 1K.1O("ad"),{3O:(c(){7.2o.64(7.6);7.2o.7U(1j)}),3N:(c(31){31.4X(1d).U((c(2Z){7.6.62(2Z,7.6.4W)}).1A(7))})});1O.7V=1z.1y();1O.7V.I=D.B(P 1K.1O("ac"),{3O:(c(){7.2o.64(7.6);7.2o.7U(7.6)}),3N:(c(31){31.U((c(2Z){7.6.63(2Z)}).1A(7))})});1O.7T=1z.1y();1O.7T.I=D.B(P 1K.1O("ab"),{3O:(c(){7.2o.aa(7.6)}),3N:(c(31){31.U((c(2Z){7.6.2i.62(2Z,7.6.4V)}).1A(7))})});m.4U=1z.1y();m.4U.I={24:(c(6){7.6=$(6)}),2N:(c(L){7.6.1g.3m(/\\s+/).25((c(v){f v.o>0})).2N(L)}),5Z:(c(1g){7.6.1g=1g}),61:(c(60){h(7.1o(60)){f}7.5Z($A(7).1S(60).2M(" "))}),4e:(c(5X){h(!7.1o(5X)){f}7.5Z($A(7).5Y(5X).2M(" "))}),2m:(c(){f $A(7).2M(" ")})};D.B(m.4U.I,20);k 1Z=1z.1y();1Z.I={24:(c(18){7.Y={3n:[]};7.18=18.2m().4d();7.7S();7.7N()}),7S:(c(){c 4T(7R){2B"a9 a8 29 28: "+7R}h(7.18==""){4T("7Q 18")}k Y=7.Y,2l=7.18,E,5U,1N,5T;1u(E=2l.E(/^(.*)\\[([a-5W-5V:-]+?)(?:([~\\|!]?=)(?:"([^"]*)"|([^\\]\\s]*)))?\\]$/i)){Y.4c=Y.4c||[];Y.4c.M({v:E[2],4R:E[3],l:E[4]||E[5]||""});2l=E[1]}h(2l=="*"){f 7.Y.7P=1j}1u(E=2l.E(/^([^a-5W-5V-])?([a-5W-5V-]+)(.*)/i)){5U=E[1],1N=E[2],5T=E[3];48(5U){1v"#":Y.3M=1N;1i;1v".":Y.3n.M(1N);1i;1v"":1v 1b:Y.14=1N.23();1i;47:4T(2l.1f())}2l=5T}h(2l.o>0){4T(2l.1f())}}),7M:(c(){k Y=7.Y,2n=[],1N;h(Y.7P){2n.M("1j")}h(1N=Y.3M){2n.M("6.5S(\\"3M\\") == "+1N.1f())}h(1N=Y.14){2n.M("6.14.23() == "+1N.1f())}h((1N=Y.3n).o>0){15(k i=0,o=1N.o;i<o;i++){2n.M("6.4S("+1N[i].1f()+")")}}h(1N=Y.4c){1N.U((c(1e){k l="6.5S("+1e.v.1f()+")";k 5R=(c(7O){f l+" && "+l+".3m("+7O.1f()+")"});48(1e.4R){1v"=":2n.M(l+" == "+1e.l.1f());1i;1v"~=":2n.M(5R(" ")+".1o("+1e.l.1f()+")");1i;1v"|=":2n.M(5R("-")+".4b().23() == "+1e.l.23().1f());1i;1v"!=":2n.M(l+" != "+1e.l.1f());1i;1v"":1v 1b:2n.M("6.4L("+1e.v.1f()+")");1i;47:2B"a7 4R "+1e.4R+" 29 28"}}))}f 2n.2M(" && ")}),7N:(c(){7.E=P 5Q("6","h (!6.14) f 1d;       6 = $(6);       f "+7.7M())}),7I:(c(3l){k 6;h(6=$(7.Y.3M)){h(7.E(6)){h(!3l||m.7L(6,3l)){f[6]}}}3l=(3l||N).4a(7.Y.14||"*");k 17=[];15(k i=0,o=3l.o;i<o;i++){h(7.E(6=3l[i])){17.M(m.B(6))}}f 17}),2m:(c(){f 7.18})};D.B(1Z,{7K:(c(1p,18){k 28=P 1Z(18);f 1p.25(28.E.1A(28)).1F(m.B)}),3J:(c(1p,18,G){h(1w 18=="2Y"){G=18,18=1d}f 1Z.7K(1p,18||"*")[G||0]}),5O:(c(6,7J){f 7J.1F((c(18){f 18.E(/[^\\s"]+(?:"[^"]*"[^\\s"]+)*/g).2A([12],(c(17,2l){k 28=P 1Z(2l);f 17.2A([],(c(1p,J){f 1p.1S(28.7I(J||6))}))}))})).5P()})});c $$(){f 1Z.5O(N,$A(T))}k O={5K:(c(V){$(V).5K();f V}),7H:(c(1p,4Q){k 5N=1p.2A({},(c(J,6){h(!6.2X&&6.v){k 1q=6.v,l=$(6).1R();h(l!=1b){h(J[1q]){h(J[1q].3k!=1M){J[1q]=[J[1q]]}J[1q].M(l)}1k{J[1q]=l}}}f J}));f 4Q?5N:26.3L(5N)})};O.1t={4I:(c(V,4Q){f O.7H(O.3K(V),4Q)}),3K:(c(V){f $A($(V).4a("*")).2A([],(c(1p,3j){h(O.m.49[3j.14.1Y()]){1p.M(m.B(3j))}f 1p}))}),a6:(c(V,4O,v){V=$(V);k 4P=V.4a("2W");h(!4O&&!v){f $A(4P).1F(m.B)}15(k i=0,5M=[],o=4P.o;i<o;i++){k 2W=4P[i];h((4O&&2W.2L!=4O)||(v&&2W.v!=v)){4N}5M.M(m.B(2W))}f 5M}),7C:(c(V){V=$(V);V.3K().U((c(6){6.7A();6.2X="1j"}));f V}),7B:(c(V){V=$(V);V.3K().U((c(6){6.2X=""}));f V}),7F:(c(V){f $(V).3K().7G((c(6){f 6.2L!="4M"&&!6.2X&&["2W","25","5J"].1o(6.14.1Y())}))}),a5:(c(V){V=$(V);V.7F().7D();f V})};D.B(O,O.1t);O.m={5L:(c(6){$(6).5L();f 6}),25:(c(6){$(6).25();f 6})};O.m.1t={4I:(c(6){6=$(6);h(!6.2X&&6.v){k l=6.1R();h(l!=1b){k Q={};Q[6.v]=l;f 26.3L(Q)}}f""}),1R:(c(6){6=$(6);k 1E=6.14.1Y();f O.m.49[1E](6)}),7E:(c(6){$(6).l="";f 6}),a4:(c(6){f $(6).l!=""}),7D:(c(6){6=$(6);6.5L();h(6.25&&(6.14.1Y()!="2W"||!["5F","5K","a3"].1o(6.2L))){6.25()}f 6}),7C:(c(6){6=$(6);6.2X=1j;f 6}),7B:(c(6){6=$(6);6.7A();6.2X=1d;f 6})};D.B(O.m,O.m.1t);k a2=O.m;k $F=O.m.1R;O.m.49={2W:(c(6){48(6.2L.1Y()){1v"7s":1v"7r":f O.m.49.7z(6);47:f O.m.49.5J(6)}}),7z:(c(6){f 6.7y?6.l:12}),5J:(c(6){f 6.l}),25:(c(6){f 7[6.2L=="25-a1"?"7x":"7w"](6)}),7x:(c(6){k G=6.a0;f G>=0?7.5I(6.C[G]):12}),7w:(c(6){k 1a,o=6.o;h(!o){f 12}15(k i=0,1a=[];i<o;i++){k 3i=6.C[i];h(3i.9Z){1a.M(7.5I(3i))}}f 1a}),5I:(c(3i){f m.B(3i).4L("l")?3i.l:3i.2V})};1K.4J=(c(){});1K.4J.I={24:(c(6,2k,1L){7.2k=2k;7.6=$(6);7.1L=1L;7.2U=7.1R();7.3g()}),3g:(c(){7v(7.3h.1A(7),7.2k*5H)}),3h:(c(){k l=7.1R();k 7u=("2z"==1w 7.2U&&"2z"==1w l?7.2U!=l:2j(7.2U)!=2j(l));h(7u){7.1L(7.6,l);7.2U=l}})};O.m.4K=1z.1y();O.m.4K.I=D.B(P 1K.4J(),{1R:(c(){f O.m.1R(7.6)})});O.4K=1z.1y();O.4K.I=D.B(P 1K.4J(),{1R:(c(){f O.4I(7.6)})});1K.2T=(c(){});1K.2T.I={24:(c(6,1L){7.6=$(6);7.1L=1L;7.2U=7.1R();h(7.6.14.1Y()=="V"){7.7t()}1k{7.3g(7.6)}}),5G:(c(){k l=7.1R();h(7.2U!=l){7.1L(7.6,l);7.2U=l}}),7t:(c(){O.3K(7.6).U(7.3g.1A(7))}),3g:(c(6){h(6.2L){48(6.2L.1Y()){1v"7s":1v"7r":1s.3H(6,"9Y",7.5G.1A(7));1i;47:1s.3H(6,"9X",7.5G.1A(7));1i}}})};O.m.2T=1z.1y();O.m.2T.I=D.B(P 1K.2T(),{1R:(c(){f O.m.1R(7.6)})});O.2T=1z.1y();O.2T.I=D.B(P 1K.2T(),{1R:(c(){f O.4I(7.6)})});h(!1r.1s){k 1s=P D()}D.B(1s,{9W:8,9V:9,9U:13,9T:27,9S:37,9R:38,9Q:39,9P:40,9O:46,9N:36,9M:35,9L:33,9K:34,6:(c(19){f 19.2v||19.9J}),9I:(c(19){f(((19.7q)&&(19.7q==1))||((19.5F)&&(19.5F==1)))}),9H:(c(19){f 19.9G||(19.9F+(N.4F.3F||N.1I.3F))}),9E:(c(19){f 19.9D||(19.9C+(N.4F.3G||N.1I.3G))}),5E:(c(19){h(19.7p){19.7p();19.9B()}1k{19.4H=1d;19.9A=1j}}),3J:(c(19,14){k 6=1s.6(19);1u(6.2i&&(!6.14||(6.14.23()!=14.23()))){6=6.2i}f 6}),2x:1d,7n:(c(6,v,2w,22){h(!7.2x){7.2x=[]}h(6.7o){7.2x.M([6,v,2w,22]);6.7o(v,2w,22)}1k{h(6.5D){7.2x.M([6,v,2w,22]);6.5D("3I"+v,2w)}}}),7j:(c(){h(!1s.2x){f}15(k i=0,o=1s.2x.o;i<o;i++){1s.4G.2y(7,1s.2x[i]);1s.2x[i][0]=12}1s.2x=1d}),3H:(c(6,v,2w,22){6=$(6);22=22||1d;h(v=="7m"&&(21.5B.E(/3A|3z|3y/)||6.5D)){v="7l"}1s.7n(6,v,2w,22)}),4G:(c(6,v,2w,22){6=$(6);22=22||1d;h(v=="7m"&&(21.5B.E(/3A|3z|3y/)||6.5C)){v="7l"}h(6.7k){6.7k(v,2w,22)}1k{h(6.5C){1Q{6.5C("3I"+v,2w)}1X(e){}}}})});h(21.5B.E(/\\9z\\b/)){1s.3H(1r,"9y",1s.7j,1d)}k 2H={7i:1d,5v:(c(){7.7f=1r.9x||N.4F.3F||N.1I.3F||0;7.7e=1r.9w||N.4F.3G||N.1I.3G||0}),7g:(c(6){k 1B=0,1C=0;3x{1B+=6.3G||0;1C+=6.3F||0;6=6.2i}1u(6);f[1C,1B]}),42:(c(6){k 1B=0,1C=0;3x{1B+=6.3f||0;1C+=6.3e||0;6=6.2s}1u(6);f[1C,1B]}),78:(c(6){k 1B=0,1C=0;3x{1B+=6.3f||0;1C+=6.3e||0;6=6.2s;h(6){h(6.14=="7d"){1i}k p=m.1H(6,"1D");h(p=="4C"||p=="3d"){1i}}}1u(6);f[1C,1B]}),2s:(c(6){h(6.2s){f 6.2s}h(6==N.1I){f 6}1u((6=6.2i)&&6!=N.1I){h(m.1H(6,"1D")!="5A"){f 6}}f N.1I}),9v:(c(6,x,y){h(7.7i){f 7.7h(6,x,y)}7.44=x;7.45=y;7.1W=7.42(6);f(y>=7.1W[1]&&y<7.1W[1]+6.3C&&x>=7.1W[0]&&x<7.1W[0]+6.3D)}),7h:(c(6,x,y){k 5z=7.7g(6);7.44=x+5z[0]-7.7f;7.45=y+5z[1]-7.7e;7.1W=7.42(6);f(7.45>=7.1W[1]&&7.45<7.1W[1]+6.3C&&7.44>=7.1W[0]&&7.44<7.1W[0]+6.3D)}),9u:(c(4E,6){h(!4E){f 0}h(4E=="9t"){f((7.1W[1]+6.3C)-7.45)/ 6.3C;}h (4E == "9s") {f ((7.1W[0] + 6.3D) - 7.44) /6.3D}}),5x:(c(5y){k 1B=0,1C=0;k 6=5y;3x{1B+=6.3f||0;1C+=6.3e||0;h(6.2s==N.1I){h(m.1H(6,"1D")=="3d"){1i}}}1u(6=6.2s);6=5y;3x{h(!1r.2K||6.14=="7d"){1B-=6.3G||0;1C-=6.3F||0}}1u(6=6.2i);f[1C,1B]}),43:(c(1x,2v){k C=D.B({7c:1j,7b:1j,7a:1j,79:1j,3f:0,3e:0},T[2]||{});1x=$(1x);k p=2H.5x(1x);2v=$(2v);k 3E=[0,0];k 4D=12;h(m.1H(2v,"1D")=="3d"){4D=2H.2s(2v);3E=2H.5x(4D)}h(4D==N.1I){3E[0]-=N.1I.3e;3E[1]-=N.1I.3f}h(C.7c){2v.u.1J=(p[0]-3E[0]+C.3e)+"2h"}h(C.7b){2v.u.1V=(p[1]-3E[1]+C.3f)+"2h"}h(C.7a){2v.u.2t=1x.3D+"2h"}h(C.79){2v.u.2u=1x.3C+"2h"}}),9r:(c(6){6=$(6);h(6.u.1D=="3d"){f}2H.5v();k 5w=2H.78(6);k 1V=5w[1];k 1J=5w[0];k 2t=6.77;k 2u=6.76;6.74=1J-3B(6.u.1J||0);6.75=1V-3B(6.u.1V||0);6.72=6.u.2t;6.73=6.u.2u;6.u.1D="3d";6.u.1V=1V+"2h";6.u.1J=1J+"2h";6.u.2t=2t+"2h";6.u.2u=2u+"2h"}),9q:(c(6){6=$(6);h(6.u.1D=="4C"){f}2H.5v();6.u.1D="4C";k 1V=3B(6.u.1V||0)-(6.75||0);k 1J=3B(6.u.1J||0)-(6.74||0);6.u.1V=1V+"2h";6.u.1J=1J+"2h";6.u.2u=6.73;6.u.2t=6.72})};h(/3A|3z|3y/.2J(21.2I)){2H.42=(c(6){k 1B=0,1C=0;3x{1B+=6.3f||0;1C+=6.3e||0;h(6.2s==N.1I){h(m.1H(6,"1D")=="3d"){1i}}6=6.2s}1u(6);f[1C,1B]})}m.71();',62,770,'||||||element|this|||||function|||return||if|||var|value|Element||length||||||style|name||||||extend|options|Object|match||index||prototype|result||iterator|push|document|Form|new|pair|||arguments|each|form|object||params|||transport|null||tagName|for||results|expression|event|values|undefined|Ajax|false|attribute|inspect|className|div|break|true|else|html|Prototype|property|include|elements|key|window|Event|Methods|while|case|typeof|source|create|Class|bind|valueT|valueL|position|method|map|node|getStyle|body|left|Abstract|callback|Array|clause|Insertion|args|try|getValue|concat|url|array|top|offset|catch|toLowerCase|Selector|Enumerable|navigator|useCapture|toUpperCase|initialize|select|Hash||selector|in|display|_attributeTranslations|container|onComplete|parts|pattern|replacement|px|parentNode|String|frequency|expr|toString|conditions|range|methods|replace|gsub|offsetParent|width|height|target|observer|observers|apply|string|inject|throw|stripScripts|els|hash|toArray|slice|Position|userAgent|test|opera|type|join|_each|childNodes|innerHTML|start|json|responder|EventObserver|lastValue|text|input|disabled|number|fragment||fragments|table||||||||tbody|request|success|absolute|offsetLeft|offsetTop|registerCallback|onTimerEvent|opt|child|constructor|scope|split|classNames|content|setTimeout|cache|filter|decay|emptyFunction|readyState|headers|end|do|KHTML|Safari|Konqueror|parseFloat|offsetHeight|offsetWidth|delta|scrollLeft|scrollTop|observe|on|findElement|getElements|toQueryString|id|insertContent|initializeRange|evalScripts|destination|_overflow|pos|timer|Responders|extras|exclusive|iterable|memo|template||truncation|cumulativeOffset|clone|xcomp|ycomp||default|switch|Serializers|getElementsByTagName|first|attributes|strip|remove|createElement|adjacency|copy|_flag|nodeValue|auto|alpha|parentElement|response|receiver|Request|dispatchException|state|script|post|_|parameters|responders|pluck|keys|fillWith|Template|count|relative|parent|mode|documentElement|stopObserving|returnValue|serialize|TimedObserver|Observer|hasAttribute|hidden|continue|typeName|inputs|getHash|operator|hasClassName|abort|ClassNames|nextSibling|firstChild|reverse|klass|onlyIfAbsent|tag|depth|tr|names|overflow|right|none|cssFloat|styleFloat|opacity|gi|ancestor|nodeType|RegExp|elementClassName|nextSiblings|recursivelyCollect|evaluate|responseText|setOptions|Base|dispatch|status|asynchronous|ObjectRange|encodeURIComponent|obj|charAt|ScriptFragment|currentlyExecuting|__method|prepare|offsets|page|forElement|offsetcache|static|appVersion|detachEvent|attachEvent|stop|button|onElementEvent|1000|optionValue|textarea|reset|focus|matchingInputs|data|findChildElements|flatten|Function|splitValueBy|readAttribute|rest|modifier|9_|z0|classNameToRemove|without|set|classNameToAdd|add|insertBefore|appendChild|selectNodeContents|TR|TBODY|_nativeExtensions|HTMLElement|Simulated|findOrStore|removeChild|update|all|_getAttr|visible|_madePositioned|visibility|getDimensions|camelize|MSIE|css|defaultView|getElementsByClassName|shift|previousSiblings|last|children|XPath|BrowserFeatures|query|Updater|updater|failure|getTransport|exception|eval|getHeader|Complete|application|_complete|respondToReadyState|encoding|Version|onStateChange|toQueryParams|activeRequestCount|succ|mergedHash|_reverse|criteria|falses|trues|found|slices|interpret|before|prepareReplacement|escapedString|substring|camelized|len|digits|addMethods|_originalWidth|_originalHeight|_originalLeft|_originalTop|clientHeight|clientWidth|positionedOffset|setHeight|setWidth|setTop|setLeft|BODY|deltaY|deltaX|realOffset|withinIncludingScrolloffsets|includeScrollOffsets|unloadCache|removeEventListener|keydown|keypress|_observeAndCache|addEventListener|preventDefault|which|radio|checkbox|registerFormCallbacks|changed|setInterval|selectMany|selectOne|checked|inputSelector|blur|enable|disable|activate|clear|findFirstElement|find|serializeElements|findElements|expressions|matchElements|childOf|buildMatchExpression|compileMatcher|delimiter|wildcard|empty|message|parseExpression|After|collapse|Bottom|Top|Before|contentFromAnonymousTable|createContextualFragment|createRange|ownerDocument|insertAdjacentHTML|toggle|Toggle|times|td|TD|THEAD|specified|getAttributeNode|readonly|title|getAttribute|descendantOf|bottom|originalHeight|originalWidth|originalVisibility|originalPosition|originalDisplay|float|100|Gecko|capitalize|currentStyle|getComputedStyle|scrollTo|nextNode|descendants|ancestors|class|outerHTML|show|hide|_extended|_getElementsByXPath|lastText|updateComplete|PeriodicalUpdater|insertion|updateContent|param|onException|evalResponse|evalJSON|onreadystatechange|javascript|Content|Events|requestHeaders|2005|overrideMimeType|contentType|xml|XMLHttpRequest|setRequestHeaders|onCreate|indexOf|get|register|XMLHTTP|ActiveXObject|these|Try|reduce|compact|arrayLength|size|inline|findAll|detect|collect|collections|eachSlice|stringValue|Pattern|useDoubleQuotes|decodeURIComponent|separator|stripTags|extractScripts|matchOne|scriptTag|matchAll|img|PeriodicalExecuter|lambda|relativize|absolutize|horizontal|vertical|overlap|within|pageYOffset|pageXOffset|unload|bMSIE|cancelBubble|stopPropagation|clientY|pageY|pointerY|clientX|pageX|pointerX|isLeftClick|srcElement|KEY_PAGEDOWN|KEY_PAGEUP|KEY_END|KEY_HOME|KEY_DELETE|KEY_DOWN|KEY_RIGHT|KEY_UP|KEY_LEFT|KEY_ESC|KEY_RETURN|KEY_TAB|KEY_BACKSPACE|change|click|selected|selectedIndex|one|Field|submit|present|focusFirstElement|getInputs|Unknown|error|Parse|setStartAfter|afterEnd|beforeEnd|afterBegin|setStartBefore|beforeBegin|HTMLSelectElement|HTMLTextAreaElement|HTMLInputElement|HTMLFormElement|__proto__|HTML|Select|TextArea|Input|multiple|src|href|cssText|longDesc|longdesc|readOnly|maxLength|maxlength|encType|enctype|tabIndex|tabindex|accessKey|accesskey|dateTime|datetime|vAlign|valign|rowSpan|rowspan|colSpan|colspan|undoClipping|makeClipping|undoPositioned|makePositioned|block|00001|999999|setStyle|cleanWhitespace|toggleClassName|removeClassName|addClassName|getWidth|getHeight|getElementsBySelector|next|previous|down|up|siblings|previousSibling|immediateDescendants|replaceChild|SELECT|TEXTAREA|INPUT|FORM|contains|snapshotItem|snapshotLength|ORDERED_NODE_SNAPSHOT_TYPE|XPathResult|getElementById|clearTimeout|JSON|getResponseHeader|ecma|java|Failure|Success|300|200|setRequestHeader|close|Connection|charset|Accept|With|Requested|send|postBody|open|_method|Interactive|Loaded|Loading|Uninitialized|UTF|urlencoded|www|unregister|Microsoft|Msxml2|delete|merge|call|uniq|from|entries|member|pop|zip|sort|sortBy|reject|partition|min|max|invoke|inGroupsOf|grep|any|parseQuery|dasherize|underscore|charCodeAt|fromCharCode|unescapeHTML|createTextNode|escapeHTML|im|truncate|scan|sub|callee|finally|clearInterval|toColorPart|Number|bindAsEventListener|RangeError|instanceof'.split('|'),0,{}))

// script.aculo.us builder.js v1.7.1_beta1, Mon Mar 12 14:40:50 +0100 2007

// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/

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'
  },
  // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
  //       due to a Firefox bug
  node: function(elementName) {
    elementName = elementName.toUpperCase();
    
    // try innerHTML approach
    var parentTag = this.NODEMAP[elementName] || 'div';
    var parentElement = document.createElement(parentTag);
    try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
      parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
    } catch(e) {}
    var element = parentElement.firstChild || null;
      
    // see if browser added wrapping tags
    if(element && (element.tagName.toUpperCase() != elementName))
      element = element.getElementsByTagName(elementName)[0];
    
    // fallback to createElement approach
    if(!element) element = document.createElement(elementName);
    
    // abort if nothing could be created
    if(!element) return;

    // attributes (or text)
    if(arguments[1])
      if(this._isStringOrNumber(arguments[1]) ||
        (arguments[1] instanceof Array) ||
        arguments[1].tagName) {
          this._children(element, arguments[1]);
        } else {
          var attrs = this._attributes(arguments[1]);
          if(attrs.length) {
            try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
              parentElement.innerHTML = "<" +elementName + " " +
                attrs + "></" + elementName + ">";
            } catch(e) {}
            element = parentElement.firstChild || null;
            // workaround firefox 1.0.X bug
            if(!element) {
              element = document.createElement(elementName);
              for(attr in arguments[1]) 
                element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
            }
            if(element.tagName.toUpperCase() != elementName)
              element = parentElement.getElementsByTagName(elementName)[0];
          }
        } 

    // text, or array of children
    if(arguments[2])
      this._children(element, arguments[2]);

     return element;
  },
  _text: function(text) {
     return document.createTextNode(text);
  },

  ATTR_MAP: {
    'className': 'class',
    'htmlFor': 'for'
  },

  _attributes: function(attributes) {
    var attrs = [];
    for(attribute in attributes)
      attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) +
          '="' + attributes[attribute].toString().escapeHTML() + '"');
    return attrs.join(" ");
  },
  _children: function(element, children) {
    if(children.tagName) {
      element.appendChild(children);
      return;
    }
    if(typeof children=='object') { // array can hold nodes and text
      children.flatten().each( function(e) {
        if(typeof e=='object')
          element.appendChild(e)
        else
          if(Builder._isStringOrNumber(e))
            element.appendChild(Builder._text(e));
      });
    } else
      if(Builder._isStringOrNumber(children))
        element.appendChild(Builder._text(children));
  },
  _isStringOrNumber: function(param) {
    return(typeof param=='string' || typeof param=='number');
  },
  build: function(html) {
    var element = this.node('div');
    $(element).update(html.strip());
    return element.down();
  },
  dump: function(scope) { 
    if(typeof scope != 'object' && typeof scope != 'function') scope = window; //global scope 
  
    var tags = ("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+/);
  
    tags.each( function(tag){ 
      scope[tag] = function() { 
        return Builder.node.apply(Builder, [tag].concat($A(arguments)));  
      } 
    });
  }
}


var Prado =
{
	Version: '3.1',

	/**
	 * Returns browser information. Example
	 * <code>
	 * var browser = Prado.Browser();
	 * alert(browser.ie); //should ouput true if IE, false otherwise
	 * </code>
	 * @param ${parameter}
	 * @return ${return}
	 */
	Browser : function()
	{
		var info = { Version : "1.0" };
		var is_major = parseInt( navigator.appVersion );
		info.nver = is_major;
		info.ver = navigator.appVersion;
		info.agent = navigator.userAgent;
		info.dom = document.getElementById ? 1 : 0;
		info.opera = window.opera ? 1 : 0;
		info.ie5 = ( info.ver.indexOf( "MSIE 5" ) > -1 && info.dom && !info.opera ) ? 1 : 0;
		info.ie6 = ( info.ver.indexOf( "MSIE 6" ) > -1 && info.dom && !info.opera ) ? 1 : 0;
		info.ie4 = ( document.all && !info.dom && !info.opera ) ? 1 : 0;
		info.ie = info.ie4 || info.ie5 || info.ie6;
		info.mac = info.agent.indexOf( "Mac" ) > -1;
		info.ns6 = ( info.dom && parseInt( info.ver ) >= 5 ) ? 1 : 0;
		info.ie3 = ( info.ver.indexOf( "MSIE" ) && ( is_major < 4 ) );
		info.hotjava = ( info.agent.toLowerCase().indexOf( 'hotjava' ) != -1 ) ? 1 : 0;
		info.ns4 = ( document.layers && !info.dom && !info.hotjava ) ? 1 : 0;
		info.bw = ( info.ie6 || info.ie5 || info.ie4 || info.ns4 || info.ns6 || info.opera );
		info.ver3 = ( info.hotjava || info.ie3 );
		info.opera7 = ( ( info.agent.toLowerCase().indexOf( 'opera 7' ) > -1 ) || ( info.agent.toLowerCase().indexOf( 'opera/7' ) > -1 ) );
		info.operaOld = info.opera && !info.opera7;
		return info;
	},

	ImportCss : function(doc, css_file)
	{
		if (Prado.Browser().ie)
			var styleSheet = doc.createStyleSheet(css_file);
		else
		{
			var elm = doc.createElement("link");

			elm.rel = "stylesheet";
			elm.href = css_file;

			if (headArr = doc.getElementsByTagName("head"))
				headArr[0].appendChild(elm);
		}
	}
};


/**
 * Similar to bindAsEventLister, but takes additional arguments.
 */
Function.prototype.bindEvent = function()
{
	var __method = this, args = $A(arguments), object = args.shift();
	return function(event)
	{
		return __method.apply(object, [event || window.event].concat(args));
	}
};

/**
 * Creates a new function by copying function definition from
 * the <tt>base</tt> and optional <tt>definition</tt>.
 * @param function a base function to copy from.
 * @param array additional definition
 * @param function return a new function with definition from both
 * <tt>base</tt> and <tt>definition</tt>.
 */
Class.extend = function(base, definition)
{
		var component = Class.create();
		Object.extend(component.prototype, base.prototype);
		if(definition)
			Object.extend(component.prototype, definition);
		return component;
};

/*
	Base, version 1.0.2
	Copyright 2006, Dean Edwards
	License: http://creativecommons.org/licenses/LGPL/2.1/
*/

var Base = function() {
	if (arguments.length) {
		if (this == window) { // cast an object to this class
			Base.prototype.extend.call(arguments[0], arguments.callee.prototype);
		} else {
			this.extend(arguments[0]);
		}
	}
};

Base.version = "1.0.2";

Base.prototype = {
	extend: function(source, value) {
		var extend = Base.prototype.extend;
		if (arguments.length == 2) {
			var ancestor = this[source];
			// overriding?
			if ((ancestor instanceof Function) && (value instanceof Function) &&
				ancestor.valueOf() != value.valueOf() && /\bbase\b/.test(value)) {
				var method = value;
			//	var _prototype = this.constructor.prototype;
			//	var fromPrototype = !Base._prototyping && _prototype[source] == ancestor;
				value = function() {
					var previous = this.base;
				//	this.base = fromPrototype ? _prototype[source] : ancestor;
					this.base = ancestor;
					var returnValue = method.apply(this, arguments);
					this.base = previous;
					return returnValue;
				};
				// point to the underlying method
				value.valueOf = function() {
					return method;
				};
				value.toString = function() {
					return String(method);
				};
			}
			return this[source] = value;
		} else if (source) {
			var _prototype = {toSource: null};
			// do the "toString" and other methods manually
			var _protected = ["toString", "valueOf"];
			// if we are prototyping then include the constructor
			if (Base._prototyping) _protected[2] = "constructor";
			for (var i = 0; (name = _protected[i]); i++) {
				if (source[name] != _prototype[name]) {
					extend.call(this, name, source[name]);
				}
			}
			// copy each of the source object's properties to this object
			for (var name in source) {
				if (!_prototype[name]) {
					extend.call(this, name, source[name]);
				}
			}
		}
		return this;
	},

	base: function() {
		// call this method from any other method to invoke that method's ancestor
	}
};

Base.extend = function(_instance, _static) {
	var extend = Base.prototype.extend;
	if (!_instance) _instance = {};
	// build the prototype
	Base._prototyping = true;
	var _prototype = new this;
	extend.call(_prototype, _instance);
	var constructor = _prototype.constructor;
	_prototype.constructor = this;
	delete Base._prototyping;
	// create the wrapper for the constructor function
	var klass = function() {
		if (!Base._prototyping) constructor.apply(this, arguments);
		this.constructor = klass;
	};
	klass.prototype = _prototype;
	// build the class interface
	klass.extend = this.extend;
	klass.implement = this.implement;
	klass.toString = function() {
		return String(constructor);
	};
	extend.call(klass, _static);
	// single instance
	var object = constructor ? klass : _prototype;
	// class initialisation
	if (object.init instanceof Function) object.init();
	return object;
};

Base.implement = function(_interface) {
	if (_interface instanceof Function) _interface = _interface.prototype;
	this.prototype.extend(_interface);
};

/**
 * Performs a post-back using javascript
 *
 */
Prado.PostBack = function(event,options)
{
	var form = $(options['FormID']);
	var canSubmit = true;

	if(options['CausesValidation'] && typeof(Prado.Validation) != "undefined")
	{
		if(!Prado.Validation.validate(options['FormID'], options['ValidationGroup'], $(options['ID'])))
			return Event.stop(event);
	}

	if(options['PostBackUrl'] && options['PostBackUrl'].length > 0)
		form.action = options['PostBackUrl'];

	if(options['TrackFocus'])
	{
		var lastFocus = $('PRADO_LASTFOCUS');
		if(lastFocus)
		{
			var active = document.activeElement; //where did this come from
			if(active)
				lastFocus.value = active.id;
			else
				lastFocus.value = options['EventTarget'];
		}
	}

	$('PRADO_POSTBACK_TARGET').value = options['EventTarget'];
	$('PRADO_POSTBACK_PARAMETER').value = options['EventParameter'];
	/**
	 * Since google toolbar prevents browser default action,
	 * we will always disable default client-side browser action
	 */
	/*if(options['StopEvent']) */
		Event.stop(event);
	Event.fireEvent(form,"submit");
};

Prado.Element =
{
	/**
	 * Set the value of a particular element.
	 * @param string element id
	 * @param string new element value.
	 */
	setValue : function(element, value)
	{
		var el = $(element);
		if(el && typeof(el.value) != "undefined")
			el.value = value;
	},

	select : function(element, method, value, total)
	{
		var el = $(element);
		if(!el) return;
		var selection = Prado.Element.Selection;
		if(typeof(selection[method]) == "function")
		{
			control = selection.isSelectable(el) ? [el] : selection.getListElements(element,total);
			selection[method](control, value);
		}
	},

	click : function(element)
	{
		var el = $(element);
		if(el)
			Event.fireEvent(el,'click');
	},

	isDisabled : function(element)
	{
		if(!element.attributes['disabled']) //FF
			return false;
		var value = element.attributes['disabled'].nodeValue;
		if(typeof(value)=="string")
			return value.toLowerCase() == "disabled";
		else
			return value == true;
	},

	setAttribute : function(element, attribute, value)
	{
		var el = $(element);
		if(!el) return;
		if((attribute == "disabled" || attribute == "multiple") && value==false)
			el.removeAttribute(attribute);
		else if(attribute.match(/^on/i)) //event methods
		{
			try
			{
				eval("(func = function(event){"+value+"})");
				el[attribute] = func;
			}
			catch(e)
			{
				throw "Error in evaluating '"+value+"' for attribute "+attribute+" for element "+element.id;
			}
		}
		else
			el.setAttribute(attribute, value);
	},

	setOptions : function(element, options)
	{
		var el = $(element);
		if(!el) return;
		var previousGroup = null;
		var optGroup=null;
		if(el && el.tagName.toLowerCase() == "select")
		{
			while(el.childNodes.length > 0)
				el.removeChild(el.lastChild);

			var optDom = Prado.Element.createOptions(options);
			for(var i = 0; i < optDom.length; i++)
				el.appendChild(optDom[i]);
		}
	},

	/**
	 * Create opt-group options from an array of options[0]=text, options[1]=value, options[2]=group
	 */
	createOptions : function(options)
	{
		var previousGroup = null;
		var optgroup=null;
		var result = [];
		for(var i = 0; i<options.length; i++)
		{
			var option = options[i];
			if(option.length > 2)
			{
				var group = option[2];
				if(group!=previousGroup)
				{
					if(previousGroup!=null && optgroup!=null)
					{
						result.push(optgroup);
						previousGroup=null;
						optgroup=null;
					}
					optgroup = document.createElement('optgroup');
					optgroup.label = group;
					previousGroup = group;
				}
			}
			var opt = document.createElement('option');
			opt.text = option[0];
			opt.innerText = option[0];
			opt.value = option[1];
			if(optgroup!=null)
				optgroup.appendChild(opt);
			else
				result.push(opt);
		}
		if(optgroup!=null)
			result.push(optgroup);
		return result;
	},

	/**
	 * A delayed focus on a particular element
	 * @param {element} element to apply focus()
	 */
	focus : function(element)
	{
		var obj = $(element);
		if(typeof(obj) != "undefined" && typeof(obj.focus) != "undefined")
			setTimeout(function(){ obj.focus(); }, 100);
		return false;
	},

	replace : function(element, method, content, boundary)
	{
		if(boundary)
		{
			result = Prado.Element.extractContent(this.transport.responseText, boundary);
			if(result != null)
				content = result;
		}
		if(typeof(element) == "string")
		{
			if($(element))
				method.toFunction().apply(this,[element,""+content]);
		}
		else
		{
			method.toFunction().apply(this,[""+content]);
		}
	},

	extractContent : function(text, boundary)
	{
		var tagStart = '<!--'+boundary+'-->';
		var tagEnd = '<!--//'+boundary+'-->';
		var start = text.indexOf(tagStart);
		if(start > -1)
		{
			start += tagStart.length;
			var end = text.indexOf(tagEnd,start);
			if(end > -1)
				return text.substring(start,end);
		}
		return null;
		/*var f = RegExp('(?:<!--'+boundary+'-->)((?:.|\n|\r)+?)(?:<!--//'+boundary+'-->)',"m");
		var result = text.match(f);
		if(result && result.length >= 2)
			return result[1];
		else
			return null;*/
	},

	evaluateScript : function(content)
	{
		content.evalScripts();
	}
};

Prado.Element.Selection =
{
	isSelectable : function(el)
	{
		if(el && el.type)
		{
			switch(el.type.toLowerCase())
			{
				case 'checkbox':
				case 'radio':
				case 'select':
				case 'select-multiple':
				case 'select-one':
				return true;
			}
		}
		return false;
	},

	inputValue : function(el, value)
	{
		switch(el.type.toLowerCase())
		{
			case 'checkbox':
			case 'radio':
			return el.checked = value;
		}
	},

	selectValue : function(elements, value)
	{
		elements.each(function(el)
		{
			$A(el.options).each(function(option)
			{
				if(typeof(value) == "boolean")
					options.selected = value;
				else if(option.value == value)
					option.selected = true;
			});
		})
	},

	selectValues : function(elements, values)
	{
		selection = this;
		values.each(function(value)
		{
			selection.selectValue(elements,value);
		})
	},

	selectIndex : function(elements, index)
	{
		elements.each(function(el)
		{
			if(el.type.toLowerCase() == 'select-one')
				el.selectedIndex = index;
			else
			{
				for(var i = 0; i<el.length; i++)
				{
					if(i == index)
						el.options[i].selected = true;
				}
			}
		})
	},

	selectAll : function(elements)
	{
		elements.each(function(el)
		{
			if(el.type.toLowerCase() != 'select-one')
			{
				$A(el.options).each(function(option)
				{
					option.selected = true;
				})
			}
		})
	},

	selectInvert : function(elements)
	{
		elements.each(function(el)
		{
			if(el.type.toLowerCase() != 'select-one')
			{
				$A(el.options).each(function(option)
				{
					option.selected = !options.selected;
				})
			}
		})
	},

	selectIndices : function(elements, indices)
	{
		selection = this;
		indices.each(function(index)
		{
			selection.selectIndex(elements,index);
		})
	},

	selectClear : function(elements)
	{
		elements.each(function(el)
		{
			el.selectedIndex = -1;
		})
	},

	getListElements : function(element, total)
	{
		elements = new Array();
		for(i = 0; i < total; i++)
		{
			el = $(element+"_c"+i);
			if(el)
				elements.push(el);
		}
		return elements;
	},

	checkValue : function(elements, value)
	{
		elements.each(function(el)
		{
			if(typeof(value) == "boolean")
				el.checked = value;
			else if(el.value == value)
				el.checked = true;
		});
	},

	checkValues : function(elements, values)
	{
		selection = this;
		values.each(function(value)
		{
			selection.checkValue(elements, value);
		})
	},

	checkIndex : function(elements, index)
	{
		for(var i = 0; i<elements.length; i++)
		{
			if(i == index)
				elements[i].checked = true;
		}
	},

	checkIndices : function(elements, indices)
	{
		selection = this;
		indices.each(function(index)
		{
			selection.checkIndex(elements, index);
		})
	},

	checkClear : function(elements)
	{
		elements.each(function(el)
		{
			el.checked = false;
		});
	},

	checkAll : function(elements)
	{
		elements.each(function(el)
		{
			el.checked = true;
		})
	},

	checkInvert : function(elements)
	{
		elements.each(function(el)
		{
			el.checked != el.checked;
		})
	}
};


Prado.Element.Insert =
{
	append: function(element, content)
	{
		new Insertion.Bottom(element, content);
	},

	prepend: function(element, content)
	{
		new Insertion.Top(element, content);
	},

	after: function(element, content)
	{
		new Insertion.After(element, content);
	},

	before: function(element, content)
	{
		new Insertion.Before(element, content);
	}
};


/**
 * Export scripaculous builder utilities as window[functions]
 */
Object.extend(Builder,
{
	exportTags:function()
	{
		var tags=["BUTTON","TT","PRE","H1","H2","H3","BR","CANVAS","HR","LABEL","TEXTAREA","FORM","STRONG","SELECT","OPTION","OPTGROUP","LEGEND","FIELDSET","P","UL","OL","LI","TD","TR","THEAD","TBODY","TFOOT","TABLE","TH","INPUT","SPAN","A","DIV","IMG", "CAPTION"];
		tags.each(function(tag)
		{
			window[tag]=function()
			{
				var args=$A(arguments);
				if(args.length==0)
					return Builder.node(tag,null);
				if(args.length==1)
					return Builder.node(tag,args[0]);
				if(args.length>1)
					return Builder.node(tag,args.shift(),args);

			};
		});
	}
});

Builder.exportTags();

/**
 * @class String extensions
 */
Object.extend(String.prototype, {
	/**
	 * @param {String} "left" to pad the string on the left, "right" to pad right.
	 * @param {Number} minimum string length.
	 * @param {String} character(s) to pad
	 * @return {String} padded character(s) on the left or right to satisfy minimum string length
	 */

	pad : function(side, len, chr) {
		if (!chr) chr = ' ';
		var s = this;
		var left = side.toLowerCase()=='left';
		while (s.length<len) s = left? chr + s : s + chr;
		return s;
	},

	/**
	 * @param {Number} minimum string length.
	 * @param {String} character(s) to pad
	 * @return {String} padded character(s) on the left to satisfy minimum string length
	 */
	padLeft : function(len, chr) {
		return this.pad('left',len,chr);
	},

	/**
	 * @param {Number} minimum string length.
	 * @param {String} character(s) to pad
	 * @return {String} padded character(s) on the right to satisfy minimum string length
	 */
	padRight : function(len, chr) {
		return this.pad('right',len,chr);
	},

	/**
	 * @param {Number} minimum string length.
	 * @return {String} append zeros to the left to satisfy minimum string length.
	 */
	zerofill : function(len) {
		return this.padLeft(len,'0');
	},

	/**
	 * @return {String} removed white spaces from both ends.
	 */
	trim : function() {
		return this.replace(/^\s+|\s+$/g,'');
	},

	/**
	 * @return {String} removed white spaces from the left end.
	 */
	trimLeft : function() {
		return this.replace(/^\s+/,'');
	},

	/**
	 * @return {String} removed white spaces from the right end.
	 */
	trimRight : function() {
		return this.replace(/\s+$/,'');
	},

	/**
	 * Convert period separated function names into a function reference.
	 * e.g. "Prado.AJAX.Callback.Action.setValue".toFunction() will return
	 * the actual function Prado.AJAX.Callback.Action.setValue()
	 * @return {Function} the corresponding function represented by the string.
	 */
	toFunction : function()
	{
		var commands = this.split(/\./);
		var command = window;
		commands.each(function(action)
		{
			if(command[new String(action)])
				command=command[new String(action)];
		});
		if(typeof(command) == "function")
			return command;
		else
		{
			if(typeof Logger != "undefined")
				Logger.error("Missing function", this);

			throw new Error	("Missing function '"+this+"'");
		}
	},

	/**
	 * Convert a string into integer, returns null if not integer.
	 * @return {Number} null if string does not represent an integer.
	 */
	toInteger : function()
	{
		var exp = /^\s*[-\+]?\d+\s*$/;
		if (this.match(exp) == null)
			return null;
		var num = parseInt(this, 10);
		return (isNaN(num) ? null : num);
	},

	/**
	 * Convert a string into a double/float value. <b>Internationalization
	 * is not supported</b>
	 * @param {String} the decimal character
	 * @return {Double} null if string does not represent a float value
	 */
	toDouble : function(decimalchar)
	{
		if(this.length <= 0) return null;
		decimalchar = decimalchar || ".";
		var exp = new RegExp("^\\s*([-\\+])?(\\d+)?(\\" + decimalchar + "(\\d+))?\\s*$");
		var m = this.match(exp);

		if (m == null)
			return null;
		m[1] = m[1] || "";
		m[2] = m[2] || "0";
		m[4] = m[4] || "0";

		var cleanInput = m[1] + (m[2].length>0 ? m[2] : "0") + "." + m[4];
		var num = parseFloat(cleanInput);
		return (isNaN(num) ? null : num);
	},

	/**
	 * Convert strings that represent a currency value (e.g. a float with grouping
	 * characters) to float. E.g. "10,000.50" will become "10000.50". The number
	 * of dicimal digits, grouping and decimal characters can be specified.
	 * <i>The currency input format is <b>very</b> strict, null will be returned if
	 * the pattern does not match</i>.
	 * @param {String} the grouping character, default is ","
	 * @param {Number} number of decimal digits
	 * @param {String} the decimal character, default is "."
	 * @type {Double} the currency value as float.
	 */
	toCurrency : function(groupchar, digits, decimalchar)
	{
		groupchar = groupchar || ",";
		decimalchar = decimalchar || ".";
		digits = typeof(digits) == "undefined" ? 2 : digits;

		var exp = new RegExp("^\\s*([-\\+])?(((\\d+)\\" + groupchar + ")*)(\\d+)"
			+ ((digits > 0) ? "(\\" + decimalchar + "(\\d{1," + digits + "}))?" : "")
			+ "\\s*$");
		var m = this.match(exp);
		if (m == null)
			return null;
		var intermed = m[2] + m[5] ;
		var cleanInput = m[1] + intermed.replace(
				new RegExp("(\\" + groupchar + ")", "g"), "")
								+ ((digits > 0) ? "." + m[7] : "");
		var num = parseFloat(cleanInput);
		return (isNaN(num) ? null : num);
	},

	/**
	 * Converts the string to a date by finding values that matches the
	 * date format pattern.
	 * @param string date format pattern, e.g. MM-dd-yyyy
	 * @return {Date} the date extracted from the string
	 */
	toDate : function(format)
	{
		return Date.SimpleParse(this, format);
	}
});

/**
 * @class Event extensions.
 */
Object.extend(Event,
{
	/**
	 * Register a function to be executed when the page is loaded.
	 * Note that the page is only loaded if all resources (e.g. images)
	 * are loaded.
	 *
	 * Example: Show an alert box with message "Page Loaded!" when the
	 * page finished loading.
	 * <code>
	 * Event.OnLoad(function(){ alert("Page Loaded!"); });
	 * </code>
	 *
	 * @param {Function} function to execute when page is loaded.
	 */
	OnLoad : function (fn)
	{
		// opera onload is in document, not window
		var w = document.addEventListener &&
					!window.addEventListener ? document : window;
		Event.observe(w,'load',fn);
	},

	/**
	 * @param {Event} a keyboard event
	 * @return {Number} the Unicode character code generated by the key
	 * that was struck.
	 */
	keyCode : function(e)
	{
	   return e.keyCode != null ? e.keyCode : e.charCode
	},

	/**
	 * @param {String} event type or event name.
	 * @return {Boolean} true if event type is of HTMLEvent, false
	 * otherwise
	 */
	isHTMLEvent : function(type)
	{
		var events = ['abort', 'blur', 'change', 'error', 'focus',
					'load', 'reset', 'resize', 'scroll', 'select',
					'submit', 'unload'];
		return events.include(type);
	},

	/**
	 * @param {String} event type or event name
	 * @return {Boolean} true if event type is of MouseEvent,
	 * false otherwise
	 */
	isMouseEvent : function(type)
	{
		var events = ['click', 'mousedown', 'mousemove', 'mouseout',
					'mouseover', 'mouseup'];
		return events.include(type);
	},

	/**
	 * Dispatch the DOM event of a given <tt>type</tt> on a DOM
	 * <tt>element</tt>. Only HTMLEvent and MouseEvent can be
	 * dispatched, keyboard events or UIEvent can not be dispatch
	 * via javascript consistently.
	 * For the "submit" event the submit() method is called.
	 * @param {Object} element id string or a DOM element.
	 * @param {String} event type to dispatch.
	 */
	fireEvent : function(element,type)
	{
		element = $(element);
		if(type == "submit")
			return element.submit();
		if(document.createEvent)
        {
			if(Event.isHTMLEvent(type))
			{
				var event = document.createEvent('HTMLEvents');
	            event.initEvent(type, true, true);
			}
			else if(Event.isMouseEvent(type))
			{
				var event = document.createEvent('MouseEvents');
				if (event.initMouseEvent)
		        {
					event.initMouseEvent(type,true,true,
						document.defaultView, 1, 0, 0, 0, 0, false,
								false, false, false, 0, null);
		        }
		        else
		        {
		            // Safari
		            // TODO we should be initialising other mouse-event related attributes here
		            event.initEvent(type, true, true);
		        }
			}
            element.dispatchEvent(event);
        }
        else if(document.createEventObject)
        {
        	var evObj = document.createEventObject();
            element.fireEvent('on'+type, evObj);
        }
        else if(typeof(element['on'+type]) == "function")
            element['on'+type]();
	}
});



Object.extend(Date.prototype,
{
	SimpleFormat: function(format, data)
	{
		data = data || {};
		var bits = new Array();
		bits['d'] = this.getDate();
		bits['dd'] = String(this.getDate()).zerofill(2);

		bits['M'] = this.getMonth()+1;
		bits['MM'] = String(this.getMonth()+1).zerofill(2);
		if(data.AbbreviatedMonthNames)
			bits['MMM'] = data.AbbreviatedMonthNames[this.getMonth()];
		if(data.MonthNames)
			bits['MMMM'] = data.MonthNames[this.getMonth()];
		var yearStr = "" + this.getFullYear();
		yearStr = (yearStr.length == 2) ? '19' + yearStr: yearStr;
		bits['yyyy'] = yearStr;
		bits['yy'] = bits['yyyy'].toString().substr(2,2);

		// do some funky regexs to replace the format string
		// with the real values
		var frm = new String(format);
		for (var sect in bits)
		{
			var reg = new RegExp("\\b"+sect+"\\b" ,"g");
			frm = frm.replace(reg, bits[sect]);
		}
		return frm;
	},

	toISODate : function()
	{
		var y = this.getFullYear();
		var m = String(this.getMonth() + 1).zerofill(2);
		var d = String(this.getDate()).zerofill(2);
		return String(y) + String(m) + String(d);
	}
});

Object.extend(Date,
{
	SimpleParse: function(value, format)
	{
		val=String(value);
		format=String(format);

		if(val.length <= 0) return null;

		if(format.length <= 0) return new Date(value);

		var isInteger = function (val)
		{
			var digits="1234567890";
			for (var i=0; i < val.length; i++)
			{
				if (digits.indexOf(val.charAt(i))==-1) { return false; }
			}
			return true;
		};

		var getInt = function(str,i,minlength,maxlength)
		{
			for (var x=maxlength; x>=minlength; x--)
			{
				var token=str.substring(i,i+x);
				if (token.length < minlength) { return null; }
				if (isInteger(token)) { return token; }
			}
			return null;
		};

		var i_val=0;
		var i_format=0;
		var c="";
		var token="";
		var token2="";
		var x,y;
		var now=new Date();
		var year=now.getFullYear();
		var month=now.getMonth()+1;
		var date=1;

		while (i_format < format.length)
		{
			// Get next token from format string
			c=format.charAt(i_format);
			token="";
			while ((format.charAt(i_format)==c) && (i_format < format.length))
			{
				token += format.charAt(i_format++);
			}

			// Extract contents of value based on format token
			if (token=="yyyy" || token=="yy" || token=="y")
			{
				if (token=="yyyy") { x=4;y=4; }
				if (token=="yy")   { x=2;y=2; }
				if (token=="y")    { x=2;y=4; }
				year=getInt(val,i_val,x,y);
				if (year==null) { return null; }
				i_val += year.length;
				if (year.length==2)
				{
					if (year > 70) { year=1900+(year-0); }
					else { year=2000+(year-0); }
				}
			}

			else if (token=="MM"||token=="M")
			{
				month=getInt(val,i_val,token.length,2);
				if(month==null||(month<1)||(month>12)){return null;}
				i_val+=month.length;
			}
			else if (token=="dd"||token=="d")
			{
				date=getInt(val,i_val,token.length,2);
				if(date==null||(date<1)||(date>31)){return null;}
				i_val+=date.length;
			}
			else
			{
				if (val.substring(i_val,i_val+token.length)!=token) {return null;}
				else {i_val+=token.length;}
			}
		}

		// If there are any trailing characters left in the value, it doesn't match
		if (i_val != val.length) { return null; }

		// Is date valid for month?
		if (month==2)
		{
			// Check for leap year
			if ( ( (year%4==0)&&(year%100 != 0) ) || (year%400==0) ) { // leap year
				if (date > 29){ return null; }
			}
			else { if (date > 28) { return null; } }
		}

		if ((month==4)||(month==6)||(month==9)||(month==11))
		{
			if (date > 30) { return null; }
		}

		var newdate=new Date(year,month-1,date, 0, 0, 0);
		return newdate;
	}
});
Prado.WebUI = Class.create();

Prado.WebUI.PostBackControl = Class.create();

Prado.WebUI.PostBackControl.prototype =
{
	initialize : function(options)
	{
		this._elementOnClick = null, //capture the element's onclick function

		this.element = $(options.ID);
		if(this.element)
		{
			if(this.onInit)
				this.onInit(options);
		}
	},

	onInit : function(options)
	{
		if(typeof(this.element.onclick)=="function")
		{
			this._elementOnClick = this.element.onclick.bind(this.element);
			this.element.onclick = null;
		}
		Event.observe(this.element, "click", this.elementClicked.bindEvent(this,options));
	},

	elementClicked : function(event, options)
	{
		var src = Event.element(event);
		var doPostBack = true;
		var onclicked = null;

		if(this._elementOnClick)
		{
			var onclicked = this._elementOnClick(event);
			if(typeof(onclicked) == "boolean")
				doPostBack = onclicked;
		}
		if(doPostBack && !Prado.Element.isDisabled(src))
			this.onPostBack(event,options);
		if(typeof(onclicked) == "boolean" && !onclicked)
			Event.stop(event);
	},

	onPostBack : function(event, options)
	{
		Prado.PostBack(event,options);
	}
};

Prado.WebUI.TButton = Class.extend(Prado.WebUI.PostBackControl);
Prado.WebUI.TLinkButton = Class.extend(Prado.WebUI.PostBackControl);
Prado.WebUI.TCheckBox = Class.extend(Prado.WebUI.PostBackControl);
Prado.WebUI.TBulletedList = Class.extend(Prado.WebUI.PostBackControl);
Prado.WebUI.TImageMap = Class.extend(Prado.WebUI.PostBackControl);

/**
 * TImageButton client-side behaviour. With validation, Firefox needs
 * to capture the x,y point of the clicked image in hidden form fields.
 */
Prado.WebUI.TImageButton = Class.extend(Prado.WebUI.PostBackControl);
Object.extend(Prado.WebUI.TImageButton.prototype,
{
	/**
	 * Override parent onPostBack function, tried to add hidden forms
	 * inputs to capture x,y clicked point.
	 */
	onPostBack : function(event, options)
	{
		if(!this.hasXYInput)
		{
			this.addXYInput(event,options);
			this.hasXYInput = true;
		}
		Prado.PostBack(event, options);
	},

	/**
	 * Add hidden inputs to capture the x,y point clicked on the image.
	 * @param event DOM click event.
	 * @param array image button options.
	 */
	addXYInput : function(event,options)
	{
		imagePos = Position.cumulativeOffset(this.element);
		clickedPos = [event.clientX, event.clientY];
		x = clickedPos[0]-imagePos[0]+1;
		y = clickedPos[1]-imagePos[1]+1;
		x = x < 0 ? 0 : x;
		y = y < 0 ? 0 : y;
		id = options['EventTarget'];
		x_input = $(id+"_x");
		y_input = $(id+"_y");
		if(x_input)
		{
			x_input.value = x;
		}
		else
		{
			x_input = INPUT({type:'hidden',name:id+'_x','id':id+'_x',value:x});
			this.element.parentNode.appendChild(x_input);
		}
		if(y_input)
		{
			y_input.value = y;
		}
		else
		{
			y_input = INPUT({type:'hidden',name:id+'_y','id':id+'_y',value:y});
			this.element.parentNode.appendChild(y_input);
		}
	}
});


/**
 * Radio button, only initialize if not already checked.
 */
Prado.WebUI.TRadioButton = Class.extend(Prado.WebUI.PostBackControl);
Prado.WebUI.TRadioButton.prototype.onRadioButtonInitialize = Prado.WebUI.TRadioButton.prototype.initialize;
Object.extend(Prado.WebUI.TRadioButton.prototype,
{
	initialize : function(options)
	{
		this.element = $(options['ID']);
		if(this.element)
		{
			if(!this.element.checked)
				this.onRadioButtonInitialize(options);
		}
	}
});


Prado.WebUI.TTextBox = Class.extend(Prado.WebUI.PostBackControl,
{
	onInit : function(options)
	{
		this.options=options;
		if(options['TextMode'] != 'MultiLine')
			Event.observe(this.element, "keydown", this.handleReturnKey.bind(this));
		if(this.options['AutoPostBack']==true)
			Event.observe(this.element, "change", Prado.PostBack.bindEvent(this,options));
	},

	handleReturnKey : function(e)
	{
		 if(Event.keyCode(e) == Event.KEY_RETURN)
        {
			var target = Event.element(e);
			if(target)
			{
				if(this.options['AutoPostBack']==true)
				{
					Event.fireEvent(target, "change");
					Event.stop(e);
				}
				else
				{
					if(this.options['CausesValidation'] && typeof(Prado.Validation) != "undefined")
					{
						if(!Prado.Validation.validate(this.options['FormID'], this.options['ValidationGroup'], $(this.options['ID'])))
							return Event.stop(e);
					}
				}
			}
		}
	}
});

Prado.WebUI.TListControl = Class.extend(Prado.WebUI.PostBackControl,
{
	onInit : function(options)
	{
		Event.observe(this.element, "change", Prado.PostBack.bindEvent(this,options));
	}
});

Prado.WebUI.TListBox = Class.extend(Prado.WebUI.TListControl);
Prado.WebUI.TDropDownList = Class.extend(Prado.WebUI.TListControl);

Prado.WebUI.DefaultButton = Class.create();
Prado.WebUI.DefaultButton.prototype =
{
	initialize : function(options)
	{
		this.options = options;
		this._event = this.triggerEvent.bindEvent(this);
		Event.observe(options['Panel'], 'keydown', this._event);
	},

	triggerEvent : function(ev, target)
	{
		var enterPressed = Event.keyCode(ev) == Event.KEY_RETURN;
		var isTextArea = Event.element(ev).tagName.toLowerCase() == "textarea";
		if(enterPressed && !isTextArea)
		{
			var defaultButton = $(this.options['Target']);
			if(defaultButton)
			{
				this.triggered = true;
				$('PRADO_POSTBACK_TARGET').value = this.options.EventTarget;
				Event.fireEvent(defaultButton, this.options['Event']);
				Event.stop(ev);
			}
		}
	}
};

Prado.WebUI.TTextHighlighter=Class.create();
Prado.WebUI.TTextHighlighter.prototype=
{
	initialize:function(id)
	{
		if(!window.clipboardData) return;
		var options =
		{
			href : 'javascript:;/'+'/copy code to clipboard',
			onclick : 'Prado.WebUI.TTextHighlighter.copy(this)',
			onmouseover : 'Prado.WebUI.TTextHighlighter.hover(this)',
			onmouseout : 'Prado.WebUI.TTextHighlighter.out(this)'
		}
		var div = DIV({className:'copycode'}, A(options, 'Copy Code'));
		document.write(DIV(null,div).innerHTML);
	}
};

Object.extend(Prado.WebUI.TTextHighlighter,
{
	copy : function(obj)
	{
		var parent = obj.parentNode.parentNode.parentNode;
		var text = '';
		for(var i = 0; i < parent.childNodes.length; i++)
		{
			var node = parent.childNodes[i];
			if(node.innerText)
				text += node.innerText == 'Copy Code' ? '' : node.innerText;
			else
				text += node.nodeValue;
		}
		if(text.length > 0)
			window.clipboardData.setData("Text", text);
	},

	hover : function(obj)
	{
		obj.parentNode.className = "copycode copycode_hover";
	},

	out : function(obj)
	{
		obj.parentNode.className = "copycode";
	}
});


Prado.WebUI.TCheckBoxList = Base.extend(
{
	constructor : function(options)
	{
		for(var i = 0; i<options.ItemCount; i++)
		{
			var checkBoxOptions = Object.extend(
			{
				ID : options.ListID+"_c"+i,
				EventTarget : options.ListName+"$c"+i
			}, options);
			new Prado.WebUI.TCheckBox(checkBoxOptions);
		}
	}
});

Prado.WebUI.TRadioButtonList = Base.extend(
{
	constructor : function(options)
	{
		for(var i = 0; i<options.ItemCount; i++)
		{
			var radioButtonOptions = Object.extend(
			{
				ID : options.ListID+"_c"+i,
				EventTarget : options.ListName+"$c"+i
			}, options);
			new Prado.WebUI.TRadioButton(radioButtonOptions);
		}
	}
});
Prado.WebUI.TRatingList = Class.create();
Prado.WebUI.TRatingList.prototype =
{
	selectedIndex : -1,

	initialize : function(options)
	{
		this.options = options;
		this.element = $(options['ID']);
		Element.addClassName(this.element,options.cssClass);
		this.radios = document.getElementsByName(options.field);
		for(var i = 0; i<this.radios.length; i++)
		{
			Event.observe(this.radios[i].parentNode, "mouseover", this.hover.bindEvent(this,i));
			Event.observe(this.radios[i].parentNode, "mouseout", this.recover.bindEvent(this,i));
			Event.observe(this.radios[i].parentNode, "click", this.click.bindEvent(this, i));
		}
		this.caption = CAPTION();
		this.element.appendChild(this.caption);
		this.selectedIndex = options.selectedIndex;
		this.setRating(this.selectedIndex);
	},

	hover : function(ev,index)
	{
		for(var i = 0; i<this.radios.length; i++)
			this.radios[i].parentNode.className = (i<=index) ? "rating_hover" : "";
		this.setCaption(index);
	},

	recover : function(ev,index)
	{
		for(var i = 0; i<=index; i++)
			Element.removeClassName(this.radios[i].parentNode, "rating_hover");
		this.setRating(this.selectedIndex);
	},

	click : function(ev, index)
	{
		for(var i = 0; i<this.radios.length; i++)
			this.radios[i].checked = (i == index);
		this.selectedIndex = index;
		this.setRating(index);
		if(typeof(this.options.onChange)=="function")
			this.options.onChange(this,index);
	},

	setRating: function(index)
	{
		for(var i = 0; i<=index; i++)
			this.radios[i].parentNode.className = "rating_selected";
		this.setCaption(index);
	},

	setCaption : function(index)
	{
		this.caption.innerHTML = index > -1 ?
			this.radios[index].value : this.options.caption;
	}
};

// script.aculo.us effects.js v1.7.1_beta1, Mon Mar 12 14:40:50 +0100 2007

// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// Contributors:
//  Justin Palmer (http://encytemedia.com/)
//  Mark Pilgrim (http://diveintomark.org/)
//  Martin Bialasinki
// 
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/ 

// converts rgb() and #xxx to #xxxxxx format,  
// returns self (or first argument) if not convertable  
String.prototype.parseColor = function() {  
  var color = '#';
  if(this.slice(0,4) == 'rgb(') {  
    var cols = this.slice(4,this.length-1).split(',');  
    var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);  
  } else {  
    if(this.slice(0,1) == '#') {  
      if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();  
      if(this.length==7) color = this.toLowerCase();  
    }  
  }  
  return(color.length==7 ? color : (arguments[0] || this));  
}

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

Element.collectTextNodes = function(element) {  
  return $A($(element).childNodes).collect( function(node) {
    return (node.nodeType==3 ? node.nodeValue : 
      (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
  }).flatten().join('');
}

Element.collectTextNodesIgnoreClass = function(element, className) {  
  return $A($(element).childNodes).collect( function(node) {
    return (node.nodeType==3 ? node.nodeValue : 
      ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? 
        Element.collectTextNodesIgnoreClass(node, className) : ''));
  }).flatten().join('');
}

Element.setContentZoom = function(element, percent) {
  element = $(element);  
  element.setStyle({fontSize: (percent/100) + 'em'});   
  if(Prototype.Browser.WebKit) window.scrollBy(0,0);
  return element;
}

Element.getInlineOpacity = function(element){
  return $(element).style.opacity || '';
}

Element.forceRerendering = function(element) {
  try {
    element = $(element);
    var n = document.createTextNode(' ');
    element.appendChild(n);
    element.removeChild(n);
  } catch(e) { }
};

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

Array.prototype.call = function() {
  var args = arguments;
  this.each(function(f){ f.apply(this, args) });
}

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

var Effect = {
  _elementDoesNotExistError: {
    name: 'ElementDoesNotExistError',
    message: 'The specified DOM element does not exist, but is required for this effect to operate'
  },
  tagifyText: function(element) {
    if(typeof Builder == 'undefined')
      throw("Effect.tagifyText requires including script.aculo.us' builder.js library");
      
    var tagifyStyle = 'position:relative';
    if(Prototype.Browser.IE) tagifyStyle += ';zoom:1';
    
    element = $(element);
    $A(element.childNodes).each( function(child) {
      if(child.nodeType==3) {
        child.nodeValue.toArray().each( function(character) {
          element.insertBefore(
            Builder.node('span',{style: tagifyStyle},
              character == ' ' ? String.fromCharCode(160) : character), 
              child);
        });
        Element.remove(child);
      }
    });
  },
  multiple: function(element, effect) {
    var elements;
    if(((typeof element == 'object') || 
        (typeof element == 'function')) && 
       (element.length))
      elements = element;
    else
      elements = $(element).childNodes;
      
    var options = Object.extend({
      speed: 0.1,
      delay: 0.0
    }, arguments[2] || {});
    var masterDelay = options.delay;

    $A(elements).each( function(element, index) {
      new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
    });
  },
  PAIRS: {
    'slide':  ['SlideDown','SlideUp'],
    'blind':  ['BlindDown','BlindUp'],
    'appear': ['Appear','Fade']
  },
  toggle: function(element, effect) {
    element = $(element);
    effect = (effect || 'appear').toLowerCase();
    var options = Object.extend({
      queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
    }, arguments[2] || {});
    Effect[element.visible() ? 
      Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
  }
};

var Effect2 = Effect; // deprecated

/* ------------- transitions ------------- */

Effect.Transitions = {
  linear: Prototype.K,
  sinoidal: function(pos) {
    return (-Math.cos(pos*Math.PI)/2) + 0.5;
  },
  reverse: function(pos) {
    return 1-pos;
  },
  flicker: function(pos) {
    var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
    return (pos > 1 ? 1 : pos);
  },
  wobble: function(pos) {
    return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
  },
  pulse: function(pos, pulses) { 
    pulses = pulses || 5; 
    return (
      Math.round((pos % (1/pulses)) * pulses) == 0 ? 
            ((pos * pulses * 2) - Math.floor(pos * pulses * 2)) : 
        1 - ((pos * pulses * 2) - Math.floor(pos * pulses * 2))
      );
  },
  none: function(pos) {
    return 0;
  },
  full: function(pos) {
    return 1;
  }
};

/* ------------- core effects ------------- */

Effect.ScopedQueue = Class.create();
Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), {
  initialize: function() {
    this.effects  = [];
    this.interval = null;    
  },
  _each: function(iterator) {
    this.effects._each(iterator);
  },
  add: function(effect) {
    var timestamp = new Date().getTime();
    
    var position = (typeof effect.options.queue == 'string') ? 
      effect.options.queue : effect.options.queue.position;
    
    switch(position) {
      case 'front':
        // move unstarted effects after this effect  
        this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
            e.startOn  += effect.finishOn;
            e.finishOn += effect.finishOn;
          });
        break;
      case 'with-last':
        timestamp = this.effects.pluck('startOn').max() || timestamp;
        break;
      case 'end':
        // start effect after last queued effect has finished
        timestamp = this.effects.pluck('finishOn').max() || timestamp;
        break;
    }
    
    effect.startOn  += timestamp;
    effect.finishOn += timestamp;

    if(!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
      this.effects.push(effect);
    
    if(!this.interval)
      this.interval = setInterval(this.loop.bind(this), 15);
  },
  remove: function(effect) {
    this.effects = this.effects.reject(function(e) { return e==effect });
    if(this.effects.length == 0) {
      clearInterval(this.interval);
      this.interval = null;
    }
  },
  loop: function() {
    var timePos = new Date().getTime();
    for(var i=0, len=this.effects.length;i<len;i++) 
      this.effects[i] && this.effects[i].loop(timePos);
  }
});

Effect.Queues = {
  instances: $H(),
  get: function(queueName) {
    if(typeof queueName != 'string') return queueName;
    
    if(!this.instances[queueName])
      this.instances[queueName] = new Effect.ScopedQueue();
      
    return this.instances[queueName];
  }
}
Effect.Queue = Effect.Queues.get('global');

Effect.DefaultOptions = {
  transition: Effect.Transitions.sinoidal,
  duration:   1.0,   // seconds
  fps:        100,   // 100= assume 66fps max.
  sync:       false, // true for combining
  from:       0.0,
  to:         1.0,
  delay:      0.0,
  queue:      'parallel'
}

Effect.Base = function() {};
Effect.Base.prototype = {
  position: null,
  start: function(options) {
    function codeForEvent(options,eventName){
      return (
        (options[eventName+'Internal'] ? 'this.options.'+eventName+'Internal(this);' : '') +
        (options[eventName] ? 'this.options.'+eventName+'(this);' : '')
      );
    }
    if(options.transition === false) options.transition = Effect.Transitions.linear;
    this.options      = Object.extend(Object.extend({},Effect.DefaultOptions), options || {});
    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;
    
    eval('this.render = function(pos){ '+
      'if(this.state=="idle"){this.state="running";'+
      codeForEvent(options,'beforeSetup')+
      (this.setup ? 'this.setup();':'')+ 
      codeForEvent(options,'afterSetup')+
      '};if(this.state=="running"){'+
      'pos=this.options.transition(pos)*'+this.fromToDelta+'+'+this.options.from+';'+
      'this.position=pos;'+
      codeForEvent(options,'beforeUpdate')+
      (this.update ? 'this.update(pos);':'')+
      codeForEvent(options,'afterUpdate')+
      '}}');
    
    this.event('beforeStart');
    if(!this.options.sync)
      Effect.Queues.get(typeof this.options.queue == 'string' ? 
        'global' : this.options.queue.scope).add(this);
  },
  loop: function(timePos) {
    if(timePos >= this.startOn) {
      if(timePos >= this.finishOn) {
        this.render(1.0);
        this.cancel();
        this.event('beforeFinish');
        if(this.finish) this.finish(); 
        this.event('afterFinish');
        return;  
      }
      var pos   = (timePos - this.startOn) / this.totalTime,
          frame = Math.round(pos * this.totalFrames);
      if(frame > this.currentFrame) {
        this.render(pos);
        this.currentFrame = frame;
      }
    }
  },
  cancel: function() {
    if(!this.options.sync)
      Effect.Queues.get(typeof this.options.queue == 'string' ? 
        'global' : this.options.queue.scope).remove(this);
    this.state = 'finished';
  },
  event: function(eventName) {
    if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
    if(this.options[eventName]) this.options[eventName](this);
  },
  inspect: function() {
    var data = $H();
    for(property in this)
      if(typeof this[property] != 'function') data[property] = this[property];
    return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
  }
}

Effect.Parallel = Class.create();
Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {
  initialize: function(effects) {
    this.effects = effects || [];
    this.start(arguments[1]);
  },
  update: function(position) {
    this.effects.invoke('render', position);
  },
  finish: function(position) {
    this.effects.each( function(effect) {
      effect.render(1.0);
      effect.cancel();
      effect.event('beforeFinish');
      if(effect.finish) effect.finish(position);
      effect.event('afterFinish');
    });
  }
});

Effect.Event = Class.create();
Object.extend(Object.extend(Effect.Event.prototype, Effect.Base.prototype), {
  initialize: function() {
    var options = Object.extend({
      duration: 0
    }, arguments[0] || {});
    this.start(options);
  },
  update: Prototype.emptyFunction
});

Effect.Opacity = Class.create();
Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
  initialize: function(element) {
    this.element = $(element);
    if(!this.element) throw(Effect._elementDoesNotExistError);
    // make this work on IE on elements without 'layout'
    if(Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
      this.element.setStyle({zoom: 1});
    var options = Object.extend({
      from: this.element.getOpacity() || 0.0,
      to:   1.0
    }, arguments[1] || {});
    this.start(options);
  },
  update: function(position) {
    this.element.setOpacity(position);
  }
});

Effect.Move = Class.create();
Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), {
  initialize: function(element) {
    this.element = $(element);
    if(!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({
      x:    0,
      y:    0,
      mode: 'relative'
    }, arguments[1] || {});
    this.start(options);
  },
  setup: function() {
    // Bug in Opera: Opera returns the "real" position of a static element or
    // relative element that does not have top/left explicitly set.
    // ==> Always set top and left for position relative elements in your stylesheets 
    // (to 0 if you do not need them) 
    this.element.makePositioned();
    this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
    this.originalTop  = parseFloat(this.element.getStyle('top')  || '0');
    if(this.options.mode == 'absolute') {
      // absolute movement, so we need to calc deltaX and deltaY
      this.options.x = this.options.x - this.originalLeft;
      this.options.y = this.options.y - this.originalTop;
    }
  },
  update: function(position) {
    this.element.setStyle({
      left: Math.round(this.options.x  * position + this.originalLeft) + 'px',
      top:  Math.round(this.options.y  * position + this.originalTop)  + 'px'
    });
  }
});

// for backwards compatibility
Effect.MoveBy = function(element, toTop, toLeft) {
  return new Effect.Move(element, 
    Object.extend({ x: toLeft, y: toTop }, arguments[3] || {}));
};

Effect.Scale = Class.create();
Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
  initialize: function(element, percent) {
    this.element = $(element);
    if(!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({
      scaleX: true,
      scaleY: true,
      scaleContent: true,
      scaleFromCenter: false,
      scaleMode: 'box',        // 'box' or 'contents' or {} with provided values
      scaleFrom: 100.0,
      scaleTo:   percent
    }, arguments[2] || {});
    this.start(options);
  },
  setup: function() {
    this.restoreAfterFinish = this.options.restoreAfterFinish || false;
    this.elementPositioning = this.element.getStyle('position');
    
    this.originalStyle = {};
    ['top','left','width','height','fontSize'].each( function(k) {
      this.originalStyle[k] = this.element.style[k];
    }.bind(this));
      
    this.originalTop  = this.element.offsetTop;
    this.originalLeft = this.element.offsetLeft;
    
    var fontSize = this.element.getStyle('font-size') || '100%';
    ['em','px','%','pt'].each( function(fontSizeType) {
      if(fontSize.indexOf(fontSizeType)>0) {
        this.fontSize     = parseFloat(fontSize);
        this.fontSizeType = fontSizeType;
      }
    }.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(position) {
    var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
    if(this.options.scaleContent && this.fontSize)
      this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
    this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
  },
  finish: function(position) {
    if(this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
  },
  setDimensions: function(height, width) {
    var d = {};
    if(this.options.scaleX) d.width = Math.round(width) + 'px';
    if(this.options.scaleY) d.height = Math.round(height) + 'px';
    if(this.options.scaleFromCenter) {
      var topd  = (height - this.dims[0])/2;
      var leftd = (width  - this.dims[1])/2;
      if(this.elementPositioning == 'absolute') {
        if(this.options.scaleY) d.top = this.originalTop-topd + 'px';
        if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
      } else {
        if(this.options.scaleY) d.top = -topd + 'px';
        if(this.options.scaleX) d.left = -leftd + 'px';
      }
    }
    this.element.setStyle(d);
  }
});

Effect.Highlight = Class.create();
Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), {
  initialize: function(element) {
    this.element = $(element);
    if(!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {});
    this.start(options);
  },
  setup: function() {
    // Prevent executing on elements not in the layout flow
    if(this.element.getStyle('display')=='none') { this.cancel(); return; }
    // Disable background image during the effect
    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');
    // init color calculations
    this._base  = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
    this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
  },
  update: function(position) {
    this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
      return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) });
  },
  finish: function() {
    this.element.setStyle(Object.extend(this.oldStyle, {
      backgroundColor: this.options.restorecolor
    }));
  }
});

Effect.ScrollTo = Class.create();
Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
  initialize: function(element) {
    this.element = $(element);
    this.start(arguments[1] || {});
  },
  setup: function() {
    Position.prepare();
    var offsets = Position.cumulativeOffset(this.element);
    if(this.options.offset) offsets[1] += this.options.offset;
    var max = window.innerHeight ? 
      window.height - window.innerHeight :
      document.body.scrollHeight - 
        (document.documentElement.clientHeight ? 
          document.documentElement.clientHeight : document.body.clientHeight);
    this.scrollStart = Position.deltaY;
    this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart;
  },
  update: function(position) {
    Position.prepare();
    window.scrollTo(Position.deltaX, 
      this.scrollStart + (position*this.delta));
  }
});

/* ------------- combination effects ------------- */

Effect.Fade = function(element) {
  element = $(element);
  var oldOpacity = element.getInlineOpacity();
  var options = Object.extend({
  from: element.getOpacity() || 1.0,
  to:   0.0,
  afterFinishInternal: function(effect) { 
    if(effect.options.to!=0) return;
    effect.element.hide().setStyle({opacity: oldOpacity}); 
  }}, arguments[1] || {});
  return new Effect.Opacity(element,options);
}

Effect.Appear = function(element) {
  element = $(element);
  var options = Object.extend({
  from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
  to:   1.0,
  // force Safari to render floated elements properly
  afterFinishInternal: function(effect) {
    effect.element.forceRerendering();
  },
  beforeSetup: function(effect) {
    effect.element.setOpacity(effect.options.from).show(); 
  }}, arguments[1] || {});
  return new Effect.Opacity(element,options);
}

Effect.Puff = function(element) {
  element = $(element);
  var oldStyle = { 
    opacity: element.getInlineOpacity(), 
    position: element.getStyle('position'),
    top:  element.style.top,
    left: element.style.left,
    width: element.style.width,
    height: element.style.height
  };
  return new Effect.Parallel(
   [ new Effect.Scale(element, 200, 
      { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), 
     new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], 
     Object.extend({ duration: 1.0, 
      beforeSetupInternal: function(effect) {
        Position.absolutize(effect.effects[0].element)
      },
      afterFinishInternal: function(effect) {
         effect.effects[0].element.hide().setStyle(oldStyle); }
     }, arguments[1] || {})
   );
}

Effect.BlindUp = function(element) {
  element = $(element);
  element.makeClipping();
  return new Effect.Scale(element, 0,
    Object.extend({ scaleContent: false, 
      scaleX: false, 
      restoreAfterFinish: true,
      afterFinishInternal: function(effect) {
        effect.element.hide().undoClipping();
      } 
    }, arguments[1] || {})
  );
}

Effect.BlindDown = function(element) {
  element = $(element);
  var elementDimensions = element.getDimensions();
  return new Effect.Scale(element, 100, Object.extend({ 
    scaleContent: false, 
    scaleX: false,
    scaleFrom: 0,
    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
    restoreAfterFinish: true,
    afterSetup: function(effect) {
      effect.element.makeClipping().setStyle({height: '0px'}).show(); 
    },  
    afterFinishInternal: function(effect) {
      effect.element.undoClipping();
    }
  }, arguments[1] || {}));
}

Effect.SwitchOff = function(element) {
  element = $(element);
  var oldOpacity = element.getInlineOpacity();
  return new Effect.Appear(element, Object.extend({
    duration: 0.4,
    from: 0,
    transition: Effect.Transitions.flicker,
    afterFinishInternal: function(effect) {
      new Effect.Scale(effect.element, 1, { 
        duration: 0.3, scaleFromCenter: true,
        scaleX: false, scaleContent: false, restoreAfterFinish: true,
        beforeSetup: function(effect) { 
          effect.element.makePositioned().makeClipping();
        },
        afterFinishInternal: function(effect) {
          effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
        }
      })
    }
  }, arguments[1] || {}));
}

Effect.DropOut = function(element) {
  element = $(element);
  var oldStyle = {
    top: element.getStyle('top'),
    left: element.getStyle('left'),
    opacity: element.getInlineOpacity() };
  return new Effect.Parallel(
    [ new Effect.Move(element, {x: 0, y: 100, sync: true }), 
      new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
    Object.extend(
      { duration: 0.5,
        beforeSetup: function(effect) {
          effect.effects[0].element.makePositioned(); 
        },
        afterFinishInternal: function(effect) {
          effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
        } 
      }, arguments[1] || {}));
}

Effect.Shake = function(element) {
  element = $(element);
  var oldStyle = {
    top: element.getStyle('top'),
    left: element.getStyle('left') };
    return new Effect.Move(element, 
      { x:  20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x:  40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x:  40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
        effect.element.undoPositioned().setStyle(oldStyle);
  }}) }}) }}) }}) }}) }});
}

Effect.SlideDown = function(element) {
  element = $(element).cleanWhitespace();
  // SlideDown need to have the content of the element wrapped in a container element with fixed height!
  var oldInnerBottom = element.down().getStyle('bottom');
  var elementDimensions = element.getDimensions();
  return new Effect.Scale(element, 100, Object.extend({ 
    scaleContent: false, 
    scaleX: false, 
    scaleFrom: window.opera ? 0 : 1,
    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
    restoreAfterFinish: true,
    afterSetup: function(effect) {
      effect.element.makePositioned();
      effect.element.down().makePositioned();
      if(window.opera) effect.element.setStyle({top: ''});
      effect.element.makeClipping().setStyle({height: '0px'}).show(); 
    },
    afterUpdateInternal: function(effect) {
      effect.element.down().setStyle({bottom:
        (effect.dims[0] - effect.element.clientHeight) + 'px' }); 
    },
    afterFinishInternal: function(effect) {
      effect.element.undoClipping().undoPositioned();
      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
    }, arguments[1] || {})
  );
}

Effect.SlideUp = function(element) {
  element = $(element).cleanWhitespace();
  var oldInnerBottom = element.down().getStyle('bottom');
  return new Effect.Scale(element, window.opera ? 0 : 1,
   Object.extend({ scaleContent: false, 
    scaleX: false, 
    scaleMode: 'box',
    scaleFrom: 100,
    restoreAfterFinish: true,
    beforeStartInternal: function(effect) {
      effect.element.makePositioned();
      effect.element.down().makePositioned();
      if(window.opera) effect.element.setStyle({top: ''});
      effect.element.makeClipping().show();
    },  
    afterUpdateInternal: function(effect) {
      effect.element.down().setStyle({bottom:
        (effect.dims[0] - effect.element.clientHeight) + 'px' });
    },
    afterFinishInternal: function(effect) {
      effect.element.hide().undoClipping().undoPositioned().setStyle({bottom: oldInnerBottom});
      effect.element.down().undoPositioned();
    }
   }, arguments[1] || {})
  );
}

// Bug in opera makes the TD containing this element expand for a instance after finish 
Effect.Squish = function(element) {
  return new Effect.Scale(element, window.opera ? 1 : 0, { 
    restoreAfterFinish: true,
    beforeSetup: function(effect) {
      effect.element.makeClipping(); 
    },  
    afterFinishInternal: function(effect) {
      effect.element.hide().undoClipping(); 
    }
  });
}

Effect.Grow = function(element) {
  element = $(element);
  var options = Object.extend({
    direction: 'center',
    moveTransition: Effect.Transitions.sinoidal,
    scaleTransition: Effect.Transitions.sinoidal,
    opacityTransition: Effect.Transitions.full
  }, arguments[1] || {});
  var oldStyle = {
    top: element.style.top,
    left: element.style.left,
    height: element.style.height,
    width: element.style.width,
    opacity: element.getInlineOpacity() };

  var dims = element.getDimensions();    
  var initialMoveX, initialMoveY;
  var moveX, moveY;
  
  switch (options.direction) {
    case 'top-left':
      initialMoveX = initialMoveY = moveX = moveY = 0; 
      break;
    case 'top-right':
      initialMoveX = dims.width;
      initialMoveY = moveY = 0;
      moveX = -dims.width;
      break;
    case 'bottom-left':
      initialMoveX = moveX = 0;
      initialMoveY = dims.height;
      moveY = -dims.height;
      break;
    case 'bottom-right':
      initialMoveX = dims.width;
      initialMoveY = dims.height;
      moveX = -dims.width;
      moveY = -dims.height;
      break;
    case 'center':
      initialMoveX = dims.width / 2;
      initialMoveY = dims.height / 2;
      moveX = -dims.width / 2;
      moveY = -dims.height / 2;
      break;
  }
  
  return new Effect.Move(element, {
    x: initialMoveX,
    y: initialMoveY,
    duration: 0.01, 
    beforeSetup: function(effect) {
      effect.element.hide().makeClipping().makePositioned();
    },
    afterFinishInternal: function(effect) {
      new Effect.Parallel(
        [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
          new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
          new Effect.Scale(effect.element, 100, {
            scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, 
            sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
        ], Object.extend({
             beforeSetup: function(effect) {
               effect.effects[0].element.setStyle({height: '0px'}).show(); 
             },
             afterFinishInternal: function(effect) {
               effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); 
             }
           }, options)
      )
    }
  });
}

Effect.Shrink = function(element) {
  element = $(element);
  var options = Object.extend({
    direction: 'center',
    moveTransition: Effect.Transitions.sinoidal,
    scaleTransition: Effect.Transitions.sinoidal,
    opacityTransition: Effect.Transitions.none
  }, arguments[1] || {});
  var oldStyle = {
    top: element.style.top,
    left: element.style.left,
    height: element.style.height,
    width: element.style.width,
    opacity: element.getInlineOpacity() };

  var dims = element.getDimensions();
  var moveX, moveY;
  
  switch (options.direction) {
    case 'top-left':
      moveX = moveY = 0;
      break;
    case 'top-right':
      moveX = dims.width;
      moveY = 0;
      break;
    case 'bottom-left':
      moveX = 0;
      moveY = dims.height;
      break;
    case 'bottom-right':
      moveX = dims.width;
      moveY = dims.height;
      break;
    case 'center':  
      moveX = dims.width / 2;
      moveY = dims.height / 2;
      break;
  }
  
  return new Effect.Parallel(
    [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
      new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
      new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
    ], Object.extend({            
         beforeStartInternal: function(effect) {
           effect.effects[0].element.makePositioned().makeClipping(); 
         },
         afterFinishInternal: function(effect) {
           effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
       }, options)
  );
}

Effect.Pulsate = function(element) {
  element = $(element);
  var options    = arguments[1] || {};
  var oldOpacity = element.getInlineOpacity();
  var transition = options.transition || Effect.Transitions.sinoidal;
  var reverser   = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) };
  reverser.bind(transition);
  return new Effect.Opacity(element, 
    Object.extend(Object.extend({  duration: 2.0, from: 0,
      afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
    }, options), {transition: reverser}));
}

Effect.Fold = function(element) {
  element = $(element);
  var oldStyle = {
    top: element.style.top,
    left: element.style.left,
    width: element.style.width,
    height: element.style.height };
  element.makeClipping();
  return new Effect.Scale(element, 5, Object.extend({   
    scaleContent: false,
    scaleX: false,
    afterFinishInternal: function(effect) {
    new Effect.Scale(element, 1, { 
      scaleContent: false, 
      scaleY: false,
      afterFinishInternal: function(effect) {
        effect.element.hide().undoClipping().setStyle(oldStyle);
      } });
  }}, arguments[1] || {}));
};

Effect.Morph = Class.create();
Object.extend(Object.extend(Effect.Morph.prototype, Effect.Base.prototype), {
  initialize: function(element) {
    this.element = $(element);
    if(!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({
      style: {}
    }, arguments[1] || {});
    if (typeof options.style == 'string') {
      if(options.style.indexOf(':') == -1) {
        var cssText = '', selector = '.' + options.style;
        $A(document.styleSheets).reverse().each(function(styleSheet) {
          if (styleSheet.cssRules) cssRules = styleSheet.cssRules;
          else if (styleSheet.rules) cssRules = styleSheet.rules;
          $A(cssRules).reverse().each(function(rule) {
            if (selector == rule.selectorText) {
              cssText = rule.style.cssText;
              throw $break;
            }
          });
          if (cssText) throw $break;
        });
        this.style = cssText.parseStyle();
        options.afterFinishInternal = function(effect){
          effect.element.addClassName(effect.options.style);
          effect.transforms.each(function(transform) {
            if(transform.style != 'opacity')
              effect.element.style[transform.style] = '';
          });
        }
      } else this.style = options.style.parseStyle();
    } else this.style = $H(options.style)
    this.start(options);
  },
  setup: function(){
    function parseColor(color){
      if(!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
      color = color.parseColor();
      return $R(0,2).map(function(i){
        return parseInt( color.slice(i*2+1,i*2+3), 16 ) 
      });
    }
    this.transforms = this.style.map(function(pair){
      var property = pair[0], value = pair[1], unit = null;

      if(value.parseColor('#zzzzzz') != '#zzzzzz') {
        value = value.parseColor();
        unit  = 'color';
      } else if(property == 'opacity') {
        value = parseFloat(value);
        if(Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
          this.element.setStyle({zoom: 1});
      } else if(Element.CSS_LENGTH.test(value)) {
          var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
          value = parseFloat(components[1]);
          unit = (components.length == 3) ? components[2] : null;
      }

      var originalValue = this.element.getStyle(property);
      return { 
        style: property.camelize(), 
        originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), 
        targetValue: unit=='color' ? parseColor(value) : value,
        unit: unit
      };
    }.bind(this)).reject(function(transform){
      return (
        (transform.originalValue == transform.targetValue) ||
        (
          transform.unit != 'color' &&
          (isNaN(transform.originalValue) || isNaN(transform.targetValue))
        )
      )
    });
  },
  update: function(position) {
    var style = {}, transform, i = this.transforms.length;
    while(i--)
      style[(transform = this.transforms[i]).style] = 
        transform.unit=='color' ? '#'+
          (Math.round(transform.originalValue[0]+
            (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
          (Math.round(transform.originalValue[1]+
            (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +
          (Math.round(transform.originalValue[2]+
            (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
        transform.originalValue + Math.round(
          ((transform.targetValue - transform.originalValue) * position) * 1000)/1000 + transform.unit;
    this.element.setStyle(style, true);
  }
});

Effect.Transform = Class.create();
Object.extend(Effect.Transform.prototype, {
  initialize: function(tracks){
    this.tracks  = [];
    this.options = arguments[1] || {};
    this.addTracks(tracks);
  },
  addTracks: function(tracks){
    tracks.each(function(track){
      var data = $H(track).values().first();
      this.tracks.push($H({
        ids:     $H(track).keys().first(),
        effect:  Effect.Morph,
        options: { style: data }
      }));
    }.bind(this));
    return this;
  },
  play: function(){
    return new Effect.Parallel(
      this.tracks.map(function(track){
        var elements = [$(track.ids) || $$(track.ids)].flatten();
        return elements.map(function(e){ return new track.effect(e, Object.extend({ sync:true }, track.options)) });
      }).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.prototype.parseStyle = function(){
  var element = document.createElement('div');
  element.innerHTML = '<div style="' + this + '"></div>';
  var style = element.childNodes[0].style, styleRules = $H();
  
  Element.CSS_PROPERTIES.each(function(property){
    if(style[property]) styleRules[property] = style[property]; 
  });
  if(Prototype.Browser.IE && this.indexOf('opacity') > -1) {
    styleRules.opacity = this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1];
  }
  return styleRules;
};

Element.morph = function(element, style) {
  new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || {}));
  return element;
};

['getInlineOpacity','forceRerendering','setContentZoom',
 'collectTextNodes','collectTextNodesIgnoreClass','morph'].each( 
  function(f) { Element.Methods[f] = Element[f]; }
);

Element.Methods.visualEffect = function(element, effect, options) {
  s = effect.dasherize().camelize();
  effect_class = s.charAt(0).toUpperCase() + s.substring(1);
  new Effect[effect_class](element, options);
  return $(element);
};

Element.addMethods();
// script.aculo.us controls.js v1.7.1_beta1, Mon Mar 12 14:40:50 +0100 2007

// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//           (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
//           (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
// Contributors:
//  Richard Livsey
//  Rahul Bhargava
//  Rob Wills
// 
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/

// Autocompleter.Base handles all the autocompletion functionality 
// that's independent of the data source for autocompletion. This
// includes drawing the autocompletion menu, observing keyboard
// and mouse events, and similar.
//
// Specific autocompleters need to provide, at the very least, 
// a getUpdatedChoices function that will be invoked every time
// the text inside the monitored textbox changes. This method 
// should get the text for which to provide autocompletion by
// invoking this.getToken(), NOT by directly accessing
// this.element.value. This is to allow incremental tokenized
// autocompletion. Specific auto-completion logic (AJAX, etc)
// belongs in getUpdatedChoices.
//
// Tokenized incremental autocompletion is enabled automatically
// when an autocompleter is instantiated with the 'tokens' option
// in the options parameter, e.g.:
// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
// will incrementally autocomplete with a comma as the token.
// Additionally, ',' in the above example can be replaced with
// a token array, e.g. { tokens: [',', '\n'] } which
// enables autocompletion on multiple tokens. This is most 
// useful when one of the tokens is \n (a newline), as it 
// allows smart autocompletion after linebreaks.

if(typeof Effect == 'undefined')
  throw("controls.js requires including script.aculo.us' effects.js library");

var Autocompleter = {}
Autocompleter.Base = function() {};
Autocompleter.Base.prototype = {
  baseInitialize: function(element, update, options) {
    this.element     = $(element); 
    this.update      = $(update);  
    this.hasFocus    = false; 
    this.changed     = false; 
    this.active      = false; 
    this.index       = 0;     
    this.entryCount  = 0;

    if(this.setOptions)
      this.setOptions(options);
    else
      this.options = options || {};

    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(element, update){ 
        if(!update.style.position || update.style.position=='absolute') {
          update.style.position = 'absolute';
          Position.clone(element, update, {
            setHeight: false, 
            offsetTop: element.offsetHeight
          });
        }
        Effect.Appear(update,{duration:0.15});
      };
    this.options.onHide = this.options.onHide || 
      function(element, update){ new Effect.Fade(update,{duration:0.15}) };

    if(typeof(this.options.tokens) == 'string') 
      this.options.tokens = new Array(this.options.tokens);

    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, "keypress", 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(event) {
    if(this.active)
      switch(event.keyCode) {
       case Event.KEY_TAB:
       case Event.KEY_RETURN:
         this.selectEntry();
         Event.stop(event);
       case Event.KEY_ESC:
         this.hide();
         this.active = false;
         Event.stop(event);
         return;
       case Event.KEY_LEFT:
       case Event.KEY_RIGHT:
         return;
       case Event.KEY_UP:
         this.markPrevious();
         this.render();
         if(Prototype.Browser.WebKit) Event.stop(event);
         return;
       case Event.KEY_DOWN:
         this.markNext();
         this.render();
         if(Prototype.Browser.WebKit) Event.stop(event);
         return;
      }
     else 
       if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || 
         (Prototype.Browser.WebKit > 0 && event.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(event) {
    var element = Event.findElement(event, 'LI');
    if(this.index != element.autocompleteIndex) 
    {
        this.index = element.autocompleteIndex;
        this.render();
    }
    Event.stop(event);
  },
  
  onClick: function(event) {
    var element = Event.findElement(event, 'LI');
    this.index = element.autocompleteIndex;
    this.selectEntry();
    this.hide();
  },
  
  onBlur: function(event) {
    // needed to make click events working
    setTimeout(this.hide.bind(this), 250);
    this.hasFocus = false;
    this.active = false;     
  }, 
  
  render: function() {
    if(this.entryCount > 0) {
      for (var i = 0; i < this.entryCount; i++)
        this.index==i ? 
          Element.addClassName(this.getEntry(i),"selected") : 
          Element.removeClassName(this.getEntry(i),"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(index) {
    return this.update.firstChild.childNodes[index];
  },
  
  getCurrentEntry: function() {
    return this.getEntry(this.index);
  },
  
  selectEntry: function() {
    this.active = false;
    this.updateElement(this.getCurrentEntry());
  },

  updateElement: function(selectedElement) {
    if (this.options.updateElement) {
      this.options.updateElement(selectedElement);
      return;
    }
    var value = '';
    if (this.options.select) {
      var nodes = document.getElementsByClassName(this.options.select, selectedElement) || [];
      if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
    } else
      value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
    
    var lastTokenPos = this.findLastToken();
    if (lastTokenPos != -1) {
      var newValue = this.element.value.substr(0, lastTokenPos + 1);
      var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
      if (whitespace)
        newValue += whitespace[0];
      this.element.value = newValue + value;
    } else {
      this.element.value = value;
    }
    this.element.focus();
    
    if (this.options.afterUpdateElement)
      this.options.afterUpdateElement(this.element, selectedElement);
  },

  updateChoices: function(choices) {
    if(!this.changed && this.hasFocus) {
      this.update.innerHTML = choices;
      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 i = 0; i < this.entryCount; i++) {
          var entry = this.getEntry(i);
          entry.autocompleteIndex = i;
          this.addObservers(entry);
        }
      } 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(element) {
    Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
    Event.observe(element, "click", this.onClick.bindAsEventListener(this));
  },

  onObserverEvent: function() {
    this.changed = false;   
    if(this.getToken().length>=this.options.minChars) {
      this.startIndicator();
      this.getUpdatedChoices();
    } else {
      this.active = false;
      this.hide();
    }
  },

  getToken: function() {
    var tokenPos = this.findLastToken();
    if (tokenPos != -1)
      var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
    else
      var ret = this.element.value;

    return /\n/.test(ret) ? '' : ret;
  },

  findLastToken: function() {
    var lastTokenPos = -1;

    for (var i=0; i<this.options.tokens.length; i++) {
      var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
      if (thisTokenPos > lastTokenPos)
        lastTokenPos = thisTokenPos;
    }
    return lastTokenPos;
  }
}

Ajax.Autocompleter = Class.create();
Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
  initialize: function(element, update, url, options) {
    this.baseInitialize(element, update, options);
    this.options.asynchronous  = true;
    this.options.onComplete    = this.onComplete.bind(this);
    this.options.defaultParams = this.options.parameters || null;
    this.url                   = url;
  },

  getUpdatedChoices: function() {
    entry = encodeURIComponent(this.options.paramName) + '=' + 
      encodeURIComponent(this.getToken());

    this.options.parameters = this.options.callback ?
      this.options.callback(this.element, entry) : entry;

    if(this.options.defaultParams) 
      this.options.parameters += '&' + this.options.defaultParams;

    new Ajax.Request(this.url, this.options);
  },

  onComplete: function(request) {
    this.updateChoices(request.responseText);
  }

});

// The local array autocompleter. Used when you'd prefer to
// inject an array of autocompletion options into the page, rather
// than sending out Ajax queries, which can be quite slow sometimes.
//
// The constructor takes four parameters. The first two are, as usual,
// the id of the monitored textbox, and id of the autocompletion menu.
// The third is the array you want to autocomplete from, and the fourth
// is the options block.
//
// Extra local autocompletion options:
// - choices - How many autocompletion choices to offer
//
// - partialSearch - If false, the autocompleter will match entered
//                    text only at the beginning of strings in the 
//                    autocomplete array. Defaults to true, which will
//                    match text at the beginning of any *word* in the
//                    strings in the autocomplete array. If you want to
//                    search anywhere in the string, additionally set
//                    the option fullSearch to true (default: off).
//
// - fullSsearch - Search anywhere in autocomplete array strings.
//
// - partialChars - How many characters to enter before triggering
//                   a partial match (unlike minChars, which defines
//                   how many characters are required to do any match
//                   at all). Defaults to 2.
//
// - ignoreCase - Whether to ignore case when autocompleting.
//                 Defaults to true.
//
// It's possible to pass in a custom function as the 'selector' 
// option, if you prefer to write your own autocompletion logic.
// In that case, the other options above will not apply unless
// you support them.

Autocompleter.Local = Class.create();
Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
  initialize: function(element, update, array, options) {
    this.baseInitialize(element, update, options);
    this.options.array = array;
  },

  getUpdatedChoices: function() {
    this.updateChoices(this.options.selector(this));
  },

  setOptions: function(options) {
    this.options = Object.extend({
      choices: 10,
      partialSearch: true,
      partialChars: 2,
      ignoreCase: true,
      fullSearch: false,
      selector: function(instance) {
        var ret       = []; // Beginning matches
        var partial   = []; // Inside matches
        var entry     = instance.getToken();
        var count     = 0;

        for (var i = 0; i < instance.options.array.length &&  
          ret.length < instance.options.choices ; i++) { 

          var elem = instance.options.array[i];
          var foundPos = instance.options.ignoreCase ? 
            elem.toLowerCase().indexOf(entry.toLowerCase()) : 
            elem.indexOf(entry);

          while (foundPos != -1) {
            if (foundPos == 0 && elem.length != entry.length) { 
              ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" + 
                elem.substr(entry.length) + "</li>");
              break;
            } else if (entry.length >= instance.options.partialChars && 
              instance.options.partialSearch && foundPos != -1) {
              if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
                partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
                  elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
                  foundPos + entry.length) + "</li>");
                break;
              }
            }

            foundPos = instance.options.ignoreCase ? 
              elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : 
              elem.indexOf(entry, foundPos + 1);

          }
        }
        if (partial.length)
          ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
        return "<ul>" + ret.join('') + "</ul>";
      }
    }, options || {});
  }
});

// AJAX in-place editor
//
// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor

// Use this if you notice weird scrolling problems on some browsers,
// the DOM might be a bit confused when this gets called so do this
// waits 1 ms (with setTimeout) until it does the activation
Field.scrollFreeActivate = function(field) {
  setTimeout(function() {
    Field.activate(field);
  }, 1);
}

Ajax.InPlaceEditor = Class.create();
Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99";
Ajax.InPlaceEditor.prototype = {
  initialize: function(element, url, options) {
    this.url = url;
    this.element = $(element);

    this.options = Object.extend({
      paramName: "value",
      okButton: true,
      okLink: false,
      okText: "ok",
      cancelButton: false,
      cancelLink: true,
      cancelText: "cancel",
      textBeforeControls: '',
      textBetweenControls: '',
      textAfterControls: '',
      savingText: "Saving...",
      clickToEditText: "Click to edit",
      okText: "ok",
      rows: 1,
      onComplete: function(transport, element) {
        new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
      },
      onFailure: function(transport) {
        alert("Error communicating with the server: " + transport.responseText.stripTags());
      },
      callback: function(form) {
        return Form.serialize(form);
      },
      handleLineBreaks: true,
      loadingText: 'Loading...',
      savingClassName: 'inplaceeditor-saving',
      loadingClassName: 'inplaceeditor-loading',
      formClassName: 'inplaceeditor-form',
      highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
      highlightendcolor: "#FFFFFF",
      externalControl: null,
      submitOnBlur: false,
      ajaxOptions: {},
      evalScripts: false
    }, options || {});

    if(!this.options.formId && this.element.id) {
      this.options.formId = this.element.id + "-inplaceeditor";
      if ($(this.options.formId)) {
        // there's already a form with that name, don't specify an id
        this.options.formId = null;
      }
    }
    
    if (this.options.externalControl) {
      this.options.externalControl = $(this.options.externalControl);
    }
    
    this.originalBackground = Element.getStyle(this.element, 'background-color');
    if (!this.originalBackground) {
      this.originalBackground = "transparent";
    }
    
    this.element.title = this.options.clickToEditText;
    
    this.onclickListener = this.enterEditMode.bindAsEventListener(this);
    this.mouseoverListener = this.enterHover.bindAsEventListener(this);
    this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
    Event.observe(this.element, 'click', this.onclickListener);
    Event.observe(this.element, 'mouseover', this.mouseoverListener);
    Event.observe(this.element, 'mouseout', this.mouseoutListener);
    if (this.options.externalControl) {
      Event.observe(this.options.externalControl, 'click', this.onclickListener);
      Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
      Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
    }
  },
  enterEditMode: function(evt) {
    if (this.saving) return;
    if (this.editing) return;
    this.editing = true;
    this.onEnterEditMode();
    if (this.options.externalControl) {
      Element.hide(this.options.externalControl);
    }
    Element.hide(this.element);
    this.createForm();
    this.element.parentNode.insertBefore(this.form, this.element);
    if (!this.options.loadTextURL) Field.scrollFreeActivate(this.editField);
    // stop the event to avoid a page refresh in Safari
    if (evt) {
      Event.stop(evt);
    }
    return false;
  },
  createForm: function() {
    this.form = document.createElement("form");
    this.form.id = this.options.formId;
    Element.addClassName(this.form, this.options.formClassName)
    this.form.onsubmit = this.onSubmit.bind(this);

    this.createEditField();

    if (this.options.textarea) {
      var br = document.createElement("br");
      this.form.appendChild(br);
    }
    
    if (this.options.textBeforeControls)
      this.form.appendChild(document.createTextNode(this.options.textBeforeControls));

    if (this.options.okButton) {
      var okButton = document.createElement("input");
      okButton.type = "submit";
      okButton.value = this.options.okText;
      okButton.className = 'editor_ok_button';
      this.form.appendChild(okButton);
    }
    
    if (this.options.okLink) {
      var okLink = document.createElement("a");
      okLink.href = "#";
      okLink.appendChild(document.createTextNode(this.options.okText));
      okLink.onclick = this.onSubmit.bind(this);
      okLink.className = 'editor_ok_link';
      this.form.appendChild(okLink);
    }
    
    if (this.options.textBetweenControls && 
      (this.options.okLink || this.options.okButton) && 
      (this.options.cancelLink || this.options.cancelButton))
      this.form.appendChild(document.createTextNode(this.options.textBetweenControls));
      
    if (this.options.cancelButton) {
      var cancelButton = document.createElement("input");
      cancelButton.type = "submit";
      cancelButton.value = this.options.cancelText;
      cancelButton.onclick = this.onclickCancel.bind(this);
      cancelButton.className = 'editor_cancel_button';
      this.form.appendChild(cancelButton);
    }

    if (this.options.cancelLink) {
      var cancelLink = document.createElement("a");
      cancelLink.href = "#";
      cancelLink.appendChild(document.createTextNode(this.options.cancelText));
      cancelLink.onclick = this.onclickCancel.bind(this);
      cancelLink.className = 'editor_cancel editor_cancel_link';      
      this.form.appendChild(cancelLink);
    }
    
    if (this.options.textAfterControls)
      this.form.appendChild(document.createTextNode(this.options.textAfterControls));
  },
  hasHTMLLineBreaks: function(string) {
    if (!this.options.handleLineBreaks) return false;
    return string.match(/<br/i) || string.match(/<p>/i);
  },
  convertHTMLLineBreaks: function(string) {
    return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, "");
  },
  createEditField: function() {
    var text;
    if(this.options.loadTextURL) {
      text = this.options.loadingText;
    } else {
      text = this.getText();
    }

    var obj = this;
    
    if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) {
      this.options.textarea = false;
      var textField = document.createElement("input");
      textField.obj = this;
      textField.type = "text";
      textField.name = this.options.paramName;
      textField.value = text;
      textField.style.backgroundColor = this.options.highlightcolor;
      textField.className = 'editor_field';
      var size = this.options.size || this.options.cols || 0;
      if (size != 0) textField.size = size;
      if (this.options.submitOnBlur)
        textField.onblur = this.onSubmit.bind(this);
      this.editField = textField;
    } else {
      this.options.textarea = true;
      var textArea = document.createElement("textarea");
      textArea.obj = this;
      textArea.name = this.options.paramName;
      textArea.value = this.convertHTMLLineBreaks(text);
      textArea.rows = this.options.rows;
      textArea.cols = this.options.cols || 40;
      textArea.className = 'editor_field';      
      if (this.options.submitOnBlur)
        textArea.onblur = this.onSubmit.bind(this);
      this.editField = textArea;
    }
    
    if(this.options.loadTextURL) {
      this.loadExternalText();
    }
    this.form.appendChild(this.editField);
  },
  getText: function() {
    return this.element.innerHTML;
  },
  loadExternalText: function() {
    Element.addClassName(this.form, this.options.loadingClassName);
    this.editField.disabled = true;
    new Ajax.Request(
      this.options.loadTextURL,
      Object.extend({
        asynchronous: true,
        onComplete: this.onLoadedExternalText.bind(this)
      }, this.options.ajaxOptions)
    );
  },
  onLoadedExternalText: function(transport) {
    Element.removeClassName(this.form, this.options.loadingClassName);
    this.editField.disabled = false;
    this.editField.value = transport.responseText.stripTags();
    Field.scrollFreeActivate(this.editField);
  },
  onclickCancel: function() {
    this.onComplete();
    this.leaveEditMode();
    return false;
  },
  onFailure: function(transport) {
    this.options.onFailure(transport);
    if (this.oldInnerHTML) {
      this.element.innerHTML = this.oldInnerHTML;
      this.oldInnerHTML = null;
    }
    return false;
  },
  onSubmit: function() {
    // onLoading resets these so we need to save them away for the Ajax call
    var form = this.form;
    var value = this.editField.value;
    
    // do this first, sometimes the ajax call returns before we get a chance to switch on Saving...
    // which means this will actually switch on Saving... *after* we've left edit mode causing Saving...
    // to be displayed indefinitely
    this.onLoading();
    
    if (this.options.evalScripts) {
      new Ajax.Request(
        this.url, Object.extend({
          parameters: this.options.callback(form, value),
          onComplete: this.onComplete.bind(this),
          onFailure: this.onFailure.bind(this),
          asynchronous:true, 
          evalScripts:true
        }, this.options.ajaxOptions));
    } else  {
      new Ajax.Updater(
        { success: this.element,
          // don't update on failure (this could be an option)
          failure: null }, 
        this.url, Object.extend({
          parameters: this.options.callback(form, value),
          onComplete: this.onComplete.bind(this),
          onFailure: this.onFailure.bind(this)
        }, this.options.ajaxOptions));
    }
    // stop the event to avoid a page refresh in Safari
    if (arguments.length > 1) {
      Event.stop(arguments[0]);
    }
    return false;
  },
  onLoading: function() {
    this.saving = true;
    this.removeForm();
    this.leaveHover();
    this.showSaving();
  },
  showSaving: function() {
    this.oldInnerHTML = this.element.innerHTML;
    this.element.innerHTML = this.options.savingText;
    Element.addClassName(this.element, this.options.savingClassName);
    this.element.style.backgroundColor = this.originalBackground;
    Element.show(this.element);
  },
  removeForm: function() {
    if(this.form) {
      if (this.form.parentNode) Element.remove(this.form);
      this.form = null;
    }
  },
  enterHover: function() {
    if (this.saving) return;
    this.element.style.backgroundColor = this.options.highlightcolor;
    if (this.effect) {
      this.effect.cancel();
    }
    Element.addClassName(this.element, this.options.hoverClassName)
  },
  leaveHover: function() {
    if (this.options.backgroundColor) {
      this.element.style.backgroundColor = this.oldBackground;
    }
    Element.removeClassName(this.element, this.options.hoverClassName)
    if (this.saving) return;
    this.effect = new Effect.Highlight(this.element, {
      startcolor: this.options.highlightcolor,
      endcolor: this.options.highlightendcolor,
      restorecolor: this.originalBackground
    });
  },
  leaveEditMode: function() {
    Element.removeClassName(this.element, this.options.savingClassName);
    this.removeForm();
    this.leaveHover();
    this.element.style.backgroundColor = this.originalBackground;
    Element.show(this.element);
    if (this.options.externalControl) {
      Element.show(this.options.externalControl);
    }
    this.editing = false;
    this.saving = false;
    this.oldInnerHTML = null;
    this.onLeaveEditMode();
  },
  onComplete: function(transport) {
    this.leaveEditMode();
    this.options.onComplete.bind(this)(transport, this.element);
  },
  onEnterEditMode: function() {},
  onLeaveEditMode: function() {},
  dispose: function() {
    if (this.oldInnerHTML) {
      this.element.innerHTML = this.oldInnerHTML;
    }
    this.leaveEditMode();
    Event.stopObserving(this.element, 'click', this.onclickListener);
    Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
    Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
    if (this.options.externalControl) {
      Event.stopObserving(this.options.externalControl, 'click', this.onclickListener);
      Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener);
      Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
    }
  }
};

Ajax.InPlaceCollectionEditor = Class.create();
Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype);
Object.extend(Ajax.InPlaceCollectionEditor.prototype, {
  createEditField: function() {
    if (!this.cached_selectTag) {
      var selectTag = document.createElement("select");
      var collection = this.options.collection || [];
      var optionTag;
      collection.each(function(e,i) {
        optionTag = document.createElement("option");
        optionTag.value = (e instanceof Array) ? e[0] : e;
        if((typeof this.options.value == 'undefined') && 
          ((e instanceof Array) ? this.element.innerHTML == e[1] : e == optionTag.value)) optionTag.selected = true;
        if(this.options.value==optionTag.value) optionTag.selected = true;
        optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e));
        selectTag.appendChild(optionTag);
      }.bind(this));
      this.cached_selectTag = selectTag;
    }

    this.editField = this.cached_selectTag;
    if(this.options.loadTextURL) this.loadExternalText();
    this.form.appendChild(this.editField);
    this.options.callback = function(form, value) {
      return "value=" + encodeURIComponent(value);
    }
  }
});

// Delayed observer, like Form.Element.Observer, 
// but waits for delay after last key input
// Ideal for live-search fields

Form.Element.DelayedObserver = Class.create();
Form.Element.DelayedObserver.prototype = {
  initialize: function(element, delay, callback) {
    this.delay     = delay || 0.5;
    this.element   = $(element);
    this.callback  = callback;
    this.timer     = null;
    this.lastValue = $F(this.element); 
    Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
  },
  delayedListener: function(event) {
    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));
  }
};

/*
Copyright (c) 2005 JSON.org

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

The Software shall be used for Good, not Evil.

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

Array.prototype.______array = '______array';

Prado.JSON = {
    org: 'http://www.JSON.org',
    copyright: '(c)2005 JSON.org',
    license: 'http://www.crockford.com/JSON/license.html',

    stringify: function (arg) {
        var c, i, l, s = '', v;

        switch (typeof arg) {
        case 'object':
            if (arg) {
                if (arg.______array == '______array') {
                    for (i = 0; i < arg.length; ++i) {
                        v = this.stringify(arg[i]);
                        if (s) {
                            s += ',';
                        }
                        s += v;
                    }
                    return '[' + s + ']';
                } else if (typeof arg.toString != 'undefined') {
                    for (i in arg) {
                        v = arg[i];
                        if (typeof v != 'undefined' && typeof v != 'function') {
                            v = this.stringify(v);
                            if (s) {
                                s += ',';
                            }
                            s += this.stringify(i) + ':' + v;
                        }
                    }
                    return '{' + s + '}';
                }
            }
            return 'null';
        case 'number':
            return isFinite(arg) ? String(arg) : 'null';
        case 'string':
            l = arg.length;
            s = '"';
            for (i = 0; i < l; i += 1) {
                c = arg.charAt(i);
                if (c >= ' ') {
                    if (c == '\\' || c == '"') {
                        s += '\\';
                    }
                    s += c;
                } else {
                    switch (c) {
                        case '\b':
                            s += '\\b';
                            break;
                        case '\f':
                            s += '\\f';
                            break;
                        case '\n':
                            s += '\\n';
                            break;
                        case '\r':
                            s += '\\r';
                            break;
                        case '\t':
                            s += '\\t';
                            break;
                        default:
                            c = c.charCodeAt();
                            s += '\\u00' + Math.floor(c / 16).toString(16) +
                                (c % 16).toString(16);
                    }
                }
            }
            return s + '"';
        case 'boolean':
            return String(arg);
        default:
            return 'null';
        }
    },
    parse: function (text) {
        var at = 0;
        var ch = ' ';

        function error(m) {
            throw {
                name: 'JSONError',
                message: m,
                at: at - 1,
                text: text
            };
        }

        function next() {
            ch = text.charAt(at);
            at += 1;
            return ch;
        }

        function white() {
            while (ch) {
                if (ch <= ' ') {
                    next();
                } else if (ch == '/') {
                    switch (next()) {
                        case '/':
                            while (next() && ch != '\n' && ch != '\r') {}
                            break;
                        case '*':
                            next();
                            for (;;) {
                                if (ch) {
                                    if (ch == '*') {
                                        if (next() == '/') {
                                            next();
                                            break;
                                        }
                                    } else {
                                        next();
                                    }
                                } else {
                                    error("Unterminated comment");
                                }
                            }
                            break;
                        default:
                            error("Syntax error");
                    }
                } else {
                    break;
                }
            }
        }

        function string() {
            var i, s = '', t, u;

            if (ch == '"') {
outer:          while (next()) {
                    if (ch == '"') {
                        next();
                        return s;
                    } else if (ch == '\\') {
                        switch (next()) {
                        case 'b':
                            s += '\b';
                            break;
                        case 'f':
                            s += '\f';
                            break;
                        case 'n':
                            s += '\n';
                            break;
                        case 'r':
                            s += '\r';
                            break;
                        case 't':
                            s += '\t';
                            break;
                        case 'u':
                            u = 0;
                            for (i = 0; i < 4; i += 1) {
                                t = parseInt(next(), 16);
                                if (!isFinite(t)) {
                                    break outer;
                                }
                                u = u * 16 + t;
                            }
                            s += String.fromCharCode(u);
                            break;
                        default:
                            s += ch;
                        }
                    } else {
                        s += ch;
                    }
                }
            }
            error("Bad string");
        }

        function array() {
            var a = [];

            if (ch == '[') {
                next();
                white();
                if (ch == ']') {
                    next();
                    return a;
                }
                while (ch) {
                    a.push(value());
                    white();
                    if (ch == ']') {
                        next();
                        return a;
                    } else if (ch != ',') {
                        break;
                    }
                    next();
                    white();
                }
            }
            error("Bad array");
        }

        function object() {
            var k, o = {};

            if (ch == '{') {
                next();
                white();
                if (ch == '}') {
                    next();
                    return o;
                }
                while (ch) {
                    k = string();
                    white();
                    if (ch != ':') {
                        break;
                    }
                    next();
                    o[k] = value();
                    white();
                    if (ch == '}') {
                        next();
                        return o;
                    } else if (ch != ',') {
                        break;
                    }
                    next();
                    white();
                }
            }
            error("Bad object");
        }

        function number() {
            var n = '', v;
            if (ch == '-') {
                n = '-';
                next();
            }
            while (ch >= '0' && ch <= '9') {
                n += ch;
                next();
            }
            if (ch == '.') {
                n += '.';
                while (next() && ch >= '0' && ch <= '9') {
                    n += ch;
                }
            }
            if (ch == 'e' || ch == 'E') {
                n += 'e';
                next();
                if (ch == '-' || ch == '+') {
                    n += ch;
                    next();
                }
                while (ch >= '0' && ch <= '9') {
                    n += ch;
                    next();
                }
            }
            v = +n;
            if (!isFinite(v)) {
                ////error("Bad number");
            } else {
                return v;
            }
        }

        function word() {
            switch (ch) {
                case 't':
                    if (next() == 'r' && next() == 'u' && next() == 'e') {
                        next();
                        return true;
                    }
                    break;
                case 'f':
                    if (next() == 'a' && next() == 'l' && next() == 's' &&
                            next() == 'e') {
                        next();
                        return false;
                    }
                    break;
                case 'n':
                    if (next() == 'u' && next() == 'l' && next() == 'l') {
                        next();
                        return null;
                    }
                    break;
            }
            error("Syntax error");
        }

        function value() {
            white();
            switch (ch) {
                case '{':
                    return object();
                case '[':
                    return array();
                case '"':
                    return string();
                case '-':
                    return number();
                default:
                    return ch >= '0' && ch <= '9' ? number() : word();
            }
        }

        return value();
    }
};

Prado.AjaxRequest = Class.create();
Prado.AjaxRequest.prototype = Object.clone(Ajax.Request.prototype);

/**
 * Override Prototype's response implementation.
 */
Object.extend(Prado.AjaxRequest.prototype,
{
	/*initialize: function(request)
	{
		this.CallbackRequest = request;
		this.transport = Ajax.getTransport();
		this.setOptions(request.options);
		this.request(request.url);
	},*/

	/**
	 * Customize the response, dispatch onXXX response code events, and
	 * tries to execute response actions (javascript statements).
	 */
	respondToReadyState : function(readyState)
	{
	    var event = Ajax.Request.Events[readyState];
	    var transport = this.transport, json = this.getBodyDataPart(Prado.CallbackRequest.DATA_HEADER);

	    if (event == 'Complete')
	    {
			var redirectUrl = this.getBodyContentPart(Prado.CallbackRequest.REDIRECT_HEADER);
	    	if(redirectUrl)
	    		document.location.href = redirectUrl;

	      if ((this.getHeader('Content-type') || '').match(/^text\/javascript/i))
	      {
	        try
			{
	           json = eval('(' + transport.responseText + ')');
	        }catch (e)
			{
				if(typeof(json) == "string")
					json = Prado.CallbackRequest.decode(result);
			}
	      }

	      try
	      {
	      	Prado.CallbackRequest.updatePageState(this,transport);
			Ajax.Responders.dispatch('on' + transport.status, this, transport, json);
			Prado.CallbackRequest.dispatchActions(transport,this.getBodyDataPart(Prado.CallbackRequest.ACTION_HEADER));

	        (this.options['on' + this.transport.status]
	         || this.options['on' + (this.success() ? 'Success' : 'Failure')]
	         || Prototype.emptyFunction)(this, json);
	  	      } catch (e) {
	        this.dispatchException(e);
	      }
	    }

	    try {
	      (this.options['on' + event] || Prototype.emptyFunction)(this, json);
	      Ajax.Responders.dispatch('on' + event, this, transport, json);
	    } catch (e) {
	      this.dispatchException(e);
	    }

	    /* Avoid memory leak in MSIE: clean up the oncomplete event handler */
	    if (event == 'Complete')
	      this.transport.onreadystatechange = Prototype.emptyFunction;
	},

	/**
	 * Gets header data assuming JSON encoding.
	 * @param string header name
	 * @return object header data as javascript structures.
	 */
	getHeaderData : function(name)
	{
		return this.getJsonData(this.getHeader(name));
	},

	getBodyContentPart : function(name)
	{
		if(typeof(this.transport.responseText)=="string")
			return Prado.Element.extractContent(this.transport.responseText, name);
	},

	getJsonData : function(json)
	{
		try
		{
			return eval('(' + json + ')');
		}
		catch (e)
		{
			if(typeof(json) == "string")
				return Prado.CallbackRequest.decode(json);
		}
	},

	getBodyDataPart : function(name)
	{
		return this.getJsonData(this.getBodyContentPart(name));
	}
});

/**
 * Prado Callback client-side request handler.
 */
Prado.CallbackRequest = Class.create();

/**
 * Static definitions.
 */
Object.extend(Prado.CallbackRequest,
{
	/**
	 * Callback request target POST field name.
	 */
	FIELD_CALLBACK_TARGET : 'PRADO_CALLBACK_TARGET',
	/**
	 * Callback request parameter POST field name.
	 */
	FIELD_CALLBACK_PARAMETER : 'PRADO_CALLBACK_PARAMETER',
	/**
	 * Callback request page state field name,
	 */
	FIELD_CALLBACK_PAGESTATE : 'PRADO_PAGESTATE',

	FIELD_POSTBACK_TARGET : 'PRADO_POSTBACK_TARGET',

	FIELD_POSTBACK_PARAMETER : 'PRADO_POSTBACK_PARAMETER',

	/**
	 * List of form fields that will be collected during callback.
	 */
	PostDataLoaders : [],
	/**
	 * Response data header name.
	 */
	DATA_HEADER : 'X-PRADO-DATA',
	/**
	 * Response javascript execution statement header name.
	 */
	ACTION_HEADER : 'X-PRADO-ACTIONS',
	/**
	 * Response errors/exceptions header name.
	 */
	ERROR_HEADER : 'X-PRADO-ERROR',
	/**
	 * Page state header name.
	 */
	PAGESTATE_HEADER : 'X-PRADO-PAGESTATE',

	REDIRECT_HEADER : 'X-PRADO-REDIRECT',

	requestQueue : [],

	//all request objects
	requests : {},

	getRequestById : function(id)
	{
		var requests = Prado.CallbackRequest.requests;
		if(typeof(requests[id]) != "undefined")
			return requests[id];
	},

	dispatch : function(id)
	{
		var requests = Prado.CallbackRequest.requests;
		if(typeof(requests[id]) != "undefined")
			requests[id].dispatch();
	},

	/**
	 * Add ids of inputs element to post in the request.
	 */
	addPostLoaders : function(ids)
	{
		var self = Prado.CallbackRequest;
		self.PostDataLoaders = self.PostDataLoaders.concat(ids);
		var list = [];
		self.PostDataLoaders.each(function(id)
		{
			if(list.indexOf(id) < 0)
				list.push(id);
		});
		self.PostDataLoaders = list;
	},

	/**
	 * Dispatch callback response actions.
	 */
	dispatchActions : function(transport,actions)
	{
		var self = Prado.CallbackRequest;
		if(actions && actions.length > 0)
			actions.each(self.__run.bind(self,transport));
	},

	/**
	 * Prase and evaluate a Callback clien-side action
	 */
	__run : function(transport, command)
	{
		var self = Prado.CallbackRequest;
		self.transport = transport;
		for(var method in command)
		{
			try
			{
				method.toFunction().apply(self,command[method]);
			}
			catch(e)
			{
				if(typeof(Logger) != "undefined")
					self.Exception.onException(null,e);
			}
		}
	},

	/**
	 * Respond to Prado Callback request exceptions.
	 */
	Exception :
	{
		/**
		 * Server returns 500 exception. Just log it.
		 */
		"on500" : function(request, transport, data)
		{
			var e = request.getHeaderData(Prado.CallbackRequest.ERROR_HEADER);
			Logger.error("Callback Server Error "+e.code, this.formatException(e));
		},

		/**
		 * Callback OnComplete event,logs reponse and data to console.
		 */
		'on200' : function(request, transport, data)
		{
			if(transport.status < 500)
			{
				var msg = 'HTTP '+transport.status+" with response : \n";
				if(transport.responseText.trim().length >0)
				{
					var f = RegExp('(<!--X-PRADO[^>]+-->)([\\s\\S\\w\\W]*)(<!--//X-PRADO[^>]+-->)',"m");
					msg += transport.responseText.replace(f,'') + "\n";
				}
				if(typeof(data)!="undefined" && data != null)
					msg += "Data : \n"+inspect(data)+"\n";
				data = request.getBodyDataPart(Prado.CallbackRequest.ACTION_HEADER);
				if(data && data.length > 0)
				{
					msg += "Actions : \n";
					data.each(function(action)
					{
						msg += inspect(action)+"\n";
					});
				}
				Logger.info(msg);
			}
		},

		/**
		 * Uncaught exceptions during callback response.
		 */
		onException : function(request,e)
		{
			msg = "";
			$H(e).each(function(item)
			{
				msg += item.key+": "+item.value+"\n";
			})
			Logger.error('Uncaught Callback Client Exception:', msg);
		},

		/**
		 * Formats the exception message for display in console.
		 */
		formatException : function(e)
		{
			var msg = e.type + " with message \""+e.message+"\"";
			msg += " in "+e.file+"("+e.line+")\n";
			msg += "Stack trace:\n";
			var trace = e.trace;
			for(var i = 0; i<trace.length; i++)
			{
				msg += "  #"+i+" "+trace[i].file;
				msg += "("+trace[i].line+"): ";
				msg += trace[i]["class"]+"->"+trace[i]["function"]+"()"+"\n";
			}
			msg += e.version+" "+e.time+"\n";
			return msg;
		}
	},

	/**
	 * @return string JSON encoded data.
	 */
	encode : function(data)
	{
		return Prado.JSON.stringify(data);
	},

	/**
	 * @return mixed javascript data decoded from string using JSON decoding.
	 */
	decode : function(data)
	{
		if(typeof(data) == "string" && data.trim().length > 0)
			return Prado.JSON.parse(data);
		else
			return null;
	},

	/**
	 * Dispatch a normal request, no timeouts or aborting of requests.
	 */
	dispatchNormalRequest : function(callback)
	{
		callback.options.postBody = callback._getPostData(),
		callback.request(callback.url);
		return true;
	},

	/**
	 * Abort the current priority request in progress.
	 */
	tryNextRequest : function()
	{
		var self = Prado.CallbackRequest;
		//Logger.debug('trying next request');
		if(typeof(self.currentRequest) == 'undefined' || self.currentRequest==null)
		{
			if(self.requestQueue.length > 0)
				return self.dispatchQueue();
			//else
				//Logger.warn('empty queque');
		}
//		else
	//		Logger.warn('current request ' + self.currentRequest.id);
	},

	/**
	 * Updates the page state. It will update only if EnablePageStateUpdate and
	 * HasPriority options are both true.
	 */
	updatePageState : function(request, transport)
	{
		var self = Prado.CallbackRequest;
		var pagestate = $(self.FIELD_CALLBACK_PAGESTATE);
		var enabled = request.ActiveControl.EnablePageStateUpdate && request.ActiveControl.HasPriority;
		var aborted = typeof(self.currentRequest) == 'undefined' || self.currentRequest == null;
		if(enabled && !aborted && pagestate)
		{
			var data = request.getBodyContentPart(self.PAGESTATE_HEADER);
			if(typeof(data) == "string" && data.length > 0)
				pagestate.value = data;
			else
			{
				if(typeof(Logger) != "undefined")
					Logger.warn("Missing page state:"+data);
//				Logger.warn('## bad state: setting current request to null');
				self.endCurrentRequest();
				//self.tryNextRequest();
				return false;
			}
		}
		self.endCurrentRequest();
	//	Logger.warn('## state updated: setting current request to null');
	//	self.tryNextRequest();
		return true;
	},

	enqueue : function(callback)
	{
		var self = Prado.CallbackRequest;
		self.requestQueue.push(callback);
		//Logger.warn("equeued "+callback.id+", current queque length="+self.requestQueue.length);
		self.tryNextRequest();
	},

	dispatchQueue : function()
	{
		var self = Prado.CallbackRequest;
		//Logger.warn("dispatching queque, length="+self.requestQueue.length+" request="+self.currentRequest);
		var callback = self.requestQueue.shift();
		self.currentRequest = callback;

		//get data
		callback.options.postBody = callback._getPostData(),

		//callback.request = new Prado.AjaxRequest(callback);
		callback.timeout = setTimeout(function()
		{
			//Logger.warn("priority timeout");
			self.abortRequest(callback.id);
		},callback.ActiveControl.RequestTimeOut);
		callback.request(callback.url);
		//Logger.debug("dispatched "+self.currentRequest.id + " ...")
	},

	endCurrentRequest : function()
	{
		var self = Prado.CallbackRequest;
		if(typeof(self.currentRequest) != 'undefined' && self.currentRequest != null)
			clearTimeout(self.currentRequest.timeout);
		self.currentRequest=null;
	},

	abortRequest : function(id)
	{
		//Logger.warn("abort id="+id);
		var self = Prado.CallbackRequest;
		if(typeof(self.currentRequest) != 'undefined'
			&& self.currentRequest != null && self.currentRequest.id == id)
		{
			var request = self.currentRequest;
			if(request.transport.readyState < 4)
				request.transport.abort();
			//Logger.warn('## aborted: setting current request to null');
			self.endCurrentRequest();
		}
		self.tryNextRequest();
	}
});

/**
 * Automatically aborts the current request when a priority request has returned.
 */
Ajax.Responders.register({onComplete : function(request)
{
	if(request.ActiveControl.HasPriority)
		Prado.CallbackRequest.tryNextRequest();
}});

//Add HTTP exception respones when logger is enabled.
Event.OnLoad(function()
{
	if(typeof Logger != "undefined")
		Ajax.Responders.register(Prado.CallbackRequest.Exception);
});

/**
 * Create and prepare a new callback request.
 * Call the dispatch() method to start the callback request.
 * <code>
 * request = new Prado.CallbackRequest(UniqueID, callback);
 * request.dispatch();
 * </code>
 */
Prado.CallbackRequest.prototype = Object.extend(Prado.AjaxRequest.prototype,
{

	/**
	 * Prepare and inititate a callback request.
	 */
	initialize : function(id, options)
	{
	/**
	 * Callback URL, same url as the current page.
	 */
		this.url = this.getCallbackUrl();

		this.transport = Ajax.getTransport();
//		this.setOptions(request.options);
//		this.request(request.url);
		/**
		 * Current callback request.
		 */
		//this.request = null;

		this.Enabled = true;

		this.id = id;
		if(typeof(id)=="string")
			Prado.CallbackRequest.requests[id] = this;

		this.setOptions(Object.extend(
		{
			RequestTimeOut : 30000, // 30 second timeout.
			EnablePageStateUpdate : true,
			HasPriority : true,
			CausesValidation : true,
			ValidationGroup : null,
			PostInputs : true
		}, options || {}));

		this.ActiveControl = this.options;
	},

	/**
	 * Gets the url from the forms that contains the PRADO_PAGESTATE
	 * @return {String} callback url.
	 */
	getCallbackUrl : function()
	{
		return $('PRADO_PAGESTATE').form.action;
	},

	/**
	 * Sets the request parameter
	 * @param {Object} parameter value
	 */
	setCallbackParameter : function(value)
	{
		this.ActiveControl['CallbackParameter'] = value;
	},

	/**
	 * @return {Object} request paramater value.
	 */
	getCallbackParameter : function()
	{
		return this.ActiveControl['CallbackParameter'];
	},

	/**
	 * Sets the callback request timeout.
	 * @param {integer} timeout in  milliseconds
	 */
	setRequestTimeOut : function(timeout)
	{
		this.ActiveControl['RequestTimeOut'] = timeout;
	},

	/**
	 * @return {integer} request timeout in milliseconds
	 */
	getRequestTimeOut : function()
	{
		return this.ActiveControl['RequestTimeOut'];
	},

	/**
	 * Set true to enable validation on callback dispatch.
	 * @param {boolean} true to validate
	 */
	setCausesValidation : function(validate)
	{
		this.ActiveControl['CausesValidation'] = validate;
	},

	/**
	 * @return {boolean} validate on request dispatch
	 */
	getCausesValidation : function()
	{
		return this.ActiveControl['CausesValidation'];
	},

	/**
	 * Sets the validation group to validate during request dispatch.
	 * @param {string} validation group name
	 */
	setValidationGroup : function(group)
	{
		this.ActiveControl['ValidationGroup'] = group;
	},

	/**
	 * @return {string} validation group name.
	 */
	getValidationGroup : function()
	{
		return this.ActiveControl['ValidationGroup'];
	},

	/**
	 * Dispatch the callback request.
	 */
	dispatch : function()
	{
		//Logger.info("dispatching request");
		//trigger tinyMCE to save data.
		if(typeof tinyMCE != "undefined")
			tinyMCE.triggerSave();

		if(this.ActiveControl.CausesValidation && typeof(Prado.Validation) != "undefined")
		{
			var form =  this.ActiveControl.Form || Prado.Validation.getForm();
			if(Prado.Validation.validate(form,this.ActiveControl.ValidationGroup,this) == false)
				return false;
		}

		if(this.ActiveControl.onPreDispatch)
			this.ActiveControl.onPreDispatch(this,null);

		if(!this.Enabled)
			return;

		if(this.ActiveControl.HasPriority)
		{
			return Prado.CallbackRequest.enqueue(this);
			//return Prado.CallbackRequest.dispatchPriorityRequest(this);
		}
		else
			return Prado.CallbackRequest.dispatchNormalRequest(this);
	},

	abort : function()
	{
		return Prado.CallbackRequest.abortRequest(this.id);
	},

	/**
	 * Collects the form inputs, encode the parameters, and sets the callback
	 * target id. The resulting string is the request content body.
	 * @return string request body content containing post data.
	 */
	_getPostData : function()
	{
		var data = {};
		var callback = Prado.CallbackRequest;
		if(this.ActiveControl.PostInputs != false)
		{
			callback.PostDataLoaders.each(function(name)
			{
				$A(document.getElementsByName(name)).each(function(element)
				{
					//IE will try to get elements with ID == name as well.
					if(element.type && element.name == name)
					{
						value = $F(element);
						if(typeof(value) != "undefined" && value != null)
							data[name] = value;
					}
				})
			})
		}
		if(typeof(this.ActiveControl.CallbackParameter) != "undefined")
			data[callback.FIELD_CALLBACK_PARAMETER] = callback.encode(this.ActiveControl.CallbackParameter);
		var pageState = $F(callback.FIELD_CALLBACK_PAGESTATE);
		if(typeof(pageState) != "undefined")
			data[callback.FIELD_CALLBACK_PAGESTATE] = pageState;
		data[callback.FIELD_CALLBACK_TARGET] = this.id;
		if(this.ActiveControl.EventTarget)
			data[callback.FIELD_POSTBACK_TARGET] = this.ActiveControl.EventTarget;
		if(this.ActiveControl.EventParameter)
			data[callback.FIELD_POSTBACK_PARAMETER] = this.ActiveControl.EventParameter;
		return $H(data).toQueryString();
	}
});

/**
 * Create a new callback request using default settings.
 * @param string callback handler unique ID.
 * @param mixed parameter to pass to callback handler on the server side.
 * @param function client side onSuccess event handler.
 * @param object additional request options.
 * @return boolean always false.
 */
Prado.Callback = function(UniqueID, parameter, onSuccess, options)
{
	var callback =
	{
		'CallbackParameter' : parameter || '',
		'onSuccess' : onSuccess || Prototype.emptyFunction
	};

	Object.extend(callback, options || {});

	request = new Prado.CallbackRequest(UniqueID, callback);
	request.dispatch();
	return false;
};

/**
 * Generic postback control.
 */
Prado.WebUI.CallbackControl = Class.extend(Prado.WebUI.PostBackControl,
{
	onPostBack : function(event, options)
	{
		var request = new Prado.CallbackRequest(options.EventTarget, options);
		request.dispatch();
		Event.stop(event);
	}
});

/**
 * TActiveButton control.
 */
Prado.WebUI.TActiveButton = Class.extend(Prado.WebUI.CallbackControl);
/**
 * TActiveLinkButton control.
 */
Prado.WebUI.TActiveLinkButton = Class.extend(Prado.WebUI.CallbackControl);

Prado.WebUI.TActiveImageButton = Class.extend(Prado.WebUI.TImageButton,
{
	onPostBack : function(event, options)
	{
		this.addXYInput(event,options);
		var request = new Prado.CallbackRequest(options.EventTarget, options);
		request.dispatch();
		Event.stop(event);
	}
});
/**
 * Active check box.
 */
Prado.WebUI.TActiveCheckBox = Class.extend(Prado.WebUI.CallbackControl,
{
	onPostBack : function(event, options)
	{
		var request = new Prado.CallbackRequest(options.EventTarget, options);
		if(request.dispatch()==false)
			Event.stop(event);
	}
});

/**
 * TActiveRadioButton control.
 */
Prado.WebUI.TActiveRadioButton = Class.extend(Prado.WebUI.TActiveCheckBox);


Prado.WebUI.TActiveCheckBoxList = Base.extend(
{
	constructor : function(options)
	{
		for(var i = 0; i<options.ItemCount; i++)
		{
			var checkBoxOptions = Object.extend(
			{
				ID : options.ListID+"_c"+i,
				EventTarget : options.ListName+"$c"+i
			}, options);
			new Prado.WebUI.TActiveCheckBox(checkBoxOptions);
		}
	}
});

Prado.WebUI.TActiveRadioButtonList = Prado.WebUI.TActiveCheckBoxList;

/**
 * TActiveTextBox control, handles onchange event.
 */
Prado.WebUI.TActiveTextBox = Class.extend(Prado.WebUI.TTextBox,
{
	onInit : function(options)
	{
		this.options=options;
		if(options['TextMode'] != 'MultiLine')
			Event.observe(this.element, "keydown", this.handleReturnKey.bind(this));
		if(this.options['AutoPostBack']==true)
			Event.observe(this.element, "change", this.doCallback.bindEvent(this,options));
	},

	doCallback : function(event, options)
	{
		var request = new Prado.CallbackRequest(options.EventTarget, options);
		request.dispatch();
		Event.stop(event);
	}
});

/**
 * TAutoComplete control.
 */
Prado.WebUI.TAutoComplete = Class.extend(Autocompleter.Base, Prado.WebUI.TActiveTextBox.prototype);
Prado.WebUI.TAutoComplete = Class.extend(Prado.WebUI.TAutoComplete,
{
	initialize : function(options)
	{
		this.options = options;
		this.hasResults = false;
		this.baseInitialize(options.ID, options.ResultPanel, options);
		Object.extend(this.options,
		{
			onSuccess : this.onComplete.bind(this)
		});

		if(options.AutoPostBack)
			this.onInit(options);
	},

	doCallback : function(event, options)
	{
		if(!this.active)
		{
			var request = new Prado.CallbackRequest(this.options.EventTarget, options);
			request.dispatch();
			Event.stop(event);
		}
	},

	 //Overrides parent implementation, fires onchange event.
	onClick: function(event)
	{
	    var element = Event.findElement(event, 'LI');
	    this.index = element.autocompleteIndex;
	    this.selectEntry();
	    this.hide();
		Event.fireEvent(this.element, "change");
	},

	getUpdatedChoices : function()
	{
		var options = new Array(this.getToken(),"__TAutoComplete_onSuggest__");
		Prado.Callback(this.options.EventTarget, options, null, this.options);
	},

	/**
	 * Overrides parent implements, don't update if no results.
	 */
	selectEntry: function()
	{
		if(this.hasResults)
		{
			this.active = false;
			this.updateElement(this.getCurrentEntry());
			var options = [this.index, "__TAutoComplete_onSuggestionSelected__"];
			Prado.Callback(this.options.EventTarget, options, null, this.options);
		}
	},

	onComplete : function(request, boundary)
  	{
  		var result = Prado.Element.extractContent(request.transport.responseText, boundary);
  		if(typeof(result) == "string")
		{
			if(result.length > 0)
			{
				this.hasResults = true;
				this.updateChoices(result);
			}
			else
			{
				this.active = false;
				this.hasResults = false;
				this.hide();
			}
		}
	}
});

/**
 * Time Triggered Callback class.
 */
Prado.WebUI.TTimeTriggeredCallback = Base.extend(
{
	constructor : function(options)
	{
		this.options = Object.extend({ Interval : 1	}, options || {});
		Prado.WebUI.TTimeTriggeredCallback.register(this);
	},

	startTimer : function()
	{
		setTimeout(this.onTimerEvent.bind(this), 100);
		if(typeof(this.timer) == 'undefined' || this.timer == null)
			this.timer = setInterval(this.onTimerEvent.bind(this),this.options.Interval*1000);
	},

	stopTimer : function()
	{
		if(typeof(this.timer) != 'undefined')
		{
			clearInterval(this.timer);
			this.timer = null;
		}
	},

	onTimerEvent : function()
	{
		var request = new Prado.CallbackRequest(this.options.EventTarget, this.options);
		request.dispatch();
	}
},
//class methods
{
	timers : {},

	register : function(timer)
	{
		Prado.WebUI.TTimeTriggeredCallback.timers[timer.options.ID] = timer;
	},

	start : function(id)
	{
		if(Prado.WebUI.TTimeTriggeredCallback.timers[id])
			Prado.WebUI.TTimeTriggeredCallback.timers[id].startTimer();
	},

	stop : function(id)
	{
		if(Prado.WebUI.TTimeTriggeredCallback.timers[id])
			Prado.WebUI.TTimeTriggeredCallback.timers[id].stopTimer();
	}
});

Prado.WebUI.ActiveListControl = Base.extend(
{
	constructor : function(options)
	{
		this.element = $(options.ID);
		if(this.element)
		{
			this.options = options;
			Event.observe(this.element, "change", this.doCallback.bind(this));
		}
	},

	doCallback : function(event)
	{
		var request = new Prado.CallbackRequest(this.options.EventTarget, this.options);
		request.dispatch();
		Event.stop(event);
	}
});

Prado.WebUI.TActiveDropDownList = Prado.WebUI.ActiveListControl;
Prado.WebUI.TActiveListBox = Prado.WebUI.ActiveListControl;

/**
 * Observe event of a particular control to trigger a callback request.
 */
Prado.WebUI.TEventTriggeredCallback = Base.extend(
{
	constructor : function(options)
	{
		this.options = options;
		var element = $(options['ControlID']);
		if(element)
			Event.observe(element, this.getEventName(element), this.doCallback.bind(this));
	},

	getEventName : function(element)
	{
		var name = this.options.EventName;
   		if(typeof(name) == "undefined" && element.type)
		{
      		switch (element.type.toLowerCase())
			{
          		case 'password':
		        case 'text':
		        case 'textarea':
		        case 'select-one':
		        case 'select-multiple':
          			return 'change';
      		}
		}
		return typeof(name) == "undefined"  || name == "undefined" ? 'click' : name;
    },

	doCallback : function(event)
	{
		var request = new Prado.CallbackRequest(this.options.EventTarget, this.options);
		request.dispatch();
		if(this.options.StopEvent == true)
			Event.stop(event);
	}
});

/**
 * Observe changes to a property of a particular control to trigger a callback.
 */
Prado.WebUI.TValueTriggeredCallback = Base.extend(
{
	count : 1,

	observing : true,

	constructor : function(options)
	{
		this.options = options;
		this.options.PropertyName = this.options.PropertyName || 'value';
		var element = $(options['ControlID']);
		this.value = element ? element[this.options.PropertyName] : undefined;
		Prado.WebUI.TValueTriggeredCallback.register(this);
		this.startObserving();
	},

	stopObserving : function()
	{
		clearTimeout(this.timer);
		this.observing = false;
	},

	startObserving : function()
	{
		this.timer = setTimeout(this.checkChanges.bind(this), this.options.Interval*1000);
	},

	checkChanges : function()
	{
		var element = $(this.options.ControlID);
		if(element)
		{
			var value = element[this.options.PropertyName];
			if(this.value != value)
			{
				this.doCallback(this.value, value);
				this.value = value;
				this.count=1;
			}
			else
				this.count = this.count + this.options.Decay;
			if(this.observing)
				this.time = setTimeout(this.checkChanges.bind(this),
					parseInt(this.options.Interval*1000*this.count));
		}
	},

	doCallback : function(oldValue, newValue)
	{
		var request = new Prado.CallbackRequest(this.options.EventTarget, this.options);
		var param = {'OldValue' : oldValue, 'NewValue' : newValue};
		request.setCallbackParameter(param);
		request.dispatch();
	}
},
//class methods
{
	timers : {},

	register : function(timer)
	{
		Prado.WebUI.TValueTriggeredCallback.timers[timer.options.ID] = timer;
	},

	stop : function(id)
	{
		Prado.WebUI.TValueTriggeredCallback.timers[id].stopObserving();
	}
});

Prado.WebUI.TInPlaceTextBox = Base.extend(
{
	constructor : function(options)
	{

		this.isSaving = false;
		this.isEditing = false;
		this.editField = null;

		this.options = Object.extend(
		{
			LoadTextFromSource : false,
			TextMode : 'SingleLine'

		}, options || {});
		this.element = $(this.options.ID);
		Prado.WebUI.TInPlaceTextBox.register(this);
		this.createEditorInput();
		this.initializeListeners();
	},

	/**
	 * Initialize the listeners.
	 */
	initializeListeners : function()
	{
		this.onclickListener = this.enterEditMode.bindAsEventListener(this);
	    Event.observe(this.element, 'click', this.onclickListener);
	    if (this.options.ExternalControl)
			Event.observe($(this.options.ExternalControl), 'click', this.onclickListener);
	},

	/**
	 * Changes the panel to an editable input.
	 * @param {Event} evt event source
	 */
	enterEditMode :  function(evt)
	{
	    if (this.isSaving || this.isEditing) return;
	    this.isEditing = true;
		this.onEnterEditMode();
		this.createEditorInput();
		this.showTextBox();
		this.editField.disabled = false;
		if(this.options.LoadTextOnEdit)
			this.loadExternalText();
		Prado.Element.focus(this.editField);
		if (evt)
			Event.stop(evt);
    	return false;
	},

	exitEditMode : function(evt)
	{
		this.isEditing = false;
		this.isSaving = false;
		this.editField.disabled = false;
		this.element.innerHTML = this.editField.value;
		this.showLabel();
	},

	showTextBox : function()
	{
		Element.hide(this.element);
		Element.show(this.editField);
	},

	showLabel : function()
	{
		Element.show(this.element);
		Element.hide(this.editField);
	},

	/**
	 * Create the edit input field.
	 */
	createEditorInput : function()
	{
		if(this.editField == null)
			this.createTextBox();

		this.editField.value = this.getText();
	},

	loadExternalText : function()
	{
		this.editField.disabled = true;
		this.onLoadingText();
		options = new Array('__InlineEditor_loadExternalText__', this.getText());
		request = new Prado.CallbackRequest(this.options.EventTarget, this.options);
		request.setCausesValidation(false);
		request.setCallbackParameter(options);
		request.ActiveControl.onSuccess = this.onloadExternalTextSuccess.bind(this);
		request.ActiveControl.onFailure = this.onloadExternalTextFailure.bind(this);
		request.dispatch();
	},

	/**
	 * Create a new input textbox or textarea
	 */
	createTextBox : function()
	{
		cssClass= this.element.className || '';
		inputName = this.options.EventTarget;
		options = {'className' : cssClass, name : inputName, id : this.options.TextBoxID};
		if(this.options.TextMode == 'SingleLine')
		{
			if(this.options.MaxLength > 0)
				options['maxlength'] = this.options.MaxLength;
			this.editField = INPUT(options);
		}
		else
		{
			if(this.options.Rows > 0)
				options['rows'] = this.options.Rows;
			if(this.options.Columns > 0)
				options['cols'] = this.options.Columns;
			if(this.options.Wrap)
				options['wrap'] = 'off';
			this.editField = TEXTAREA(options);
		}

		this.editField.style.display="none";
		this.element.parentNode.insertBefore(this.editField,this.element)

		//handle return key within single line textbox
		if(this.options.TextMode == 'SingleLine')
		{
			Event.observe(this.editField, "keydown", function(e)
			{
				 if(Event.keyCode(e) == Event.KEY_RETURN)
		        {
					var target = Event.element(e);
					if(target)
					{
						Event.fireEvent(target, "blur");
						Event.stop(e);
					}
				}
			});
		}

		Event.observe(this.editField, "blur", this.onTextBoxBlur.bind(this));
		Event.observe(this.editField, "keypress", this.onKeyPressed.bind(this));
	},

	/**
	 * @return {String} panel inner html text.
	 */
	getText: function()
	{
    	return this.element.innerHTML;
  	},

	/**
	 * Edit mode entered, calls optional event handlers.
	 */
	onEnterEditMode : function()
	{
		if(typeof(this.options.onEnterEditMode) == "function")
			this.options.onEnterEditMode(this,null);
	},

	onTextBoxBlur : function(e)
	{
		text = this.element.innerHTML;
		if(this.options.AutoPostBack && text != this.editField.value)
		{
			if(this.isEditing)
				this.onTextChanged(text);
		}
		else
		{
			this.element.innerHTML = this.editField.value;
			this.isEditing = false;
			if(this.options.AutoHide)
				this.showLabel();
		}
	},

	onKeyPressed : function(e)
	{
		if (Event.keyCode(e) == Event.KEY_ESC)
		{
			this.editField.value = this.getText();
			this.isEditing = false;
			if(this.options.AutoHide)
				this.showLabel();
		}
		else if (Event.keyCode(e) == Event.KEY_RETURN)
			Event.stop(e);
	},

	/**
	 * When the text input value has changed.
	 * @param {String} original text
	 */
	onTextChanged : function(text)
	{
		request = new Prado.CallbackRequest(this.options.EventTarget, this.options);
		request.setCallbackParameter(text);
		request.ActiveControl.onSuccess = this.onTextChangedSuccess.bind(this);
		request.ActiveControl.onFailure = this.onTextChangedFailure.bind(this);
		if(request.dispatch())
		{
			this.isSaving = true;
			this.editField.disabled = true;
		}
	},

	/**
	 * When loading external text.
	 */
	onLoadingText : function()
	{
		//Logger.info("on loading text");
	},

	onloadExternalTextSuccess : function(request, parameter)
	{
		this.isEditing = true;
		this.editField.disabled = false;
		this.editField.value = this.getText();
		Prado.Element.focus(this.editField);
		if(typeof(this.options.onSuccess)=="function")
			this.options.onSuccess(sender,parameter);
	},

	onloadExternalTextFailure : function(request, parameter)
	{
		this.isSaving = false;
		this.isEditing = false;
		this.showLabel();
		if(typeof(this.options.onFailure)=="function")
			this.options.onFailure(sender,parameter);
	},

	/**
	 * Text change successfully.
	 * @param {Object} sender
	 * @param {Object} parameter
	 */
	onTextChangedSuccess : function(sender, parameter)
	{
		this.isSaving = false;
		this.isEditing = false;
		if(this.options.AutoHide)
			this.showLabel();
		this.element.innerHTML = parameter == null ? this.editField.value : parameter;
		this.editField.disabled = false;
		if(typeof(this.options.onSuccess)=="function")
			this.options.onSuccess(sender,parameter);
	},

	onTextChangedFailure : function(sender, parameter)
	{
		this.editField.disabled = false;
		this.isSaving = false;
		this.isEditing = false;
		if(typeof(this.options.onFailure)=="function")
			this.options.onFailure(sender,parameter);
	}
},
{
	textboxes : {},

	register : function(obj)
	{
		Prado.WebUI.TInPlaceTextBox.textboxes[obj.options.TextBoxID] = obj;
	},

	setDisplayTextBox : function(id,value)
	{
		var textbox = Prado.WebUI.TInPlaceTextBox.textboxes[id];
		if(textbox)
		{
			if(value)
				textbox.enterEditMode(null);
			else
			{
				textbox.exitEditMode(null);
			}
		}
	}
});
Prado.WebUI.TActiveRatingList = Base.extend(
{
	selectedIndex : -1,
	rating: -1,
	enabled : true,
	readOnly : false,

	constructor : function(options)
	{
		var cap = $(options.CaptionID);
		this.options = Object.extend(
		{
			caption : cap ? cap.innerHTML : ''
		}, options || {});

		Prado.WebUI.TActiveRatingList.register(this);
		this._init();
		this.selectedIndex = options.SelectedIndex;
		this.rating = options.Rating;
		if(options.Rating <= 0 && options.SelectedIndex >= 0)
			this.rating = options.SelectedIndex+1;
		this.showRating(this.rating);
	},

	_init: function(options)
	{
		Element.addClassName($(this.options.ListID),this.options.Style);
		this.radios = new Array();
		var index=0;
		for(var i = 0; i<this.options.ItemCount; i++)
		{
			var radio = $(this.options.ListID+'_c'+i);
			var td = radio.parentNode;
			if(radio && td.tagName.toLowerCase()=='td')
			{
				this.radios.push(radio);
				Event.observe(td, "mouseover", this.hover.bindEvent(this,index));
				Event.observe(td, "mouseout", this.recover.bindEvent(this,index));
				Event.observe(td, "click", this.click.bindEvent(this, index));
				index++;
				Element.addClassName(td,"rating");
			}
		}
	},

	hover : function(ev,index)
	{
		if(this.enabled==false) return;
		for(var i = 0; i<this.radios.length; i++)
		{
			var node = this.radios[i].parentNode;
			var action = i <= index ? 'addClassName' : 'removeClassName'
			Element[action](node,"rating_hover");
			Element.removeClassName(node,"rating_selected");
			Element.removeClassName(node,"rating_half");
		}
		this.showCaption(this.getIndexCaption(index));
	},

	recover : function(ev,index)
	{
		if(this.enabled==false) return;
		this.showRating(this.rating);
		this.showCaption(this.options.caption);
	},

	click : function(ev, index)
	{
		if(this.enabled==false) return;
		for(var i = 0; i<this.radios.length; i++)
			this.radios[i].checked = (i == index);

		this.selectedIndex = index;
		this.setRating(index+1);

		this.dispatchRequest(ev);
	},

	dispatchRequest : function(ev)
	{
		var requestOptions = Object.extend(
		{
			ID : this.options.ListID+"_c"+this.selectedIndex,
			EventTarget : this.options.ListName+"$c"+this.selectedIndex
		},this.options);
		var request = new Prado.CallbackRequest(requestOptions.EventTarget, requestOptions);
		if(request.dispatch()==false)
			Event.stop(ev);
	},

	setRating : function(value)
	{
		this.rating = value;
		var base = Math.floor(value-1);
		var remainder = value - base-1;
		var halfMax = this.options.HalfRating["1"];
		var index = remainder > halfMax ? base+1 : base;
		for(var i = 0; i<this.radios.length; i++)
			this.radios[i].checked = (i == index);

		var caption = this.getIndexCaption(index);
		this.setCaption(caption);
		this.showCaption(caption);

		this.showRating(value);
	},

	showRating: function(value)
	{
		var base = Math.floor(value-1);
		var remainder = value - base-1;
		var halfMin = this.options.HalfRating["0"];
		var halfMax = this.options.HalfRating["1"];
		var index = remainder > halfMax ? base+1 : base;
		var hasHalf = remainder >= halfMin && remainder <= halfMax;
		for(var i = 0; i<this.radios.length; i++)
		{
			var node = this.radios[i].parentNode;
			var action = i > index ? 'removeClassName' : 'addClassName';
			Element[action](node, "rating_selected");
			if(i==index+1 && hasHalf)
				Element.addClassName(node, "rating_half");
			else
				Element.removeClassName(node, "rating_half");
			Element.removeClassName(node,"rating_hover");
		}
	},

	getIndexCaption : function(index)
	{
		return index > -1 ? this.radios[index].value : this.options.caption;
	},

	showCaption : function(value)
	{
		var caption = $(this.options.CaptionID);
		if(caption) caption.innerHTML = value;
		$(this.options.ListID).title = value;
	},

	setCaption : function(value)
	{
		this.options.caption = value;
		this.showCaption(value);
	},

	setEnabled : function(value)
	{
		this.enabled = value;
		for(var i = 0; i<this.radios.length; i++)
		{
			var action = value ? 'removeClassName' : 'addClassName'
			Element[action](this.radios[i].parentNode, "rating_disabled");
		}
	}
},
{
ratings : {},
register : function(rating)
{
	Prado.WebUI.TActiveRatingList.ratings[rating.options.ListID] = rating;
},

setEnabled : function(id,value)
{
	Prado.WebUI.TActiveRatingList.ratings[id].setEnabled(value);
},

setRating : function(id,value)
{
	Prado.WebUI.TActiveRatingList.ratings[id].setRating(value);
},

setCaption : function(id,value)
{
	Prado.WebUI.TActiveRatingList.ratings[id].setCaption(value);
}
});
/**
 * Prado client-side javascript validation fascade.
 *
 * There are 4 basic classes, Validation, ValidationManager, ValidationSummary
 * and TBaseValidator, that interact together to perform validation.
 * The <tt>Prado.Validation</tt> class co-ordinates together the
 * validation scheme and is responsible for maintaining references
 * to ValidationManagers.
 *
 * The ValidationManager class is responsible for maintaining refereneces
 * to individual validators, validation summaries and their associated
 * groupings.
 *
 * The ValidationSummary take cares of display the validator error messages
 * as html output or an alert output.
 *
 * The TBaseValidator is the base class for all validators and contains
 * methods to interact with the actual inputs, data type conversion.
 *
 * An instance of ValidationManager must be instantiated first for a
 * particular form before instantiating validators and summaries.
 *
 * Usage example: adding a required field to a text box input with
 * ID "input1" in a form with ID "form1".
 * <code>
 * <script type="text/javascript" src="../prado.js"></script>
 * <script type="text/javascript" src="../validator.js"></script>
 * <form id="form1" action="...">
 * <div>
 * 	<input type="text" id="input1" />
 *  <span id="validator1" style="display:none; color:red">*</span>
 *  <input type="submit text="submit" />
 * <script type="text/javascript">
 * new Prado.ValidationManager({FormID : 'form1'});
 * var options =
 * {
 *		ID :				'validator1',
 *		FormID :			'form1',
 *		ErrorMessage :		'*',
 *		ControlToValidate : 'input1'
 *	}
 * new Prado.WebUI.TRequiredFieldValidator(options);
 * new Prado.WebUI.TValidationSummary({ID:'summary1',FormID:'form1'});
 *
 * //watch the form onsubmit event, check validators, stop if not valid.
 * Event.observe("form1", "submit" function(ev)
 * {
 * 	 if(Prado.WebUI.Validation.isValid("form1") == false)
 * 		Event.stop(ev);
 * });
 * </script>
 * </div>
 * </form>
 * </code>
 */
Prado.Validation =  Class.create();

/**
 * A global validation manager.
 * To validate the inputs of a particular form, call
 * <code>Prado.Validation.validate(formID, groupID)</code>
 * where <tt>formID</tt> is the HTML form ID, and the optional
 * <tt>groupID</tt> if present will only validate the validators
 * in a particular group.
 */
Object.extend(Prado.Validation,
{
	managers : {},

	/**
	 * Validate the validators (those that <strong>DO NOT</strong>
	 * belong to a particular group) the form specified by the
	 * <tt>formID</tt> parameter. If <tt>groupID</tt> is specified
	 * then only validators belonging to that group will be validated.
	 * @param string ID of the form to validate
	 * @param string ID of the group to validate.
	 * @param HTMLElement element that calls for validation
	 */
	validate : function(formID, groupID, invoker)
	{
		formID = formID || this.getForm();
		if(this.managers[formID])
		{
			return this.managers[formID].validate(groupID, invoker);
		}
		else
		{
			throw new Error("Form '"+form+"' is not registered with Prado.Validation");
		}
	},

	/**
	 * @return string first form ID.
	 */
	getForm : function()
	{
		var keys = $H(this.managers).keys();
		return keys[0];
	},

	/**
	 * Check if the validators are valid for a particular form (and group).
	 * The validators states will not be changed.
	 * The <tt>validate</tt> function should be called first.
	 * @param string ID of the form to validate
	 * @param string ID of the group to validate.
	 */
	isValid : function(formID, groupID)
	{
		formID = formID || this.getForm();
		if(this.managers[formID])
			return this.managers[formID].isValid(groupID);
		return true;
	},

	/**
	 * Reset the validators for a given group.
	 */
	reset : function(groupID)
	{
		var formID = this.getForm();
		if(this.managers[formID])
			this.managers[formID].reset(groupID);
	},

	/**
	 * Add a new validator to a particular form.
	 * @param string the form that the validator belongs.
	 * @param object a validator
	 * @return object the manager
	 */
	addValidator : function(formID, validator)
	{
		if(this.managers[formID])
			this.managers[formID].addValidator(validator);
		else
			throw new Error("A validation manager for form '"+formID+"' needs to be created first.");
		return this.managers[formID];
	},

	/**
	 * Add a new validation summary.
	 * @param string the form that the validation summary belongs.
	 * @param object a validation summary
	 * @return object manager
	 */
	addSummary : function(formID, validator)
	{
		if(this.managers[formID])
			this.managers[formID].addSummary(validator);
		else
			throw new Error("A validation manager for form '"+formID+"' needs to be created first.");
		return this.managers[formID];
	},

	setErrorMessage : function(validatorID, message)
	{
		$H(Prado.Validation.managers).each(function(manager)
		{
			manager[1].validators.each(function(validator)
			{
				if(validator.options.ID == validatorID)
				{
					validator.options.ErrorMessage = message;
					$(validatorID).innerHTML = message;
				}
			});
		});
	}
});

Prado.ValidationManager = Class.create();
/**
 * Validation manager instances. Manages validators for a particular
 * HTML form. The manager contains references to all the validators
 * summaries, and their groupings for a particular form.
 * Generally, <tt>Prado.Validation</tt> methods should be called rather
 * than calling directly the ValidationManager.
 */
Prado.ValidationManager.prototype =
{
	/**
	 * <code>
	 * options['FormID']*	The ID of HTML form to manage.
	 * </code>
	 */
	initialize : function(options)
	{
		this.validators = []; // list of validators
		this.summaries = []; // validation summaries
		this.groups = []; // validation groups
		this.options = {};

		this.options = options;
		if(!Prado.Validation.managers[options.FormID])
			Prado.Validation.managers[options.FormID] = this;
	},

	/**
	 * Reset all validators in the given group (if group is null, validators without a group are used).
	 */
	reset : function(group)
	{
		this.validatorPartition(group)[0].invoke('reset');
		this.updateSummary(group, true);
	},

	/**
	 * Validate the validators managed by this validation manager.
	 * @param string only validate validators belonging to a group (optional)
	 * @param HTMLElement element that calls for validation
	 * @return boolean true if all validators are valid, false otherwise.
	 */
	validate : function(group, source)
	{
		var partition = this.validatorPartition(group);
		var valid = partition[0].invoke('validate', source).all();
		this.focusOnError(partition[0]);
		partition[1].invoke('hide');
		this.updateSummary(group, true);
		return valid;
	},

	/**
	 * Focus on the first validator that is invalid and options.FocusOnError is true.
	 */
	focusOnError : function(validators)
	{
		for(var i = 0; i < validators.length; i++)
		{
			if(!validators[i].isValid && validators[i].options.FocusOnError)
				return Prado.Element.focus(validators[i].options.FocusElementID);
		}
	},

	/**
	 * @return array[0] validators belong to a group if group is given, otherwise validators
	 * not belongining to any group. array[1] the opposite of array[0].
	 */
	validatorPartition : function(group)
	{
		return group ? this.validatorsInGroup(group) : this.validatorsWithoutGroup();
	},

	/**
	 * @return array validatiors in a given group in first array and
	 * validators not belonging to the group in 2nd array.
	 */
	validatorsInGroup : function(groupID)
	{
		if(this.groups.include(groupID))
		{
			return this.validators.partition(function(val)
			{
				return val.group == groupID;
			});
		}
		else
			return [[],[]];
	},

	/**
	 * @return array validators without any group in first array, and those
	 * with groups in 2nd array.
	 */
	validatorsWithoutGroup : function()
	{
		return this.validators.partition(function(val)
		{
			return !val.group;
		});
	},

	/**
	 * Gets the state of all the validators, true if they are all valid.
	 * @return boolean true if the validators are valid.
	 */
	isValid : function(group)
	{
		return this.validatorPartition(group)[0].pluck('isValid').all();
	},

	/**
	 * Add a validator to this manager.
	 * @param Prado.WebUI.TBaseValidator a new validator
	 */
	addValidator : function(validator)
	{
		this.validators.push(validator);
		if(validator.group && !this.groups.include(validator.group))
			this.groups.push(validator.group);
	},

	/**
	 * Add a validation summary.
	 * @param Prado.WebUI.TValidationSummary validation summary.
	 */
	addSummary : function(summary)
	{
		this.summaries.push(summary);
	},

	/**
	 * Gets all validators that belong to a group or that the validator
	 * group is null and the validator validation was false.
	 * @return array list of validators with error.
	 */
	getValidatorsWithError : function(group)
	{
		return this.validatorPartition(group)[0].findAll(function(validator)
		{
			return !validator.isValid;
		});
	},

	/**
	 * Update the summary of a particular group.
	 * @param string validation group to update.
	 */
	updateSummary : function(group, refresh)
	{
		var validators = this.getValidatorsWithError(group);
		this.summaries.each(function(summary)
		{
			var inGroup = group && summary.group == group;
			var noGroup = !group && !summary.group;
			if(inGroup || noGroup)
				summary.updateSummary(validators, refresh);
			else
				summary.hideSummary(true);
		});
	}
};

/**
 * TValidationSummary displays a summary of validation errors inline on a Web page,
 * in a message box, or both. By default, a validation summary will collect
 * <tt>ErrorMessage</tt> of all failed validators on the page. If
 * <tt>ValidationGroup</tt> is not empty, only those validators who belong
 * to the group will show their error messages in the summary.
 *
 * The summary can be displayed as a list, as a bulleted list, or as a single
 * paragraph based on the <tt>DisplayMode</tt> option.
 * The messages shown can be prefixed with <tt>HeaderText</tt>.
 *
 * The summary can be displayed on the Web page and in a message box by setting
 * the <tt>ShowSummary</tt> and <tt>ShowMessageBox</tt>
 * options, respectively.
 */
Prado.WebUI.TValidationSummary = Class.create();
Prado.WebUI.TValidationSummary.prototype =
{
	/**
	 * <code>
	 * options['ID']*				Validation summary ID, i.e., an HTML element ID
	 * options['FormID']*			HTML form that this summary belongs.
	 * options['ShowMessageBox']	True to show the summary in an alert box.
	 * options['ShowSummary']		True to show the inline summary.
	 * options['HeaderText']		Summary header text
	 * options['DisplayMode']		Summary display style, 'BulletList', 'List', 'SingleParagraph'
	 * options['Refresh']			True to update the summary upon validator state change.
	 * options['ValidationGroup']	Validation summary group
	 * options['Display']			Display mode, 'None', 'Fixed', 'Dynamic'.
	 * options['ScrollToSummary']	True to scroll to the validation summary upon refresh.
	 * </code>
	 */
	initialize : function(options)
	{
		this.options = options;
		this.group = options.ValidationGroup;
		this.messages = $(options.ID);
		if(this.messages)
		{
			this.visible = this.messages.style.visibility != "hidden"
			this.visible = this.visible && this.messages.style.display != "none";
			Prado.Validation.addSummary(options.FormID, this);
		}
	},

	/**
	 * Update the validation summary to show the error message from
	 * validators that failed validation.
	 * @param array list of validators that failed validation.
	 * @param boolean update the summary;
	 */
	updateSummary : function(validators, update)
	{
		if(validators.length <= 0)
		{
			if(update || this.options.Refresh != false)
			{
				return this.hideSummary(validators);
			}
			return;
		}

		var refresh = update || this.visible == false || this.options.Refresh != false;

		if(this.options.ShowSummary != false && refresh)
		{
			this.updateHTMLMessages(this.getMessages(validators));
			this.showSummary(validators);
		}

		if(this.options.ScrollToSummary != false && refresh)
			window.scrollTo(this.messages.offsetLeft-20, this.messages.offsetTop-20);

		if(this.options.ShowMessageBox == true && refresh)
		{
			this.alertMessages(this.getMessages(validators));
			this.visible = true;
		}
	},

	/**
	 * Display the validator error messages as inline HTML.
	 */
	updateHTMLMessages : function(messages)
	{
		while(this.messages.childNodes.length > 0)
			this.messages.removeChild(this.messages.lastChild);
		new Insertion.Bottom(this.messages, this.formatSummary(messages));
	},

	/**
	 * Display the validator error messages as an alert box.
	 */
	alertMessages : function(messages)
	{
		var text = this.formatMessageBox(messages);
		setTimeout(function(){ alert(text); },20);
	},

	/**
	 * @return array list of validator error messages.
	 */
	getMessages : function(validators)
	{
		var messages = [];
		validators.each(function(validator)
		{
			var message = validator.getErrorMessage();
			if(typeof(message) == 'string' && message.length > 0)
				messages.push(message);
		})
		return messages;
	},

	/**
	 * Hides the validation summary.
	 */
	hideSummary : function(validators)
	{	if(typeof(this.options.OnHideSummary) == "function")
		{
			this.messages.style.visibility="visible";
			this.options.OnHideSummary(this,validators)
		}
		else
		{
			this.messages.style.visibility="hidden";
			if(this.options.Display == "None" || this.options.Display == "Dynamic")
				this.messages.hide();
		}
		this.visible = false;
	},

	/**
	 * Shows the validation summary.
	 */
	showSummary : function(validators)
	{
		this.messages.style.visibility="visible";
		if(typeof(this.options.OnShowSummary) == "function")
			this.options.OnShowSummary(this,validators);
		else
			this.messages.show();
		this.visible = true;
	},

	/**
	 * Return the format parameters for the summary.
	 * @param string format type, "List", "SingleParagraph" or "BulletList"
	 * @type array formatting parameters
	 */
	formats : function(type)
	{
		switch(type)
		{
			case "List":
				return { header : "<br />", first : "", pre : "", post : "<br />", last : ""};
			case "SingleParagraph":
				return { header : " ", first : "", pre : "", post : " ", last : "<br />"};
			case "BulletList":
			default:
				return { header : "", first : "<ul>", pre : "<li>", post : "</li>", last : "</ul>"};
		}
	},

	/**
	 * Format the message summary.
	 * @param array list of error messages.
	 * @type string formatted message
	 */
	formatSummary : function(messages)
	{
		var format = this.formats(this.options.DisplayMode);
		var output = this.options.HeaderText ? this.options.HeaderText + format.header : "";
		output += format.first;
		messages.each(function(message)
		{
			output += message.length > 0 ? format.pre + message + format.post : "";
		});
//		for(var i = 0; i < messages.length; i++)
	//		output += (messages[i].length>0) ? format.pre + messages[i] + format.post : "";
		output += format.last;
		return output;
	},
	/**
	 * Format the message alert box.
	 * @param array a list of error messages.
	 * @type string format message for alert.
	 */
	formatMessageBox : function(messages)
	{
		var output = this.options.HeaderText ? this.options.HeaderText + "\n" : "";
		for(var i = 0; i < messages.length; i++)
		{
			switch(this.options.DisplayMode)
			{
				case "List":
					output += messages[i] + "\n";
					break;
				case "BulletList":
                default:
					output += "  - " + messages[i] + "\n";
					break;
				case "SingleParagraph":
					output += messages[i] + " ";
					break;
			}
		}
		return output;
	}
};

/**
 * TBaseValidator serves as the base class for validator controls.
 *
 * Validation is performed when a postback control, such as a TButton,
 * a TLinkButton or a TTextBox (under AutoPostBack mode) is submitting
 * the page and its <tt>CausesValidation</tt> option is true.
 * The input control to be validated is specified by <tt>ControlToValidate</tt>
 * option.
 */
Prado.WebUI.TBaseValidator = Class.create();
Prado.WebUI.TBaseValidator.prototype =
{
	/**
	 * <code>
	 * options['ID']*				Validator ID, e.g. span with message
	 * options['FormID']*			HTML form that the validator belongs
	 * options['ControlToValidate']*HTML form input to validate
	 * options['Display']			Display mode, 'None', 'Fixed', 'Dynamic'
	 * options['ErrorMessage']		Validation error message
	 * options['FocusOnError']		True to focus on validation error
	 * options['FocusElementID']	Element to focus on error
	 * options['ValidationGroup']	Validation group
	 * options['ControlCssClass']	Css class to use on the input upon error
	 * options['OnValidate']		Function to call immediately after validation
	 * options['OnValidationSuccess']			Function to call upon after successful validation
	 * options['OnValidationError']			Function to call upon after error in validation.
	 * options['ObserveChanges'] 	True to observe changes in input
	 * </code>
	 */
	initialize : function(options)
	{
	/*	options.OnValidate = options.OnValidate || Prototype.emptyFunction;
		options.OnSuccess = options.OnSuccess || Prototype.emptyFunction;
		options.OnError = options.OnError || Prototype.emptyFunction;
	*/

		this.enabled = true;
		this.visible = false;
		this.isValid = true;
		this._isObserving = {};
		this.group = null;
		this.requestDispatched = false;

		this.options = options;
		this.control = $(options.ControlToValidate);
		this.message = $(options.ID);
		if(this.control && this.message)
		{
			this.group = options.ValidationGroup;

			this.manager = Prado.Validation.addValidator(options.FormID, this);
		}
	},

	/**
	 * @return string validation error message.
	 */
	getErrorMessage : function()
	{
		return this.options.ErrorMessage;
	},

	/**
	 * Update the validator span, input CSS class, and focus particular
	 * element. Updating the validator control will set the validator
	 * <tt>visible</tt> property to true.
	 */
	updateControl: function(focus)
	{
		this.refreshControlAndMessage();

		//if(this.options.FocusOnError && !this.isValid )
		//	Prado.Element.focus(this.options.FocusElementID);

		this.visible = true;
	},

	refreshControlAndMessage : function()
	{
		this.visible = true;
		if(this.message)
		{
			if(this.options.Display == "Dynamic")
			{
				var msg=this.message;
				this.isValid ? setTimeout(function() { msg.hide(); }, 250) : msg.show();
			}
			this.message.style.visibility = this.isValid ? "hidden" : "visible";
		}
		if(this.control)
			this.updateControlCssClass(this.control, this.isValid);
	},

	/**
	 * Add a css class to the input control if validator is invalid,
	 * removes the css class if valid.
	 * @param object html control element
	 * @param boolean true to remove the css class, false to add.
	 */
	updateControlCssClass : function(control, valid)
	{
		var CssClass = this.options.ControlCssClass;
		if(typeof(CssClass) == "string" && CssClass.length > 0)
		{
			if(valid)
				control.removeClassName(CssClass);
			else
				control.addClassName(CssClass);
		}
	},

	/**
	 * Hides the validator messages and remove any validation changes.
	 */
	hide : function()
	{
		this.reset();
		this.visible = false;
	},

	/**
	 * Sets isValid = true and updates the validator display.
	 */
	reset : function()
	{
		this.isValid = true;
		this.updateControl();
	},

	/**
	 * Calls evaluateIsValid() function to set the value of isValid property.
	 * Triggers onValidate event and onSuccess or onError event.
	 * @param HTMLElement element that calls for validation
	 * @return boolean true if valid.
	 */
	validate : function(invoker)
	{
		//try to find the control.
		if(!this.control)
			this.control = $(this.options.ControlToValidate);

		if(!this.control || this.control.disabled)
		{
			this.isValid = true;
			return this.isValid;
		}

		if(typeof(this.options.OnValidate) == "function")
		{
			if(this.requestDispatched == false)
				this.options.OnValidate(this, invoker);
		}

		if(this.enabled && !this.control.getAttribute('disabled'))
			this.isValid = this.evaluateIsValid();
		else
			this.isValid = true;

		this.updateValidationDisplay(invoker);
		this.observeChanges(this.control);

		return this.isValid;
	},

	/**
	 * Updates the validation messages, update the control to be validated.
	 */
	updateValidationDisplay : function(invoker)
	{
		if(this.isValid)
		{
			if(typeof(this.options.OnValidationSuccess) == "function")
			{
				if(this.requestDispatched == false)
				{
					this.refreshControlAndMessage();
					this.options.OnValidationSuccess(this, invoker);
				}
			}
			else
				this.updateControl();
		}
		else
		{
			if(typeof(this.options.OnValidationError) == "function")
			{
				if(this.requestDispatched == false)
				{
					this.refreshControlAndMessage();
					this.options.OnValidationError(this, invoker)
				}
			}
			else
				this.updateControl();
		}
	},

	/**
	 * Observe changes to the control input, re-validate upon change. If
	 * the validator is not visible, no updates are propagated.
	 * @param HTMLElement element that calls for validation
	 */
	observeChanges : function(control)
	{
		if(!control) return;

		var canObserveChanges = this.options.ObserveChanges != false;
		var currentlyObserving = this._isObserving[control.id+this.options.ID];

		if(canObserveChanges && !currentlyObserving)
		{
			var validator = this;

			Event.observe(control, 'change', function()
			{
				if(validator.visible)
				{
					validator.validate();
					validator.manager.updateSummary(validator.group);
				}
			});
			this._isObserving[control.id+this.options.ID] = true;
		}
	},

	/**
	 * @return string trims the string value, empty string if value is not string.
	 */
	trim : function(value)
	{
		return typeof(value) == "string" ? value.trim() : "";
	},

	/**
	 * Convert the value to a specific data type.
	 * @param {string} the data type, "Integer", "Double", "Date" or "String"
	 * @param {string} the value to convert.
	 * @type {mixed|null} the converted data value.
	 */
	convert : function(dataType, value)
	{
		if(typeof(value) == "undefined")
			value = this.getValidationValue();
		var string = new String(value);
		switch(dataType)
		{
			case "Integer":
				return string.toInteger();
			case "Double" :
			case "Float" :
				return string.toDouble(this.options.DecimalChar);
			case "Date":
				if(typeof(value) != "string")
					return value;
				else
				{
					var value = string.toDate(this.options.DateFormat);
					if(value && typeof(value.getTime) == "function")
						return value.getTime();
					else
						return null;
				}
			case "String":
				return string.toString();
		}
		return value;
	},

	/**
	 * The ControlType property comes from TBaseValidator::getClientControlClass()
	 * Be sure to update the TBaseValidator::$_clientClass if new cases are added.
	 * @return mixed control value to validate
	 */
	 getValidationValue : function(control)
	 {
	 	if(!control)
	 		control = this.control
	 	switch(this.options.ControlType)
	 	{
	 		case 'TDatePicker':
	 			if(control.type == "text")
	 			{
	 				value = this.trim($F(control));

					if(this.options.DateFormat)
	 				{
	 					date = value.toDate(this.options.DateFormat);
	 					return date == null ? value : date;
	 				}
	 				else
		 				return value;
	 			}
	 			else
	 			{
	 				this.observeDatePickerChanges();

	 				return Prado.WebUI.TDatePicker.getDropDownDate(control);//.getTime();
	 			}
	 		case 'THtmlArea':
	 			if(typeof tinyMCE != "undefined")
					tinyMCE.triggerSave();
				return this.trim($F(control));
			case 'TRadioButton':
				if(this.options.GroupName)
					return this.getRadioButtonGroupValue();
	 		default:
	 			if(this.isListControlType())
	 				return this.getFirstSelectedListValue();
	 			else
		 			return this.trim($F(control));
	 	}
	 },

	getRadioButtonGroupValue : function()
	{
		name = this.control.name;
		value = "";
		$A(document.getElementsByName(name)).each(function(el)
		{
			if(el.checked)
				value =  el.value;
		});
		return value;
	},

	 /**
	  * Observe changes in the drop down list date picker, IE only.
	  */
	 observeDatePickerChanges : function()
	 {
	 	if(Prado.Browser().ie)
	 	{
	 		var DatePicker = Prado.WebUI.TDatePicker;
	 		this.observeChanges(DatePicker.getDayListControl(this.control));
			this.observeChanges(DatePicker.getMonthListControl(this.control));
			this.observeChanges(DatePicker.getYearListControl(this.control));
	 	}
	 },

	/**
	 * Gets numeber selections and their values.
	 * @return object returns selected values in <tt>values</tt> property
	 * and number of selections in <tt>checks</tt> property.
	 */
	getSelectedValuesAndChecks : function(elements, initialValue)
	{
		var checked = 0;
		var values = [];
		var isSelected = this.isCheckBoxType(elements[0]) ? 'checked' : 'selected';
		elements.each(function(element)
		{
			if(element[isSelected] && element.value != initialValue)
			{
				checked++;
				values.push(element.value);
			}
		});
		return {'checks' : checked, 'values' : values};
	},

	/**
	 * Gets an array of the list control item input elements, for TCheckBoxList
	 * checkbox inputs are returned, for TListBox HTML option elements are returned.
	 * @return array list control option elements.
	 */
	getListElements : function()
	{
		switch(this.options.ControlType)
		{
			case 'TCheckBoxList': case 'TRadioButtonList':
				var elements = [];
				for(var i = 0; i < this.options.TotalItems; i++)
				{
					var element = $(this.options.ControlToValidate+"_c"+i);
					if(this.isCheckBoxType(element))
						elements.push(element);
				}
				return elements;
			case 'TListBox':
				var elements = [];
				var element = $(this.options.ControlToValidate);
				if(element && (type = element.type.toLowerCase()))
				{
					if(type == "select-one" || type == "select-multiple")
						elements = $A(element.options);
				}
				return elements;
			default:
				return [];
		}
	},

	/**
	 * @return boolean true if element is of checkbox or radio type.
	 */
	isCheckBoxType : function(element)
	{
		if(element && element.type)
		{
			var type = element.type.toLowerCase();
			return type == "checkbox" || type == "radio";
		}
		return false;
	},

	/**
	 * @return boolean true if control to validate is of some of the TListControl type.
	 */
	isListControlType : function()
	{
		var list = ['TCheckBoxList', 'TRadioButtonList', 'TListBox'];
		return list.include(this.options.ControlType);
	},

	/**
	 * @return string gets the first selected list value, initial value if none found.
	 */
	getFirstSelectedListValue : function()
	{
		var initial = "";
		if(typeof(this.options.InitialValue) != "undefined")
			initial = this.options.InitialValue;
		var elements = this.getListElements();
		var selection = this.getSelectedValuesAndChecks(elements, initial);
		return selection.values.length > 0 ? selection.values[0] : initial;
	}
}


/**
 * TRequiredFieldValidator makes the associated input control a required field.
 * The input control fails validation if its value does not change from
 * the <tt>InitialValue<tt> option upon losing focus.
 * <code>
 * options['InitialValue']		Validation fails if control input equals initial value.
 * </code>
 */
Prado.WebUI.TRequiredFieldValidator = Class.extend(Prado.WebUI.TBaseValidator,
{
	/**
	 * @return boolean true if the input value is not empty nor equal to the initial value.
	 */
	evaluateIsValid : function()
	{
		var inputType = this.control.getAttribute("type");
    	if(inputType == 'file')
    	{
        	return true;
    	}
	    else
	    {
        	var a = this.getValidationValue();
        	var b = this.trim(this.options.InitialValue);
        	return(a != b);
    	}
	}
});


/**
 * TCompareValidator compares the value entered by the user into an input
 * control with the value entered into another input control or a constant value.
 * To compare the associated input control with another input control,
 * set the <tt>ControlToCompare</tt> option to the ID path
 * of the control to compare with. To compare the associated input control with
 * a constant value, specify the constant value to compare with by setting the
 * <tt>ValueToCompare</tt> option.
 *
 * The <tt>DataType</tt> property is used to specify the data type
 * of both comparison values. Both values are automatically converted to this data
 * type before the comparison operation is performed. The following value types are supported:
 * - <b>Integer</b> A 32-bit signed integer data type.
 * - <b>Float</b> A double-precision floating point number data type.
 * - <b>Date</b> A date data type. The format can be by the <tt>DateFormat</tt> option.
 * - <b>String</b> A string data type.
 *
 * Use the <tt>Operator</tt> property to specify the type of comparison
 * to perform. Valid operators include Equal, NotEqual, GreaterThan, GreaterThanEqual,
 * LessThan and LessThanEqual.
 * <code>
 * options['ControlToCompare']
 * options['ValueToCompare']
 * options['Operator']
 * options['Type']
 * options['DateFormat']
 * </code>
 */
Prado.WebUI.TCompareValidator = Class.extend(Prado.WebUI.TBaseValidator,
{
	//_observingComparee : false,

	/**
	 * Compares the input to another input or a given value.
	 */
	evaluateIsValid : function()
	{
		var value = this.getValidationValue();
	    if (value.length <= 0)
	    	return true;

    	var comparee = $(this.options.ControlToCompare);

		if(comparee)
			var compareTo = this.getValidationValue(comparee);
		else
			var compareTo = this.options.ValueToCompare || "";

	    var isValid =  this.compare(value, compareTo);

		if(comparee)
		{
			this.updateControlCssClass(comparee, isValid);
			this.observeChanges(comparee);
		}
		return isValid;
	},

	/**
	 * Compares two values, their values are casted to type defined
	 * by <tt>DataType</tt> option. False is returned if the first
	 * operand converts to null. Returns true if the second operand
	 * converts to null. The comparision is done based on the
	 * <tt>Operator</tt> option.
	 */
	compare : function(operand1, operand2)
	{
		var op1, op2;
		if((op1 = this.convert(this.options.DataType, operand1)) == null)
			return false;
		if ((op2 = this.convert(this.options.DataType, operand2)) == null)
        	return true;
    	switch (this.options.Operator)
		{
	        case "NotEqual":
	            return (op1 != op2);
	        case "GreaterThan":
	            return (op1 > op2);
	        case "GreaterThanEqual":
	            return (op1 >= op2);
	        case "LessThan":
	            return (op1 < op2);
	        case "LessThanEqual":
	            return (op1 <= op2);
	        default:
	            return (op1 == op2);
	    }
	}
});

/**
 * TCustomValidator performs user-defined client-side validation on an
 * input component.
 *
 * To create a client-side validation function, add the client-side
 * validation javascript function to the page template.
 * The function should have the following signature:
 * <code>
 * <script type="text/javascript"><!--
 * function ValidationFunctionName(sender, parameter)
 * {
 *    // if(parameter == ...)
 *    //    return true;
 *    // else
 *    //    return false;
 * }
 * -->
 * </script>
 * </code>
 * Use the <tt>ClientValidationFunction</tt> option
 * to specify the name of the client-side validation script function associated
 * with the TCustomValidator.
 * <code>
 * options['ClientValidationFunction']	custom validation function.
 * </code>
 */
Prado.WebUI.TCustomValidator = Class.extend(Prado.WebUI.TBaseValidator,
{
	/**
	 * Calls custom validation function.
	 */
	evaluateIsValid : function()
	{
		var value = this.getValidationValue();
		var clientFunction = this.options.ClientValidationFunction;
		if(typeof(clientFunction) == "string" && clientFunction.length > 0)
		{
			validate = clientFunction.toFunction();
			return validate(this, value);
		}
		return true;
	}
});

/**
 * Uses callback request to perform validation.
 */
Prado.WebUI.TActiveCustomValidator = Class.extend(Prado.WebUI.TBaseValidator,
{
	validatingValue : null,

	/**
	 * Calls custom validation function.
	 */
	evaluateIsValid : function()
	{
		value = this.getValidationValue();
		if(!this.requestDispatched && (""+value) != (""+this.validatingValue))
		//if((""+value) != (""+this.validatingValue))
		{
			this.validatingValue = value;
			request = new Prado.CallbackRequest(this.options.EventTarget, this.options);
			if(this.options.DateFormat && value instanceof Date) //change date to string with formatting.
				value = value.SimpleFormat(this.options.DateFormat);
			request.setCallbackParameter(value);
			request.setCausesValidation(false);
			request.options.onSuccess = this.callbackOnSuccess.bind(this);
			request.options.onFailure = this.callbackOnFailure.bind(this);
			request.dispatch();
			this.requestDispatched = true;
			return false;
		}
		return this.isValid;
	},

	callbackOnSuccess : function(request, data)
	{
		this.isValid = data;
		this.requestDispatched = false;
		if(typeof(this.options.onSuccess) == "function")
			this.options.onSuccess(request,data);
		this.updateValidationDisplay();
	},

	callbackOnFailure : function(request, data)
	{
		this.requestDispatched = false;
		if(typeof(this.options.onFailure) == "function")
			this.options.onFailure(request,data);
	}
});

/**
 * TRangeValidator tests whether an input value is within a specified range.
 *
 * TRangeValidator uses three key properties to perform its validation.
 * The <tt>MinValue</tt> and <tt>MaxValue</tt> options specify the minimum
 * and maximum values of the valid range. The <tt>DataType</tt> option is
 * used to specify the data type of the value and the minimum and maximum range values.
 * These values are converted to this data type before the validation
 * operation is performed. The following value types are supported:
 * - <b>Integer</b> A 32-bit signed integer data type.
 * - <b>Float</b> A double-precision floating point number data type.
 * - <b>Date</b> A date data type. The date format can be specified by
 *   setting <tt>DateFormat</tt> option, which must be recognizable
 *   by <tt>Date.SimpleParse</tt> javascript function.
 * - <b>String</b> A string data type.
 * <code>
 * options['MinValue']		Minimum range value
 * options['MaxValue']		Maximum range value
 * options['DataType']		Value data type
 * options['DateFormat']	Date format for date data type.
 * </code>
 */
Prado.WebUI.TRangeValidator = Class.extend(Prado.WebUI.TBaseValidator,
{
	/**
	 * Compares the input value with a minimum and/or maximum value.
	 * @return boolean true if the value is empty, returns false if conversion fails.
	 */
	evaluateIsValid : function()
	{
		var value = this.getValidationValue();
		if(value.length <= 0)
			return true;
		if(typeof(this.options.DataType) == "undefined")
			this.options.DataType = "String";

		if(this.options.DataType != "StringLength")
		{
			var min = this.convert(this.options.DataType, this.options.MinValue || null);
			var max = this.convert(this.options.DataType, this.options.MaxValue || null);
			value = this.convert(this.options.DataType, value);
		}
		else
		{
			var min = this.options.MinValue || 0;
			var max = this.options.MaxValue || Number.POSITIVE_INFINITY;
			value = value.length;
		}

		if(value == null)
			return false;

		var valid = true;

		if(min != null)
			valid = valid && (this.options.StrictComparison ? value > min : value >= min);
		if(max != null)
			valid = valid && (this.options.StrictComparison ? value < max : value <= max);
		return valid;
	}
});

/**
 * TRegularExpressionValidator validates whether the value of an associated
 * input component matches the pattern specified by a regular expression.
 * <code>
 * options['ValidationExpression']	regular expression to match against.
 * </code>
 */
Prado.WebUI.TRegularExpressionValidator = Class.extend(Prado.WebUI.TBaseValidator,
{
	/**
	 * Compare the control input against a regular expression.
	 */
	evaluateIsValid : function()
	{
		var value = this.getValidationValue();
	    if (value.length <= 0)
	    	return true;

	    var rx = new RegExp(this.options.ValidationExpression);
	    var matches = rx.exec(value);
	    return (matches != null && value == matches[0]);
	}
});

/**
 * TEmailAddressValidator validates whether the value of an associated
 * input component is a valid email address.
 */
Prado.WebUI.TEmailAddressValidator = Prado.WebUI.TRegularExpressionValidator;


/**
 * TListControlValidator checks the number of selection and their values
 * for a TListControl that allows multiple selections.
 */
Prado.WebUI.TListControlValidator = Class.extend(Prado.WebUI.TBaseValidator,
{
	/**
	 * @return true if the number of selections and/or their values
	 * match the requirements.
	 */
	evaluateIsValid : function()
	{
		var elements = this.getListElements();
		if(elements && elements.length <= 0)
			return true;

		this.observeListElements(elements);

		var selection = this.getSelectedValuesAndChecks(elements);
		return this.isValidList(selection.checks, selection.values);
	},

	/**
	 * Observe list elements for IE browsers of changes
	 */
	 observeListElements : function(elements)
	 {
		if(Prado.Browser().ie && this.isCheckBoxType(elements[0]))
		{
			var validator = this;
			elements.each(function(element)
			{
				validator.observeChanges(element);
			});
		}
	 },

	/**
	 * Determine if the number of checked and the checked values
	 * satisfy the required number of checks and/or the checked values
	 * equal to the required values.
	 * @return boolean true if checked values and number of checks are satisfied.
	 */
	isValidList : function(checked, values)
	{
		var exists = true;

		//check the required values
		var required = this.getRequiredValues();
		if(required.length > 0)
		{
			if(values.length < required.length)
				return false;
			required.each(function(requiredValue)
			{
				exists = exists && values.include(requiredValue);
			});
		}

		var min = typeof(this.options.Min) == "undefined" ?
					Number.NEGATIVE_INFINITY : this.options.Min;
		var max = typeof(this.options.Max) == "undefined" ?
					Number.POSITIVE_INFINITY : this.options.Max;
		return exists && checked >= min && checked <= max;
	},

	/**
	 * @return array list of required options that must be selected.
	 */
	getRequiredValues : function()
	{
		var required = [];
		if(this.options.Required && this.options.Required.length > 0)
			required = this.options.Required.split(/,\s*/);
		return required;
	}
});


/**
 * TDataTypeValidator verifies if the input data is of the type specified
 * by <tt>DataType</tt> option.
 * The following data types are supported:
 * - <b>Integer</b> A 32-bit signed integer data type.
 * - <b>Float</b> A double-precision floating point number data type.
 * - <b>Date</b> A date data type.
 * - <b>String</b> A string data type.
 * For <b>Date</b> type, the option <tt>DateFormat</tt>
 * will be used to determine how to parse the date string.
 */
Prado.WebUI.TDataTypeValidator = Class.extend(Prado.WebUI.TBaseValidator,
{
        evaluateIsValid : function()
        {
			value = this.getValidationValue();
			if(value.length <= 0)
				return true;
            return this.convert(this.options.DataType, value) != null;
        }
});

/**
 * TCaptchaValidator verifies if the input data is the same as the token shown in the associated CAPTCHA contorl.
 */
Prado.WebUI.TCaptchaValidator = Class.extend(Prado.WebUI.TBaseValidator,
{
	/**
	 * @return boolean true if the input value is not empty nor equal to the initial value.
	 */
	evaluateIsValid : function()
	{
		var a = this.getValidationValue();
		var h = 0;
		for(var i = a.length-1; i >= 0; --i)
			h += a.charCodeAt(i);
		return h == this.options.TokenHash;
	},

	crc32 : function(str)
	{
	    function Utf8Encode(string)
		{
	        string = string.replace(/\r\n/g,"\n");
	        var utftext = "";

	        for (var n = 0; n < string.length; n++)
			{
	            var c = string.charCodeAt(n);

	            if (c < 128) {
	                utftext += String.fromCharCode(c);
	            }
	            else if((c > 127) && (c < 2048)) {
	                utftext += String.fromCharCode((c >> 6) | 192);
	                utftext += String.fromCharCode((c & 63) | 128);
	            }
	            else {
	                utftext += String.fromCharCode((c >> 12) | 224);
	                utftext += String.fromCharCode(((c >> 6) & 63) | 128);
	                utftext += String.fromCharCode((c & 63) | 128);
	            }

	        }

	        return utftext;
	    };

	    str = Utf8Encode(str);

	    var table = "00000000 77073096 EE0E612C 990951BA 076DC419 706AF48F E963A535 9E6495A3 0EDB8832 79DCB8A4 E0D5E91E 97D2D988 09B64C2B 7EB17CBD E7B82D07 90BF1D91 1DB71064 6AB020F2 F3B97148 84BE41DE 1ADAD47D 6DDDE4EB F4D4B551 83D385C7 136C9856 646BA8C0 FD62F97A 8A65C9EC 14015C4F 63066CD9 FA0F3D63 8D080DF5 3B6E20C8 4C69105E D56041E4 A2677172 3C03E4D1 4B04D447 D20D85FD A50AB56B 35B5A8FA 42B2986C DBBBC9D6 ACBCF940 32D86CE3 45DF5C75 DCD60DCF ABD13D59 26D930AC 51DE003A C8D75180 BFD06116 21B4F4B5 56B3C423 CFBA9599 B8BDA50F 2802B89E 5F058808 C60CD9B2 B10BE924 2F6F7C87 58684C11 C1611DAB B6662D3D 76DC4190 01DB7106 98D220BC EFD5102A 71B18589 06B6B51F 9FBFE4A5 E8B8D433 7807C9A2 0F00F934 9609A88E E10E9818 7F6A0DBB 086D3D2D 91646C97 E6635C01 6B6B51F4 1C6C6162 856530D8 F262004E 6C0695ED 1B01A57B 8208F4C1 F50FC457 65B0D9C6 12B7E950 8BBEB8EA FCB9887C 62DD1DDF 15DA2D49 8CD37CF3 FBD44C65 4DB26158 3AB551CE A3BC0074 D4BB30E2 4ADFA541 3DD895D7 A4D1C46D D3D6F4FB 4369E96A 346ED9FC AD678846 DA60B8D0 44042D73 33031DE5 AA0A4C5F DD0D7CC9 5005713C 270241AA BE0B1010 C90C2086 5768B525 206F85B3 B966D409 CE61E49F 5EDEF90E 29D9C998 B0D09822 C7D7A8B4 59B33D17 2EB40D81 B7BD5C3B C0BA6CAD EDB88320 9ABFB3B6 03B6E20C 74B1D29A EAD54739 9DD277AF 04DB2615 73DC1683 E3630B12 94643B84 0D6D6A3E 7A6A5AA8 E40ECF0B 9309FF9D 0A00AE27 7D079EB1 F00F9344 8708A3D2 1E01F268 6906C2FE F762575D 806567CB 196C3671 6E6B06E7 FED41B76 89D32BE0 10DA7A5A 67DD4ACC F9B9DF6F 8EBEEFF9 17B7BE43 60B08ED5 D6D6A3E8 A1D1937E 38D8C2C4 4FDFF252 D1BB67F1 A6BC5767 3FB506DD 48B2364B D80D2BDA AF0A1B4C 36034AF6 41047A60 DF60EFC3 A867DF55 316E8EEF 4669BE79 CB61B38C BC66831A 256FD2A0 5268E236 CC0C7795 BB0B4703 220216B9 5505262F C5BA3BBE B2BD0B28 2BB45A92 5CB36A04 C2D7FFA7 B5D0CF31 2CD99E8B 5BDEAE1D 9B64C2B0 EC63F226 756AA39C 026D930A 9C0906A9 EB0E363F 72076785 05005713 95BF4A82 E2B87A14 7BB12BAE 0CB61B38 92D28E9B E5D5BE0D 7CDCEFB7 0BDBDF21 86D3D2D4 F1D4E242 68DDB3F8 1FDA836E 81BE16CD F6B9265B 6FB077E1 18B74777 88085AE6 FF0F6A70 66063BCA 11010B5C 8F659EFF F862AE69 616BFFD3 166CCF45 A00AE278 D70DD2EE 4E048354 3903B3C2 A7672661 D06016F7 4969474D 3E6E77DB AED16A4A D9D65ADC 40DF0B66 37D83BF0 A9BCAE53 DEBB9EC5 47B2CF7F 30B5FFE9 BDBDF21C CABAC28A 53B39330 24B4A3A6 BAD03605 CDD70693 54DE5729 23D967BF B3667A2E C4614AB8 5D681B02 2A6F2B94 B40BBE37 C30C8EA1 5A05DF1B 2D02EF8D";
		var crc = 0;
	    var x = 0;
	    var y = 0;

	    crc = crc ^ (-1);
	    for( var i = 0, iTop = str.length; i < iTop; i++ )
		{
	        y = ( crc ^ str.charCodeAt( i ) ) & 0xFF;
	        x = "0x" + table.substr( y * 9, 8 );
	        crc = ( crc >>> 8 ) ^ x;
	    }
	    return crc ^ (-1);
	}
});
