From b6ab336e97facfaa5596aa4eaacbe2b5ddf9c44e Mon Sep 17 00:00:00 2001
From: Simon Elliott Now that you have the easiest and most-powerful Backbone boilerplate available,
+ you're probably wondering how to use it to start building applications... Backbone Boilerplate is the product of much research and frustration. While
+ existing boilerplates for Backbone exist, they will often modify the Backbone
+ core, don't have an integrated build system, or impose too much on your
+ application's structure. This boilerplate attempts to improve that.
+
+ Organize your application in a logical filesystem, and develop Models,
+ Collections, Views, and Routers inside modules. Build your application knowing
+ you have efficient, compact code. Backbone Boilerplate extends on the
+ versatile Backbone core, and helps developers manage their application. If you're encountering issues, need assistance, or have a question that hasn't been answered in this
+ tutorial or the GitHub project page
+ you may find help in one of these places: I want this project to be the best it possibly can and represent the interests of the community, please
+ submit issues with features you find useful and anything that you question. Your application may be made up of third-party libraries, plugins, application code, templates, and lots of logic. All of this will need
+ to be well structured to keep it maintainable and it also needs to be compiled if deployed into production. Before you can get started you
+ will need to clean out all the existing defaults that are in the boilerplate are necessary to display this tutorial.
+ Strongly recommend you read through this tutorial before cleaning out any files that may hold clues on how to use the Boilerplate. There are several places where customization may be required. If you cloned the Backbone Boilerplate with Git, you should delete the git directory and then initialize your own Git history:
+
+
+
+
+ Congratulations!
+ Seeing this means you have installed Backbone Boilerplate correctly.
+
+ Contents
+
+
+Overview
+
+ Core Features
+
+
+
+
+ Getting help
+
+
+
+
+ Writing your application
+ Cleaning out default files and code
+
+
Removing the Git history
+
+
+ $ rm -rf .git
+ $ git init
+
If you are not planning on testing your application with QUnit you should delete this directory.
+ +If you are not planning on using the build tool, delete the
+ grunt.js
file. It contains configuration you will not need.
+
At the root level of the project simply change the favicon.ico
file to point to your own branded icon.
This tutorial is rendered in the app/modules/example.js
file and written in app/templates/example.html
.
+ Both of these files are safe to remove.
Routes are defined in the app/main.js
file. Familiarize yourself with it's contents. You'll notice the default router has two existing routes and callback defined, reset it to:
+
+
+ // Defining the application router, you can attach sub routers here.
+ var Router = Backbone.Router.extend({
+ routes: {
+ "": "index",
+ },
+
+ index: function() {
+ // Put your homepage route logic here
+ }
+ });
+
+
+
+
+ Above the Router definition you'll see a reference to the example module, + this is safe to delete as well. + +
+ // Include the example module
+ var Example = namespace.module("example");
+
+
+
+ The default styles for this tutorial are stored in assets/css/style.css
. You will probably want to remove these since they only make sense for this specific page. They start on Line 209
. With the following H5BP header:
+
+
+ /* ==|== primary styles =====================================================
+ Author: Backbone Boilerplate
+ ========================================================================== */
+
+
+
+ You may also want to change the name to yours, if you're planning on putting your custom CSS here as well.
+ +You should delete the assets/img/backbone.png
file if you are not planning on using it in your app.
The namespace.js
file is very important since it contains logic that should
+ exist for every module in your application. This also contains the module
+ shortcut function and fetchTemplate
function.
Following the Bocoup post on Organizing Your Backbone.js Application With Modules this boilerplate provides the same module definition structure.
+
+ Modules are placed in the app/modules/
directory. There is an example module
+ there named: example.js
. The actual module definition function is located
+ inside the app/index.js
file. You create and reference modules with the same
+ function call: namespace.module("<module_name>")
.
+
+ Typically a module contains a single Model/Collection/Router and many Views.
+ Therefore the returned module object is empty except for a Views object
+ property that can be used to attach many Views to, like:
+
+
+ MyModule.Views.Detailed = Backbone.View.extend({ /* ... */ });
+
+ MyModule.Views.Main = Backbone.View.extend({ /* ... */ });
+
+
+
+ + Attaching Models/Collections/Routers happen on the same level of the module, + like so: + +
+ MyModule.Model = Backbone.Model.extend({ /* ... */ });
+
+ MyModule.Router = Backbone.Router.extend({ /* ... */ });
+
+
+
+ Templates are a super useful way to separate concerns in your application. Instead of generating markup from inside your JavaScript + application, you instead create it in a separate file and load it into your application. There are numerous ways of loading in a + template, but this boilerplate has chosen the most performant way to build all your templates into a single file.
+ +This tutorial itself is a template that exists in app/templates/example.html
. You can edit this file and hit refresh
+ in here to see the changes. The boilerplate comes with a built in function to handle the loading of templates. It's called:
+
+
+ namespace.fetchTemplate("app/templates/name.html", function(template) {
+ // Template here is a function, that accepts an object. Identical to _.template.
+ console.log(template({ ... }));
+ });
+
+
+
+ By defining a custom function this will ensure that if you use the build tool or AJAX, that your templates will load consistently.
+ You can see it in action inside the app/modules/example.js
module.
If you use the build process to compile your templates, it will automatically find all the HTML files inside the templates + directory and compile them into a templates.js file. These are actual JavaScript template functions being compiled on the server, which + is different from Jammit and most other server-side builders that just invoke functions on page load.
+ +You can access a compiled template like so: + +
+ var template = window.JST["app/modules/example.html"];
+ template({ ... });
+
+
+
+ Application wide events provide a convenient way for modules to communicate with each other. namespace.app
references a copy of the Backbone.Events object. More information on Backbone Events
+ To bind a callback function to an event: + +
+ //binding an annonymous function to the event "all" with a context of this
.
+ namespace.app.on("all", function(){...}, this);
+
+
+
+ + To remove a callback function (or many) from an event: + +
+ // Removes just the doSomething callback.
+ namespace.app.off("change", doSomething);
+
+ // Removes all "change" events.
+ namespace.app.off("change");
+
+ // Removes all events from the namespace.app object.
+ namespace.app.off();
+
+
+
+ + To trigger the "change" event: + +
+ namespace.app.trigger("change", [*args]);
+
+
+
+ Libraries and plugins are easily added to the application, by placing them inside the assets/js/libs/
directory.
+ If you have many plugins in your application, it may make sense to create a separate folder such as assets/js/plugins/
+ for them.
The Backbone Boilerplate build process is a state-of-the-art task driven + Node.js application that utilizes @cowboy's grunt project. + + To run the defaults, execute the following command from the project root, + and *not from inside the build folder*.
+ +To run the defaults, execute the following command from the project root, + and *not from inside the build folder*. + +
+ node build
+
+
+
+
+ This will do a number of things for you. First it will concatenate all your
+ libs, app code, and templates into separate files inside the `dist/debug`
+ folder. It will then minify those files and your CSS into production ready
+ files inside the dist/release
folder.
To customize and configure the build tool, open `build/config.js` and tweak +the settings.
+ +
+ While writing an application that leverages pushState
you can run the
+ following command to run a server that will always resolve to the index.html
+
+
+ node build/server
+
+
+
+
+ This will spawn up an HTTP server on port 8000
. This server is intended
+ for development and not production. You should use url rewriting or forwarding
+ all requests in your production server to achieve this same effect.
If you are using the build tool in conjunction with this development server
+ you can optionally update the index.html
file to remove the existing script
+ tags and uncomment out the scripts tag at the bottom to load the dist/debug
+ or dist/release
assets. You can achieve this by specifying either debug
+ or release after the server command, like so:
+
+
+ node build/server release
+
+
+
+ To add a new task into the build system, you simply copy and paste the task JavaScript folder/file into the build/tasks
folder
+ or extract the task archive into the same directory. At the very least in order to run this task, you'll need to add it to the build/config.js
+ file. The last line should look something like:
+
+
+ task.registerTask("default", "clean lint:files concat jst min mincss new_module_here");
+
+
+
+ It's possible the custom task will have additional setup instructions, so make
+ sure you read the README for any task.
+|JkRn-df=w7)$}IprH*+ zNDR#@67Zm*`cF8+Jc1O8io`EOz{WGnAwKlINuci|e6D@5yH$JW=g{-Q{P81J)2P(M z?=*CFL2sDR?RR|;t zk&+M@7SSRyFbEEuIJeujb4Qplam2CVeS4impM3S3<7MX+7ORIpLu-rym{|<7q@@&| zT{?CKy%*m=${xF{jH&ZHIJ6DKzlRhGRZ~Eo`o@nqs1VOvL4G|t3ede&455_CG==&< zoMIQ3lqrS3c-;nabAXYXhwz-m5b$>h50zC^j^BCg+}u6K&sydsCIK2K2Y;~l5i5px zKYXYBK}IIhu~?9X_I( q)t<2inalaG08!;^wB57O4E5aSND zw>>eiw@uLDvsc}>96d8MEj$tdn<8f*H31?KMrhEL2(?vP0<`J{pk;=H7XrUmXurqY z11S6qV2oaGW{wsZJrJ` Wu8|6)o8lCuX};_70=Oo51$^rVA{Ax z%Vtg-jy{|71M8+fw0d|Q9@+185E8(8tpKuf;na2SnOrceZeZ(>Y=qFLkNe!VyAQkT z<);o+l^ni8|F7EC1X}9q#M^W y{%;>V zl_zB#@hqz Z_o44i<31-4 z<@}NFLwNM)>GX3>_Tvw3aUH6ys(kk+mc3xQCY@GBnw(>6f}sx2@7~vNzi{1u-k70! z^<;hii>Op;iM*mb qWLWxLtzsx5BK6ZnsJIvuk!2_$CKlEZ z3NDRCEe9n9w8A2wtRP>3d0EiC68SlC VZ?2&8o0@DlhkMP$X7cIR^w~lmYbZk!{f# zfuJ160xU;bJsoNDV#rd=P6l{(7MZkf0gP;k *qLOA&pA)LX-bZ%x z4GOV|e)FDK+iCGhuzcA%+efe7KE1MU(-c{0si1mxbrFU=b;Ela6c!O726Q^y33w)* zTTs|vL00BY0|TLw#|`hjb5?8|!^EJ08&M j2}7Z$cVl@voutdaeJ}ipWUQw zD+8x*UjJ&VM?oPDcOM0IyXqTYlbV$Sn-|SGLZML7pmEMX2|&F67`J}#Warhz$!WxL z?KONr#f>97?2v$XERgWUS}(G!uI}A54Q=KJg8ulI^vwP#>6sl;GP0^)y$jb5d=obE zC026K-Yr5(&c5vj+dg>_a_`d7ofD08v^}|VLaF0(Hs5;mtow=$JDrFHi!B*xNtiNb zSlBR!o_FlJSe@g#f`7JdZ@-=vR8~&CaK~5s$}JxkzXwm~z+@t2@Rr!y_YvM6`?vV_ zv9=t9K34kMz=cH^0&JHp`*a< yUPq_a)xcX6G(9}CO zz11xS_Jx7AU8mIgVMV^VE~%=V|C) >xmejm%Kde$U)XH2~pY~1a!Z@aq|NJtP@sIsmOxXqh-W74nzn #jp*{crnF zv4!;R;_NlbHQW3&uE99 zy?)0+ H?^v(`31Ki7me?#l#`YUap=)`+p8O=Mo*eO=_xroQ&Wa}XRj)4IercY7idBK zM4gd)T=RX{J1gp=DAwvbcC >AL4m#$lP(mhw=d-Z(eG$V16{0)#}QM{=@tAZY?b-`H}ltLkc^ROPi3IzRrI1 zbhotVkHq`Y$ pkUp}()kP$h{DJz2+&eM|p&mQWCfN(Mw2)|!9k)Oz# z@5dcG%E@j?@O6(xR=s=V;h+~NXTrD{i);d3g+AbdR^9lqv<$`#?gttg>I4v9+`SLS zuish0zZd=wWQibGQvgFe-?EFhxx1x?hLd;+bsg<8zjKFrA|Twx1;StP&(HWiwTro_ zyQ!g`HTOQ$-_;~|Hx3?RXFKMt-(|fsd)J0iH4OkPM<7u)3t22iobx~8?iA>J7~K&m zx{?a$duWV7R{)+IMwtE*I_fLIhs^^fUmV{~-#|rj3wMLk;Lqo~H8%0)ohl_Ee#v9Y z>i#_)dsmV`5^-_Zb@WW@{KBFZKlU%zTIs>}P&;$`o+EL~(P$;RR?PSA+|GnB=kup8 zASf)tf#2$>2aS{n9as0`o7}hTl>kMg2o)8>xas3d9-lj8tF57Wlgr%n-&BHVeBjfN zoB!*XTY663TE_`<-9EhuBT;Xby=|)fJoY;($Vff $^YmEAD q3Q_8*kOM9pq=YWLa5 zR PzH+BDNGWk$L?5G|0U=4I;@O4B+3nM*mgfS;Pg~U7THL^73pfz;a?f zfmgt@kz7CY?`X5+yKf?*or3~{7}!Or+e}lkv$^T2p4OKB`23SySOn)heWv}uCGk7I z$@Y5|e0bB^9o8U=)<3@x`i~h}aCYaq?&!U@eA-}rS#b@WmVzxMR(?_OGKUFs)
?ts)>VvC^`#2P|B< zaTq!CM+(~5856&(pF5=|dM~japOXkf8kPF|%HbVzo40L40)YxYC&HA~TX7U t7%8lM3~4LlGXcF x%K8*4WIA4G-INvl79F>NvRN$*Z?WBLRuu1Ss;| zlh?iGL2)V3nupr=c!*wlZ{M}keTup&ag|=b|3o`0H-8HJeu@RLL<@^cdQ4xvbtZ9K zl$V3KZI{w(?pv`y4uQYS9nJ&n;hRrz5PdnOSX zX-WTKeS4)5Pk@vJ1cycRX0fVu1>hwNmP)hN?_67)l1>~?`W?*S%r>`fT#K+Fo|7Uc zC3*ew-c7qjq$DcvnUdcXPsP)>?&5+TVzpM7(3lfb($ulib!y9McLwgrBO?uuZhEWl zKY#6MQ+AjBKho3J?^vWBbr}=-_vnJ-CSn@OgHl&8tm0}&2ENf*ySwjs7PH{OyJ26_ zi^NDBQ7SY*!aI_aD#`?0FQ2-o(V_oXj}^NP1r?Q+j^}RD|GLJeY-QS-lXpX-LQ-ma zR()7%`GrNpulomx0=t@|(e&%y )^ioo-YiJ z$>vmjkrw6Cg&RV&ut^jnu6J6HlLuSZY}aq=IQopomD{J#yE;5;291;cALARWex$b4 z(%1l%6{Pw~MounH4*y+2E_T7oZ)8^VM>?K+D{IS`&gQ14xy<-*v}2!eQUm}o>v?a# z0oW?|4FD!(!P2sFy6io6)`;X0m6gG~S(9Sh8tQN2KI7kXHcWsJ@uKC3xh|&z@A#5D z#bze0Dg&;2I2h?@KWw7ULu7)kww5bdW;EvP+&m~ODG@}GX}<2zshK4GCcgmsIN25V z?P|4>k4GDdh(fV`&g8X<2FQRF6Ni+r2)O9wyPn^}A-Hi!rhr(LOD2t4W@&Fr0&7YV zzQEWeYj7T$1=k=Do*y OJzqmsHVIBTe(g?wsBI)H+B?O#q}R9aM0sp=n@3YqMLa1A zFTDKW#C5Ob_22~&(l1C{Otgqf<#sDVvW&&C*#8h1^6~{MN %V_SB;RD1%L1R&~4zjO$*m=3HcbG z&;sPq>r}>nrR@`D5{1m%{Pozih|655D9YigH+8)q5d^QoBm2B~`Vv4!3Z%5uVNfrd zP5gY*Tth9`(Y9LwKH#p*&V{qLeP-506aV@UES4b>VD=;e5m_VCMhyC*C?|XBFIx-V zSvq6V@^yZ<&tB2MxtU7(Ex`B6$rWY>x>uoTyL*X%&&HL`c4WK6$4cR@voU~f=Z1pQr9?BwsH&M(2Rpzxoy8`|i# zIIv^&qG%{7Bj<;6-L?#iic8o5!dQg{C3O|0tJ0DZCB$8yUjUiec?~MeDL>uM7(|%G z1PGyx6@;tV!2GKb25|{U>r%u`b}4le6C@eG5+IKWosAU}T&DFuZrs6=Q=}LB=GjpD zpc_0{IG6+dz}~&bRoz?`sY2pY^e5s~30ZF8*8giz#1BW0`1rX4sn1za4kiY=vdD_7 ztI#E2cGlBp7755HD1rgKd!!)dy!oALDt>t1o<~8P;~u|s_aVEaw5)AiC;P?Q_Zy?5 zVmYiLD+8nZ^ T1!CU$B% z_yz`v*Vc-o{xjo@SnvzRj~HobXPb@-i yAyr%l&6RvUH@|Skm3t4>fGt9_ z;pxNrUjWYG^*_wb{ SAGb5gI>vn cg zsNvd=CcOb{W&*VAMgk S}n>eepjt#P0u&|@aNQysl zTQI{e^!EA5V`fguAr&QX8y I01A^iIg51h|2NMD z+2j|z{MILZh$3Aa)XZ9ggN^ki_|}f6*7v*S`_L&nCQcOdgubb9j S3LJ=DH>{=hhCkaXI4q()E yLyA&9O$aMFL* zrrlQfIjG3Gy>rU_8MkI;5j3@OUO%z0LL+0HpFDip5||a_Tz=ZH{=V?7V$6@N`gh?w zig3|yq=-p0Zw5$_xd;tk!2O40VyGuiWEfHmTMr9Uk5DPpXvvvs}8 zd>_(jLqZ%@Z9mXFAtik{2%}3If(YM??Qg%IffMf045vlK!h$V(aYq%MhOiF53AVCA z+#CkLpc6pv`2ZG3p`sxzuMBDGtctql3l=a*i=`1=bw0ga*~5PKKx11~DaIK-7sOX> zSyx;0)cIZO%!41?Sl_d6uUwpLjgx+P-3bKW&TNili3@<^bHk5FfqSqI;N1;Qad7N9 zrPBX)9_Cph)e&(Cqvo&QX+y%wkyg8F#e&M;t=~O9+J{BQ~13ezXHSj z!XMW3z_O}r`Q*_HJM`^I6u**$B$&8--4(9oDU2?yf1U*W=FLt{pD;2J($Yyqq*Dhy zS}xwS=MCSS*W0{PC>ZEUku|8M2he2_K*#X_a?Lq)L|MO42%U+>m{SVv4VB^M%#P aNtoBDNz$(5ATO!I&UcX!5IDYoUqS#L|*pgnbVu9c2etiaV zHF-)Megob_#7uwnEJz#!97RC^C)#_}#ITm8#@8&&O#p*&d1f|T_Ia>@&*Bq^5|YzO zGH~z(u1GH-0fsG_%l%Cj;dYOUek+!)wZg7N8k)E3r}jq-?q%Zuzf&~2Vd;Bbf~e1l zV;m>W^LhU;Pz$@JdXE|$zutKoF2$DmD_;7XoLY{pLTnLhsVF0zhC%sL0<>i_O*e8} zfc8EsCl@lab9(agcOBfM{4y%mV~gtosv0sj#l+yr?R(&H(|dJo?k^;P3D0-M>RA() z820E2BuW?o;_hwx6r85azZV)6JBDu${ u zj>eQ-0;-CVW$u&PpLxBsTjxQh>iDqv3m*vnOfc{m4*zi0 *p;Miphi={vryDk>|-)JA}1 F6OX1(#3k4H?qQ#vHwu*PuYi4LR3TRZgoVFOleD%uB_kWhyU1 z-YuHBZpT}bUPI15yLMF>SDl% eA9M$941Y&tFp3aqr)F)*)JZ zv_941qM|u ygu=FP^<>j+=X8Hx#iVYMVMCiZsS*1dJ(>0P(SqF&J6f z9W)zJ*P)WUts|Fj&XSXs7PNm}HM6`zGbrrCIs}C9;Kggmjf S9}*OUfq$n z 6c^;JfQHVE#7ZH9funwh8??~Y6O@E}JrD*iD}Yw}S6K(O~8m08%EV`=8kBj^4{|wC6@^pIcBUdiM6c zsSFye7=AZO87VUAS{h(u+0LK4_Dum0N&RIN1-bLS7miX^Z+D-3;Gl;Mp5E$`5;$}C zl;!!G-j>7r_S`UfP`@2Ltu5b*i!%Iwux7tr*a{I*PH;>$t8T$6c**n2h>#y_t(~{3 zuhBYlO_?)X0SZv}gj+Uzi$*ImG0@$0VfThTQ$`G0xnb{-^^biYi4b9cyC(DUAqCZ@ zNks!`fYM@Mm6tZ|(jo>fMi$hpsS9@#1)?@qH?}+w!%FJvz?ey+!LEaOfKy-F)pAl& zFZo;qzq1i(7w-6u&W`y+$3UoQZd%o^N0(IiLHGCX*GW%X>t;7Ai-iw7@2Oym(&Lto z0|G-_4lDi_76R6$W%7$k=8_;B#D@xUGDy#%;t=ZMCg-cJuBMAJ=(yA!xL>&bbkQON zgt&&Mq$nrgtFj7)%vjRx?cK{`^_y!oETCfmmJowAuA7&Vh`ZX57 hRHUGai-}6}@kXPQDB35oxPKyxV+fX)SJt-! z2=9egM>nrJYw<4Jg;i7n_b=a4JJhS&li4E&w}!d}uGO9-Bp^LKLt?_H8OQ4-pPHEk zZ3m2ix1MJ@p}&HlX$7xQ g=%{N4r{^l|WH0;`TZM8m@gw`9l@4ctJq| zU);|#=uncI2T^ee5S5f-5*YkeCpO_rH$JecYu*!Pn06QL-gm@+i+`_g=Z KGA+PGq&59$9XB?-$n?=_B3PUS_HHl9@_QcoB9pC%ju#B13GeXK1fJh^(( z^6H7b*X>6R;&dX!QRqlEMuMzMTgKS@s`0y~CghpFOU7orzCQ8_LJeRW+IXwH65E z{EZ=w^v6#CpI*S!PJ_UywF$1sP*xAtNllg_jr%aK#tj>!5G eLW`+*D55 z1VgN8SDisXBPA~fyFK ~DlRS4d=dJ7LkSW(4_>@xhrW0%6MD;22{G__ zF=JzJwCx(QaPpY*o!gllN1rdP<=S#8&b*K4@$&HtI;j9dr^ED-1I}~ZeFEKUP;Z-E zF8aDdGg8x`G$j=-c>6D2K6R`&cgKT*xYv+J55vo8@b_ z8)A38tf3xUIJC{zv77bq&K72)t(?Xj!DnZ$z5QX~cK5COUFP3{h6 _h@iZLwg>IWtI%zTr{tf9zGr`mij_GO@J#X$yL2nGFo0g2J0;B;JLKwMJKK$1 zcV~@?2?871SHMB*hS%?pTkt#&5E)W~@Dgcm?lN`E^2>Mq9l{ X)JKU2fce(*NR(JBGzkAK}KQ`1ZH%J! O^vJXoN`}aZPpr}6+uT+ 2H5SM@#*6ZN56p-u52m4`k6*e~9@s=JLIV zBd>e;Sto_Og$EaJXg_%Lbjj3l!>71!aG51Wr(daQFEi-$N; Zep*-r;E4D OG@PS>1ws?==$XrA$Kp&8#ZGC zIZv!cmdaY^l`ZD2-}wSKiSLyH&M|J$Gxg%#`oIzS?RaVi4&2gENoSv};8Y0pVTRdpKQZlmCYROA9uEm!vE+(35 ziJoDXi*x5U-WNwYuUeEwJOc>SPV77=?>Kq>WdtS;HMy72J+dUk#j3H38w=>d!s3eG z-1oml%<^(heNigytEZtM)mu|Tz1^E!oWGFhQltdUbKNp5J~?FrG<0PewDy%%Rg|*w za>Y8V|F5pUAqkt!H{LQS3GrN~p52yQ+_m2Fjn~CdhmP(#Zen3piBn*(f?ris0w<1~ z*0Hc1c=`0LyS(;kl{`MB(6PId{R>E-a2uw-MPT~W10XAr qA zMAoXJ9zeGl0LI-pCFOoaNX*OA|C&5m%6H6)g>Q+WPO}475ASfAF>h)m32j$bg#%mn zNnO5se=X1I5=0-ka__ %Uqx9M$@P69ge z^5MLfpBZ{tz6N1R+)eT_(mM$sq79y3Sj?!XtdeYUD}b#HWI1;q+P~WS{2{02>Z$?X z2@m{l=&`lS=Sb=_BU>yxA{wTz-GM{DW%xH<6Va~uc?p!{Wv}dWTheRqjy0KNU#Y9W zqf0ko#h$~v`1a(F?sHR0T>K4Tr79K_m6l2WDP5D}?W;s%DCL|b0gf{f6Kmjk6j?T9 z?6{70wyBsu%94`tn$O_ldtowu ULuK^xE2Zar?w7)h_mdz)Gy^m~K Gi 4A-VoDft;0nJiL~23Z$Z zy#3p3T|C=HLq*B6re{%>m(RtFioa9BWQwI_XS>SFO3!IhGmbWsnvs5~&uRC5;tWQ> zPpoE2Gj)}Fm)y79?>~A*Z~RR6F5Xbx*TpJi{@9`Ifl~~D+C+H?Q{?=Kqbw3LvYzeP zy1zLz(|~RJjvI~XXSd(PK-U?592~+_kdtMSXyB@9+)EP&4sF3MMEC;{i6v!axf!@} z6;{%j< =DI80XTJm!& z;~iRKK`1K&?_RtT8T>YU$*_SA McK*q<<;2rSHVT_aAG5C4D z8#&VmjfkH3>Uppfjshi(fj=j~$rrD?qW8MiX+${P1@iz^6e?0u;r7F)ZnH)W_JkS& zX;hUJMCnS3P>Hm*Dr8kArlhwv)@{+`EJFoZnQ}1>5dO>!h=pFMQ}@n&7cHFmcK5m+ zCRo8(x_*a#S96ou=EkIRMdL;r)?UL-UN?8@tjl+NebN$>NymUo-hTZ|7R~+{7=nIt zEh_0_h~D~=o+*vCsvZ&r(SPCr@(PMN6kssKji-@y*i22;wFa3}Z~M^HKzGN6RSVm# zSiIT+8wM-3>}}=Hx#Q55+M3sd81=_^nYT}$O&;R(%t*II_3$~%W~Rh`CNgT3+t%(@ zCapHwTD70oFcAto(83Kd#l+y+h3l$jy@t3i+PpU;J1@WgcP t17ce1B!w| zShDlb@ak%|N$tR}wt$2`kF_&RbzoIgz@Ck}_1g6xd;8$ot7p+WR`BhtNi;E^Ic)l+ zuqeAjFXufDN#?YQk`d#c3)W>z{3o@b$gbw5Z$Xq!(f~dte6dE0Y+KWe )g?(w`$vLkKO+S!-OJBT0;fq5`}Pexvd> zD%MgkFysZQ
3`Gv^fE2*(X`9&}PaXGP{X!vYJ zQLiP;7&-6}veHt~m4dFMI$o*Mh7X8t)=HmrF@Vf$@VIq%2-iAf{Y0yN+^KDw0OD#x z0Qx#I#;S~2ZU=vPuef%t3^pIx=5_&!k>#zTf}`1JS9~9B`OZ7iBuPh0gF?rV!FXGt zeUHFU{AZtSYh >POdI&`jJsD=cZijora zaOllRS-{o9>tZG1AC{FSU>6+t5*+q;&272l c{?=m{)TiqjY`K6nP~5_IoD5XzDY@nD }|7JP `k_zLN ztzC*1NVh3JTSa9RRN?)_-QP9v6P6=i2KBPJM!ZJox)bo6k(QM`v=N6(=F#_Qka CM~b1fTFSrf7?zSw?4adw#ANZZVyzNX#k|Ab6lr2;UNaVG6bB8 z&5$iJ4xsZ?00lk74do=1AIeJrg0BHQISh~x%qc+3kp8s-Re}V6VL5a(QHKZ4oj0Bs zW!6Dml6p9!xH9Lzy27PpWo)7#AV5S`9!Q9ZN&E~9&)xBzPueb(GoectE9hv}x?U?; zw9?hNZr7s=0XBRldE%Pa1oW~#{N(IJQe5or2!~#YI6o1T6>&EPll;P>E_|xb-%28~ zQj%{k>|E!GtzN8n1o=Gzw}U6~d@Dm^xBd~3m`rAMWhF {^8-#RfReGrfT(R`9>RHRsekQ(2MhH-{u2;#EHB7*0T;rf0R6_uoFia3Lw zuw?r9-XV7{&6~M!X0`}E=Sj;168!MhN)+LwIN JLSo9}Sd>L=RW@;+wVq&6ifAl#7GLtTN`)iOUsHJ5vVSqh$k7mQ~wt@Ml zwNNqIv9~8DoP-9?f y9AJ_ZYlwYlje z*Jblw5U@u=aKpykR*!<;?Bjw(ld}*b;}U?KK^k=6P9jXb=30+`@=?;c1ZmoSlLz;U z;uXj~eZ#AL6^qri5uS~piO-W4%U?bUqyV }?f}okY>Vd=S zUCaWno^-Vv+%KP489BKez``z3{BNsn0Jd`g+Bl)VML8B?b=ALl8M{|Rr8rkY7y`xP z0B?~}SB8u{tdL^YDb}EJN N{aT1jj{h|B1Pu6DZiKmowC3_wU(tKX3}I*O%ZR)`DaA&hBa^ z#)Tx9AS)Zr+`6lSUYGlUwLp4U>gzPSFl4Ax7ETSpXHN%?oz;s=N?E}__P-bqOEx%9 z8{chUUlMwcpJx0bx8a#NdG^q>A`LNk=?8{}%^)32s1z_W)Q=Yz6}?%5vV@ p4#8h^YZa?cD7b|w4yu!t=0fNW&v~<4Ir(-X&lUA{^ER*o3xM54RM$1FkgU2 z2LWOqkYsu(S%r$({Y+wmmUf%DrN6P2f;jyZ_lvau_`(v+`(aQ>*QnQTB}gS81$l6? z?aKOrixI!p;>>MdGveMxn{imbKA%(+6`pbhU3~!#;Sm`r$w%Y*+sBYU5kOz?^0zN3 z0YezP=Pk4NfwjQP&nww7fBJNZmO5nArD+*3dBuj!Y>qblH=iByo-gj=uJvvTEp$jg zSxVRk>Z~=}aAyy5XgblQ?9|Ncc0T@3 v$yhA6Dr zd9ZEd=frUhsLWuGfzs|XS6&ABK9t6D%1~1Q_mvCwpx3kD&sKgRW`U(it4(W`J162& zhdBLYj_Z~|NC6rpFDo4_N*7v&e}Z1UkDUMJ%{wt(ZfrLTvmN!}5~Fb$E_OgKo2hHI zZr`IZa4`~HV*yl*5C|3{gS40%eOe0zjmAK_o&u15g+W&lC?17@GKrA~hZwoEQk6Q& zXQ9%BOG~T@bJsmb=dtk_50eR9tu3Lmx#_we3FJ2)qDQ}a7A#Au#3;zY m+52m PS%VsT~ z`>!VJx7uauxXot0Ikg}wa`Ry5^o3_j%a{{@uhe2ajujpThhAK=en%Tpkh#1Z)-9d; zRIhonBhWO?VM%o9>KPMfwYBX+YW9{Sr^4J#yKz+ R* yBdm$$hl$%%XVPZwvAgu3gXs!EjRV*)`=AE#+vcbE1px}=V=ART{>gL zz}}-~PK<`s3{p1f)IpD~^VaXcWzdZIHsRk2h%mz5cEi*;lZweYNJxP5ij6};BBFQx zLE*wHfT6Pc)Wx9FM@|`%^W^+OSUz)7ppv|tFZ}vEnddPIyyCuXx;(d>UR2N<=rhUr zbZ%kMc!ibEgb-+DtTH1_K@iy%CFvPjsz8Q^Eo@?d&$U z?lyxQbMZ5QiXt4?bENygX$xPZX5}p5t~al}7yCEov$1&xKi`J~u8aG%>)9;>pUahJ zWy8c}YZs%}ji90X;QfS`&?J~|Ida->@zQn17@%?LRIibPV8MhDqk+@GL{M;{8tODV ze(}K8MWo0-0@dd}_rZ45^fM803DbddW?WSVA ^H;(x|Dwa(Vpwj%+rS z4}6rpBQw@_-xc^_x<|;e O(5!_*Pw6#>^!LpWOnCk}bEYHK&_{`TYN{7cH1 zWB(dUfg8rFq9~vBopmS3^;LkSXXQ+GKX>hJr~c#a9X{Y8NkaCsb7A!C$*_0%yqSDL z_aBRK`R@G**|BjH9Nnha#t4S@?Rg&>wyHj9kus>4O}KVDGZIzD`jSFBb@Og7_!j)| zBcVs7QK=cXj_!1_8RQTH=@}%T=Jr)jSqq1eJ9c?oILIt7Z_AyJ@7qk(WJUjIKK}&o z&_o%GC&*ejMm8pc4iV2@!Q2hIFQNAoK+xz}#7~NHvU$J5OQlSv-C0i`j~>IOyji(= zYg=Abj`sFF;Nq@ztI%J`P`e&?1jy~Xblh|O?17V#IDHD;GwfL~bm~9$=$ie B4#Jk_zUEqI@HUn3 zkBo=Ra~YJ9ku|iQl}gs5ppdaoUcX!U-98m$jNi{ibxvNvxK+Ck`;VWqtba8QUPC}A zudW6++&7 F=DK{LYwu&1Z}r}|aHiGBetkA!ivoS5vdI?q<}~DlfS@ +XZ3p3B%@9xXfvO1sj^6N%YV9+=m zZ90d`NJ-vn1TC711iz1?O{c`e?>;og4R0=Z`CF}-J=s`XTr7h7`|KYGRJy#h)Qd;w zk92j}x-W3Yj)R6cC?g#Sz$FV-%|3MY>coD%Z9Wa@X>-XyPsdMHQ32^0BDg4X{Eu0O zZGlsSGO}~4Q<78FL!v)z^?LXOhB@}WI)0$Traxd!P}&;lAKbELX^$04*LDGUIXJlY zaNF)3+j$M`gX8gkagK}Eo=SzFun*?Uit^%G@RCSEO4|DHm^kgQn2+qxx8bJopW=zz zoCI^ws4#8bwD|q&UG|HKGWOPSUa%Xya!Bv)Q=8p5^LEtS h{{ z?>!jUyW9I=_B{_7wbXefFDrw=;>`~P9Igka^Cc|
+Ap6v?MK^+i;u$ zQ)W&`IJj}8yC|K$yNLqfH+Lz8l5}Q^>&}i&W0#Pk^f;Qax}>gm17rx$kUX$K&t6b3 z{L88ax+nt-T4-m`X|%K-&E> G zZH73F=-H`*NY}P)uB)mkdl|N9PGO?oMZ6is^;rCfbV^Fg =r?^m-mNd=L^IOW~BE{kI)HbNLYdGdVnh;{W2+k^w!tP2rA@@)HY@ zh7c}m`-%m&Ru*mNY}j>Z<-3=!$hphs;5QQcBce_ByPq)CG|=5*prZ|X&9(Bh)KvDu zKSS!Pkrk;C6c#xHNAG?3_!<3vB#=VA#pz&fVw~`Y-0b+7GhC*O?fd#HX8QE~^5A z22+=>x3)4f8Kb4Dbm3RR5G#h07OdJ=tB6Fh_bNI0s3hmNoHRYf=eb>MtYP-lu?I%= z>*InxSyF>5TM(Bf=Bug>ZHx{EUiZk^arDfXy~oe@!%@uffkCh*^u6hx10F{V+89A| zEe$}5!3|{Dz31bF>d#+Nh9{?IwSF5F3%Rjz9Nx$MSH;Bve{c2Dxevznw;#-P_1AT* zjYTB7w1a8u6^rLinYe4+4ke=4*OeqCAt@zQ%F1EX<9ZumegQ}s=tA_pD?1UmE&Y*f zC}lFMIg$TdUcxEmEEcoACkt7 -WeU+V!^|_mOd}r^uaAVlVk8x_n@rlGjNI_a(%A5BP zK&8kd;lA!4&_*G<^_#05|BFM~73HBB+W1B-o4=}Q+z3deAt4FPVjd) vpzK~ zyb+5}ZE QYSHDXBL+=9 zaO#rt9iRIqsW> 4W KS9c!-e=640cOxjr#6gDblBN$B)lD zJESJ&7HBZj(~;~ffocS556@jC-iy~XnibmDR{wf_%r|&{W0{HnMf}dt)PN4{%_0_0 z9+Nb%Pj?*IoW)(^uU;v^7zi<8VQD24=Ha5IqMW|Yb(w$soia{W33EebSU;fkHb*1d z=NEbzk65$)z?itO2u^nkeAU) R8ilI<8WRy0TK#>Wwa2apPT* z+frM?6=hm#L)%scpd=@YETCq)fKxFB1P}%kBtWaWcQoJLuH~L;tR?mBXNQX}=QrS{ z|B)3$-@RhK>utT3QXnY~3`t3mN*0Tbz);XAMUWI_M2{QR-x*yy-bKaEzWwlNbwp(B zz~tOK<4;K0$cp~RafN)RNQfXI+S%L`ic8DDKwC3hPe qb ztF0Bha@(4%c;hQ-s+ZGgw6c1fH&UNRMoRM7wIe$jzV{w#V(&p=L6KB;ZoUcvLtgGD z4-6>`Iz4aW{25y^Qu~YGfTY@MW)&cZ6iB2*;l71nYN&S}pMHEdCTc@~Kx@4gCk}5~ zd1CFt8Kb=dp0$4W?!%Dq#AL%vqzp%gec%Egw}7{EVON7bi8Z(wpn&v zt+oND+LR!?FA#4Uw9tOBa{Jn?*z?ldNDr?svjO)81@X}1(>vCUxo&B0f&pJqOCv|X z-}}ao7>sE3yiDcgAf+G=a?;X|e>NW6F#MWtz@8U(e2InbvTS-ryH rHgoK8Z2;zUu0p=m3=vtb!1_h+_wKr?C z&7oVTT*U9&f50 0vJImZ14hA zs(&Pi8_7k;JGW&2#PX}n7``uGrchZ`Wss4R3u!6o#NGE#ONa$hNI60R5Aa!oq_|i# zR}tX1&VE*K;74os4~?>(=w~I_=-+MHKg~<^@tpkO_)179>J=83z(=Iau(A7}BJy?J zWRc?6TvH9{2L$wkz8f#40rzJ^tTF%Z@uHz?TI iYv^oc2s`>8Sh`cC(3__M_U*fj_bb|a2~TEly_wx;<0 z&PAGbkd~?n;HxH*6c_s$@3U*UcI*b&ErR^cpXR0dcuszSlr176k}WGO1?}4y3G8-l zr2WZmz`al#9{f}5|1;$fiDX)krG;5t)}@HVJ0QSPK-NAfR4(|Qz{ u8kxPemyrI>wG&d)Tr+_b^0bUBJ$z1#tvM|<6%ZE_ZRmY2FwfL4 z81jsN+*KmC6ii#l!;M)TcY96mU}&l?a}K^mE{gm_>);JO*Rgx&QI9Vjw?1@m+tKE@ zk_FBuDlYj3v_cY+kc1?pzlBJ|cxYtI79#n}E3o^U0gV^4(1>8jI~U9=17&$}*gMYr z>C0s{T?U(~FXn!NS__n>amN-uYs}D@ukT&y=Hj|2NE`>&;QS&SUM&PfAqh!HLi!ts zC#;n(_&lJ2gc#|!+Rd^p&h1TyCQ>bkQZJ8Lh4Tf=$iFijjc&bNX47wekCrz7WAE%^ znz+L_{`7jiZlz49;>+A3$_ft3c(pjdQkksT(1n*7HPMl=gk_>`1}31)sdErt*dRl& z(Xo*+*h?oa;@rr>24pizFeC=DIFn^oc^QK_U^fmp-2LuaQjM~SEvx!`{<)^tG|#1% z`}X<$p6C0hXzB+Cag$9UHc=FPydG*U%-LdSwp8p-T$kDlLqlRowpb#LqKF^};?9tQ zA9cw`pMBNwV~Z!LSoPHUK|-{iR4|W#oAid>nHbvp1-UU;seh!ebw~W}!_Vtd=6gMG z(m@1iC&Wm;V>Ckdcut+UD5CE3iqa@z&_Rr!`~H|{{6r80L8dk1yU`v^c9V0O 4w(t1tLyyc@ucJY=UXRKvnGvLg_ w)kJN<> zIh>C_-FE)5>SJ{qORFp)VuSwRAS^aSLr%s9(%ahuZd4R?dx3~<5WJWTdd*QoUUE=T zX56Am4+VAJA&aA?A!eSE$!aq T=De&Lls43AJ*u&&TfF&vn1DnYp93_f*>wr ze2nK4*Jb4wT7S9-A^PyGY7f;Vn+A;c|2gnfTgL^d&VwW?zxRV*R3w}_@^0=7wfYFS z*<_LbCWiKX!9g;5goZSef5$G9w%k{vDs^EdI2jYuGv;c%>)&}JD|GEDQ{J2J9ewNT z+LKbe#D&C_P5TIfAon>+hUPE#^a5vXhqKlzTH%4KomiS9b0A3T1n~r=<&d70d1mLU z88r*$&MtxJF?evY7?Ny|6vxPAkP!ZG_nwuDbC)jgChp{afop7=rV{^{K2*2&)ceIJ zzF3p=`o6M? !>Kz}m#a54js|CPz@*tjt<8`NoM!qx 6Ko zmP@b0cC_0R1?@9e7MGYSS9A&KyJ!G@V{_;9+OZ)Bg4}C3TEnS`*XXEyI)w0ACL9%# z4>QR|hTTv%u7kR1CP?-L`&m$VC}DeQP~+J=<8na=x7&o!-Y@7*7i9N4v bs`-B4Azlnji#R>CfX+Ahqknh*{ z>CvL)$ z3XlCq&$fT!vF^`#DO7r&OlSH@xZYcBf RJfuKdSBVrF9^L$e#I$|Ef`=F%sYgaP#QN 0) { + name.splice(i - 1, 2); + i -= 2; + } + } + } + //end trimDots + + name = name.join("/"); + } + } + return name; + } + + function makeRequire(relName, forceSync) { + return function () { + //A version of a require function that passes a moduleName + //value for items that may need to + //look up paths relative to the moduleName + return req.apply(undef, aps.call(arguments, 0).concat([relName, forceSync])); + }; + } + + function makeNormalize(relName) { + return function (name) { + return normalize(name, relName); + }; + } + + function makeLoad(depName) { + return function (value) { + defined[depName] = value; + }; + } + + function callDep(name) { + if (waiting.hasOwnProperty(name)) { + var args = waiting[name]; + delete waiting[name]; + main.apply(undef, args); + } + return defined[name]; + } + + /** + * Makes a name map, normalizing the name, and using a plugin + * for normalization if necessary. Grabs a ref to plugin + * too, as an optimization. + */ + function makeMap(name, relName) { + var prefix, plugin, + index = name.indexOf('!'); + + if (index !== -1) { + prefix = normalize(name.slice(0, index), relName); + name = name.slice(index + 1); + plugin = callDep(prefix); + + //Normalize according + if (plugin && plugin.normalize) { + name = plugin.normalize(name, makeNormalize(relName)); + } else { + name = normalize(name, relName); + } + } else { + name = normalize(name, relName); + } + + //Using ridiculous property names for space reasons + return { + f: prefix ? prefix + '!' + name : name, //fullName + n: name, + p: plugin + }; + } + + main = function (name, deps, callback, relName) { + var args = [], + usingExports, + cjsModule, depName, i, ret, map; + + //Use name if no relName + if (!relName) { + relName = name; + } + + //Call the callback to define the module, if necessary. + if (typeof callback === 'function') { + + //Default to require, exports, module if no deps if + //the factory arg has any arguments specified. + if (!deps.length && callback.length) { + deps = ['require', 'exports', 'module']; + } + + //Pull out the defined dependencies and pass the ordered + //values to the callback. + for (i = 0; i < deps.length; i++) { + map = makeMap(deps[i], relName); + depName = map.f; + + //Fast path CommonJS standard dependencies. + if (depName === "require") { + args[i] = makeRequire(name); + } else if (depName === "exports") { + //CommonJS module spec 1.1 + args[i] = defined[name] = {}; + usingExports = true; + } else if (depName === "module") { + //CommonJS module spec 1.1 + cjsModule = args[i] = { + id: name, + uri: '', + exports: defined[name] + }; + } else if (defined.hasOwnProperty(depName) || waiting.hasOwnProperty(depName)) { + args[i] = callDep(depName); + } else if (map.p) { + map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {}); + args[i] = defined[depName]; + } else { + throw name + ' missing ' + depName; + } + } + + ret = callback.apply(defined[name], args); + + if (name) { + //If setting exports via "module" is in play, + //favor that over return value and exports. After that, + //favor a non-undefined return value over exports use. + if (cjsModule && cjsModule.exports !== undef) { + defined[name] = cjsModule.exports; + } else if (!usingExports) { + //Use the return value from the function. + defined[name] = ret; + } + } + } else if (name) { + //May just be an object definition for the module. Only + //worry about defining if have a module name. + defined[name] = callback; + } + }; + + requirejs = req = function (deps, callback, relName, forceSync) { + if (typeof deps === "string") { + + //Just return the module wanted. In this scenario, the + //deps arg is the module name, and second arg (if passed) + //is just the relName. + //Normalize module name, if it contains . or .. + return callDep(makeMap(deps, callback).f); + } else if (!deps.splice) { + //deps is a config object, not an array. + //Drop the config stuff on the ground. + if (callback.splice) { + //callback is an array, which means it is a dependency list. + //Adjust args if there are dependencies + deps = callback; + callback = arguments[2]; + } else { + deps = []; + } + } + + //Simulate async callback; + if (forceSync) { + main(undef, deps, callback, relName); + } else { + setTimeout(function () { + main(undef, deps, callback, relName); + }, 15); + } + + return req; + }; + + /** + * Just drops the config on the floor, but returns req in case + * the config return value is used. + */ + req.config = function () { + return req; + }; + + /** + * Export require as a global, but only if it does not already exist. + */ + if (!require) { + require = req; + } + + define = function (name, deps, callback) { + + //This module may not have dependencies + if (!deps.splice) { + //deps is not an array, so probably means + //an object literal or factory function for + //the value. Adjust args. + callback = deps; + deps = []; + } + + if (define.unordered) { + waiting[name] = [name, deps, callback]; + } else { + main(name, deps, callback); + } + }; + + define.amd = { + jQuery: true + }; +}()); diff --git a/assets/js/libs/backbone.js b/assets/js/libs/backbone.js new file mode 100644 index 0000000..3373c95 --- /dev/null +++ b/assets/js/libs/backbone.js @@ -0,0 +1,1431 @@ +// Backbone.js 0.9.2 + +// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc. +// Backbone may be freely distributed under the MIT license. +// For all details and documentation: +// http://backbonejs.org + +(function(){ + + // Initial Setup + // ------------- + + // Save a reference to the global object (`window` in the browser, `global` + // on the server). + var root = this; + + // Save the previous value of the `Backbone` variable, so that it can be + // restored later on, if `noConflict` is used. + var previousBackbone = root.Backbone; + + // Create a local reference to slice/splice. + var slice = Array.prototype.slice; + var splice = Array.prototype.splice; + + // The top-level namespace. All public Backbone classes and modules will + // be attached to this. Exported for both CommonJS and the browser. + var Backbone; + if (typeof exports !== 'undefined') { + Backbone = exports; + } else { + Backbone = root.Backbone = {}; + } + + // Current version of the library. Keep in sync with `package.json`. + Backbone.VERSION = '0.9.2'; + + // Require Underscore, if we're on the server, and it's not already present. + var _ = root._; + if (!_ && (typeof require !== 'undefined')) _ = require('underscore'); + + // For Backbone's purposes, jQuery, Zepto, or Ender owns the `$` variable. + var $ = root.jQuery || root.Zepto || root.ender; + + // Set the JavaScript library that will be used for DOM manipulation and + // Ajax calls (a.k.a. the `$` variable). By default Backbone will use: jQuery, + // Zepto, or Ender; but the `setDomLibrary()` method lets you inject an + // alternate JavaScript library (or a mock library for testing your views + // outside of a browser). + Backbone.setDomLibrary = function(lib) { + $ = lib; + }; + + // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable + // to its previous owner. Returns a reference to this Backbone object. + Backbone.noConflict = function() { + root.Backbone = previousBackbone; + return this; + }; + + // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option + // will fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and + // set a `X-Http-Method-Override` header. + Backbone.emulateHTTP = false; + + // Turn on `emulateJSON` to support legacy servers that can't deal with direct + // `application/json` requests ... will encode the body as + // `application/x-www-form-urlencoded` instead and will send the model in a + // form param named `model`. + Backbone.emulateJSON = false; + + // Backbone.Events + // ----------------- + + // Regular expression used to split event strings + var eventSplitter = /\s+/; + + // A module that can be mixed in to *any object* in order to provide it with + // custom events. You may bind with `on` or remove with `off` callback functions + // to an event; trigger`-ing an event fires all callbacks in succession. + // + // var object = {}; + // _.extend(object, Backbone.Events); + // object.on('expand', function(){ alert('expanded'); }); + // object.trigger('expand'); + // + var Events = Backbone.Events = { + + // Bind one or more space separated events, `events`, to a `callback` + // function. Passing `"all"` will bind the callback to all events fired. + on: function(events, callback, context) { + + var calls, event, node, tail, list; + if (!callback) return this; + events = events.split(eventSplitter); + calls = this._callbacks || (this._callbacks = {}); + + // Create an immutable callback list, allowing traversal during + // modification. The tail is an empty object that will always be used + // as the next node. + while (event = events.shift()) { + list = calls[event]; + node = list ? list.tail : {}; + node.next = tail = {}; + node.context = context; + node.callback = callback; + calls[event] = {tail: tail, next: list ? list.next : node}; + } + + return this; + }, + + // Remove one or many callbacks. If `context` is null, removes all callbacks + // with that function. If `callback` is null, removes all callbacks for the + // event. If `events` is null, removes all bound callbacks for all events. + off: function(events, callback, context) { + var event, calls, node, tail, cb, ctx; + + // No events, or removing *all* events. + if (!(calls = this._callbacks)) return; + if (!(events || callback || context)) { + delete this._callbacks; + return this; + } + + // Loop through the listed events and contexts, splicing them out of the + // linked list of callbacks if appropriate. + events = events ? events.split(eventSplitter) : _.keys(calls); + while (event = events.shift()) { + node = calls[event]; + delete calls[event]; + if (!node || !(callback || context)) continue; + // Create a new list, omitting the indicated callbacks. + tail = node.tail; + while ((node = node.next) !== tail) { + cb = node.callback; + ctx = node.context; + if ((callback && cb !== callback) || (context && ctx !== context)) { + this.on(event, cb, ctx); + } + } + } + + return this; + }, + + // Trigger one or many events, firing all bound callbacks. Callbacks are + // passed the same arguments as `trigger` is, apart from the event name + // (unless you're listening on `"all"`, which will cause your callback to + // receive the true name of the event as the first argument). + trigger: function(events) { + var event, node, calls, tail, args, all, rest; + if (!(calls = this._callbacks)) return this; + all = calls.all; + events = events.split(eventSplitter); + rest = slice.call(arguments, 1); + + // For each event, walk through the linked list of callbacks twice, + // first to trigger the event, then to trigger any `"all"` callbacks. + while (event = events.shift()) { + if (node = calls[event]) { + tail = node.tail; + while ((node = node.next) !== tail) { + node.callback.apply(node.context || this, rest); + } + } + if (node = all) { + tail = node.tail; + args = [event].concat(rest); + while ((node = node.next) !== tail) { + node.callback.apply(node.context || this, args); + } + } + } + + return this; + } + + }; + + // Aliases for backwards compatibility. + Events.bind = Events.on; + Events.unbind = Events.off; + + // Backbone.Model + // -------------- + + // Create a new model, with defined attributes. A client id (`cid`) + // is automatically generated and assigned for you. + var Model = Backbone.Model = function(attributes, options) { + var defaults; + attributes || (attributes = {}); + if (options && options.parse) attributes = this.parse(attributes); + if (defaults = getValue(this, 'defaults')) { + attributes = _.extend({}, defaults, attributes); + } + if (options && options.collection) this.collection = options.collection; + this.attributes = {}; + this._escapedAttributes = {}; + this.cid = _.uniqueId('c'); + this.changed = {}; + this._silent = {}; + this._pending = {}; + this.set(attributes, {silent: true}); + // Reset change tracking. + this.changed = {}; + this._silent = {}; + this._pending = {}; + this._previousAttributes = _.clone(this.attributes); + this.initialize.apply(this, arguments); + }; + + // Attach all inheritable methods to the Model prototype. + _.extend(Model.prototype, Events, { + + // A hash of attributes whose current and previous value differ. + changed: null, + + // A hash of attributes that have silently changed since the last time + // `change` was called. Will become pending attributes on the next call. + _silent: null, + + // A hash of attributes that have changed since the last `'change'` event + // began. + _pending: null, + + // The default name for the JSON `id` attribute is `"id"`. MongoDB and + // CouchDB users may want to set this to `"_id"`. + idAttribute: 'id', + + // Initialize is an empty function by default. Override it with your own + // initialization logic. + initialize: function(){}, + + // Return a copy of the model's `attributes` object. + toJSON: function(options) { + return _.clone(this.attributes); + }, + + // Get the value of an attribute. + get: function(attr) { + return this.attributes[attr]; + }, + + // Get the HTML-escaped value of an attribute. + escape: function(attr) { + var html; + if (html = this._escapedAttributes[attr]) return html; + var val = this.get(attr); + return this._escapedAttributes[attr] = _.escape(val == null ? '' : '' + val); + }, + + // Returns `true` if the attribute contains a value that is not null + // or undefined. + has: function(attr) { + return this.get(attr) != null; + }, + + // Set a hash of model attributes on the object, firing `"change"` unless + // you choose to silence it. + set: function(key, value, options) { + var attrs, attr, val; + + // Handle both `"key", value` and `{key: value}` -style arguments. + if (_.isObject(key) || key == null) { + attrs = key; + options = value; + } else { + attrs = {}; + attrs[key] = value; + } + + // Extract attributes and options. + options || (options = {}); + if (!attrs) return this; + if (attrs instanceof Model) attrs = attrs.attributes; + if (options.unset) for (attr in attrs) attrs[attr] = void 0; + + // Run validation. + if (!this._validate(attrs, options)) return false; + + // Check for changes of `id`. + if (this.idAttribute in attrs) this.id = attrs[this.idAttribute]; + + var changes = options.changes = {}; + var now = this.attributes; + var escaped = this._escapedAttributes; + var prev = this._previousAttributes || {}; + + // For each `set` attribute... + for (attr in attrs) { + val = attrs[attr]; + + // If the new and current value differ, record the change. + if (!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr))) { + delete escaped[attr]; + (options.silent ? this._silent : changes)[attr] = true; + } + + // Update or delete the current value. + options.unset ? delete now[attr] : now[attr] = val; + + // If the new and previous value differ, record the change. If not, + // then remove changes for this attribute. + if (!_.isEqual(prev[attr], val) || (_.has(now, attr) != _.has(prev, attr))) { + this.changed[attr] = val; + if (!options.silent) this._pending[attr] = true; + } else { + delete this.changed[attr]; + delete this._pending[attr]; + } + } + + // Fire the `"change"` events. + if (!options.silent) this.change(options); + return this; + }, + + // Remove an attribute from the model, firing `"change"` unless you choose + // to silence it. `unset` is a noop if the attribute doesn't exist. + unset: function(attr, options) { + (options || (options = {})).unset = true; + return this.set(attr, null, options); + }, + + // Clear all attributes on the model, firing `"change"` unless you choose + // to silence it. + clear: function(options) { + (options || (options = {})).unset = true; + return this.set(_.clone(this.attributes), options); + }, + + // Fetch the model from the server. If the server's representation of the + // model differs from its current attributes, they will be overriden, + // triggering a `"change"` event. + fetch: function(options) { + options = options ? _.clone(options) : {}; + var model = this; + var success = options.success; + options.success = function(resp, status, xhr) { + if (!model.set(model.parse(resp, xhr), options)) return false; + if (success) success(model, resp); + }; + options.error = Backbone.wrapError(options.error, model, options); + return (this.sync || Backbone.sync).call(this, 'read', this, options); + }, + + // Set a hash of model attributes, and sync the model to the server. + // If the server returns an attributes hash that differs, the model's + // state will be `set` again. + save: function(key, value, options) { + var attrs, current; + + // Handle both `("key", value)` and `({key: value})` -style calls. + if (_.isObject(key) || key == null) { + attrs = key; + options = value; + } else { + attrs = {}; + attrs[key] = value; + } + options = options ? _.clone(options) : {}; + + // If we're "wait"-ing to set changed attributes, validate early. + if (options.wait) { + if (!this._validate(attrs, options)) return false; + current = _.clone(this.attributes); + } + + // Regular saves `set` attributes before persisting to the server. + var silentOptions = _.extend({}, options, {silent: true}); + if (attrs && !this.set(attrs, options.wait ? silentOptions : options)) { + return false; + } + + // After a successful server-side save, the client is (optionally) + // updated with the server-side state. + var model = this; + var success = options.success; + options.success = function(resp, status, xhr) { + var serverAttrs = model.parse(resp, xhr); + if (options.wait) { + delete options.wait; + serverAttrs = _.extend(attrs || {}, serverAttrs); + } + if (!model.set(serverAttrs, options)) return false; + if (success) { + success(model, resp); + } else { + model.trigger('sync', model, resp, options); + } + }; + + // Finish configuring and sending the Ajax request. + options.error = Backbone.wrapError(options.error, model, options); + var method = this.isNew() ? 'create' : 'update'; + var xhr = (this.sync || Backbone.sync).call(this, method, this, options); + if (options.wait) this.set(current, silentOptions); + return xhr; + }, + + // Destroy this model on the server if it was already persisted. + // Optimistically removes the model from its collection, if it has one. + // If `wait: true` is passed, waits for the server to respond before removal. + destroy: function(options) { + options = options ? _.clone(options) : {}; + var model = this; + var success = options.success; + + var triggerDestroy = function() { + model.trigger('destroy', model, model.collection, options); + }; + + if (this.isNew()) { + triggerDestroy(); + return false; + } + + options.success = function(resp) { + if (options.wait) triggerDestroy(); + if (success) { + success(model, resp); + } else { + model.trigger('sync', model, resp, options); + } + }; + + options.error = Backbone.wrapError(options.error, model, options); + var xhr = (this.sync || Backbone.sync).call(this, 'delete', this, options); + if (!options.wait) triggerDestroy(); + return xhr; + }, + + // Default URL for the model's representation on the server -- if you're + // using Backbone's restful methods, override this to change the endpoint + // that will be called. + url: function() { + var base = getValue(this, 'urlRoot') || getValue(this.collection, 'url') || urlError(); + if (this.isNew()) return base; + return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id); + }, + + // **parse** converts a response into the hash of attributes to be `set` on + // the model. The default implementation is just to pass the response along. + parse: function(resp, xhr) { + return resp; + }, + + // Create a new model with identical attributes to this one. + clone: function() { + return new this.constructor(this.attributes); + }, + + // A model is new if it has never been saved to the server, and lacks an id. + isNew: function() { + return this.id == null; + }, + + // Call this method to manually fire a `"change"` event for this model and + // a `"change:attribute"` event for each changed attribute. + // Calling this will cause all objects observing the model to update. + change: function(options) { + options || (options = {}); + var changing = this._changing; + this._changing = true; + + // Silent changes become pending changes. + for (var attr in this._silent) this._pending[attr] = true; + + // Silent changes are triggered. + var changes = _.extend({}, options.changes, this._silent); + this._silent = {}; + for (var attr in changes) { + this.trigger('change:' + attr, this, this.get(attr), options); + } + if (changing) return this; + + // Continue firing `"change"` events while there are pending changes. + while (!_.isEmpty(this._pending)) { + this._pending = {}; + this.trigger('change', this, options); + // Pending and silent changes still remain. + for (var attr in this.changed) { + if (this._pending[attr] || this._silent[attr]) continue; + delete this.changed[attr]; + } + this._previousAttributes = _.clone(this.attributes); + } + + this._changing = false; + return this; + }, + + // Determine if the model has changed since the last `"change"` event. + // If you specify an attribute name, determine if that attribute has changed. + hasChanged: function(attr) { + if (!arguments.length) return !_.isEmpty(this.changed); + return _.has(this.changed, attr); + }, + + // Return an object containing all the attributes that have changed, or + // false if there are no changed attributes. Useful for determining what + // parts of a view need to be updated and/or what attributes need to be + // persisted to the server. Unset attributes will be set to undefined. + // You can also pass an attributes object to diff against the model, + // determining if there *would be* a change. + changedAttributes: function(diff) { + if (!diff) return this.hasChanged() ? _.clone(this.changed) : false; + var val, changed = false, old = this._previousAttributes; + for (var attr in diff) { + if (_.isEqual(old[attr], (val = diff[attr]))) continue; + (changed || (changed = {}))[attr] = val; + } + return changed; + }, + + // Get the previous value of an attribute, recorded at the time the last + // `"change"` event was fired. + previous: function(attr) { + if (!arguments.length || !this._previousAttributes) return null; + return this._previousAttributes[attr]; + }, + + // Get all of the attributes of the model at the time of the previous + // `"change"` event. + previousAttributes: function() { + return _.clone(this._previousAttributes); + }, + + // Check if the model is currently in a valid state. It's only possible to + // get into an *invalid* state if you're using silent changes. + isValid: function() { + return !this.validate(this.attributes); + }, + + // Run validation against the next complete set of model attributes, + // returning `true` if all is well. If a specific `error` callback has + // been passed, call that instead of firing the general `"error"` event. + _validate: function(attrs, options) { + if (options.silent || !this.validate) return true; + attrs = _.extend({}, this.attributes, attrs); + var error = this.validate(attrs, options); + if (!error) return true; + if (options && options.error) { + options.error(this, error, options); + } else { + this.trigger('error', this, error, options); + } + return false; + } + + }); + + // Backbone.Collection + // ------------------- + + // Provides a standard collection class for our sets of models, ordered + // or unordered. If a `comparator` is specified, the Collection will maintain + // its models in sort order, as they're added and removed. + var Collection = Backbone.Collection = function(models, options) { + options || (options = {}); + if (options.model) this.model = options.model; + if (options.comparator) this.comparator = options.comparator; + this._reset(); + this.initialize.apply(this, arguments); + if (models) this.reset(models, {silent: true, parse: options.parse}); + }; + + // Define the Collection's inheritable methods. + _.extend(Collection.prototype, Events, { + + // The default model for a collection is just a **Backbone.Model**. + // This should be overridden in most cases. + model: Model, + + // Initialize is an empty function by default. Override it with your own + // initialization logic. + initialize: function(){}, + + // The JSON representation of a Collection is an array of the + // models' attributes. + toJSON: function(options) { + return this.map(function(model){ return model.toJSON(options); }); + }, + + // Add a model, or list of models to the set. Pass **silent** to avoid + // firing the `add` event for every new model. + add: function(models, options) { + var i, index, length, model, cid, id, cids = {}, ids = {}, dups = []; + options || (options = {}); + models = _.isArray(models) ? models.slice() : [models]; + + // Begin by turning bare objects into model references, and preventing + // invalid models or duplicate models from being added. + for (i = 0, length = models.length; i < length; i++) { + if (!(model = models[i] = this._prepareModel(models[i], options))) { + throw new Error("Can't add an invalid model to a collection"); + } + cid = model.cid; + id = model.id; + if (cids[cid] || this._byCid[cid] || ((id != null) && (ids[id] || this._byId[id]))) { + dups.push(i); + continue; + } + cids[cid] = ids[id] = model; + } + + // Remove duplicates. + i = dups.length; + while (i--) { + models.splice(dups[i], 1); + } + + // Listen to added models' events, and index models for lookup by + // `id` and by `cid`. + for (i = 0, length = models.length; i < length; i++) { + (model = models[i]).on('all', this._onModelEvent, this); + this._byCid[model.cid] = model; + if (model.id != null) this._byId[model.id] = model; + } + + // Insert models into the collection, re-sorting if needed, and triggering + // `add` events unless silenced. + this.length += length; + index = options.at != null ? options.at : this.models.length; + splice.apply(this.models, [index, 0].concat(models)); + if (this.comparator) this.sort({silent: true}); + if (options.silent) return this; + for (i = 0, length = this.models.length; i < length; i++) { + if (!cids[(model = this.models[i]).cid]) continue; + options.index = i; + model.trigger('add', model, this, options); + } + return this; + }, + + // Remove a model, or a list of models from the set. Pass silent to avoid + // firing the `remove` event for every model removed. + remove: function(models, options) { + var i, l, index, model; + options || (options = {}); + models = _.isArray(models) ? models.slice() : [models]; + for (i = 0, l = models.length; i < l; i++) { + model = this.getByCid(models[i]) || this.get(models[i]); + if (!model) continue; + delete this._byId[model.id]; + delete this._byCid[model.cid]; + index = this.indexOf(model); + this.models.splice(index, 1); + this.length--; + if (!options.silent) { + options.index = index; + model.trigger('remove', model, this, options); + } + this._removeReference(model); + } + return this; + }, + + // Add a model to the end of the collection. + push: function(model, options) { + model = this._prepareModel(model, options); + this.add(model, options); + return model; + }, + + // Remove a model from the end of the collection. + pop: function(options) { + var model = this.at(this.length - 1); + this.remove(model, options); + return model; + }, + + // Add a model to the beginning of the collection. + unshift: function(model, options) { + model = this._prepareModel(model, options); + this.add(model, _.extend({at: 0}, options)); + return model; + }, + + // Remove a model from the beginning of the collection. + shift: function(options) { + var model = this.at(0); + this.remove(model, options); + return model; + }, + + // Get a model from the set by id. + get: function(id) { + if (id == null) return void 0; + return this._byId[id.id != null ? id.id : id]; + }, + + // Get a model from the set by client id. + getByCid: function(cid) { + return cid && this._byCid[cid.cid || cid]; + }, + + // Get the model at the given index. + at: function(index) { + return this.models[index]; + }, + + // Return models with matching attributes. Useful for simple cases of `filter`. + where: function(attrs) { + if (_.isEmpty(attrs)) return []; + return this.filter(function(model) { + for (var key in attrs) { + if (attrs[key] !== model.get(key)) return false; + } + return true; + }); + }, + + // Force the collection to re-sort itself. You don't need to call this under + // normal circumstances, as the set will maintain sort order as each item + // is added. + sort: function(options) { + options || (options = {}); + if (!this.comparator) throw new Error('Cannot sort a set without a comparator'); + var boundComparator = _.bind(this.comparator, this); + if (this.comparator.length == 1) { + this.models = this.sortBy(boundComparator); + } else { + this.models.sort(boundComparator); + } + if (!options.silent) this.trigger('reset', this, options); + return this; + }, + + // Pluck an attribute from each model in the collection. + pluck: function(attr) { + return _.map(this.models, function(model){ return model.get(attr); }); + }, + + // When you have more items than you want to add or remove individually, + // you can reset the entire set with a new list of models, without firing + // any `add` or `remove` events. Fires `reset` when finished. + reset: function(models, options) { + models || (models = []); + options || (options = {}); + for (var i = 0, l = this.models.length; i < l; i++) { + this._removeReference(this.models[i]); + } + this._reset(); + this.add(models, _.extend({silent: true}, options)); + if (!options.silent) this.trigger('reset', this, options); + return this; + }, + + // Fetch the default set of models for this collection, resetting the + // collection when they arrive. If `add: true` is passed, appends the + // models to the collection instead of resetting. + fetch: function(options) { + options = options ? _.clone(options) : {}; + if (options.parse === undefined) options.parse = true; + var collection = this; + var success = options.success; + options.success = function(resp, status, xhr) { + collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options); + if (success) success(collection, resp); + }; + options.error = Backbone.wrapError(options.error, collection, options); + return (this.sync || Backbone.sync).call(this, 'read', this, options); + }, + + // Create a new instance of a model in this collection. Add the model to the + // collection immediately, unless `wait: true` is passed, in which case we + // wait for the server to agree. + create: function(model, options) { + var coll = this; + options = options ? _.clone(options) : {}; + model = this._prepareModel(model, options); + if (!model) return false; + if (!options.wait) coll.add(model, options); + var success = options.success; + options.success = function(nextModel, resp, xhr) { + if (options.wait) coll.add(nextModel, options); + if (success) { + success(nextModel, resp); + } else { + nextModel.trigger('sync', model, resp, options); + } + }; + model.save(null, options); + return model; + }, + + // **parse** converts a response into a list of models to be added to the + // collection. The default implementation is just to pass it through. + parse: function(resp, xhr) { + return resp; + }, + + // Proxy to _'s chain. Can't be proxied the same way the rest of the + // underscore methods are proxied because it relies on the underscore + // constructor. + chain: function () { + return _(this.models).chain(); + }, + + // Reset all internal state. Called when the collection is reset. + _reset: function(options) { + this.length = 0; + this.models = []; + this._byId = {}; + this._byCid = {}; + }, + + // Prepare a model or hash of attributes to be added to this collection. + _prepareModel: function(model, options) { + options || (options = {}); + if (!(model instanceof Model)) { + var attrs = model; + options.collection = this; + model = new this.model(attrs, options); + if (!model._validate(model.attributes, options)) model = false; + } else if (!model.collection) { + model.collection = this; + } + return model; + }, + + // Internal method to remove a model's ties to a collection. + _removeReference: function(model) { + if (this == model.collection) { + delete model.collection; + } + model.off('all', this._onModelEvent, this); + }, + + // Internal method called every time a model in the set fires an event. + // Sets need to update their indexes when models change ids. All other + // events simply proxy through. "add" and "remove" events that originate + // in other collections are ignored. + _onModelEvent: function(event, model, collection, options) { + if ((event == 'add' || event == 'remove') && collection != this) return; + if (event == 'destroy') { + this.remove(model, options); + } + if (model && event === 'change:' + model.idAttribute) { + delete this._byId[model.previous(model.idAttribute)]; + this._byId[model.id] = model; + } + this.trigger.apply(this, arguments); + } + + }); + + // Underscore methods that we want to implement on the Collection. + var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find', + 'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any', + 'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex', + 'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf', + 'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy']; + + // Mix in each Underscore method as a proxy to `Collection#models`. + _.each(methods, function(method) { + Collection.prototype[method] = function() { + return _[method].apply(_, [this.models].concat(_.toArray(arguments))); + }; + }); + + // Backbone.Router + // ------------------- + + // Routers map faux-URLs to actions, and fire events when routes are + // matched. Creating a new one sets its `routes` hash, if not set statically. + var Router = Backbone.Router = function(options) { + options || (options = {}); + if (options.routes) this.routes = options.routes; + this._bindRoutes(); + this.initialize.apply(this, arguments); + }; + + // Cached regular expressions for matching named param parts and splatted + // parts of route strings. + var namedParam = /:\w+/g; + var splatParam = /\*\w+/g; + var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g; + + // Set up all inheritable **Backbone.Router** properties and methods. + _.extend(Router.prototype, Events, { + + // Initialize is an empty function by default. Override it with your own + // initialization logic. + initialize: function(){}, + + // Manually bind a single named route to a callback. For example: + // + // this.route('search/:query/p:num', 'search', function(query, num) { + // ... + // }); + // + route: function(route, name, callback) { + Backbone.history || (Backbone.history = new History); + if (!_.isRegExp(route)) route = this._routeToRegExp(route); + if (!callback) callback = this[name]; + Backbone.history.route(route, _.bind(function(fragment) { + var args = this._extractParameters(route, fragment); + callback && callback.apply(this, args); + this.trigger.apply(this, ['route:' + name].concat(args)); + Backbone.history.trigger('route', this, name, args); + }, this)); + return this; + }, + + // Simple proxy to `Backbone.history` to save a fragment into the history. + navigate: function(fragment, options) { + Backbone.history.navigate(fragment, options); + }, + + // Bind all defined routes to `Backbone.history`. We have to reverse the + // order of the routes here to support behavior where the most general + // routes can be defined at the bottom of the route map. + _bindRoutes: function() { + if (!this.routes) return; + var routes = []; + for (var route in this.routes) { + routes.unshift([route, this.routes[route]]); + } + for (var i = 0, l = routes.length; i < l; i++) { + this.route(routes[i][0], routes[i][1], this[routes[i][1]]); + } + }, + + // Convert a route string into a regular expression, suitable for matching + // against the current location hash. + _routeToRegExp: function(route) { + route = route.replace(escapeRegExp, '\\$&') + .replace(namedParam, '([^\/]+)') + .replace(splatParam, '(.*?)'); + return new RegExp('^' + route + '$'); + }, + + // Given a route, and a URL fragment that it matches, return the array of + // extracted parameters. + _extractParameters: function(route, fragment) { + return route.exec(fragment).slice(1); + } + + }); + + // Backbone.History + // ---------------- + + // Handles cross-browser history management, based on URL fragments. If the + // browser does not support `onhashchange`, falls back to polling. + var History = Backbone.History = function() { + this.handlers = []; + _.bindAll(this, 'checkUrl'); + }; + + // Cached regex for cleaning leading hashes and slashes . + var routeStripper = /^[#\/]/; + + // Cached regex for detecting MSIE. + var isExplorer = /msie [\w.]+/; + + // Has the history handling already been started? + History.started = false; + + // Set up all inheritable **Backbone.History** properties and methods. + _.extend(History.prototype, Events, { + + // The default interval to poll for hash changes, if necessary, is + // twenty times a second. + interval: 50, + + // Gets the true hash value. Cannot use location.hash directly due to bug + // in Firefox where location.hash will always be decoded. + getHash: function(windowOverride) { + var loc = windowOverride ? windowOverride.location : window.location; + var match = loc.href.match(/#(.*)$/); + return match ? match[1] : ''; + }, + + // Get the cross-browser normalized URL fragment, either from the URL, + // the hash, or the override. + getFragment: function(fragment, forcePushState) { + if (fragment == null) { + if (this._hasPushState || forcePushState) { + fragment = window.location.pathname; + var search = window.location.search; + if (search) fragment += search; + } else { + fragment = this.getHash(); + } + } + if (!fragment.indexOf(this.options.root)) fragment = fragment.substr(this.options.root.length); + return fragment.replace(routeStripper, ''); + }, + + // Start the hash change handling, returning `true` if the current URL matches + // an existing route, and `false` otherwise. + start: function(options) { + if (History.started) throw new Error("Backbone.history has already been started"); + History.started = true; + + // Figure out the initial configuration. Do we need an iframe? + // Is pushState desired ... is it available? + this.options = _.extend({}, {root: '/'}, this.options, options); + this._wantsHashChange = this.options.hashChange !== false; + this._wantsPushState = !!this.options.pushState; + this._hasPushState = !!(this.options.pushState && window.history && window.history.pushState); + var fragment = this.getFragment(); + var docMode = document.documentMode; + var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7)); + + if (oldIE) { + this.iframe = $('').hide().appendTo('body')[0].contentWindow; + this.navigate(fragment); + } + + // Depending on whether we're using pushState or hashes, and whether + // 'onhashchange' is supported, determine how we check the URL state. + if (this._hasPushState) { + $(window).bind('popstate', this.checkUrl); + } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) { + $(window).bind('hashchange', this.checkUrl); + } else if (this._wantsHashChange) { + this._checkUrlInterval = setInterval(this.checkUrl, this.interval); + } + + // Determine if we need to change the base url, for a pushState link + // opened by a non-pushState browser. + this.fragment = fragment; + var loc = window.location; + var atRoot = loc.pathname == this.options.root; + + // If we've started off with a route from a `pushState`-enabled browser, + // but we're currently in a browser that doesn't support it... + if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) { + this.fragment = this.getFragment(null, true); + window.location.replace(this.options.root + '#' + this.fragment); + // Return immediately as browser will do redirect to new url + return true; + + // Or if we've started out with a hash-based route, but we're currently + // in a browser where it could be `pushState`-based instead... + } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) { + this.fragment = this.getHash().replace(routeStripper, ''); + window.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment); + } + + if (!this.options.silent) { + return this.loadUrl(); + } + }, + + // Disable Backbone.history, perhaps temporarily. Not useful in a real app, + // but possibly useful for unit testing Routers. + stop: function() { + $(window).unbind('popstate', this.checkUrl).unbind('hashchange', this.checkUrl); + clearInterval(this._checkUrlInterval); + History.started = false; + }, + + // Add a route to be tested when the fragment changes. Routes added later + // may override previous routes. + route: function(route, callback) { + this.handlers.unshift({route: route, callback: callback}); + }, + + // Checks the current URL to see if it has changed, and if it has, + // calls `loadUrl`, normalizing across the hidden iframe. + checkUrl: function(e) { + var current = this.getFragment(); + if (current == this.fragment && this.iframe) current = this.getFragment(this.getHash(this.iframe)); + if (current == this.fragment) return false; + if (this.iframe) this.navigate(current); + this.loadUrl() || this.loadUrl(this.getHash()); + }, + + // Attempt to load the current URL fragment. If a route succeeds with a + // match, returns `true`. If no defined routes matches the fragment, + // returns `false`. + loadUrl: function(fragmentOverride) { + var fragment = this.fragment = this.getFragment(fragmentOverride); + var matched = _.any(this.handlers, function(handler) { + if (handler.route.test(fragment)) { + handler.callback(fragment); + return true; + } + }); + return matched; + }, + + // Save a fragment into the hash history, or replace the URL state if the + // 'replace' option is passed. You are responsible for properly URL-encoding + // the fragment in advance. + // + // The options object can contain `trigger: true` if you wish to have the + // route callback be fired (not usually desirable), or `replace: true`, if + // you wish to modify the current URL without adding an entry to the history. + navigate: function(fragment, options) { + if (!History.started) return false; + if (!options || options === true) options = {trigger: options}; + var frag = (fragment || '').replace(routeStripper, ''); + if (this.fragment == frag) return; + + // If pushState is available, we use it to set the fragment as a real URL. + if (this._hasPushState) { + if (frag.indexOf(this.options.root) != 0) frag = this.options.root + frag; + this.fragment = frag; + window.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, frag); + + // If hash changes haven't been explicitly disabled, update the hash + // fragment to store history. + } else if (this._wantsHashChange) { + this.fragment = frag; + this._updateHash(window.location, frag, options.replace); + if (this.iframe && (frag != this.getFragment(this.getHash(this.iframe)))) { + // Opening and closing the iframe tricks IE7 and earlier to push a history entry on hash-tag change. + // When replace is true, we don't want this. + if(!options.replace) this.iframe.document.open().close(); + this._updateHash(this.iframe.location, frag, options.replace); + } + + // If you've told us that you explicitly don't want fallback hashchange- + // based history, then `navigate` becomes a page refresh. + } else { + window.location.assign(this.options.root + fragment); + } + if (options.trigger) this.loadUrl(fragment); + }, + + // Update the hash location, either replacing the current entry, or adding + // a new one to the browser history. + _updateHash: function(location, fragment, replace) { + if (replace) { + location.replace(location.toString().replace(/(javascript:|#).*$/, '') + '#' + fragment); + } else { + location.hash = fragment; + } + } + }); + + // Backbone.View + // ------------- + + // Creating a Backbone.View creates its initial element outside of the DOM, + // if an existing element is not provided... + var View = Backbone.View = function(options) { + this.cid = _.uniqueId('view'); + this._configure(options || {}); + this._ensureElement(); + this.initialize.apply(this, arguments); + this.delegateEvents(); + }; + + // Cached regex to split keys for `delegate`. + var delegateEventSplitter = /^(\S+)\s*(.*)$/; + + // List of view options to be merged as properties. + var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName']; + + // Set up all inheritable **Backbone.View** properties and methods. + _.extend(View.prototype, Events, { + + // The default `tagName` of a View's element is `"div"`. + tagName: 'div', + + // jQuery delegate for element lookup, scoped to DOM elements within the + // current view. This should be prefered to global lookups where possible. + $: function(selector) { + return this.$el.find(selector); + }, + + // Initialize is an empty function by default. Override it with your own + // initialization logic. + initialize: function(){}, + + // **render** is the core function that your view should override, in order + // to populate its element (`this.el`), with the appropriate HTML. The + // convention is for **render** to always return `this`. + render: function() { + return this; + }, + + // Remove this view from the DOM. Note that the view isn't present in the + // DOM by default, so calling this method may be a no-op. + remove: function() { + this.$el.remove(); + return this; + }, + + // For small amounts of DOM Elements, where a full-blown template isn't + // needed, use **make** to manufacture elements, one at a time. + // + // var el = this.make('li', {'class': 'row'}, this.model.escape('title')); + // + make: function(tagName, attributes, content) { + var el = document.createElement(tagName); + if (attributes) $(el).attr(attributes); + if (content) $(el).html(content); + return el; + }, + + // Change the view's element (`this.el` property), including event + // re-delegation. + setElement: function(element, delegate) { + if (this.$el) this.undelegateEvents(); + this.$el = (element instanceof $) ? element : $(element); + this.el = this.$el[0]; + if (delegate !== false) this.delegateEvents(); + return this; + }, + + // Set callbacks, where `this.events` is a hash of + // + // *{"event selector": "callback"}* + // + // { + // 'mousedown .title': 'edit', + // 'click .button': 'save' + // 'click .open': function(e) { ... } + // } + // + // pairs. Callbacks will be bound to the view, with `this` set properly. + // Uses event delegation for efficiency. + // Omitting the selector binds the event to `this.el`. + // This only works for delegate-able events: not `focus`, `blur`, and + // not `change`, `submit`, and `reset` in Internet Explorer. + delegateEvents: function(events) { + if (!(events || (events = getValue(this, 'events')))) return; + this.undelegateEvents(); + for (var key in events) { + var method = events[key]; + if (!_.isFunction(method)) method = this[events[key]]; + if (!method) throw new Error('Method "' + events[key] + '" does not exist'); + var match = key.match(delegateEventSplitter); + var eventName = match[1], selector = match[2]; + method = _.bind(method, this); + eventName += '.delegateEvents' + this.cid; + if (selector === '') { + this.$el.bind(eventName, method); + } else { + this.$el.delegate(selector, eventName, method); + } + } + }, + + // Clears all callbacks previously bound to the view with `delegateEvents`. + // You usually don't need to use this, but may wish to if you have multiple + // Backbone views attached to the same DOM element. + undelegateEvents: function() { + this.$el.unbind('.delegateEvents' + this.cid); + }, + + // Performs the initial configuration of a View with a set of options. + // Keys with special meaning *(model, collection, id, className)*, are + // attached directly to the view. + _configure: function(options) { + if (this.options) options = _.extend({}, this.options, options); + for (var i = 0, l = viewOptions.length; i < l; i++) { + var attr = viewOptions[i]; + if (options[attr]) this[attr] = options[attr]; + } + this.options = options; + }, + + // Ensure that the View has a DOM element to render into. + // If `this.el` is a string, pass it through `$()`, take the first + // matching element, and re-assign it to `el`. Otherwise, create + // an element from the `id`, `className` and `tagName` properties. + _ensureElement: function() { + if (!this.el) { + var attrs = getValue(this, 'attributes') || {}; + if (this.id) attrs.id = this.id; + if (this.className) attrs['class'] = this.className; + this.setElement(this.make(this.tagName, attrs), false); + } else { + this.setElement(this.el, false); + } + } + + }); + + // The self-propagating extend function that Backbone classes use. + var extend = function (protoProps, classProps) { + var child = inherits(this, protoProps, classProps); + child.extend = this.extend; + return child; + }; + + // Set up inheritance for the model, collection, and view. + Model.extend = Collection.extend = Router.extend = View.extend = extend; + + // Backbone.sync + // ------------- + + // Map from CRUD to HTTP for our default `Backbone.sync` implementation. + var methodMap = { + 'create': 'POST', + 'update': 'PUT', + 'delete': 'DELETE', + 'read': 'GET' + }; + + // Override this function to change the manner in which Backbone persists + // models to the server. You will be passed the type of request, and the + // model in question. By default, makes a RESTful Ajax request + // to the model's `url()`. Some possible customizations could be: + // + // * Use `setTimeout` to batch rapid-fire updates into a single request. + // * Send up the models as XML instead of JSON. + // * Persist models via WebSockets instead of Ajax. + // + // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests + // as `POST`, with a `_method` parameter containing the true HTTP method, + // as well as all requests with the body as `application/x-www-form-urlencoded` + // instead of `application/json` with the model in a param named `model`. + // Useful when interfacing with server-side languages like **PHP** that make + // it difficult to read the body of `PUT` requests. + Backbone.sync = function(method, model, options) { + var type = methodMap[method]; + + // Default options, unless specified. + options || (options = {}); + + // Default JSON-request options. + var params = {type: type, dataType: 'json'}; + + // Ensure that we have a URL. + if (!options.url) { + params.url = getValue(model, 'url') || urlError(); + } + + // Ensure that we have the appropriate request data. + if (!options.data && model && (method == 'create' || method == 'update')) { + params.contentType = 'application/json'; + params.data = JSON.stringify(model.toJSON()); + } + + // For older servers, emulate JSON by encoding the request into an HTML-form. + if (Backbone.emulateJSON) { + params.contentType = 'application/x-www-form-urlencoded'; + params.data = params.data ? {model: params.data} : {}; + } + + // For older servers, emulate HTTP by mimicking the HTTP method with `_method` + // And an `X-HTTP-Method-Override` header. + if (Backbone.emulateHTTP) { + if (type === 'PUT' || type === 'DELETE') { + if (Backbone.emulateJSON) params.data._method = type; + params.type = 'POST'; + params.beforeSend = function(xhr) { + xhr.setRequestHeader('X-HTTP-Method-Override', type); + }; + } + } + + // Don't process data on a non-GET request. + if (params.type !== 'GET' && !Backbone.emulateJSON) { + params.processData = false; + } + + // Make the request, allowing the user to override any Ajax options. + return $.ajax(_.extend(params, options)); + }; + + // Wrap an optional error callback with a fallback error event. + Backbone.wrapError = function(onError, originalModel, options) { + return function(model, resp) { + resp = model === originalModel ? resp : model; + if (onError) { + onError(originalModel, resp, options); + } else { + originalModel.trigger('error', originalModel, resp, options); + } + }; + }; + + // Helpers + // ------- + + // Shared empty constructor function to aid in prototype-chain creation. + var ctor = function(){}; + + // Helper function to correctly set up the prototype chain, for subclasses. + // Similar to `goog.inherits`, but uses a hash of prototype properties and + // class properties to be extended. + var inherits = function(parent, protoProps, staticProps) { + var child; + + // The constructor function for the new subclass is either defined by you + // (the "constructor" property in your `extend` definition), or defaulted + // by us to simply call the parent's constructor. + if (protoProps && protoProps.hasOwnProperty('constructor')) { + child = protoProps.constructor; + } else { + child = function(){ parent.apply(this, arguments); }; + } + + // Inherit class (static) properties from parent. + _.extend(child, parent); + + // Set the prototype chain to inherit from `parent`, without calling + // `parent`'s constructor function. + ctor.prototype = parent.prototype; + child.prototype = new ctor(); + + // Add prototype properties (instance properties) to the subclass, + // if supplied. + if (protoProps) _.extend(child.prototype, protoProps); + + // Add static properties to the constructor function, if supplied. + if (staticProps) _.extend(child, staticProps); + + // Correctly set child's `prototype.constructor`. + child.prototype.constructor = child; + + // Set a convenience property in case the parent's prototype is needed later. + child.__super__ = parent.prototype; + + return child; + }; + + // Helper function to get a value from a Backbone object as a property + // or as a function. + var getValue = function(object, prop) { + if (!(object && object[prop])) return null; + return _.isFunction(object[prop]) ? object[prop]() : object[prop]; + }; + + // Throw an error when a URL is needed, and none is supplied. + var urlError = function() { + throw new Error('A "url" property or function must be specified'); + }; + +}).call(this); diff --git a/assets/js/libs/crafty.js b/assets/js/libs/crafty.js new file mode 100644 index 0000000..d2dc50e --- /dev/null +++ b/assets/js/libs/crafty.js @@ -0,0 +1,8976 @@ +/*! +* Crafty v0.4.7 +* http://craftyjs.com +* +* Copyright 2010, Louis Stowasser +* Dual licensed under the MIT or GPL licenses. +*/ + (function (window, undefined) { + + /**@ +* #Crafty +* @category Core +* Select a set of or single entities by components or an entity's ID. +* +* Crafty uses syntax similar to jQuery by having a selector engine to select entities by their components. +* +* @example +* ~~~ +* Crafty("MyComponent") +* Crafty("Hello 2D Component") +* Crafty("Hello, 2D, Component") +* ~~~ +* The first selector will return all entities that has the component `MyComponent`. The second will return all entities that has `Hello` and `2D` and `Component` whereas the last will return all entities that has at least one of those components (or). +* ~~~ +* Crafty(1) +* ~~~ +* Passing an integer will select the entity with that `ID`. +* +* Finding out the `ID` of an entity can be done by returning the property `0`. +* ~~~ +* var ent = Crafty.e("2D"); +* ent[0]; //ID +* ~~~ +*/ + var Crafty = function (selector) { + return new Crafty.fn.init(selector); + }, + + GUID = 1, //GUID for entity IDs + FPS = 50, + frame = 1, + + components = {}, //map of components and their functions + entities = {}, //map of entities and their data + handlers = {}, //global event handlers + onloads = [], //temporary storage of onload handlers + tick, + + /* + * `window.requestAnimationFrame` or its variants is called for animation. + * `.requestID` keeps a record of the return value previous `window.requestAnimationFrame` call. + * This is an internal variable. Used to stop frame. + */ + requestID, + + noSetter, + + loops = 0, + milliSecPerFrame = 1000 / FPS, + nextGameTick = (new Date).getTime(), + + slice = Array.prototype.slice, + rlist = /\s*,\s*/, + rspace = /\s+/; + + /**@ +* #Crafty Core +* @category Core +* @trigger NewComponent - when a new component is added to the entity - String - Component +* @trigger RemoveComponent - when a component is removed from the entity - String - Component +* @trigger Remove - when the entity is removed by calling .destroy() +* Set of methods added to every single entity. +*/ + Crafty.fn = Crafty.prototype = { + + init: function (selector) { + //select entities by component + if (typeof selector === "string") { + var elem = 0, //index elements + e, //entity forEach + current, + and = false, //flags for multiple + or = false, + del, + comps, + score, + i, l; + + if (selector === '*') { + for (e in entities) { + this[+e] = entities[e]; + elem++; + } + this.length = elem; + return this; + } + + //multiple components OR + if (selector.indexOf(',') !== -1) { + or = true; + del = rlist; + //deal with multiple components AND + } else if (selector.indexOf(' ') !== -1) { + and = true; + del = rspace; + } + + //loop over entities + for (e in entities) { + if (!entities.hasOwnProperty(e)) continue; //skip + current = entities[e]; + + if (and || or) { //multiple components + comps = selector.split(del); + i = 0; + l = comps.length; + score = 0; + + for (; i < l; i++) //loop over components + if (current.__c[comps[i]]) score++; //if component exists add to score + + //if anded comps and has all OR ored comps and at least 1 + if (and && score === l || or && score > 0) this[elem++] = +e; + + } else if (current.__c[selector]) this[elem++] = +e; //convert to int + } + + //extend all common components + if (elem > 0 && !and && !or) this.extend(components[selector]); + if (comps && and) for (i = 0; i < l; i++) this.extend(components[comps[i]]); + + this.length = elem; //length is the last index (already incremented) + + } else { //Select a specific entity + + if (!selector) { //nothin passed creates God entity + selector = 0; + if (!(selector in entities)) entities[selector] = this; + } + + //if not exists, return undefined + if (!(selector in entities)) { + this.length = 0; + return this; + } + + this[0] = selector; + this.length = 1; + + //update from the cache + if (!this.__c) this.__c = {}; + + //update to the cache if NULL + if (!entities[selector]) entities[selector] = this; + return entities[selector]; //return the cached selector + } + + return this; + }, + + /**@ + * #.addComponent + * @comp Crafty Core + * @sign public this .addComponent(String componentList) + * @param componentList - A string of components to add seperated by a comma `,` + * @sign public this .addComponent(String Component1[, .., String ComponentN]) + * @param Component# - Component ID to add. + * Adds a component to the selected entities or entity. + * + * Components are used to extend the functionality of entities. + * This means it will copy properties and assign methods to + * augment the functionality of the entity. + * + * There are multiple methods of adding components. Passing a + * string with a list of component names or passing multiple + * arguments with the component names. + * + * If the component has a function named `init` it will be called. + * + * @example + * ~~~ + * this.addComponent("2D, Canvas"); + * this.addComponent("2D", "Canvas"); + * ~~~ + */ + addComponent: function (id) { + var uninit = [], c = 0, ul, //array of components to init + i = 0, l, comps; + + //add multiple arguments + if (arguments.length > 1) { + l = arguments.length; + for (; i < l; i++) { + this.__c[arguments[i]] = true; + uninit.push(arguments[i]); + } + //split components if contains comma + } else if (id.indexOf(',') !== -1) { + comps = id.split(rlist); + l = comps.length; + for (; i < l; i++) { + this.__c[comps[i]] = true; + uninit.push(comps[i]); + } + //single component passed + } else { + this.__c[id] = true; + uninit.push(id); + } + + //extend the components + ul = uninit.length; + for (; c < ul; c++) { + comp = components[uninit[c]]; + this.extend(comp); + + //if constructor, call it + if (comp && "init" in comp) { + comp.init.call(this); + } + } + + this.trigger("NewComponent", ul); + return this; + }, + /**@ + * @comp Crafty Core + * @sign public this. toggleComponent(String componentID,String componentToggle) + * @param componentID - Component ID to add or remove. + * @param componentToggle - Component ID to replace instead of remove + * Add or Remove Components + * @example + * ~~~ + * var e = Crafty.e("2D,DOM,Test"); + * e.toggleComponent("Test,Test2"); //Remove Test add Test2 and vice versa + * ~~~ + */ + toggleComponent:function(toggle){ + var i = 0, l, comps; + if (arguments.length > 1) { + l = arguments.length; + + for (; i < l; i++) { + if(this.has(arguments[i])){ + this.removeComponent(arguments[i]); + }else{ + this.addComponent(arguments[i]); + } + } + //split components if contains comma + } else if (toggle.indexOf(',') !== -1) { + comps = toggle.split(rlist); + l = comps.length; + for (; i < l; i++) { + if(this.has(comps[i])){ + this.removeComponent(comps[i]); + }else{ + this.addComponent(comps[i]); + } + } + + //single component passed + } else { + if(this.has(toggle)){ + this.removeComponent(toggle); + }else{ + this.addComponent(toggle); + } + } + + return this; + }, + /**@ + * #.requires + * @comp Crafty Core + * @sign public this .requires(String componentList) + * @param componentList - List of components that must be added + * Makes sure the entity has the components listed. If the entity does not + * have the component, it will add it. + * @see .addComponent + */ + requires: function (list) { + var comps = list.split(rlist), + i = 0, l = comps.length, + comp; + + //loop over the list of components and add if needed + for (; i < l; ++i) { + comp = comps[i]; + if (!this.has(comp)) this.addComponent(comp); + } + + return this; + }, + + /**@ + * #.removeComponent + * @comp Crafty Core + * @sign public this .removeComponent(String Component[, soft]) + * @param component - Component to remove + * @param soft - Whether to soft remove it (defaults to `true`) + * + * Removes a component from an entity. A soft remove (the default) will only + * refrain `.has()` from returning true. Hard will remove all + * associated properties and methods. + */ + removeComponent: function (id, soft) { + if (soft === false) { + var props = components[id], prop; + for (prop in props) { + delete this[prop]; + } + } + delete this.__c[id]; + + this.trigger("RemoveComponent", id); + return this; + }, + + /**@ + * #.has + * @comp Crafty Core + * @sign public Boolean .has(String component) + * Returns `true` or `false` depending on if the + * entity has the given component. + * + * For better performance, simply use the `.__c` object + * which will be `true` if the entity has the component or + * will not exist (or be `false`). + */ + has: function (id) { + return !!this.__c[id]; + }, + + /**@ + * #.attr + * @comp Crafty Core + * @sign public this .attr(String property, * value) + * @param property - Property of the entity to modify + * @param value - Value to set the property to + * @sign public this .attr(Object map) + * @param map - Object where the key is the property to modify and the value as the property value + * @trigger Change - when properties change - {key: value} + * Use this method to set any property of the entity. + * @example + * ~~~ + * this.attr({key: "value", prop: 5}); + * this.key; //value + * this.prop; //5 + * + * this.attr("key", "newvalue"); + * this.key; //newvalue + * ~~~ + */ + attr: function (key, value) { + if (arguments.length === 1) { + //if just the key, return the value + if (typeof key === "string") { + return this[key]; + } + + //extend if object + this.extend(key); + this.trigger("Change", key); //trigger change event + return this; + } + //if key value pair + this[key] = value; + + var change = {}; + change[key] = value; + this.trigger("Change", change); //trigger change event + return this; + }, + + /**@ + * #.toArray + * @comp Crafty Core + * @sign public this .toArray(void) + * This method will simply return the found entities as an array. + */ + toArray: function () { + return slice.call(this, 0); + }, + + /**@ + * #.timeout + * @comp Crafty Core + * @sign public this .timeout(Function callback, Number delay) + * @param callback - Method to execute after given amount of milliseconds + * @param delay - Amount of milliseconds to execute the method + * The delay method will execute a function after a given amount of time in milliseconds. + * + * Essentially a wrapper for `setTimeout`. + * + * @example + * Destroy itself after 100 milliseconds + * ~~~ + * this.timeout(function() { + this.destroy(); + * }, 100); + * ~~~ + */ + timeout: function (callback, duration) { + this.each(function () { + var self = this; + setTimeout(function () { + callback.call(self); + }, duration); + }); + return this; + }, + + /**@ + * #.bind + * @comp Crafty Core + * @sign public this .bind(String eventName, Function callback) + * @param eventName - Name of the event to bind to + * @param callback - Method to execute when the event is triggered + * Attach the current entity (or entities) to listen for an event. + * + * Callback will be invoked when an event with the event name passed + * is triggered. Depending on the event, some data may be passed + * via an argument to the callback function. + * + * The first argument is the event name (can be anything) whilst the + * second argument is the callback. If the event has data, the + * callback should have an argument. + * + * Events are arbitrary and provide communication between components. + * You can trigger or bind an event even if it doesn't exist yet. + * @example + * ~~~ + * this.attr("triggers", 0); //set a trigger count + * this.bind("myevent", function() { + * this.triggers++; //whenever myevent is triggered, increment + * }); + * this.bind("EnterFrame", function() { + * this.trigger("myevent"); //trigger myevent on every frame + * }); + * ~~~ + * @see .trigger, .unbind + */ + bind: function (event, callback) { + //optimization for 1 entity + if (this.length === 1) { + if (!handlers[event]) handlers[event] = {}; + var h = handlers[event]; + + if (!h[this[0]]) h[this[0]] = []; //init handler array for entity + h[this[0]].push(callback); //add current callback + return this; + } + + this.each(function () { + //init event collection + if (!handlers[event]) handlers[event] = {}; + var h = handlers[event]; + + if (!h[this[0]]) h[this[0]] = []; //init handler array for entity + h[this[0]].push(callback); //add current callback + }); + return this; + }, + + /**@ + * #.unbind + * @comp Crafty Core + * @sign public this .unbind(String eventName[, Function callback]) + * @param eventName - Name of the event to unbind + * @param callback - Function to unbind + * Removes binding with an event from current entity. + * + * Passing an event name will remove all events binded to + * that event. Passing a reference to the callback will + * unbind only that callback. + * @see .bind, .trigger + */ + unbind: function (event, callback) { + this.each(function () { + var hdl = handlers[event], i = 0, l, current; + //if no events, cancel + if (hdl && hdl[this[0]]) l = hdl[this[0]].length; + else return this; + + //if no function, delete all + if (!callback) { + delete hdl[this[0]]; + return this; + } + //look for a match if the function is passed + for (; i < l; i++) { + current = hdl[this[0]]; + if (current[i] == callback) { + current.splice(i, 1); + i--; + } + } + }); + + return this; + }, + + /**@ + * #.trigger + * @comp Crafty Core + * @sign public this .trigger(String eventName[, Object data]) + * @param eventName - Event to trigger + * @param data - Arbitrary data that will be passed into every callback as an argument + * Trigger an event with arbitrary data. Will invoke all callbacks with + * the context (value of `this`) of the current entity object. + * + * *Note: This will only execute callbacks within the current entity, no other entity.* + * + * The first argument is the event name to trigger and the optional + * second argument is the arbitrary event data. This can be absolutely anything. + */ + trigger: function (event, data) { + if (this.length === 1) { + //find the handlers assigned to the event and entity + if (handlers[event] && handlers[event][this[0]]) { + var callbacks = handlers[event][this[0]], i = 0, l = callbacks.length; + for (; i < l; i++) { + callbacks[i].call(this, data); + } + } + return this; + } + + this.each(function () { + //find the handlers assigned to the event and entity + if (handlers[event] && handlers[event][this[0]]) { + var callbacks = handlers[event][this[0]], i = 0, l = callbacks.length; + for (; i < l; i++) { + callbacks[i].call(this, data); + } + } + }); + return this; + }, + + /**@ + * #.each + * @sign public this .each(Function method) + * @param method - Method to call on each iteration + * Iterates over found entities, calling a function for every entity. + * + * The function will be called for every entity and will pass the index + * in the iteration as an argument. The context (value of `this`) of the + * function will be the current entity in the iteration. + * @example + * Destroy every second 2D entity + * ~~~ + * Crafty("2D").each(function(i) { + * if(i % 2 === 0) { + * this.destroy(); + * } + * }); + * ~~~ + */ + each: function (func) { + var i = 0, l = this.length; + for (; i < l; i++) { + //skip if not exists + if (!entities[this[i]]) continue; + func.call(entities[this[i]], i); + } + return this; + }, + + /**@ + * #.clone + * @comp Crafty Core + * @sign public Entity .clone(void) + * @returns Cloned entity of the current entity + * Method will create another entity with the exact same + * properties, components and methods as the current entity. + */ + clone: function () { + var comps = this.__c, + comp, + prop, + clone = Crafty.e(); + + for (comp in comps) { + clone.addComponent(comp); + } + for (prop in this) { + if (prop != "0" && prop != "_global" && prop != "_changed" && typeof this[prop] != "function" && typeof this[prop] != "object") { + clone[prop] = this[prop]; + } + } + + return clone; + }, + + /**@ + * #.setter + * @comp Crafty Core + * @sign public this .setter(String property, Function callback) + * @param property - Property to watch for modification + * @param callback - Method to execute if the property is modified + * Will watch a property waiting for modification and will then invoke the + * given callback when attempting to modify. + * + * *Note: Support in IE<9 is slightly different. The method will be executed + * after the property has been set* + */ + setter: function (prop, callback) { + if (Crafty.support.setter) { + this.__defineSetter__(prop, callback); + } else if (Crafty.support.defineProperty) { + Object.defineProperty(this, prop, { + set: callback, + configurable: true + }); + } else { + noSetter.push({ + prop: prop, + obj: this, + fn: callback + }); + } + return this; + }, + + /**@ + * #.destroy + * @comp Crafty Core + * @sign public this .destroy(void) + * Will remove all event listeners and delete all properties as well as removing from the stage + */ + destroy: function () { + //remove all event handlers, delete from entities + this.each(function () { + this.trigger("Remove"); + for (var e in handlers) { + this.unbind(e); + } + delete entities[this[0]]; + }); + } + }; + + //give the init instances the Crafty prototype + Crafty.fn.init.prototype = Crafty.fn; + + /** +* Extension method to extend the namespace and +* selector instances +*/ + Crafty.extend = Crafty.fn.extend = function (obj) { + var target = this, key; + + //don't bother with nulls + if (!obj) return target; + + for (key in obj) { + if (target === obj[key]) continue; //handle circular reference + target[key] = obj[key]; + } + + return target; + }; + + /**@ +* #Crafty.extend +* @category Core +* Used to extend the Crafty namespace. +*/ + Crafty.extend({ + /**@ + * #Crafty.init + * @category Core + * @trigger EnterFrame - on each frame - { frame: Number } + * @trigger Load - Just after the viewport is initialised. Before the EnterFrame loops is started + * @sign public this Crafty.init([Number width, Number height]) + * @param width - Width of the stage + * @param height - Height of the stage + * Create a div with id `cr-stage`, if there is not already an HTMLElement with id `cr-stage` (by `Crafty.viewport.init`). + * + * Starts the `EnterFrame` interval. This will call the `EnterFrame` event for every frame. + * + * Can pass width and height values for the stage otherwise will default to window size (see `Crafty.DOM.window`). + * + * All `Load` events will be executed. + * + * Uses `requestAnimationFrame` to sync the drawing with the browser but will default to `setInterval` if the browser does not support it. + * @see Crafty.stop + * @see Crafty.viewport.init + */ + init: function (w, h) { + Crafty.viewport.init(w, h); + + //call all arbitrary functions attached to onload + this.trigger("Load"); + this.timer.init(); + + return this; + }, + + /**@ + * #Crafty.stop + * @category Core + * @trigger CraftyStop - when the game is stopped + * @sign public this Crafty.stop(void) + * Stops the EnterFrame interval and removes the stage element. + * + * To restart, use `Crafty.init()`. + * @see Crafty.init + */ + stop: function () { + this.timer.stop(); + Crafty.stage.elem.parentNode.removeChild(Crafty.stage.elem); + + return this; + }, + + /**@ + * #Crafty.pause + * @category Core + * @trigger Pause - when the game is paused + * @trigger Unpause - when the game is unpaused + * @sign public this Crafty.pause(void) + * Pauses the game by stoping the EnterFrame event from firing. If the game is already paused it is unpaused. + * You can pass a boolean parameter if you want to pause or unpause mo matter what the current state is. + * Modern browsers pauses the game when the page is not visible to the user. If you want the Pause event + * to be triggered when that happens you can enable autoPause in `Crafty.settings`. + * @example + * Have an entity pause the game when it is clicked. + * ~~~ + * button.bind("click", function() { + * Crafty.pause(); + * }); + * ~~~ + */ + pause: function (toggle) { + if (arguments.length == 1 ? toggle : !this._paused) { + this.trigger('Pause'); + this._paused = true; + + Crafty.timer.stop(); + Crafty.keydown = {}; + } else { + this.trigger('Unpause'); + this._paused = false; + + Crafty.timer.init(); + } + return this; + }, + + /**@ + * #Crafty.isPaused + * @category Core + * @sign public this Crafty.isPaused() + * Check whether the game is already paused or not. + * ~~~ + * Crafty.isPaused(); + * ~~~ + */ + isPaused: function () { + return this._paused; + }, + /**@ + * #Crafty.timer + * @category Internal + * Handles game ticks + */ + timer: { + prev: (+new Date), + current: (+new Date), + curTime: Date.now(), + + init: function () { + var onFrame = window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + null; + + if (onFrame) { + tick = function () { + Crafty.timer.step(); + requestID = onFrame(tick); + //console.log(requestID + ', ' + frame) + } + + tick(); + } else { + tick = setInterval(Crafty.timer.step, 1000 / FPS); + } + }, + + stop: function () { + Crafty.trigger("CraftyStop"); + + if (typeof tick === "number") clearInterval(tick); + + var onFrame = window.cancelRequestAnimationFrame || + window.webkitCancelRequestAnimationFrame || + window.mozCancelRequestAnimationFrame || + window.oCancelRequestAnimationFrame || + window.msCancelRequestAnimationFrame || + null; + + if (onFrame) onFrame(requestID); + tick = null; + }, + + /**@ + * #Crafty.timer.step + * @comp Crafty.timer + * @sign public void Crafty.timer.step() + * Advances the game by triggering `EnterFrame` and calls `Crafty.DrawManager.draw` to update the stage. + */ + step: function () { + loops = 0; + this.curTime = Date.now(); + if (this.curTime - nextGameTick > 60 * milliSecPerFrame) { + nextGameTick = this.curTime - milliSecPerFrame; + } + while (this.curTime > nextGameTick) { + Crafty.trigger("EnterFrame", { frame: frame++ }); + nextGameTick += milliSecPerFrame; + loops++; + } + if (loops) { + Crafty.DrawManager.draw(); + } + }, + /**@ + * #Crafty.timer.getFPS + * @comp Crafty.timer + * @sign public void Crafty.timer.getFPS() + * Returns the target frames per second. This is not an actual frame rate. + */ + getFPS: function () { + return FPS; + }, + /**@ + * #Crafty.timer.simulateFrames + * @comp Crafty.timer + * Advances the game state by a number of frames and draws the resulting stage at the end. Useful for tests and debugging. + * @sign public this Crafty.timer.simulateFrames(Number frames) + * @param frames - number of frames to simulate + */ + simulateFrames: function (frames) { + while (frames-- > 0) { + Crafty.trigger("EnterFrame", { frame: frame++ }); + } + Crafty.DrawManager.draw(); + } + + }, + + /**@ + * #Crafty.e + * @category Core + * @trigger NewEntity - When the entity is created and all components are added - { id:Number } + * @sign public Entity Crafty.e(String componentList) + * @param componentList - List of components to assign to new entity + * @sign public Entity Crafty.e(String component1[, .., String componentN]) + * @param component# - Component to add + * Creates an entity. Any arguments will be applied in the same + * way `.addComponent()` is applied as a quick way to add components. + * + * Any component added will augment the functionality of + * the created entity by assigning the properties and methods from the component to the entity. + * ~~~ + * var myEntity = Crafty.e("2D, DOM, Color"); + * ~~~ + * @see Crafty.c + */ + e: function () { + var id = UID(), craft; + + entities[id] = null; //register the space + entities[id] = craft = Crafty(id); + + if (arguments.length > 0) { + craft.addComponent.apply(craft, arguments); + } + craft.addComponent("obj"); //every entity automatically assumes obj + + Crafty.trigger("NewEntity", { id: id }); + + return craft; + }, + + /**@ + * #Crafty.c + * @category Core + * @sign public void Crafty.c(String name, Object component) + * @param name - Name of the component + * @param component - Object with the components properties and methods + * Creates a component where the first argument is the ID and the second + * is the object that will be inherited by entities. + * + * There is a convention for writing components. + * + * - Properties or methods that start with an underscore are considered private. + * - A method called `init` will automatically be called as soon as the + * component is added to an entity. + * - A methid called `uninit` will be called when the component is removed from an entity. + * A sample use case for this is the native DOM component that removes its div element wehen removed from an entity. + * - A method with the same name as the component is considered to be a constructor + * and is generally used when you need to pass configuration data to the component on a per entity basis. + * + * ~~~ + * Crafty.c("Annoying", { + * _message: "HiHi", + * init: function() { + * this.bind("EnterFrame", function() { alert(this.message); }); + * }, + * annoying: function(message) { this.message = message; } + * }); + * + * Crafty.e("Annoying").annoying("I'm an orange..."); + * ~~~ + * @see Crafty.e + */ + c: function (compName, component) { + components[compName] = component; + }, + + /**@ + * #Crafty.trigger + * @category Core, Events + * @sign public void Crafty.trigger(String eventName, * data) + * @param eventName - Name of the event to trigger + * @param data - Arbitrary data to pass into the callback as an argument + * This method will trigger every single callback attached to the event name. This means + * every global event and every entity that has a callback. + * @see Crafty.bind + */ + trigger: function (event, data) { + var hdl = handlers[event], h, i, l; + //loop over every object bound + for (h in hdl) { + if (!hdl.hasOwnProperty(h)) continue; + + //loop over every handler within object + for (i = 0, l = hdl[h].length; i < l; i++) { + if (hdl[h] && hdl[h][i]) { + //if an entity, call with that context + if (entities[h]) { + hdl[h][i].call(Crafty(+h), data); + } else { //else call with Crafty context + hdl[h][i].call(Crafty, data); + } + } + } + } + }, + + /**@ + * #Crafty.bind + * @category Core, Events + * @sign public Number bind(String eventName, Function callback) + * @param eventName - Name of the event to bind to + * @param callback - Method to execute upon event triggered + * @returns ID of the current callback used to unbind + * Binds to a global event. Method will be executed when `Crafty.trigger` is used + * with the event name. + * @see Crafty.trigger, Crafty.unbind + */ + bind: function (event, callback) { + if (!handlers[event]) handlers[event] = {}; + var hdl = handlers[event]; + + if (!hdl.global) hdl.global = []; + return hdl.global.push(callback) - 1; + }, + + /**@ + * #Crafty.unbind + * @category Core, Events + * @sign public Boolean Crafty.unbind(String eventName, Function callback) + * @param eventName - Name of the event to unbind + * @param callback - Function to unbind + * @sign public Boolean Crafty.unbind(String eventName, Number callbackID) + * @param callbackID - ID of the callback + * @returns True or false depending on if a callback was unbound + * Unbind any event from any entity or global event. + */ + unbind: function (event, callback) { + var hdl = handlers[event], h, i, l; + + //loop over every object bound + for (h in hdl) { + if (!hdl.hasOwnProperty(h)) continue; + + //if passed the ID + if (typeof callback === "number") { + delete hdl[h][callback]; + return true; + } + + //loop over every handler within object + for (i = 0, l = hdl[h].length; i < l; i++) { + if (hdl[h][i] === callback) { + delete hdl[h][i]; + return true; + } + } + } + + return false; + }, + + /**@ + * #Crafty.frame + * @category Core + * @sign public Number Crafty.frame(void) + * Returns the current frame number + */ + frame: function () { + return frame; + }, + + components: function () { + return components; + }, + + isComp: function (comp) { + return comp in components; + }, + + debug: function () { + return entities; + }, + + /**@ + * #Crafty.settings + * @category Core + * Modify the inner workings of Crafty through the settings. + */ + settings: (function () { + var states = {}, + callbacks = {}; + + return { + /**@ + * #Crafty.settings.register + * @comp Crafty.settings + * @sign public void Crafty.settings.register(String settingName, Function callback) + * @param settingName - Name of the setting + * @param callback - Function to execute when use modifies setting + * Use this to register custom settings. Callback will be executed when `Crafty.settings.modify` is used. + * @see Crafty.settings.modify + */ + register: function (setting, callback) { + callbacks[setting] = callback; + }, + + /**@ + * #Crafty.settings.modify + * @comp Crafty.settings + * @sign public void Crafty.settings.modify(String settingName, * value) + * @param settingName - Name of the setting + * @param value - Value to set the setting to + * Modify settings through this method. + * @see Crafty.settings.register, Crafty.settings.get + */ + modify: function (setting, value) { + if (!callbacks[setting]) return; + callbacks[setting].call(states[setting], value); + states[setting] = value; + }, + + /**@ + * #Crafty.settings.get + * @comp Crafty.settings + * @sign public * Crafty.settings.get(String settingName) + * @param settingName - Name of the setting + * @returns Current value of the setting + * Returns the current value of the setting. + * @see Crafty.settings.register, Crafty.settings.get + */ + get: function (setting) { + return states[setting]; + } + }; + })(), + + clone: clone + }); + + /** +* Return a unique ID +*/ + function UID() { + var id = GUID++; + //if GUID is not unique + if (id in entities) { + return UID(); //recurse until it is unique + } + return id; + } + + /**@ + * #Crafty.clone + * @category Core + * @sign public Object .clone(Object obj) + * @param obj - an object + * Deep copy (a.k.a clone) of an object. + */ + function clone(obj) { + if (obj === null || typeof(obj) != 'object') + return obj; + + var temp = obj.constructor(); // changed + + for (var key in obj) + temp[key] = clone(obj[key]); + return temp; + } + + Crafty.bind("Load", function () { + if (!Crafty.support.setter && Crafty.support.defineProperty) { + noSetter = []; + Crafty.bind("EnterFrame", function () { + var i = 0, l = noSetter.length, current; + for (; i < l; ++i) { + current = noSetter[i]; + if (current.obj[current.prop] !== current.obj['_' + current.prop]) { + current.fn.call(current.obj, current.obj[current.prop]); + } + } + }); + } + }); + + //make Crafty global + window.Crafty = Crafty; +})(window); + //wrap around components +(function(Crafty, window, document) { + /** +* Spatial HashMap for broad phase collision +* +* @author Louis Stowasser +*/ +(function (parent) { + + + /**@ + * #Crafty.HashMap.constructor + * @comp Crafty.HashMap + * @sign public void Crafty.HashMap([cellsize]) + * @param cellsize - the cell size. If omitted, `cellsize` is 64. + * Set `cellsize`. + * And create `this.map`. + */ + var cellsize, + HashMap = function (cell) { + cellsize = cell || 64; + this.map = {}; + }, + SPACE = " "; + + HashMap.prototype = { + /**@ + * #Crafty.map.insert + * @comp Crafty.map + * @sign public Object Crafty.map.insert(Object obj) + * @param obj - An entity to be inserted. + * `obj` is instered in '.map' of the corresponding broad phase cells. An object of the following fields is returned. + * + * - the object that keep track of cells (keys) + * - `obj` + * - the HashMap object + */ + insert: function (obj) { + var keys = HashMap.key(obj), + entry = new Entry(keys, obj, this), + i = 0, + j, + hash; + + //insert into all x buckets + for (i = keys.x1; i <= keys.x2; i++) { + //insert into all y buckets + for (j = keys.y1; j <= keys.y2; j++) { + hash = i + SPACE + j; + if (!this.map[hash]) this.map[hash] = []; + this.map[hash].push(obj); + } + } + + return entry; + }, + + /**@ + * #Crafty.map.search + * @comp Crafty.map + * @sign public Object Crafty.map.search(Object rect[, Boolean filter]) + * @param rect - the rectangular region to search for entities. + * @param filter - Default value is true. Otherwise, must be false. + * - If `filter` is `false`, just search for all the entries in the give `rect` region by broad phase collision. Entity may be returned duplicated. + * - If `filter` is `true`, filter the above results by checking that they actually overlap `rect`. + * The easier usage is with `filter`=`true`. For performance reason, you may use `filter`=`false`, and filter the result youself. See examples in drawing.js and collision.js + */ + search: function (rect, filter) { + var keys = HashMap.key(rect), + i, j, + hash, + results = []; + + if (filter === undefined) filter = true; //default filter to true + + //search in all x buckets + for (i = keys.x1; i <= keys.x2; i++) { + //insert into all y buckets + for (j = keys.y1; j <= keys.y2; j++) { + hash = i + SPACE + j; + + if (this.map[hash]) { + results = results.concat(this.map[hash]); + } + } + } + + if (filter) { + var obj, id, finalresult = [], found = {}; + //add unique elements to lookup table with the entity ID as unique key + for (i = 0, l = results.length; i < l; i++) { + obj = results[i]; + if (!obj) continue; //skip if deleted + id = obj[0]; //unique ID + + //check if not added to hash and that actually intersects + if (!found[id] && obj.x < rect._x + rect._w && obj._x + obj._w > rect._x && + obj.y < rect._y + rect._h && obj._h + obj._y > rect._y) + found[id] = results[i]; + } + + //loop over lookup table and copy to final array + for (obj in found) finalresult.push(found[obj]); + + return finalresult; + } else { + return results; + } + }, + + /**@ + * #Crafty.map.remove + * @comp Crafty.map + * @sign public void Crafty.map.remove([Object keys, ]Object obj) + * @param keys - key region. If omitted, it will be derived from obj by `Crafty.HashMap.key`. + * @param obj - need more document. + * Remove an entity in a broad phase map. + * - The second form is only used in Crafty.HashMap to save time for computing keys again, where keys were computed previously from obj. End users should not call this form directly. + * ~~~ + * Crafty.map.remove(e); + * ~~~ + */ + remove: function (keys, obj) { + var i = 0, j, hash; + + if (arguments.length == 1) { + obj = keys; + keys = HashMap.key(obj); + } + + //search in all x buckets + for (i = keys.x1; i <= keys.x2; i++) { + //insert into all y buckets + for (j = keys.y1; j <= keys.y2; j++) { + hash = i + SPACE + j; + + if (this.map[hash]) { + var cell = this.map[hash], + m, + n = cell.length; + //loop over objs in cell and delete + for (m = 0; m < n; m++) + if (cell[m] && cell[m][0] === obj[0]) + cell.splice(m, 1); + } + } + } + }, + + /**@ + * #Crafty.map.boundaries + * @comp Crafty.map + * @sign public Object Crafty.map.boundaries() + * The return `Object` is of the following format. + * { + * min: { + * x: val_x, + * y: val_y + * }, + * max: { + * x: val_x, + * y: val_y + * } + * } + */ + boundaries: function () { + var k, ent, + hash = { + max: { x: -Infinity, y: -Infinity }, + min: { x: Infinity, y: Infinity } + }, + coords = { + max: { x: -Infinity, y: -Infinity }, + min: { x: Infinity, y: Infinity } + }; + + //Using broad phase hash to speed up the computation of boundaries. + for (var h in this.map) { + if (!this.map[h].length) continue; + + //broad phase coordinate + var map_coord = h.split(SPACE), + i=map_coord[0], + j=map_coord[0]; + if (i >= hash.max.x) { + hash.max.x = i; + for (k in this.map[h]) { + ent = this.map[h][k]; + //make sure that this is a Crafty entity + if (typeof ent == 'object' && 'requires' in ent) { + coords.max.x = Math.max(coords.max.x, ent.x + ent.w); + } + } + } + if (i <= hash.min.x) { + hash.min.x = i; + for (k in this.map[h]) { + ent = this.map[h][k]; + if (typeof ent == 'object' && 'requires' in ent) { + coords.min.x = Math.min(coords.min.x, ent.x); + } + } + } + if (j >= hash.max.y) { + hash.max.y = j; + for (k in this.map[h]) { + ent = this.map[h][k]; + if (typeof ent == 'object' && 'requires' in ent) { + coords.max.y = Math.max(coords.max.y, ent.y + ent.h); + } + } + } + if (j <= hash.min.y) { + hash.min.y = j; + for (k in this.map[h]) { + ent = this.map[h][k]; + if (typeof ent == 'object' && 'requires' in ent) { + coords.min.y = Math.min(coords.min.y, ent.y); + } + } + } + } + + return coords; + } + }; + +/**@ +* #Crafty.HashMap +* @category 2D +* Broad-phase collision detection engine. See background information at +* +* - [N Tutorial B - Broad-Phase Collision](http://www.metanetsoftware.com/technique/tutorialB.html) +* - [Broad-Phase Collision Detection with CUDA](http.developer.nvidia.com/GPUGems3/gpugems3_ch32.html) +* @see Crafty.map +*/ + + /**@ + * #Crafty.HashMap.key + * @comp Crafty.HashMap + * @sign public Object Crafty.HashMap.key(Object obj) + * @param obj - an Object that has .mbr() or _x, _y, _w and _h. + * Get the rectangular region (in terms of the grid, with grid size `cellsize`), where the object may fall in. This region is determined by the object's bounding box. + * The `cellsize` is 64 by default. + * @see Crafty.HashMap.constructor + */ + HashMap.key = function (obj) { + if (obj.hasOwnProperty('mbr')) { + obj = obj.mbr(); + } + var x1 = Math.floor(obj._x / cellsize), + y1 = Math.floor(obj._y / cellsize), + x2 = Math.floor((obj._w + obj._x) / cellsize), + y2 = Math.floor((obj._h + obj._y) / cellsize); + return { x1: x1, y1: y1, x2: x2, y2: y2 }; + }; + + HashMap.hash = function (keys) { + return keys.x1 + SPACE + keys.y1 + SPACE + keys.x2 + SPACE + keys.y2; + }; + + function Entry(keys, obj, map) { + this.keys = keys; + this.map = map; + this.obj = obj; + } + + Entry.prototype = { + update: function (rect) { + //check if buckets change + if (HashMap.hash(HashMap.key(rect)) != HashMap.hash(this.keys)) { + this.map.remove(this.keys, this.obj); + var e = this.map.insert(this.obj); + this.keys = e.keys; + } + } + }; + + parent.HashMap = HashMap; +})(Crafty); + /**@ +* #Crafty.map +* @category 2D +* Functions related with quering entities. +* @see Crafty.HashMap +*/ +Crafty.map = new Crafty.HashMap(); +var M = Math, + Mc = M.cos, + Ms = M.sin, + PI = M.PI, + DEG_TO_RAD = PI / 180; + + +/**@ +* #2D +* @category 2D +* Component for any entity that has a position on the stage. +* @trigger Move - when the entity has moved - { _x:Number, _y:Number, _w:Number, _h:Number } - Old position +* @trigger Change - when the entity has moved - { _x:Number, _y:Number, _w:Number, _h:Number } - Old position +* @trigger Rotate - when the entity is rotated - { cos:Number, sin:Number, deg:Number, rad:Number, o: {x:Number, y:Number}, matrix: {M11, M12, M21, M22} } +*/ +Crafty.c("2D", { +/**@ + * #.x + * @comp 2D + * The `x` position on the stage. When modified, will automatically be redrawn. + * Is actually a getter/setter so when using this value for calculations and not modifying it, + * use the `._x` property. + * @see ._attr + */ + _x: 0, + /**@ + * #.y + * @comp 2D + * The `y` position on the stage. When modified, will automatically be redrawn. + * Is actually a getter/setter so when using this value for calculations and not modifying it, + * use the `._y` property. + * @see ._attr + */ + _y: 0, + /**@ + * #.w + * @comp 2D + * The width of the entity. When modified, will automatically be redrawn. + * Is actually a getter/setter so when using this value for calculations and not modifying it, + * use the `._w` property. + * + * Changing this value is not recommended as canvas has terrible resize quality and DOM will just clip the image. + * @see ._attr + */ + _w: 0, + /**@ + * #.h + * @comp 2D + * The height of the entity. When modified, will automatically be redrawn. + * Is actually a getter/setter so when using this value for calculations and not modifying it, + * use the `._h` property. + * + * Changing this value is not recommended as canvas has terrible resize quality and DOM will just clip the image. + * @see ._attr + */ + _h: 0, + /**@ + * #.z + * @comp 2D + * The `z` index on the stage. When modified, will automatically be redrawn. + * Is actually a getter/setter so when using this value for calculations and not modifying it, + * use the `._z` property. + * + * A higher `z` value will be closer to the front of the stage. A smaller `z` value will be closer to the back. + * A global Z index is produced based on its `z` value as well as the GID (which entity was created first). + * Therefore entities will naturally maintain order depending on when it was created if same z value. + * @see ._attr + */ + _z: 0, + /**@ + * #.rotation + * @comp 2D + * Set the rotation of your entity. Rotation takes degrees in a clockwise direction. + * It is important to note there is no limit on the rotation value. Setting a rotation + * mod 360 will give the same rotation without reaching huge numbers. + * @see ._attr + */ + _rotation: 0, + /**@ + * #.alpha + * @comp 2D + * Transparency of an entity. Must be a decimal value between 0.0 being fully transparent to 1.0 being fully opaque. + */ + _alpha: 1.0, + /**@ + * #.visible + * @comp 2D + * If the entity is visible or not. Accepts a true or false value. + * Can be used for optimization by setting an entities visibility to false when not needed to be drawn. + * + * The entity will still exist and can be collided with but just won't be drawn. + * @see Crafty.DrawManager.draw, Crafty.DrawManager.drawAll + */ + _visible: true, + + /**@ + * #._globalZ + * @comp 2D + * When two entities overlap, the one with the larger `_globalZ` will be on top of the other. + * @see Crafty.DrawManager.draw, Crafty.DrawManager.drawAll + */ + _globalZ: null, + + _origin: null, + _mbr: null, + _entry: null, + _children: null, + _parent: null, + _changed: false, + + _defineGetterSetter_setter: function() { + //create getters and setters using __defineSetter__ and __defineGetter__ + this.__defineSetter__('x', function (v) { this._attr('_x', v); }); + this.__defineSetter__('y', function (v) { this._attr('_y', v); }); + this.__defineSetter__('w', function (v) { this._attr('_w', v); }); + this.__defineSetter__('h', function (v) { this._attr('_h', v); }); + this.__defineSetter__('z', function (v) { this._attr('_z', v); }); + this.__defineSetter__('rotation', function (v) { this._attr('_rotation', v); }); + this.__defineSetter__('alpha', function (v) { this._attr('_alpha', v); }); + this.__defineSetter__('visible', function (v) { this._attr('_visible', v); }); + + this.__defineGetter__('x', function () { return this._x; }); + this.__defineGetter__('y', function () { return this._y; }); + this.__defineGetter__('w', function () { return this._w; }); + this.__defineGetter__('h', function () { return this._h; }); + this.__defineGetter__('z', function () { return this._z; }); + this.__defineGetter__('rotation', function () { return this._rotation; }); + this.__defineGetter__('alpha', function () { return this._alpha; }); + this.__defineGetter__('visible', function () { return this._visible; }); + this.__defineGetter__('parent', function () { return this._parent; }); + this.__defineGetter__('numChildren', function () { return this._children.length; }); + }, + + _defineGetterSetter_defineProperty: function() { + Object.defineProperty(this, 'x', { + set: function (v) { this._attr('_x', v); } + , get: function () { return this._x; } + , configurable: true + }); + + Object.defineProperty(this, 'y', { + set: function (v) { this._attr('_y', v); } + , get: function () { return this._y; } + , configurable: true + }); + + Object.defineProperty(this, 'w', { + set: function (v) { this._attr('_w', v); } + , get: function () { return this._w; } + , configurable: true + }); + + Object.defineProperty(this, 'h', { + set: function (v) { this._attr('_h', v); } + , get: function () { return this._h; } + , configurable: true + }); + + Object.defineProperty(this, 'z', { + set: function (v) { this._attr('_z', v); } + , get: function () { return this._z; } + , configurable: true + }); + + Object.defineProperty(this, 'rotation', { + set: function (v) { this._attr('_rotation', v); } + , get: function () { return this._rotation; } + , configurable: true + }); + + Object.defineProperty(this, 'alpha', { + set: function (v) { this._attr('_alpha', v); } + , get: function () { return this._alpha; } + , configurable: true + }); + + Object.defineProperty(this, 'visible', { + set: function (v) { this._attr('_visible', v); } + , get: function () { return this._visible; } + , configurable: true + }); + }, + + _defineGetterSetter_fallback: function() { + //set the public properties to the current private properties + this.x = this._x; + this.y = this._y; + this.w = this._w; + this.h = this._h; + this.z = this._z; + this.rotation = this._rotation; + this.alpha = this._alpha; + this.visible = this._visible; + + //on every frame check for a difference in any property + this.bind("EnterFrame", function () { + //if there are differences between the public and private properties + if (this.x !== this._x || this.y !== this._y || + this.w !== this._w || this.h !== this._h || + this.z !== this._z || this.rotation !== this._rotation || + this.alpha !== this._alpha || this.visible !== this._visible) { + + //save the old positions + var old = this.mbr() || this.pos(); + + //if rotation has changed, use the private rotate method + if (this.rotation !== this._rotation) { + this._rotate(this.rotation); + } else { + //update the MBR + var mbr = this._mbr, moved = false; + // If the browser doesn't have getters or setters, + // {x, y, w, h, z} and {_x, _y, _w, _h, _z} may be out of synce, + // in which case t checks if they are different on tick and executes the Change event. + if (mbr) { //check each value to see which has changed + if (this.x !== this._x) { mbr._x -= this.x - this._x; moved = true; } + else if (this.y !== this._y) { mbr._y -= this.y - this._y; moved = true; } + else if (this.w !== this._w) { mbr._w -= this.w - this._w; moved = true; } + else if (this.h !== this._h) { mbr._h -= this.h - this._h; moved = true; } + else if (this.z !== this._z) { mbr._z -= this.z - this._z; moved = true; } + } + + //if the moved flag is true, trigger a move + if (moved) this.trigger("Move", old); + } + + //set the public properties to the private properties + this._x = this.x; + this._y = this.y; + this._w = this.w; + this._h = this.h; + this._z = this.z; + this._rotation = this.rotation; + this._alpha = this.alpha; + this._visible = this.visible; + + //trigger the changes + this.trigger("Change", old); + //without this entities weren't added correctly to Crafty.map.map in IE8. + //not entirely sure this is the best way to fix it though + this.trigger("Move", old); + } + }); + }, + + init: function() { + this._globalZ = this[0]; + this._origin = { x: 0, y: 0 }; + this._children = []; + + if(Crafty.support.setter) { + this._defineGetterSetter_setter(); + } else if (Crafty.support.defineProperty) { + //IE9 supports Object.defineProperty + this._defineGetterSetter_defineProperty(); + } else { + /* + If no setters and getters are supported (e.g. IE8) supports, + check on every frame for a difference between this._(x|y|w|h|z...) + and this.(x|y|w|h|z) and update accordingly. + */ + this._defineGetterSetter_fallback(); + } + + //insert self into the HashMap + this._entry = Crafty.map.insert(this); + + //when object changes, update HashMap + this.bind("Move", function (e) { + var area = this._mbr || this; + this._entry.update(area); + this._cascade(e); + }); + + this.bind("Rotate", function (e) { + var old = this._mbr || this; + this._entry.update(old); + this._cascade(e); + }); + + //when object is removed, remove from HashMap and destroy attached children + this.bind("Remove", function () { + if (this._children) { + for (var i = 0; i < this._children.length; i++) { + if (this._children[i].destroy) { + this._children[i].destroy(); + } + } + this._children = []; + } + + Crafty.map.remove(this); + + this.detach(); + }); + }, + + /** + * Calculates the MBR when rotated with an origin point + */ + _rotate: function (v) { + var theta = -1 * (v % 360), //angle always between 0 and 359 + rad = theta * DEG_TO_RAD, + ct = Math.cos(rad), //cache the sin and cosine of theta + st = Math.sin(rad), + o = { + x: this._origin.x + this._x, + y: this._origin.y + this._y + }; + + //if the angle is 0 and is currently 0, skip + if (!theta) { + this._mbr = null; + if (!this._rotation % 360) return; + } + + var x0 = o.x + (this._x - o.x) * ct + (this._y - o.y) * st, + y0 = o.y - (this._x - o.x) * st + (this._y - o.y) * ct, + x1 = o.x + (this._x + this._w - o.x) * ct + (this._y - o.y) * st, + y1 = o.y - (this._x + this._w - o.x) * st + (this._y - o.y) * ct, + x2 = o.x + (this._x + this._w - o.x) * ct + (this._y + this._h - o.y) * st, + y2 = o.y - (this._x + this._w - o.x) * st + (this._y + this._h - o.y) * ct, + x3 = o.x + (this._x - o.x) * ct + (this._y + this._h - o.y) * st, + y3 = o.y - (this._x - o.x) * st + (this._y + this._h - o.y) * ct, + minx = Math.floor(Math.min(x0, x1, x2, x3)), + miny = Math.floor(Math.min(y0, y1, y2, y3)), + maxx = Math.ceil(Math.max(x0, x1, x2, x3)), + maxy = Math.ceil(Math.max(y0, y1, y2, y3)); + + this._mbr = { _x: minx, _y: miny, _w: maxx - minx, _h: maxy - miny }; + + //trigger rotation event + var difference = this._rotation - v, + drad = difference * DEG_TO_RAD; + + this.trigger("Rotate", { + cos: Math.cos(drad), + sin: Math.sin(drad), + deg: difference, + rad: drad, + o: { x: o.x, y: o.y }, + matrix: { M11: ct, M12: st, M21: -st, M22: ct } + }); + }, + + /**@ + * #.area + * @comp 2D + * @sign public Number .area(void) + * Calculates the area of the entity + */ + area: function () { + return this._w * this._h; + }, + + /**@ + * #.intersect + * @comp 2D + * @sign public Boolean .intersect(Number x, Number y, Number w, Number h) + * @param x - X position of the rect + * @param y - Y position of the rect + * @param w - Width of the rect + * @param h - Height of the rect + * @sign public Boolean .intersect(Object rect) + * @param rect - An object that must have the `x, y, w, h` values as properties + * Determines if this entity intersects a rectangle. + */ + intersect: function (x, y, w, h) { + var rect, obj = this._mbr || this; + if (typeof x === "object") { + rect = x; + } else { + rect = { x: x, y: y, w: w, h: h }; + } + + return obj._x < rect.x + rect.w && obj._x + obj._w > rect.x && + obj._y < rect.y + rect.h && obj._h + obj._y > rect.y; + }, + + /**@ + * #.within + * @comp 2D + * @sign public Boolean .within(Number x, Number y, Number w, Number h) + * @param x - X position of the rect + * @param y - Y position of the rect + * @param w - Width of the rect + * @param h - Height of the rect + * @sign public Boolean .within(Object rect) + * @param rect - An object that must have the `x, y, w, h` values as properties + * Determines if this current entity is within another rectangle. + */ + within: function (x, y, w, h) { + var rect; + if (typeof x === "object") { + rect = x; + } else { + rect = { x: x, y: y, w: w, h: h }; + } + + return rect.x <= this.x && rect.x + rect.w >= this.x + this.w && + rect.y <= this.y && rect.y + rect.h >= this.y + this.h; + }, + + /**@ + * #.contains + * @comp 2D + * @sign public Boolean .contains(Number x, Number y, Number w, Number h) + * @param x - X position of the rect + * @param y - Y position of the rect + * @param w - Width of the rect + * @param h - Height of the rect + * @sign public Boolean .contains(Object rect) + * @param rect - An object that must have the `x, y, w, h` values as properties + * Determines if the rectangle is within the current entity. + */ + contains: function (x, y, w, h) { + var rect; + if (typeof x === "object") { + rect = x; + } else { + rect = { x: x, y: y, w: w, h: h }; + } + + return rect.x >= this.x && rect.x + rect.w <= this.x + this.w && + rect.y >= this.y && rect.y + rect.h <= this.y + this.h; + }, + + /**@ + * #.pos + * @comp 2D + * @sign public Object .pos(void) + * Returns the x, y, w, h properties as a rect object + * (a rect object is just an object with the keys _x, _y, _w, _h). + * + * The keys have an underscore prefix. This is due to the x, y, w, h + * properties being merely setters and getters that wrap the properties with an underscore (_x, _y, _w, _h). + */ + pos: function () { + return { + _x: (this._x), + _y: (this._y), + _w: (this._w), + _h: (this._h) + }; + }, + + /**@ + * #.mbr + * @comp 2D + * @sign public Object .mbr() + * Returns the minimum bounding rectangle. If there is no rotation + * on the entity it will return the rect. + */ + mbr: function () { + if (!this._mbr) return this.pos(); + return { + _x: (this._mbr._x), + _y: (this._mbr._y), + _w: (this._mbr._w), + _h: (this._mbr._h) + }; + }, + + /**@ + * #.isAt + * @comp 2D + * @sign public Boolean .isAt(Number x, Number y) + * @param x - X position of the point + * @param y - Y position of the point + * Determines whether a point is contained by the entity. Unlike other methods, + * an object can't be passed. The arguments require the x and y value + */ + isAt: function (x, y) { + if (this.map) { + return this.map.containsPoint(x, y); + } + return this.x <= x && this.x + this.w >= x && + this.y <= y && this.y + this.h >= y; + }, + + /**@ + * #.move + * @comp 2D + * @sign public this .move(String dir, Number by) + * @param dir - Direction to move (n,s,e,w,ne,nw,se,sw) + * @param by - Amount to move in the specified direction + * Quick method to move the entity in a direction (n, s, e, w, ne, nw, se, sw) by an amount of pixels. + */ + move: function (dir, by) { + if (dir.charAt(0) === 'n') this.y -= by; + if (dir.charAt(0) === 's') this.y += by; + if (dir === 'e' || dir.charAt(1) === 'e') this.x += by; + if (dir === 'w' || dir.charAt(1) === 'w') this.x -= by; + + return this; + }, + + /**@ + * #.shift + * @comp 2D + * @sign public this .shift(Number x, Number y, Number w, Number h) + * @param x - Amount to move X + * @param y - Amount to move Y + * @param w - Amount to widen + * @param h - Amount to increase height + * Shift or move the entity by an amount. Use negative values + * for an opposite direction. + */ + shift: function (x, y, w, h) { + if (x) this.x += x; + if (y) this.y += y; + if (w) this.w += w; + if (h) this.h += h; + + return this; + }, + + /**@ + * #._cascade + * @comp 2D + * @sign public void ._cascade(e) + * @param e - Amount to move X + * Shift or move the entity by an amount. Use negative values + * for an opposite direction. + */ + /** + * Move or rotate all the children for this entity + */ + _cascade: function (e) { + if (!e) return; //no change in position + var i = 0, children = this._children, l = children.length, obj; + //rotation + if (e.cos) { + for (; i < l; ++i) { + obj = children[i]; + if ('rotate' in obj) obj.rotate(e); + } + } else { + //use MBR or current + var rect = this._mbr || this, + dx = rect._x - e._x, + dy = rect._y - e._y, + dw = rect._w - e._w, + dh = rect._h - e._h; + + for (; i < l; ++i) { + obj = children[i]; + obj.shift(dx, dy, dw, dh); + } + } + }, + + /** + * #.attach + * @comp 2D + * @sign public this .attach(Entity obj[, .., Entity objN]) + * @param obj - Entity(s) to attach + * Attaches an entities position and rotation to current entity. When the current entity moves, + * the attached entity will move by the same amount. + * + * As many objects as wanted can be attached and a hierarchy of objects is possible by attaching. + */ + attach: function () { + var i = 0, arg = arguments, l = arguments.length, obj; + for (; i < l; ++i) { + obj = arg[i]; + if (obj._parent) { obj._parent.detach(obj); } + obj._parent = this; + this._children.push(obj); + } + + return this; + }, + + /**@ + * #.detach + * @comp 2D + * @sign public this .detach([Entity obj]) + * @param obj - The entity to detach. Left blank will remove all attached entities + * Stop an entity from following the current entity. Passing no arguments will stop + * every entity attached. + */ + detach: function (obj) { + //if nothing passed, remove all attached objects + if (!obj) { + for (var i = 0; i < this._children.length; i++) { + this._children[i]._parent = null; + } + this._children = []; + return this; + } + + //if obj passed, find the handler and unbind + for (var i = 0; i < this._children.length; i++) { + if (this._children[i] == obj) { + this._children.splice(i, 1); + } + } + obj._parent = null; + + return this; + }, + + /**@ + * #.origin + * @comp 2D + * @sign public this .origin(Number x, Number y) + * @param x - Pixel value of origin offset on the X axis + * @param y - Pixel value of origin offset on the Y axis + * @sign public this .origin(String offset) + * @param offset - Combination of center, top, bottom, middle, left and right + * Set the origin point of an entity for it to rotate around. + * @example + * ~~~ + * this.origin("top left") + * this.origin("center") + * this.origin("bottom right") + * this.origin("middle right") + * ~~~ + * @see .rotation + */ + origin: function (x, y) { + //text based origin + if (typeof x === "string") { + if (x === "centre" || x === "center" || x.indexOf(' ') === -1) { + x = this._w / 2; + y = this._h / 2; + } else { + var cmd = x.split(' '); + if (cmd[0] === "top") y = 0; + else if (cmd[0] === "bottom") y = this._h; + else if (cmd[0] === "middle" || cmd[1] === "center" || cmd[1] === "centre") y = this._h / 2; + + if (cmd[1] === "center" || cmd[1] === "centre" || cmd[1] === "middle") x = this._w / 2; + else if (cmd[1] === "left") x = 0; + else if (cmd[1] === "right") x = this._w; + } + } + + this._origin.x = x; + this._origin.y = y; + + return this; + }, + + flip: function (dir) { + dir = dir || "X"; + this["_flip" + dir] = true; + this.trigger("Change"); + }, + + /** + * Method for rotation rather than through a setter + */ + rotate: function (e) { + //assume event data origin + this._origin.x = e.o.x - this._x; + this._origin.y = e.o.y - this._y; + + //modify through the setter method + this._attr('_rotation', e.theta); + }, + + /**@ + * #._attr + * @comp 2D + * Setter method for all 2D properties including + * x, y, w, h, alpha, rotation and visible. + */ + _attr: function (name, value) { + //keep a reference of the old positions + var pos = this.pos(), + old = this.mbr() || pos; + + //if rotation, use the rotate method + if (name === '_rotation') { + this._rotate(value); + this.trigger("Rotate"); + //set the global Z and trigger reorder just incase + } else if (name === '_z') { + this._globalZ = parseInt(value + Crafty.zeroFill(this[0], 5), 10); //magic number 10e5 is the max num of entities + this.trigger("reorder"); + //if the rect bounds change, update the MBR and trigger move + } else if (name == '_x' || name === '_y' || name === '_w' || name === '_h') { + var mbr = this._mbr; + if (mbr) { + mbr[name] -= this[name] - value; + } + this[name] = value; + this.trigger("Move", old); + } + + //everything will assume the value + this[name] = value; + + //trigger a change + this.trigger("Change", old); + } +}); + +Crafty.c("Physics", { + _gravity: 0.4, + _friction: 0.2, + _bounce: 0.5, + + gravity: function (gravity) { + this._gravity = gravity; + } +}); + +/**@ +* #Gravity +* @category 2D +* Adds gravitational pull to the entity. +*/ +Crafty.c("Gravity", { + _gravityConst: 0.2, + _gy: 0, + _falling: true, + _anti: null, + + init: function () { + this.requires("2D"); + }, + + /**@ + * #.gravity + * @comp Gravity + * @sign public this .gravity([comp]) + * @param comp - The name of a component that will stop this entity from falling + * Enable gravity for this entity no matter whether comp parameter is not specified, + * If comp parameter is specified all entities with that component will stop this entity from falling. + * For a player entity in a platform game this would be a component that is added to all entities + * that the player should be able to walk on. + * ~~~ + * Crafty.e("2D, DOM, Color, Gravity") + * .color("red") + * .attr({ w: 100, h: 100 }) + * .gravity("platform") + * ~~~ + */ + gravity: function (comp) { + if (comp) this._anti = comp; + + this.bind("EnterFrame", this._enterframe); + + return this; + }, + + /**@ + * #.gravityConst + * @comp Gravity + * @sign public this .gravityConst(g) + * @param g - gravitational constant + * Set the gravitational constant to g. The default is .2. The greater g, the faster the object falls. + * ~~~ + * Crafty.e("2D, DOM, Color, Gravity") + * .color("red") + * .attr({ w: 100, h: 100 }) + * .gravity("platform") + * .gravityConst(2) + * ~~~ + */ + gravityConst: function(g) { + this._gravityConst=g; + return this; + }, + + _enterframe: function () { + if (this._falling) { + //if falling, move the players Y + //it used to be this._gy += this._gravityConst * 2; + //2 seems to be unnecessary. So 2 is removed. by pengyu + this._gy += this._gravityConst; + this.y += this._gy; + } else { + this._gy = 0; //reset change in y + } + + var obj, hit = false, pos = this.pos(), + q, i = 0, l; + + //Increase by 1 to make sure map.search() finds the floor + pos._y++; + + //map.search wants _x and intersect wants x... + pos.x = pos._x; + pos.y = pos._y; + pos.w = pos._w; + pos.h = pos._h; + + q = Crafty.map.search(pos); + l = q.length; + + for (; i < l; ++i) { + obj = q[i]; + //check for an intersection directly below the player + if (obj !== this && obj.has(this._anti) && obj.intersect(pos)) { + hit = obj; + break; + } + } + + if (hit) { //stop falling if found + if (this._falling) this.stopFalling(hit); + } else { + this._falling = true; //keep falling otherwise + } + }, + + stopFalling: function (e) { + if (e) this.y = e._y - this._h; //move object + + //this._gy = -1 * this._bounce; + this._falling = false; + if (this._up) this._up = false; + this.trigger("hit"); + }, + + /**@ + * #.antigravity + * @comp Gravity + * @sign public this .antigravity() + * Disable gravity for this component. It can be reenabled by calling .gravity() + */ + antigravity: function () { + this.unbind("EnterFrame", this._enterframe); + } +}); + +/**@ +* #Crafty.Polygon +* @category 2D +* Polygon object used for hitboxes and click maps. Must pass an Array for each point as an +* argument where index 0 is the x position and index 1 is the y position. +* +* For example one point of a polygon will look like this: `[0,5]` where the `x` is `0` and the `y` is `5`. +* +* Can pass an array of the points or simply put each point as an argument. +* +* When creating a polygon for an entity, each point should be offset or relative from the entities `x` and `y` +* (don't include the absolute values as it will automatically calculate this). +* +* +* @example +* ~~~ +* new Crafty.polygon([50,0],[100,100],[0,100]); +* new Crafty.polygon([[50,0],[100,100],[0,100]]); +* ~~~ +*/ +Crafty.polygon = function (poly) { + if (arguments.length > 1) { + poly = Array.prototype.slice.call(arguments, 0); + } + this.points = poly; +}; + +Crafty.polygon.prototype = { +/**@ + * #.containsPoint + * @comp Crafty.Polygon + * @sign public Boolean .containsPoint(Number x, Number y) + * @param x - X position of the point + * @param y - Y position of the point + * Method is used to determine if a given point is contained by the polygon. + * @example + * ~~~ + * var poly = new Crafty.polygon([50,0],[100,100],[0,100]); + * poly.containsPoint(50, 50); //TRUE + * poly.containsPoint(0, 0); //FALSE + * ~~~ + */ + containsPoint: function (x, y) { + var p = this.points, i, j, c = false; + + for (i = 0, j = p.length - 1; i < p.length; j = i++) { + if (((p[i][1] > y) != (p[j][1] > y)) && (x < (p[j][0] - p[i][0]) * (y - p[i][1]) / (p[j][1] - p[i][1]) + p[i][0])) { + c = !c; + } + } + + return c; + }, + + /**@ + * #.shift + * @comp Crafty.Polygon + * @sign public void .shift(Number x, Number y) + * @param x - Amount to shift the `x` axis + * @param y - Amount to shift the `y` axis + * Shifts every single point in the polygon by the specified amount. + * @example + * ~~~ + * var poly = new Crafty.polygon([50,0],[100,100],[0,100]); + * poly.shift(5,5); + * //[[55,5], [105,5], [5,105]]; + * ~~~ + */ + shift: function (x, y) { + var i = 0, l = this.points.length, current; + for (; i < l; i++) { + current = this.points[i]; + current[0] += x; + current[1] += y; + } + }, + + rotate: function (e) { + var i = 0, l = this.points.length, + current, x, y; + + for (; i < l; i++) { + current = this.points[i]; + + x = e.o.x + (current[0] - e.o.x) * e.cos + (current[1] - e.o.y) * e.sin; + y = e.o.y - (current[0] - e.o.x) * e.sin + (current[1] - e.o.y) * e.cos; + + current[0] = x; + current[1] = y; + } + } +}; + +/**@ +* #Crafty.Circle +* @category 2D +* Circle object used for hitboxes and click maps. Must pass a `x`, a `y` and a `radius` value. +* +* ~~~ +* var centerX = 5, +* centerY = 10, +* radius = 25; +* +* new Crafty.circle(centerX, centerY, radius); +* ~~~ +* +* When creating a circle for an entity, each point should be offset or relative from the entities `x` and `y` +* (don't include the absolute values as it will automatically calculate this). +*/ +Crafty.circle = function (x, y, radius) { + this.x = x; + this.y = y; + this.radius = radius; + + // Creates an octogon that aproximate the circle for backward compatibility. + this.points = []; + var theta; + + for (var i = 0; i < 8; i++) { + theta = i * Math.PI / 4; + this.points[i] = [Math.sin(theta) * radius, Math.cos(theta) * radius]; + } +}; + +Crafty.circle.prototype = { +/**@ + * #.containsPoint + * @comp Crafty.Circle + * @sign public Boolean .containsPoint(Number x, Number y) + * @param x - X position of the point + * @param y - Y position of the point + * Method is used to determine if a given point is contained by the circle. + * @example + * ~~~ + * var circle = new Crafty.circle(0, 0, 10); + * circle.containsPoint(0, 0); //TRUE + * circle.containsPoint(50, 50); //FALSE + * ~~~ + */ + containsPoint: function (x, y) { + var radius = this.radius, + sqrt = Math.sqrt, + deltaX = this.x - x, + deltaY = this.y - y; + + return (deltaX * deltaX + deltaY * deltaY) < (radius * radius); + }, + + /**@ + * #.shift + * @comp Crafty.Circle + * @sign public void .shift(Number x, Number y) + * @param x - Amount to shift the `x` axis + * @param y - Amount to shift the `y` axis + * Shifts the circle by the specified amount. + * @example + * ~~~ + * var poly = new Crafty.circle(0, 0, 10); + * circle.shift(5,5); + * //{x: 5, y: 5, radius: 10}; + * ~~~ + */ + shift: function (x, y) { + this.x += x; + this.y += y; + + var i = 0, l = this.points.length, current; + for (; i < l; i++) { + current = this.points[i]; + current[0] += x; + current[1] += y; + } + }, + + rotate: function () { + // We are a circle, we don't have to rotate :) + } +}; + + +Crafty.matrix = function (m) { + this.mtx = m; + this.width = m[0].length; + this.height = m.length; +}; + +Crafty.matrix.prototype = { + x: function (other) { + if (this.width != other.height) { + return; + } + + var result = []; + for (var i = 0; i < this.height; i++) { + result[i] = []; + for (var j = 0; j < other.width; j++) { + var sum = 0; + for (var k = 0; k < this.width; k++) { + sum += this.mtx[i][k] * other.mtx[k][j]; + } + result[i][j] = sum; + } + } + return new Crafty.matrix(result); + }, + + + e: function (row, col) { + //test if out of bounds + if (row < 1 || row > this.mtx.length || col < 1 || col > this.mtx[0].length) return null; + return this.mtx[row - 1][col - 1]; + } +} + /**@ +* #Collision +* @category 2D +* Component to detect collision between any two convex polygons. +*/ +Crafty.c("Collision", { + + init: function () { + this.requires("2D"); + }, + + /**@ + * #.collision + * @comp Collision + * @sign public this .collision([Crafty.Polygon polygon]) + * @param polygon - Crafty.Polygon object that will act as the hit area + * Constructor takes a polygon to use as the hit area. If left empty, + * will create a rectangle polygon based on the x, y, w, h dimensions. + * + * This must be called before any .hit() or .onhit() methods. + * + * The hit area (polygon) must be a convex shape and not concave + * for the collision detection to work. + * @example + * ~~~ + * Crafty.e("2D, Collision").collision( + * new Crafty.polygon([50,0], [100,100], [0,100]) + * ); + * ~~~ + * @see Crafty.Polygon + */ + collision: function (poly) { + var area = this._mbr || this; + + //if no polygon presented, create a square + if (!poly) { + poly = new Crafty.polygon([0, 0], [area._w, 0], [area._w, area._h], [0, area._h]); + } + this.map = poly; + this.attach(this.map); + this.map.shift(area._x, area._y); + + return this; + }, + + /**@ + * #.hit + * @comp Collision + * @sign public Boolean/Array hit(String component) + * @param component - Check collision with entities that has this component + * @return `false` if no collision. If a collision is detected, returns an Array of objects that are colliding. + * Takes an argument for a component to test collision for. If a collision is found, an array of + * every object in collision along with the amount of overlap is passed. + * + * If no collision, will return false. The return collision data will be an Array of Objects with the + * type of collision used, the object collided and if the type used was SAT (a polygon was used as the hitbox) then an amount of overlap. + * ~~~ + * [{ + * obj: [entity], + * type "MBR" or "SAT", + * overlap: [number] + * }] + * ~~~ + * `MBR` is your standard axis aligned rectangle intersection (`.intersect` in the 2D component). + * `SAT` is collision between any convex polygon. + * @see .onHit, 2D + */ + hit: function (comp) { + var area = this._mbr || this, + results = Crafty.map.search(area, false), + i = 0, l = results.length, + dupes = {}, + id, obj, oarea, key, + hasMap = ('map' in this && 'containsPoint' in this.map), + finalresult = []; + + if (!l) { + return false; + } + + for (; i < l; ++i) { + obj = results[i]; + oarea = obj._mbr || obj; //use the mbr + + if (!obj) continue; + id = obj[0]; + + //check if not added to hash and that actually intersects + if (!dupes[id] && this[0] !== id && obj.__c[comp] && + oarea._x < area._x + area._w && oarea._x + oarea._w > area._x && + oarea._y < area._y + area._h && oarea._h + oarea._y > area._y) + dupes[id] = obj; + } + + for (key in dupes) { + obj = dupes[key]; + + if (hasMap && 'map' in obj) { + var SAT = this._SAT(this.map, obj.map); + SAT.obj = obj; + SAT.type = "SAT"; + if (SAT) finalresult.push(SAT); + } else { + finalresult.push({ obj: obj, type: "MBR" }); + } + } + + if (!finalresult.length) { + return false; + } + + return finalresult; + }, + + /**@ + * #.onHit + * @comp Collision + * @sign public this .onHit(String component, Function hit[, Function noHit]) + * @param component - Component to check collisions for + * @param hit - Callback method to execute when collided with component + * @param noHit - Callback method executed once as soon as collision stops + * Creates an enterframe event calling .hit() each time and if collision detected will invoke the callback. + * @see .hit + */ + onHit: function (comp, callback, callbackOff) { + var justHit = false; + this.bind("EnterFrame", function () { + var hitdata = this.hit(comp); + if (hitdata) { + justHit = true; + callback.call(this, hitdata); + } else if (justHit) { + if (typeof callbackOff == 'function') { + callbackOff.call(this); + } + justHit = false; + } + }); + return this; + }, + + _SAT: function (poly1, poly2) { + var points1 = poly1.points, + points2 = poly2.points, + i = 0, l = points1.length, + j, k = points2.length, + normal = { x: 0, y: 0 }, + length, + min1, min2, + max1, max2, + interval, + MTV = null, + MTV2 = null, + MN = null, + dot, + nextPoint, + currentPoint; + + //loop through the edges of Polygon 1 + for (; i < l; i++) { + nextPoint = points1[(i == l - 1 ? 0 : i + 1)]; + currentPoint = points1[i]; + + //generate the normal for the current edge + normal.x = -(nextPoint[1] - currentPoint[1]); + normal.y = (nextPoint[0] - currentPoint[0]); + + //normalize the vector + length = Math.sqrt(normal.x * normal.x + normal.y * normal.y); + normal.x /= length; + normal.y /= length; + + //default min max + min1 = min2 = -1; + max1 = max2 = -1; + + //project all vertices from poly1 onto axis + for (j = 0; j < l; ++j) { + dot = points1[j][0] * normal.x + points1[j][1] * normal.y; + if (dot > max1 || max1 === -1) max1 = dot; + if (dot < min1 || min1 === -1) min1 = dot; + } + + //project all vertices from poly2 onto axis + for (j = 0; j < k; ++j) { + dot = points2[j][0] * normal.x + points2[j][1] * normal.y; + if (dot > max2 || max2 === -1) max2 = dot; + if (dot < min2 || min2 === -1) min2 = dot; + } + + //calculate the minimum translation vector should be negative + if (min1 < min2) { + interval = min2 - max1; + + normal.x = -normal.x; + normal.y = -normal.y; + } else { + interval = min1 - max2; + } + + //exit early if positive + if (interval >= 0) { + return false; + } + + if (MTV === null || interval > MTV) { + MTV = interval; + MN = { x: normal.x, y: normal.y }; + } + } + + //loop through the edges of Polygon 2 + for (i = 0; i < k; i++) { + nextPoint = points2[(i == k - 1 ? 0 : i + 1)]; + currentPoint = points2[i]; + + //generate the normal for the current edge + normal.x = -(nextPoint[1] - currentPoint[1]); + normal.y = (nextPoint[0] - currentPoint[0]); + + //normalize the vector + length = Math.sqrt(normal.x * normal.x + normal.y * normal.y); + normal.x /= length; + normal.y /= length; + + //default min max + min1 = min2 = -1; + max1 = max2 = -1; + + //project all vertices from poly1 onto axis + for (j = 0; j < l; ++j) { + dot = points1[j][0] * normal.x + points1[j][1] * normal.y; + if (dot > max1 || max1 === -1) max1 = dot; + if (dot < min1 || min1 === -1) min1 = dot; + } + + //project all vertices from poly2 onto axis + for (j = 0; j < k; ++j) { + dot = points2[j][0] * normal.x + points2[j][1] * normal.y; + if (dot > max2 || max2 === -1) max2 = dot; + if (dot < min2 || min2 === -1) min2 = dot; + } + + //calculate the minimum translation vector should be negative + if (min1 < min2) { + interval = min2 - max1; + + normal.x = -normal.x; + normal.y = -normal.y; + } else { + interval = min1 - max2; + + + } + + //exit early if positive + if (interval >= 0) { + return false; + } + + if (MTV === null || interval > MTV) MTV = interval; + if (interval > MTV2 || MTV2 === null) { + MTV2 = interval; + MN = { x: normal.x, y: normal.y }; + } + } + + return { overlap: MTV2, normal: MN }; + } +}); + /**@ +* #DOM +* @category Graphics +* Draws entities as DOM nodes, specifically ` `s. +*/ +Crafty.c("DOM", { +/**@ + * #._element + * @comp DOM + * The DOM element used to represent the entity. + */ + _element: null, + + init: function () { + this._element = document.createElement("div"); + Crafty.stage.inner.appendChild(this._element); + this._element.style.position = "absolute"; + this._element.id = "ent" + this[0]; + + this.bind("Change", function () { + if (!this._changed) { + this._changed = true; + Crafty.DrawManager.add(this); + } + }); + + function updateClass() { + var i = 0, c = this.__c, str = ""; + for (i in c) { + str += ' ' + i; + } + str = str.substr(1); + this._element.className = str; + } + + this.bind("NewComponent", updateClass).bind("RemoveComponent", updateClass); + + if (Crafty.support.prefix === "ms" && Crafty.support.version < 9) { + this._filters = {}; + + this.bind("Rotate", function (e) { + var m = e.matrix, + elem = this._element.style, + M11 = m.M11.toFixed(8), + M12 = m.M12.toFixed(8), + M21 = m.M21.toFixed(8), + M22 = m.M22.toFixed(8); + + this._filters.rotation = "progid:DXImageTransform.Microsoft.Matrix(M11=" + M11 + ", M12=" + M12 + ", M21=" + M21 + ", M22=" + M22 + ",sizingMethod='auto expand')"; + }); + } + + this.bind("Remove", this.undraw); + this.bind("RemoveComponent", function (compName) { + if (compName === "DOM") + this.undraw(); + }); + }, + + /**@ + * #.getDomId + * @comp DOM + * @sign public this .getId() + * Get the Id of the DOM element used to represent the entity. + */ + getDomId: function() { + return this._element.id; + }, + + /**@ + * #.DOM + * @comp DOM + * @trigger Draw - when the entity is ready to be drawn to the stage - { style:String, type:"DOM", co} + * @sign public this .DOM(HTMLElement elem) + * @param elem - HTML element that will replace the dynamically created one + * Pass a DOM element to use rather than one created. Will set `._element` to this value. Removes the old element. + */ + DOM: function (elem) { + if (elem && elem.nodeType) { + this.undraw(); + this._element = elem; + this._element.style.position = 'absolute'; + } + return this; + }, + + /**@ + * #.draw + * @comp DOM + * @sign public this .draw(void) + * Updates the CSS properties of the node to draw on the stage. + */ + draw: function () { + var style = this._element.style, + coord = this.__coord || [0, 0, 0, 0], + co = { x: coord[0], y: coord[1] }, + prefix = Crafty.support.prefix, + trans = []; + + if (!this._visible) style.visibility = "hidden"; + else style.visibility = "visible"; + + //utilize CSS3 if supported + if (Crafty.support.css3dtransform) { + trans.push("translate3d(" + (~~this._x) + "px," + (~~this._y) + "px,0)"); + } else { + style.left = ~~(this._x) + "px"; + style.top = ~~(this._y) + "px"; + } + + style.width = ~~(this._w) + "px"; + style.height = ~~(this._h) + "px"; + style.zIndex = this._z; + + style.opacity = this._alpha; + style[prefix + "Opacity"] = this._alpha; + + //if not version 9 of IE + if (prefix === "ms" && Crafty.support.version < 9) { + //for IE version 8, use ImageTransform filter + if (Crafty.support.version === 8) { + this._filters.alpha = "progid:DXImageTransform.Microsoft.Alpha(Opacity=" + (this._alpha * 100) + ")"; // first! + //all other versions use filter + } else { + this._filters.alpha = "alpha(opacity=" + (this._alpha * 100) + ")"; + } + } + + if (this._mbr) { + var origin = this._origin.x + "px " + this._origin.y + "px"; + style.transformOrigin = origin; + style[prefix + "TransformOrigin"] = origin; + if (Crafty.support.css3dtransform) trans.push("rotateZ(" + this._rotation + "deg)"); + else trans.push("rotate(" + this._rotation + "deg)"); + } + + if (this._flipX) { + trans.push("scaleX(-1)"); + if (prefix === "ms" && Crafty.support.version < 9) { + this._filters.flipX = "fliph"; + } + } + + if (this._flipY) { + trans.push("scaleY(-1)"); + if (prefix === "ms" && Crafty.support.version < 9) { + this._filters.flipY = "flipv"; + } + } + + //apply the filters if IE + if (prefix === "ms" && Crafty.support.version < 9) { + this.applyFilters(); + } + + style.transform = trans.join(" "); + style[prefix + "Transform"] = trans.join(" "); + + this.trigger("Draw", { style: style, type: "DOM", co: co }); + + return this; + }, + + applyFilters: function () { + this._element.style.filter = ""; + var str = ""; + + for (var filter in this._filters) { + if (!this._filters.hasOwnProperty(filter)) continue; + str += this._filters[filter] + " "; + } + + this._element.style.filter = str; + }, + + /**@ + * #.undraw + * @comp DOM + * @sign public this .undraw(void) + * Removes the element from the stage. + */ + undraw: function () { + if (this._element) { + Crafty.stage.inner.removeChild(this._element); + } + return this; + }, + + /**@ + * #.css + * @comp DOM + * @sign public * css(String property, String value) + * @param property - CSS property to modify + * @param value - Value to give the CSS property + * @sign public * css(Object map) + * @param map - Object where the key is the CSS property and the value is CSS value + * Apply CSS styles to the element. + * + * Can pass an object where the key is the style property and the value is style value. + * + * For setting one style, simply pass the style as the first argument and the value as the second. + * + * The notation can be CSS or JS (e.g. `text-align` or `textAlign`). + * + * To return a value, pass the property. + * @example + * ~~~ + * this.css({'text-align', 'center', font: 'Arial'}); + * this.css("textAlign", "center"); + * this.css("text-align"); //returns center + * ~~~ + */ + css: function (obj, value) { + var key, + elem = this._element, + val, + style = elem.style; + + //if an object passed + if (typeof obj === "object") { + for (key in obj) { + if (!obj.hasOwnProperty(key)) continue; + val = obj[key]; + if (typeof val === "number") val += 'px'; + + style[Crafty.DOM.camelize(key)] = val; + } + } else { + //if a value is passed, set the property + if (value) { + if (typeof value === "number") value += 'px'; + style[Crafty.DOM.camelize(obj)] = value; + } else { //otherwise return the computed property + return Crafty.DOM.getStyle(elem, obj); + } + } + + this.trigger("Change"); + + return this; + } +}); + +/** +* Fix IE6 background flickering +*/ +try { + document.execCommand("BackgroundImageCache", false, true); +} catch (e) { } + +Crafty.extend({ +/**@ + * #Crafty.DOM + * @category Graphics + * Collection of utilities for using the DOM. + */ + DOM: { + /**@ + * #Crafty.DOM.window + * @comp Crafty.DOM + * Object with `width` and `height` values representing the width + * and height of the `window`. + */ + window: { + init: function () { + this.width = window.innerWidth || (window.document.documentElement.clientWidth || window.document.body.clientWidth); + this.height = window.innerHeight || (window.document.documentElement.clientHeight || window.document.body.clientHeight); + }, + + width: 0, + height: 0 + }, + + /**@ + * #Crafty.DOM.inner + * @comp Crafty.DOM + * @sign public Object Crafty.DOM.inner(HTMLElement obj) + * @param obj - HTML element to calculate the position + * @returns Object with `x` key being the `x` position, `y` being the `y` position + * Find a DOM elements position including + * padding and border. + */ + inner: function (obj) { + var rect = obj.getBoundingClientRect(), + x = rect.left + (window.pageXOffset ? window.pageXOffset : document.body.scrollTop), + y = rect.top + (window.pageYOffset ? window.pageYOffset : document.body.scrollLeft), + + //border left + borderX = parseInt(this.getStyle(obj, 'border-left-width') || 0, 10) || parseInt(this.getStyle(obj, 'borderLeftWidth') || 0, 10) || 0, + borderY = parseInt(this.getStyle(obj, 'border-top-width') || 0, 10) || parseInt(this.getStyle(obj, 'borderTopWidth') || 0, 10) || 0; + + x += borderX; + y += borderY; + + return { x: x, y: y }; + }, + + /**@ + * #Crafty.DOM.getStyle + * @comp Crafty.DOM + * @sign public Object Crafty.DOM.getStyle(HTMLElement obj, String property) + * @param obj - HTML element to find the style + * @param property - Style to return + * Determine the value of a style on an HTML element. Notation can be + * in either CSS or JS. + */ + getStyle: function (obj, prop) { + var result; + if (obj.currentStyle) + result = obj.currentStyle[this.camelize(prop)]; + else if (window.getComputedStyle) + result = document.defaultView.getComputedStyle(obj, null).getPropertyValue(this.csselize(prop)); + return result; + }, + + /** + * Used in the Zepto framework + * + * Converts CSS notation to JS notation + */ + camelize: function (str) { + return str.replace(/-+(.)?/g, function (match, chr){ return chr ? chr.toUpperCase() : '' }); + }, + + /** + * Converts JS notation to CSS notation + */ + csselize: function (str) { + return str.replace(/[A-Z]/g, function (chr){ return chr ? '-' + chr.toLowerCase() : '' }); + }, + + /**@ + * #Crafty.DOM.translate + * @comp Crafty.DOM + * @sign public Object Crafty.DOM.translate(Number x, Number y) + * @param x - x position to translate + * @param y - y position to translate + * @return Object with x and y as keys and translated values + * + * Method will translate x and y positions to positions on the + * stage. Useful for mouse events with `e.clientX` and `e.clientY`. + */ + translate: function (x, y) { + return { + x: x - Crafty.stage.x + document.body.scrollLeft + document.documentElement.scrollLeft - Crafty.viewport._x, + y: y - Crafty.stage.y + document.body.scrollTop + document.documentElement.scrollTop - Crafty.viewport._y + } + } + } +}); + /**@ +* #Crafty.support +* @category Misc, Core +* Determines feature support for what Crafty can do. +*/ +(function testSupport() { + var support = Crafty.support = {}, + ua = navigator.userAgent.toLowerCase(), + match = /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(o)pera(?:.*version)?[ \/]([\w.]+)/.exec(ua) || + /(ms)ie ([\w.]+)/.exec(ua) || + /(moz)illa(?:.*? rv:([\w.]+))?/.exec(ua) || [], + mobile = /iPad|iPod|iPhone|Android|webOS/i.exec(ua); + + if (mobile) Crafty.mobile = mobile[0]; + + /**@ + * #Crafty.support.setter + * @comp Crafty.support + * Is `__defineSetter__` supported? + */ + support.setter = ('__defineSetter__' in this && '__defineGetter__' in this); + + /**@ + * #Crafty.support.defineProperty + * @comp Crafty.support + * Is `Object.defineProperty` supported? + */ + support.defineProperty = (function () { + if (!'defineProperty' in Object) return false; + try { Object.defineProperty({}, 'x', {}); } + catch (e) { return false }; + return true; + })(); + + /**@ + * #Crafty.support.audio + * @comp Crafty.support + * Is HTML5 `Audio` supported? + */ + support.audio = ('Audio' in window); + + /**@ + * #Crafty.support.prefix + * @comp Crafty.support + * Returns the browser specific prefix (`Moz`, `O`, `ms`, `webkit`). + */ + support.prefix = (match[1] || match[0]); + + //browser specific quirks + if (support.prefix === "moz") support.prefix = "Moz"; + if (support.prefix === "o") support.prefix = "O"; + + if (match[2]) { + /**@ + * #Crafty.support.versionName + * @comp Crafty.support + * Version of the browser + */ + support.versionName = match[2]; + + /**@ + * #Crafty.support.version + * @comp Crafty.support + * Version number of the browser as an Integer (first number) + */ + support.version = +(match[2].split("."))[0]; + } + + /**@ + * #Crafty.support.canvas + * @comp Crafty.support + * Is the `canvas` element supported? + */ + support.canvas = ('getContext' in document.createElement("canvas")); + + /**@ + * #Crafty.support.webgl + * @comp Crafty.support + * Is WebGL supported on the canvas element? + */ + if (support.canvas) { + var gl; + try { + gl = document.createElement("canvas").getContext("experimental-webgl"); + gl.viewportWidth = canvas.width; + gl.viewportHeight = canvas.height; + } + catch (e) { } + support.webgl = !!gl; + } + else { + support.webgl = false; + } + + support.css3dtransform = (typeof document.createElement("div").style["Perspective"] !== "undefined") + || (typeof document.createElement("div").style[support.prefix + "Perspective"] !== "undefined"); +})(); +Crafty.extend({ + + zeroFill: function (number, width) { + width -= number.toString().length; + if (width > 0) + return new Array(width + (/\./.test(number) ? 2 : 1)).join('0') + number; + return number.toString(); + }, + + /**@ + * #Crafty.sprite + * @category Graphics + * @sign public this Crafty.sprite([Number tile], String url, Object map[, Number paddingX[, Number paddingY]]) + * @param tile - Tile size of the sprite map, defaults to 1 + * @param url - URL of the sprite image + * @param map - Object where the key is what becomes a new component and the value points to a position on the sprite map + * @param paddingX - Horizontal space inbetween tiles. Defaults to 0. + * @param paddingY - Vertical space inbetween tiles. Defaults to paddingX. + * Generates components based on positions in a sprite image to be applied to entities. + * + * Accepts a tile size, URL and map for the name of the sprite and it's position. + * + * The position must be an array containing the position of the sprite where index `0` + * is the `x` position, `1` is the `y` position and optionally `2` is the width and `3` + * is the height. If the sprite map has padding, pass the values for the `x` padding + * or `y` padding. If they are the same, just add one value. + * + * If the sprite image has no consistent tile size, `1` or no argument need be + * passed for tile size. + * + * Entities that add the generated components are also given a component called `Sprite`. + * @see Sprite + */ + sprite: function (tile, tileh, url, map, paddingX, paddingY) { + var spriteName, temp, x, y, w, h, img; + + //if no tile value, default to 1 + if (typeof tile === "string") { + paddingY = paddingX; + paddingX = map; + map = tileh; + url = tile; + tile = 1; + tileh = 1; + } + + if (typeof tileh == "string") { + paddingY = paddingX; + paddingX = map; + map = url; + url = tileh; + tileh = tile; + } + + //if no paddingY, use paddingX + if (!paddingY && paddingX) paddingY = paddingX; + paddingX = parseInt(paddingX || 0, 10); //just incase + paddingY = parseInt(paddingY || 0, 10); + + img = Crafty.assets[url]; + if (!img) { + img = new Image(); + img.src = url; + Crafty.assets[url] = img; + img.onload = function () { + //all components with this img are now ready + for (spriteName in map) { + Crafty(spriteName).each(function () { + this.ready = true; + this.trigger("Change"); + }); + } + }; + } + + for (spriteName in map) { + if (!map.hasOwnProperty(spriteName)) continue; + + temp = map[spriteName]; + x = temp[0] * (tile + paddingX); + y = temp[1] * (tileh + paddingY); + w = temp[2] * tile || tile; + h = temp[3] * tileh || tileh; + + //generates sprite components for each tile in the map + Crafty.c(spriteName, { + ready: false, + __coord: [x, y, w, h], + + init: function () { + this.requires("Sprite"); + this.__trim = [0, 0, 0, 0]; + this.__image = url; + this.__coord = [this.__coord[0], this.__coord[1], this.__coord[2], this.__coord[3]]; + this.__tile = tile; + this.__tileh = tileh; + this.__padding = [paddingX, paddingY]; + this.img = img; + + //draw now + if (this.img.complete && this.img.width > 0) { + this.ready = true; + this.trigger("Change"); + } + + //set the width and height to the sprite size + this.w = this.__coord[2]; + this.h = this.__coord[3]; + } + }); + } + + return this; + }, + + _events: {}, + + /**@ + * #Crafty.addEvent + * @category Events, Misc + * @sign public this Crafty.addEvent(Object ctx, HTMLElement obj, String event, Function callback) + * @param ctx - Context of the callback or the value of `this` + * @param obj - Element to add the DOM event to + * @param event - Event name to bind to + * @param callback - Method to execute when triggered + * Adds DOM level 3 events to elements. The arguments it accepts are the call + * context (the value of `this`), the DOM element to attach the event to, + * the event name (without `on` (`click` rather than `onclick`)) and + * finally the callback method. + * + * If no element is passed, the default element will be `window.document`. + * + * Callbacks are passed with event data. + * @see Crafty.removeEvent + */ + addEvent: function (ctx, obj, type, callback) { + if (arguments.length === 3) { + callback = type; + type = obj; + obj = window.document; + } + + //save anonymous function to be able to remove + var afn = function (e) { var e = e || window.event; callback.call(ctx, e) }, + id = ctx[0] || ""; + + if (!this._events[id + obj + type + callback]) this._events[id + obj + type + callback] = afn; + else return; + + if (obj.attachEvent) { //IE + obj.attachEvent('on' + type, afn); + } else { //Everyone else + obj.addEventListener(type, afn, false); + } + }, + + /**@ + * #Crafty.removeEvent + * @category Events, Misc + * @sign public this Crafty.removeEvent(Object ctx, HTMLElement obj, String event, Function callback) + * @param ctx - Context of the callback or the value of `this` + * @param obj - Element the event is on + * @param event - Name of the event + * @param callback - Method executed when triggered + * Removes events attached by `Crafty.addEvent()`. All parameters must + * be the same that were used to attach the event including a reference + * to the callback method. + * @see Crafty.addEvent + */ + removeEvent: function (ctx, obj, type, callback) { + if (arguments.length === 3) { + callback = type; + type = obj; + obj = window.document; + } + + //retrieve anonymouse function + var id = ctx[0] || "", + afn = this._events[id + obj + type + callback]; + + if (afn) { + if (obj.detachEvent) { + obj.detachEvent('on' + type, afn); + } else obj.removeEventListener(type, afn, false); + delete this._events[id + obj + type + callback]; + } + }, + + /**@ + * #Crafty.background + * @category Graphics, Stage + * @sign public void Crafty.background(String value) + * @param color - Modify the background with a color or image + * This method is essentially a shortcut for adding a background + * style to the stage element. + */ + background: function (color) { + Crafty.stage.elem.style.background = color; + }, + + /**@ + * #Crafty.viewport + * @category Stage + * Viewport is essentially a 2D camera looking at the stage. Can be moved which + * in turn will react just like a camera moving in that direction. + */ + viewport: { + /**@ + * #Crafty.viewport.clampToEntities + * @comp Crafty.viewport + * Decides if the viewport functions should clamp to game entities. + * When set to `true` functions such as Crafty.viewport.mouselook() will not allow you to move the + * viewport over areas of the game that has no entities. + * For development it can be useful to set this to false. + */ + clampToEntities: true, + width: 0, + height: 0, + /**@ + * #Crafty.viewport.x + * @comp Crafty.viewport + * Will move the stage and therefore every visible entity along the `x` + * axis in the opposite direction. + * + * When this value is set, it will shift the entire stage. This means that entity + * positions are not exactly where they are on screen. To get the exact position, + * simply add `Crafty.viewport.x` onto the entities `x` position. + */ + _x: 0, + /**@ + * #Crafty.viewport.y + * @comp Crafty.viewport + * Will move the stage and therefore every visible entity along the `y` + * axis in the opposite direction. + * + * When this value is set, it will shift the entire stage. This means that entity + * positions are not exactly where they are on screen. To get the exact position, + * simply add `Crafty.viewport.y` onto the entities `y` position. + */ + _y: 0, + + /**@ + * #Crafty.viewport.scroll + * @comp Crafty.viewport + * @sign Crafty.viewport.scroll(String axis, Number v) + * @param axis - 'x' or 'y' + * @param v - The new absolute position on the axis + * + * Will move the viewport to the position given on the specified axis + * @example Crafty.viewport.scroll('_x', 500); + * Will move the camera 500 pixels right of its initial position, in effect + * shifting everything in the viewport 500 pixels to the left. + */ + scroll: function (axis, v) { + v = Math.floor(v); + var change = v - this[axis], //change in direction + context = Crafty.canvas.context, + style = Crafty.stage.inner.style, + canvas; + + //update viewport and DOM scroll + this[axis] = v; + if (axis == '_x') { + if (context) context.translate(change, 0); + } else { + if (context) context.translate(0, change); + } + if (context) Crafty.DrawManager.drawAll(); + style[axis == '_x' ? "left" : "top"] = v + "px"; + }, + + rect: function () { + return { _x: -this._x, _y: -this._y, _w: this.width, _h: this.height }; + }, + + /** + * #Crafty.viewport.pan + * @comp Crafty.viewport + * @sign public void Crafty.viewport.pan(String axis, Number v, Number time) + * @param String axis - 'x' or 'y'. The axis to move the camera on + * @param Number v - the distance to move the camera by + * @param Number time - The duration in frames for the entire camera movement + * + * Pans the camera a given number of pixels over a given number of frames + */ + pan: (function () { + var tweens = {}, i, bound = false; + + function enterFrame(e) { + var l = 0; + for (i in tweens) { + var prop = tweens[i]; + if (prop.remTime > 0) { + prop.current += prop.diff; + prop.remTime--; + Crafty.viewport[i] = Math.floor(prop.current); + l++; + } + else { + delete tweens[i]; + } + } + if (l) Crafty.viewport._clamp(); + } + + return function (axis, v, time) { + Crafty.viewport.follow(); + if (axis == 'reset') { + for (i in tweens) { + tweens[i].remTime = 0; + } + return; + } + if (time == 0) time = 1; + tweens[axis] = { + diff: -v / time, + current: Crafty.viewport[axis], + remTime: time + }; + if (!bound) { + Crafty.bind("EnterFrame", enterFrame); + bound = true; + } + } + })(), + + /** + * #Crafty.viewport.follow + * @comp Crafty.viewport + * @sign public void Crafty.viewport.follow(Object target, Number offsetx, Number offsety) + * @param Object target - An entity with the 2D component + * @param Number offsetx - Follow target should be offsetx pixels away from center + * @param Number offsety - Positive puts targ to the right of center + * + * Follows a given entity with the 2D component. If following target will take a portion of + * the viewport out of bounds of the world, following will stop until the target moves away. + * @example + * var ent = Crafty.e('2D, DOM').attr({w: 100, h: 100:}); + * Crafty.viewport.follow(ent, 0, 0); + */ + follow: (function () { + var oldTarget, offx, offy; + + function change() { + Crafty.viewport.scroll('_x', -(this.x + (this.w / 2) - (Crafty.viewport.width / 2) - offx)); + Crafty.viewport.scroll('_y', -(this.y + (this.h / 2) - (Crafty.viewport.height / 2) - offy)); + Crafty.viewport._clamp(); + } + + return function (target, offsetx, offsety) { + if (oldTarget) + oldTarget.unbind('Change', change); + if (!target || !target.has('2D')) + return; + Crafty.viewport.pan('reset'); + + oldTarget = target; + offx = (typeof offsetx != 'undefined') ? offsetx : 0; + offy = (typeof offsety != 'undefined') ? offsety : 0; + + target.bind('Change', change); + change.call(target); + } + })(), + + /** + * #Crafty.viewport.centerOn + * @comp Crafty.viewport + * @sign public void Crafty.viewport.centerOn(Object target, Number time) + * @param Object target - An entity with the 2D component + * @param Number time - The number of frames to perform the centering over + * + * Centers the viewport on the given entity + */ + centerOn: function (targ, time) { + var x = targ.x, + y = targ.y, + mid_x = targ.w / 2, + mid_y = targ.h / 2, + cent_x = Crafty.viewport.width / 2, + cent_y = Crafty.viewport.height / 2, + new_x = x + mid_x - cent_x, + new_y = y + mid_y - cent_y; + + Crafty.viewport.pan('reset'); + Crafty.viewport.pan('x', new_x, time); + Crafty.viewport.pan('y', new_y, time); + }, + + /** + * #Crafty.viewport.zoom + * @comp Crafty.viewport + * @sign public void Crafty.viewport.zoom(Number amt, Number cent_x, Number cent_y, Number time) + * @param Number amt - amount to zoom in on the target by (eg. 2, 4, 0.5) + * @param Number cent_x - the center to zoom on + * @param Number cent_y - the center to zoom on + * @param Number time - the duration in frames of the entire zoom operation + * + * Zooms the camera in on a given point. amt > 1 will bring the camera closer to the subject + * amt < 1 will bring it farther away. amt = 0 will do nothing. + * Zooming is multiplicative. To reset the zoom amount, pass 0. + */ + zoom: (function () { + var zoom = 1, + zoom_tick = 0, + dur = 0, + prop = Crafty.support.prefix + "Transform", + bound = false, + act = {}, + prct = {}; + // what's going on: + // 1. Get the original point as a percentage of the stage + // 2. Scale the stage + // 3. Get the new size of the stage + // 4. Get the absolute position of our point using previous percentage + // 4. Offset inner by that much + + function enterFrame() { + if (dur > 0) { + var old = { + width: act.width * zoom, + height: act.height * zoom + }; + zoom += zoom_tick; + var new_s = { + width: act.width * zoom, + height: act.height * zoom + }, + diff = { + width: new_s.width - old.width, + height: new_s.height - old.height + }; + Crafty.stage.inner.style[prop] = 'scale(' + zoom + ',' + zoom + ')'; + Crafty.viewport.x -= diff.width * prct.width; + Crafty.viewport.y -= diff.height * prct.height; + dur--; + } + } + + return function (amt, cent_x, cent_y, time) { + var bounds = Crafty.map.boundaries(), + final_zoom = amt ? zoom * amt : 1; + + act.width = bounds.max.x - bounds.min.x; + act.height = bounds.max.y - bounds.min.y; + + prct.width = cent_x / act.width; + prct.height = cent_y / act.height; + + if (time == 0) time = 1; + zoom_tick = (final_zoom - zoom) / time; + dur = time; + + Crafty.viewport.pan('reset'); + if (!bound) { + Crafty.bind('EnterFrame', enterFrame); + bound = true; + } + } + })(), + + /** + * #Crafty.viewport.mouselook + * @comp Crafty.viewport + * @sign public void Crafty.viewport.mouselook(Boolean active) + * @param Boolean active - Activate or deactivate mouselook + * + * Toggle mouselook on the current viewport. + * Simply call this function and the user will be able to + * drag the viewport around. + */ + mouselook: (function () { + var active = false, + dragging = false, + lastMouse = {} + old = {}; + + + return function (op, arg) { + if (typeof op == 'boolean') { + active = op; + if (active) { + Crafty.mouseObjs++; + } + else { + Crafty.mouseObjs = Math.max(0, Crafty.mouseObjs - 1); + } + return; + } + if (!active) return; + switch (op) { + case 'move': + case 'drag': + if (!dragging) return; + diff = { + x: arg.clientX - lastMouse.x, + y: arg.clientY - lastMouse.y + }; + + Crafty.viewport.x += diff.x; + Crafty.viewport.y += diff.y; + Crafty.viewport._clamp(); + case 'start': + lastMouse.x = arg.clientX; + lastMouse.y = arg.clientY; + dragging = true; + break; + case 'stop': + dragging = false; + break; + } + }; + })(), + + _clamp: function () { + // clamps the viewport to the viewable area + // under no circumstances should the viewport see something outside the boundary of the 'world' + if (!this.clampToEntities) return; + var bound = Crafty.map.boundaries(); + if (bound.max.x - bound.min.x > Crafty.viewport.width) { + bound.max.x -= Crafty.viewport.width; + + if (Crafty.viewport.x < -bound.max.x) { + Crafty.viewport.x = -bound.max.x; + } + else if (Crafty.viewport.x > -bound.min.x) { + Crafty.viewport.x = -bound.min.x; + } + } + else { + Crafty.viewport.x = -1 * (bound.min.x + (bound.max.x - bound.min.x) / 2 - Crafty.viewport.width / 2); + } + if (bound.max.y - bound.min.y > Crafty.viewport.height) { + bound.max.y -= Crafty.viewport.height; + + if (Crafty.viewport.y < -bound.max.y) { + Crafty.viewport.y = -bound.max.y; + } + else if (Crafty.viewport.y > -bound.min.y) { + Crafty.viewport.y = -bound.min.y; + } + } + else { + Crafty.viewport.y = -1 * (bound.min.y + (bound.max.y - bound.min.y) / 2 - Crafty.viewport.height / 2); + } + }, + + /**@ + * #Crafty.viewport.init + * @comp Crafty.viewport + * @sign public void Crafty.viewport.init([Number width, Number height]) + * @param width - Width of the viewport + * @param height - Height of the viewport + * + * Initialize the viewport. If the arguments 'width' or 'height' are missing, or Crafty.mobile is true, use Crafty.DOM.window.width and Crafty.DOM.window.height (full screen model). + * Create a div with id `cr-stage`, if there is not already an HTMLElement with id `cr-stage` (by `Crafty.viewport.init`). + * + * @see Crafty.mobile + * @see Crafty.DOM.window.width + * @see Crafty.DOM.window.height + * @see Crafty.stage + */ + init: function (w, h) { + Crafty.DOM.window.init(); + + //fullscreen if mobile or not specified + this.width = (!w || Crafty.mobile) ? Crafty.DOM.window.width : w; + this.height = (!h || Crafty.mobile) ? Crafty.DOM.window.height : h; + + //check if stage exists + var crstage = document.getElementById("cr-stage"); + + /**@ + * #Crafty.stage + * @category Core + * The stage where all the DOM entities will be placed. + */ + + /**@ + * #Crafty.stage.elem + * @comp Crafty.stage + * The `#cr-stage` div element. + */ + + /**@ + * #Crafty.stage.inner + * @comp Crafty.stage + * `Crafty.stage.inner` is a div inside the `#cr-stage` div that holds all DOM entities. + * If you use canvas, a `canvas` element is created at the same level in the dom + * as the the `Crafty.stage.inner` div. So the hierarchy in the DOM is + * + * `Crafty.stage.elem` + * + * + * - `Crafty.stage.inner` (a div HTMLElement) + * + * - `Crafty.canvas._canvas` (a canvas HTMLElement) + */ + + //create stage div to contain everything + Crafty.stage = { + x: 0, + y: 0, + fullscreen: false, + elem: (crstage ? crstage : document.createElement("div")), + inner: document.createElement("div") + }; + + //fullscreen, stop scrollbars + if ((!w && !h) || Crafty.mobile) { + document.body.style.overflow = "hidden"; + Crafty.stage.fullscreen = true; + } + + Crafty.addEvent(this, window, "resize", function () { + Crafty.DOM.window.init(); + var w = Crafty.DOM.window.width, + h = Crafty.DOM.window.height, + offset; + + + if (Crafty.stage.fullscreen) { + this.width = w; + this.height = h; + Crafty.stage.elem.style.width = w + "px"; + Crafty.stage.elem.style.height = h + "px"; + + if (Crafty.canvas._canvas) { + Crafty.canvas._canvas.width = w; + Crafty.canvas._canvas.height = h; + Crafty.DrawManager.drawAll(); + } + } + + offset = Crafty.DOM.inner(Crafty.stage.elem); + Crafty.stage.x = offset.x; + Crafty.stage.y = offset.y; + }); + + Crafty.addEvent(this, window, "blur", function () { + if (Crafty.settings.get("autoPause")) { + Crafty.pause(); + } + }); + Crafty.addEvent(this, window, "focus", function () { + if (Crafty._paused && Crafty.settings.get("autoPause")) { + Crafty.pause(); + } + }); + + //make the stage unselectable + Crafty.settings.register("stageSelectable", function (v) { + Crafty.stage.elem.onselectstart = v ? function () { return true; } : function () { return false; }; + }); + Crafty.settings.modify("stageSelectable", false); + + //make the stage have no context menu + Crafty.settings.register("stageContextMenu", function (v) { + Crafty.stage.elem.oncontextmenu = v ? function () { return true; } : function () { return false; }; + }); + Crafty.settings.modify("stageContextMenu", false); + + Crafty.settings.register("autoPause", function (){ }); + Crafty.settings.modify("autoPause", false); + + //add to the body and give it an ID if not exists + if (!crstage) { + document.body.appendChild(Crafty.stage.elem); + Crafty.stage.elem.id = "cr-stage"; + } + + var elem = Crafty.stage.elem.style, + offset; + + Crafty.stage.elem.appendChild(Crafty.stage.inner); + Crafty.stage.inner.style.position = "absolute"; + Crafty.stage.inner.style.zIndex = "1"; + + //css style + elem.width = this.width + "px"; + elem.height = this.height + "px"; + elem.overflow = "hidden"; + + if (Crafty.mobile) { + elem.position = "absolute"; + elem.left = "0px"; + elem.top = "0px"; + + var meta = document.createElement("meta"), + head = document.getElementsByTagName("HEAD")[0]; + + //stop mobile zooming and scrolling + meta.setAttribute("name", "viewport"); + meta.setAttribute("content", "width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"); + head.appendChild(meta); + + //hide the address bar + meta = document.createElement("meta"); + meta.setAttribute("name", "apple-mobile-web-app-capable"); + meta.setAttribute("content", "yes"); + head.appendChild(meta); + setTimeout(function () { window.scrollTo(0, 1); }, 0); + + Crafty.addEvent(this, window, "touchmove", function (e) { + e.preventDefault(); + }); + + Crafty.stage.x = 0; + Crafty.stage.y = 0; + + } else { + elem.position = "relative"; + //find out the offset position of the stage + offset = Crafty.DOM.inner(Crafty.stage.elem); + Crafty.stage.x = offset.x; + Crafty.stage.y = offset.y; + } + + if (Crafty.support.setter) { + //define getters and setters to scroll the viewport + this.__defineSetter__('x', function (v) { this.scroll('_x', v); }); + this.__defineSetter__('y', function (v) { this.scroll('_y', v); }); + this.__defineGetter__('x', function () { return this._x; }); + this.__defineGetter__('y', function () { return this._y; }); + //IE9 + } else if (Crafty.support.defineProperty) { + Object.defineProperty(this, 'x', { set: function (v) { this.scroll('_x', v); }, get: function () { return this._x; } }); + Object.defineProperty(this, 'y', { set: function (v) { this.scroll('_y', v); }, get: function () { return this._y; } }); + } else { + //create empty entity waiting for enterframe + this.x = this._x; + this.y = this._y; + Crafty.e("viewport"); + } + } + }, + + /**@ + * #Crafty.keys + * @category Input + * Object of key names and the corresponding key code. + * ~~~ + * BACKSPACE: 8, + * TAB: 9, + * ENTER: 13, + * PAUSE: 19, + * CAPS: 20, + * ESC: 27, + * SPACE: 32, + * PAGE_UP: 33, + * PAGE_DOWN: 34, + * END: 35, + * HOME: 36, + * LEFT_ARROW: 37, + * UP_ARROW: 38, + * RIGHT_ARROW: 39, + * DOWN_ARROW: 40, + * INSERT: 45, + * DELETE: 46, + * 0: 48, + * 1: 49, + * 2: 50, + * 3: 51, + * 4: 52, + * 5: 53, + * 6: 54, + * 7: 55, + * 8: 56, + * 9: 57, + * A: 65, + * B: 66, + * C: 67, + * D: 68, + * E: 69, + * F: 70, + * G: 71, + * H: 72, + * I: 73, + * J: 74, + * K: 75, + * L: 76, + * M: 77, + * N: 78, + * O: 79, + * P: 80, + * Q: 81, + * R: 82, + * S: 83, + * T: 84, + * U: 85, + * V: 86, + * W: 87, + * X: 88, + * Y: 89, + * Z: 90, + * NUMPAD_0: 96, + * NUMPAD_1: 97, + * NUMPAD_2: 98, + * NUMPAD_3: 99, + * NUMPAD_4: 100, + * NUMPAD_5: 101, + * NUMPAD_6: 102, + * NUMPAD_7: 103, + * NUMPAD_8: 104, + * NUMPAD_9: 105, + * MULTIPLY: 106, + * ADD: 107, + * SUBSTRACT: 109, + * DECIMAL: 110, + * DIVIDE: 111, + * F1: 112, + * F2: 113, + * F3: 114, + * F4: 115, + * F5: 116, + * F6: 117, + * F7: 118, + * F8: 119, + * F9: 120, + * F10: 121, + * F11: 122, + * F12: 123, + * SHIFT: 16, + * CTRL: 17, + * ALT: 18, + * PLUS: 187, + * COMMA: 188, + * MINUS: 189, + * PERIOD: 190 + * ~~~ + */ + keys: { + 'BACKSPACE': 8, + 'TAB': 9, + 'ENTER': 13, + 'PAUSE': 19, + 'CAPS': 20, + 'ESC': 27, + 'SPACE': 32, + 'PAGE_UP': 33, + 'PAGE_DOWN': 34, + 'END': 35, + 'HOME': 36, + 'LEFT_ARROW': 37, + 'UP_ARROW': 38, + 'RIGHT_ARROW': 39, + 'DOWN_ARROW': 40, + 'INSERT': 45, + 'DELETE': 46, + '0': 48, + '1': 49, + '2': 50, + '3': 51, + '4': 52, + '5': 53, + '6': 54, + '7': 55, + '8': 56, + '9': 57, + 'A': 65, + 'B': 66, + 'C': 67, + 'D': 68, + 'E': 69, + 'F': 70, + 'G': 71, + 'H': 72, + 'I': 73, + 'J': 74, + 'K': 75, + 'L': 76, + 'M': 77, + 'N': 78, + 'O': 79, + 'P': 80, + 'Q': 81, + 'R': 82, + 'S': 83, + 'T': 84, + 'U': 85, + 'V': 86, + 'W': 87, + 'X': 88, + 'Y': 89, + 'Z': 90, + 'NUMPAD_0': 96, + 'NUMPAD_1': 97, + 'NUMPAD_2': 98, + 'NUMPAD_3': 99, + 'NUMPAD_4': 100, + 'NUMPAD_5': 101, + 'NUMPAD_6': 102, + 'NUMPAD_7': 103, + 'NUMPAD_8': 104, + 'NUMPAD_9': 105, + 'MULTIPLY': 106, + 'ADD': 107, + 'SUBSTRACT': 109, + 'DECIMAL': 110, + 'DIVIDE': 111, + 'F1': 112, + 'F2': 113, + 'F3': 114, + 'F4': 115, + 'F5': 116, + 'F6': 117, + 'F7': 118, + 'F8': 119, + 'F9': 120, + 'F10': 121, + 'F11': 122, + 'F12': 123, + 'SHIFT': 16, + 'CTRL': 17, + 'ALT': 18, + 'PLUS': 187, + 'COMMA': 188, + 'MINUS': 189, + 'PERIOD': 190 + }, + + /**@ + * #Crafty.mouseButtons + * @category Input + * Object of mouseButton names and the corresponding button ID. + * In all mouseEvents we add the e.mouseButton property with a value normalized to match e.button of modern webkit + * ~~~ + * LEFT: 0, + * MIDDLE: 1, + * RIGHT: 2 + * ~~~ + */ + mouseButtons: { + LEFT: 0, + MIDDLE: 1, + RIGHT: 2 + } +}); + + + +/** +* Entity fixes the lack of setter support +*/ +Crafty.c("viewport", { + init: function () { + this.bind("EnterFrame", function () { + if (Crafty.viewport._x !== Crafty.viewport.x) { + Crafty.viewport.scroll('_x', Crafty.viewport.x); + } + + if (Crafty.viewport._y !== Crafty.viewport.y) { + Crafty.viewport.scroll('_y', Crafty.viewport.y); + } + }); + } +}); + /**@ +* #Canvas +* @category Graphics +* Draws itself onto a canvas. Crafty.canvas.init() will be automatically called it is not called already (hence the canvas element dosen't exist). +* @trigger Draw - when the entity is ready to be drawn to the stage - {type: "canvas", pos, co, ctx} +* @trigger NoCanvas - if the browser does not support canvas +*/ +Crafty.c("Canvas", { + + init: function () { + if (!Crafty.canvas.context) { + Crafty.canvas.init(); + } + + //increment the amount of canvas objs + Crafty.DrawManager.total2D++; + + this.bind("Change", function (e) { + //if within screen, add to list + if (this._changed === false) { + this._changed = Crafty.DrawManager.add(e || this, this); + } else { + if (e) this._changed = Crafty.DrawManager.add(e, this); + } + }); + + this.bind("Remove", function () { + Crafty.DrawManager.total2D--; + Crafty.DrawManager.add(this, this); + }); + }, + + /**@ + * #.draw + * @comp Canvas + * @sign public this .draw([[Context ctx, ]Number x, Number y, Number w, Number h]) + * @param ctx - Canvas 2D context if drawing on another canvas is required + * @param x - X offset for drawing a segment + * @param y - Y offset for drawing a segment + * @param w - Width of the segement to draw + * @param h - Height of the segment to draw + * Method to draw the entity on the canvas element. Can pass rect values for redrawing a segment of the entity. + */ + draw: function (ctx, x, y, w, h) { + if (!this.ready) return; + if (arguments.length === 4) { + h = w; + w = y; + y = x; + x = ctx; + ctx = Crafty.canvas.context; + } + + var pos = { //inlined pos() function, for speed + _x: (this._x + (x || 0)), + _y: (this._y + (y || 0)), + _w: (w || this._w), + _h: (h || this._h) + }, + context = ctx || Crafty.canvas.context, + coord = this.__coord || [0, 0, 0, 0], + co = { + x: coord[0] + (x || 0), + y: coord[1] + (y || 0), + w: w || coord[2], + h: h || coord[3] + }; + + if (this._mbr) { + context.save(); + + context.translate(this._origin.x + this._x, this._origin.y + this._y); + pos._x = -this._origin.x; + pos._y = -this._origin.y; + + context.rotate((this._rotation % 360) * (Math.PI / 180)); + } + + if(this._flipX || this._flipY) { + context.save(); + context.scale((this._flipX ? -1 : 1), (this._flipY ? -1 : 1)); + if(this._flipX) { + pos._x = -(pos._x + pos._w) + } + if(this._flipY) { + pos._y = -(pos._y + pos._h) + } + } + + //draw with alpha + if (this._alpha < 1.0) { + var globalpha = context.globalAlpha; + context.globalAlpha = this._alpha; + } + + this.trigger("Draw", { type: "canvas", pos: pos, co: co, ctx: context }); + + if (this._mbr || (this._flipX || this._flipY)) { + context.restore(); + } + if (globalpha) { + context.globalAlpha = globalpha; + } + return this; + } +}); + +/**@ +* #Crafty.canvas +* @category Graphics +* Collection of methods to draw on canvas. +*/ +Crafty.extend({ + canvas: { + /**@ + * #Crafty.canvas.context + * @comp Crafty.canvas + * This will return the 2D context of the main canvas element. + * The value returned from `Crafty.canvas._canvas.getContext('2d')`. + */ + context: null, + /**@ + * #Crafty.canvas._canvas + * @comp Crafty.canvas + * Main Canvas element + */ + + /**@ + * #Crafty.canvas.init + * @comp Crafty.canvas + * @sign public void Crafty.canvas.init(void) + * @trigger NoCanvas - triggered if `Crafty.support.canvas` is false + * Creates a `canvas` element inside `Crafty.stage.elem`. Must be called + * before any entities with the Canvas component can be drawn. + * + * This method will automatically be called if no `Crafty.canvas.context` is + * found. + */ + init: function () { + //check if canvas is supported + if (!Crafty.support.canvas) { + Crafty.trigger("NoCanvas"); + Crafty.stop(); + return; + } + + //create 3 empty canvas elements + var c; + c = document.createElement("canvas"); + c.width = Crafty.viewport.width; + c.height = Crafty.viewport.height; + c.style.position = 'absolute'; + c.style.left = "0px"; + c.style.top = "0px"; + + Crafty.stage.elem.appendChild(c); + Crafty.canvas.context = c.getContext('2d'); + Crafty.canvas._canvas = c; + } + } +}); + Crafty.extend({ + over: null, //object mouseover, waiting for out + mouseObjs: 0, + mousePos: {}, + lastEvent: null, + keydown: {}, + + /**@ + * #Crafty.keydown + * @category Input + * Remembering what keys (referred by Unicode) are down. + * ~~~ + * Crafty.c("Keyboard", { + * isDown: function (key) { + * if (typeof key === "string") { + * key = Crafty.keys[key]; + * } + * return !!Crafty.keydown[key]; + * } + * }); + * ~~~ + * @see Keyboard + * @see Crafty.keys + */ + + mouseDispatch: function (e) { + if (!Crafty.mouseObjs) return; + Crafty.lastEvent = e; + + var maxz = -1, + closest, + q, + i = 0, l, + pos = Crafty.DOM.translate(e.clientX, e.clientY), + x, y, + dupes = {}, + tar = e.target ? e.target : e.srcElement, + type = e.type; + + if (type === "touchstart") type = "mousedown"; + else if (type === "touchmove") type = "mousemove"; + else if (type === "touchend") type = "mouseup"; + + //Normalize button according to http://unixpapa.com/js/mouse.html + if (e.which == null) { + e.mouseButton = (e.button < 2) ? Crafty.mouseButtons.LEFT : ((e.button == 4) ? Crafty.mouseButtons.MIDDLE : Crafty.mouseButtons.RIGHT); + } else { + e.mouseButton = (e.which < 2) ? Crafty.mouseButtons.LEFT : ((e.which == 2) ? Crafty.mouseButtons.MIDDLE : Crafty.mouseButtons.RIGHT); + } + + e.realX = x = Crafty.mousePos.x = pos.x; + e.realY = y = Crafty.mousePos.y = pos.y; + + //if it's a DOM element with Mouse component we are done + if (tar.nodeName != "CANVAS") { + while (typeof (tar.id) != 'string' && tar.id.indexOf('ent') == -1) { + tar = tar.parentNode; + } + ent = Crafty(parseInt(tar.id.replace('ent', ''))) + if (ent.has('Mouse') && ent.isAt(x, y)) + closest = ent; + } + //else we search for an entity with Mouse component + if (!closest) { + q = Crafty.map.search({ _x: x, _y: y, _w: 1, _h: 1 }, false); + + for (l = q.length; i < l; ++i) { + if (!q[i].__c.Mouse || !q[i]._visible) continue; + + var current = q[i], + flag = false; + + //weed out duplicates + if (dupes[current[0]]) continue; + else dupes[current[0]] = true; + + if (current.map) { + if (current.map.containsPoint(x, y)) { + flag = true; + } + } else if (current.isAt(x, y)) flag = true; + + if (flag && (current._z >= maxz || maxz === -1)) { + //if the Z is the same, select the closest GUID + if (current._z === maxz && current[0] < closest[0]) { + continue; + } + maxz = current._z; + closest = current; + } + } + } + + //found closest object to mouse + if (closest) { + //click must mousedown and out on tile + if (type === "mousedown") { + closest.trigger("MouseDown", e); + } else if (type === "mouseup") { + closest.trigger("MouseUp", e); + } else if (type == "dblclick") { + closest.trigger("DoubleClick", e); + } else if (type == "click") { + closest.trigger("Click", e); + }else if (type === "mousemove") { + closest.trigger("MouseMove", e); + if (this.over !== closest) { //if new mousemove, it is over + if (this.over) { + this.over.trigger("MouseOut", e); //if over wasn't null, send mouseout + this.over = null; + } + this.over = closest; + closest.trigger("MouseOver", e); + } + } else closest.trigger(type, e); //trigger whatever it is + } else { + if (type === "mousemove" && this.over) { + this.over.trigger("MouseOut", e); + this.over = null; + } + if (type === "mousedown") { + Crafty.viewport.mouselook('start', e); + } + else if (type === "mousemove") { + Crafty.viewport.mouselook('drag', e); + } + else if (type == "mouseup") { + Crafty.viewport.mouselook('stop'); + } + } + + if (type === "mousemove") { + this.lastEvent = e; + } + + }, + + + /**@ + * #KeyboardEvent + * @category Input + * Keyboard Event triggerd by Crafty Core + * @trigger KeyDown - is triggered for each entity when the DOM 'keydown' event is triggered. + * @trigger KeyUp - is triggered for each entity when the DOM 'keyup' event is triggered. + * @example + * ~~~ + * Crafty.e("2D, DOM, Color") + * .attr({x: 100, y: 100, w: 50, h: 50}) + * .color("red") + * .bind('KeyDown', function(e) { + * if(e.key == Crafty.keys['LEFT_ARROW']) { + * this.x=this.x-1; + * } else if (e.key == Crafty.keys['RIGHT_ARROW']) { + * this.x=this.x+1; + * } else if (e.key == Crafty.keys['UP_ARROW']) { + * this.y=this.y-1; + * } else if (e.key == Crafty.keys['DOWN_ARROW']) { + * this.y=this.y+1; + * } + * }); + * ~~~ + * @see Crafty.keys + */ + + /**@ + * #Crafty.eventObject + * @category Input + * Event Object used in Crafty for cross browser compatiblity + */ + + /**@ + * #.key + * @comp Crafty.eventObject + * Unicode of the key pressed + */ + keyboardDispatch: function (e) { + e.key = e.keyCode || e.which; + if (e.type === "keydown") { + if (Crafty.keydown[e.key] !== true) { + Crafty.keydown[e.key] = true; + Crafty.trigger("KeyDown", e); + } + } else if (e.type === "keyup") { + delete Crafty.keydown[e.key]; + Crafty.trigger("KeyUp", e); + } + + //prevent searchable keys + /* + if((e.metaKey || e.altKey || e.ctrlKey) && !(e.key == 8 || e.key >= 112 && e.key <= 135)) { + console.log(e); + if(e.preventDefault) e.preventDefault(); + else e.returnValue = false; + return false; + }*/ + } +}); + +//initialize the input events onload +Crafty.bind("Load", function () { + Crafty.addEvent(this, "keydown", Crafty.keyboardDispatch); + Crafty.addEvent(this, "keyup", Crafty.keyboardDispatch); + + Crafty.addEvent(this, Crafty.stage.elem, "mousedown", Crafty.mouseDispatch); + Crafty.addEvent(this, Crafty.stage.elem, "mouseup", Crafty.mouseDispatch); + Crafty.addEvent(this, Crafty.stage.elem, "mousemove", Crafty.mouseDispatch); + Crafty.addEvent(this, Crafty.stage.elem, "click", Crafty.mouseDispatch); + Crafty.addEvent(this, Crafty.stage.elem, "dblclick", Crafty.mouseDispatch); + + Crafty.addEvent(this, Crafty.stage.elem, "touchstart", Crafty.mouseDispatch); + Crafty.addEvent(this, Crafty.stage.elem, "touchmove", Crafty.mouseDispatch); + Crafty.addEvent(this, Crafty.stage.elem, "touchend", Crafty.mouseDispatch); +}); + +/**@ +* #Mouse +* @category Input +* Provides the entity with mouse related events +* @trigger MouseOver - when the mouse enters the entity - MouseEvent +* @trigger MouseOut - when the mouse leaves the entity - MouseEvent +* @trigger MouseDown - when the mouse button is pressed on the entity - MouseEvent +* @trigger MouseUp - when the mouse button is released on the entity - MouseEvent +* @trigger Click - when the user clicks the entity. [See documentation](http://www.quirksmode.org/dom/events/click.html) - MouseEvent +* @trigger DoubleClick - when the user double clicks the entity - MouseEvent +* @trigger MouseMove - when the mouse is over the entity and moves - MouseEvent +* Crafty adds the mouseButton property to MouseEvents that match one of +* +* - Crafty.mouseButtons.LEFT +* - Crafty.mouseButtons.RIGHT +* - Crafty.mouseButtons.MIDDLE +* @example +* ~~~ +* myEntity.bind('Click', function() { +* console.log("Clicked!!"); +* }) +* +* myEntity.bind('Click', function(e) { +* if( e.mouseButton == Crafty.mouseButtons.RIGHT ) +* console.log("Clicked right button"); +* }) +* ~~~ +*/ +Crafty.c("Mouse", { + init: function () { + Crafty.mouseObjs++; + this.bind("Remove", function () { + Crafty.mouseObjs--; + }); + }, + + /**@ + * #.areaMap + * @comp Mouse + * @sign public this .areaMap(Crafty.Polygon polygon) + * @param polygon - Instance of Crafty.Polygon used to check if the mouse coordinates are inside this region + * @sign public this .areaMap(Array point1, .., Array pointN) + * @param point# - Array with an `x` and `y` position to generate a polygon + * Assign a polygon to the entity so that mouse events will only be triggered if + * the coordinates are inside the given polygon. + * ~~~ + * Crafty.e("2D, DOM, Color, Mouse") + * .color("red") + * .attr({ w: 100, h: 100 }) + * .bind('MouseOver', function() {console.log("over")}) + * .areaMap([0,0], [50,0], [50,50], [0,50]) + * ~~~ + * @see Crafty.Polygon + */ + areaMap: function (poly) { + //create polygon + if (arguments.length > 1) { + //convert args to array to create polygon + var args = Array.prototype.slice.call(arguments, 0); + poly = new Crafty.polygon(args); + } + + poly.shift(this._x, this._y); + this.map = poly; + + this.attach(this.map); + return this; + } +}); + +/**@ +* #Draggable +* @category Input +* Enable drag and drop of the entity. +* @trigger Dragging - is triggered each frame the entity is being dragged - MouseEvent +* @trigger StartDrag - is triggered when dragging begins - MouseEvent +* @trigger StopDrag - is triggered when dragging ends - MouseEvent +*/ +Crafty.c("Draggable", { + _origMouseDOMPos: null, + _oldX: null, + _oldY: null, + _dragging: false, + _dir:null, + + _ondrag: null, + _ondown: null, + _onup: null, + + //Note: the code is note tested with zoom, etc., that may distort the direction between the viewport and the coordinate on the canvas. + init: function () { + this.requires("Mouse"); + this._ondrag = function (e) { + var pos = Crafty.DOM.translate(e.clientX, e.clientY); + if(this._dir) { + var len = (pos.x - this._origMouseDOMPos.x) * this._dir.x + + (pos.y - this._origMouseDOMPos.y) * this._dir.y; + this.x = this._oldX + len * this._dir.x; + this.y = this._oldY + len * this._dir.y; + } else { + this.x = this._oldX + (pos.x - this._origMouseDOMPos.x); + this.y = this._oldY + (pos.y - this._origMouseDOMPos.y); + } + + this.trigger("Dragging", e); + }; + + this._ondown = function (e) { + if (e.mouseButton !== Crafty.mouseButtons.LEFT) return; + + //start drag + this._origMouseDOMPos = Crafty.DOM.translate(e.clientX, e.clientY); + this._oldX = this._x; + this._oldY = this._y; + this._dragging = true; + + Crafty.addEvent(this, Crafty.stage.elem, "mousemove", this._ondrag); + Crafty.addEvent(this, Crafty.stage.elem, "mouseup", this._onup); + this.trigger("StartDrag", e); + }; + + this._onup = function upper(e) { + Crafty.removeEvent(this, Crafty.stage.elem, "mousemove", this._ondrag); + Crafty.removeEvent(this, Crafty.stage.elem, "mouseup", this._onup); + this._dragging = false; + this.trigger("StopDrag", e); + }; + + this.enableDrag(); + }, + + /**@ + * #.dragDirection + * @comp Draggable + * @sign public this .dragDirection() + * Remove any previously specifed direction. + * + * @sign public this .dragDirection(vector) + * @param vector - Of the form of {x: valx, y: valy}, the vector (valx, valy) denotes the move direction. + * @sign public this .dragDirection(degree) + * @param degree - A number, the degree (clockwise) of the move direction with respect to the x axis. + * Specify the dragging direction. + * @example + * ~~~ + * this.dragDirection() + * this.dragDirection({x:1, y:0}) //Horizonatal + * this.dragDirection({x:0, y:1}) //Vertical + * // Note: because of the orientation of x and y axis, + * // this is 45 degree clockwise with respect to the x axis. + * this.dragDirection({x:1, y:1}) //45 degree. + * this.dragDirection(60) //60 degree. + * ~~~ + */ + dragDirection: function(dir) { + if (typeof dir === 'undefined') { + this._dir=null; + } else if (("" + parseInt(dir)) == dir) { //dir is a number + this._dir={ + x: Math.cos(dir/180*Math.PI) + , y: Math.sin(dir/180*Math.PI) + }; + } + else { + var r=Math.sqrt(dir.x * dir.x + dir.y * dir.y) + this._dir={ + x: dir.x/r + , y: dir.y/r + }; + } + }, + + /**@ + * #.stopDrag + * @comp Draggable + * @sign public this .stopDrag(void) + * Stop the entity from dragging. Essentially reproducing the drop. + * @trigger StopDrag - Called right after the mouse listeners are removed + * @see .startDrag + */ + stopDrag: function () { + Crafty.removeEvent(this, Crafty.stage.elem, "mousemove", this._ondrag); + Crafty.removeEvent(this, Crafty.stage.elem, "mouseup", this._onup); + + this._dragging = false; + this.trigger("StopDrag"); + return this; + }, + + /**@ + * #.startDrag + * @comp Draggable + * @sign public this .startDrag(void) + * Make the entity follow the mouse positions. + * @see .stopDrag + */ + startDrag: function () { + if (!this._dragging) { + this._dragging = true; + Crafty.addEvent(this, Crafty.stage.elem, "mousemove", this._ondrag); + } + return this; + }, + + /**@ + * #.enableDrag + * @comp Draggable + * @sign public this .enableDrag(void) + * Rebind the mouse events. Use if `.disableDrag` has been called. + * @see .disableDrag + */ + enableDrag: function () { + this.bind("MouseDown", this._ondown); + + Crafty.addEvent(this, Crafty.stage.elem, "mouseup", this._onup); + return this; + }, + + /**@ + * #.disableDrag + * @comp Draggable + * @sign public this .disableDrag(void) + * Stops entity from being draggable. Reenable with `.enableDrag()`. + * @see .enableDrag + */ + disableDrag: function () { + this.unbind("MouseDown", this._ondown); + this.stopDrag(); + return this; + } +}); + +/**@ +* #Keyboard +* @category Input +* Give entities keyboard events (`keydown` and `keyup`). +*/ +Crafty.c("Keyboard", { +/**@ + * #.isDown + * @comp Keyboard + * @sign public Boolean isDown(String keyName) + * @param keyName - Name of the key to check. See `Crafty.keys`. + * @sign public Boolean isDown(Number keyCode) + * @param keyCode - Key code in `Crafty.keys`. + * Determine if a certain key is currently down. + * ~~~ + * entity.requires('KeyBoard').bind('KeyDown', function () { if (this.isDown('SPACE')) jump(); }); + * ~~~ + * @see Crafty.keys + */ + isDown: function (key) { + if (typeof key === "string") { + key = Crafty.keys[key]; + } + return !!Crafty.keydown[key]; + } +}); + +/**@ +* #Multiway +* @category Input +* Used to bind keys to directions and have the entity move acordingly +* @trigger NewDirection - triggered when direction changes - { x:Number, y:Number } - New direction +* @trigger Moved - triggered on movement on either x or y axis. If the entity has moved on both axes for diagonal movement the event is triggered twice - { x:Number, y:Number } - Old position +*/ +Crafty.c("Multiway", { + _speed: 3, + + _keydown: function (e) { + if (this._keys[e.key]) { + this._movement.x = Math.round((this._movement.x + this._keys[e.key].x) * 1000) / 1000; + this._movement.y = Math.round((this._movement.y + this._keys[e.key].y) * 1000) / 1000; + this.trigger('NewDirection', this._movement); + } + }, + + _keyup: function (e) { + if (this._keys[e.key]) { + this._movement.x = Math.round((this._movement.x - this._keys[e.key].x) * 1000) / 1000; + this._movement.y = Math.round((this._movement.y - this._keys[e.key].y) * 1000) / 1000; + this.trigger('NewDirection', this._movement); + } + }, + + _enterframe: function () { + if (this.disableControls) return; + + if (this._movement.x !== 0) { + this.x += this._movement.x; + this.trigger('Moved', { x: this.x - this._movement.x, y: this.y }); + } + if (this._movement.y !== 0) { + this.y += this._movement.y; + this.trigger('Moved', { x: this.x, y: this.y - this._movement.y }); + } + }, + + init: function () { + this._keyDirection = {}; + this._keys = {}; + this._movement = { x: 0, y: 0 }; + this._speed = { x: 3, y: 3 }; + }, + + /**@ + * #.multiway + * @comp Multiway + * @sign public this .multiway([Number speed,] Object keyBindings ) + * @param speed - Amount of pixels to move the entity whilst a key is down + * @param keyBindings - What keys should make the entity go in which direction. Direction is specified in degrees + * Constructor to initialize the speed and keyBindings. Component will listen to key events and move the entity appropriately. + * + * When direction changes a NewDirection event is triggered with an object detailing the new direction: {x: x_movement, y: y_movement} + * When entity has moved on either x- or y-axis a Moved event is triggered with an object specifying the old position {x: old_x, y: old_y} + * @example + * ~~~ + * this.multiway(3, {UP_ARROW: -90, DOWN_ARROW: 90, RIGHT_ARROW: 0, LEFT_ARROW: 180}); + * this.multiway({x:3,y:1.5}, {UP_ARROW: -90, DOWN_ARROW: 90, RIGHT_ARROW: 0, LEFT_ARROW: 180}); + * this.multiway({W: -90, S: 90, D: 0, A: 180}); + * ~~~ + */ + multiway: function (speed, keys) { + if (keys) { + if (speed.x && speed.y) { + this._speed.x = speed.x; + this._speed.y = speed.y; + } else { + this._speed.x = speed; + this._speed.y = speed; + } + } else { + keys = speed; + } + + this._keyDirection = keys; + this.speed(this._speed); + + this.enableControl(); + + //Apply movement if key is down when created + for (var k in keys) { + if (Crafty.keydown[Crafty.keys[k]]) { + this.trigger("KeyDown", { key: Crafty.keys[k] }); + } + } + + return this; + }, + + /**@ + * #.enableControl + * @comp Multiway + * @sign public this .enableControl() + * Enable the component to listen to key events. + * + * @example + * ~~~ + * this.enableControl(); + * ~~~ + */ + enableControl: function() { + this.bind("KeyDown", this._keydown) + .bind("KeyUp", this._keyup) + .bind("EnterFrame", this._enterframe); + return this; + }, + + /**@ + * #.disableControl + * @comp Multiway + * @sign public this .disableControl() + * Disable the component to listen to key events. + * + * @example + * ~~~ + * this.disableControl(); + * ~~~ + */ + + disableControl: function() { + this.unbind("KeyDown", this._keydown) + .unbind("KeyUp", this._keyup) + .unbind("EnterFrame", this._enterframe); + return this; + }, + + speed: function (speed) { + for (var k in this._keyDirection) { + var keyCode = Crafty.keys[k] || k; + this._keys[keyCode] = { + x: Math.round(Math.cos(this._keyDirection[k] * (Math.PI / 180)) * 1000 * speed.x) / 1000, + y: Math.round(Math.sin(this._keyDirection[k] * (Math.PI / 180)) * 1000 * speed.y) / 1000 + }; + } + return this; + } +}); + +/**@ +* #Fourway +* @category Input +* Move an entity in four directions by using the +* arrow keys or `W`, `A`, `S`, `D`. +*/ +Crafty.c("Fourway", { + + init: function () { + this.requires("Multiway"); + }, + + /**@ + * #.fourway + * @comp Fourway + * @sign public this .fourway(Number speed) + * @param speed - Amount of pixels to move the entity whilst a key is down + * Constructor to initialize the speed. Component will listen for key events and move the entity appropriately. + * This includes `Up Arrow`, `Right Arrow`, `Down Arrow`, `Left Arrow` as well as `W`, `A`, `S`, `D`. + * + * When direction changes a NewDirection event is triggered with an object detailing the new direction: {x: x_movement, y: y_movement} + * When entity has moved on either x- or y-axis a Moved event is triggered with an object specifying the old position {x: old_x, y: old_y} + * + * The key presses will move the entity in that direction by the speed passed in the argument. + * @see Multiway + */ + fourway: function (speed) { + this.multiway(speed, { + UP_ARROW: -90, + DOWN_ARROW: 90, + RIGHT_ARROW: 0, + LEFT_ARROW: 180, + W: -90, + S: 90, + D: 0, + A: 180 + }); + + return this; + } +}); + +/**@ +* #Twoway +* @category Input +* Move an entity left or right using the arrow keys or `D` and `A` and jump using up arrow or `W`. +* +* When direction changes a NewDirection event is triggered with an object detailing the new direction: {x: x_movement, y: y_movement}. This is consistent with Fourway and Multiway components. +* When entity has moved on x-axis a Moved event is triggered with an object specifying the old position {x: old_x, y: old_y} +* +*/ +Crafty.c("Twoway", { + _speed: 3, + _up: false, + + init: function () { + this.requires("Fourway, Keyboard"); + }, + + /**@ + * #.twoway + * @comp Twoway + * @sign public this .twoway(Number speed[, Number jumpSpeed]) + * @param speed - Amount of pixels to move left or right + * @param jumpSpeed - How high the entity should jump + * Constructor to initialize the speed and power of jump. Component will + * listen for key events and move the entity appropriately. This includes + * `Up Arrow`, `Right Arrow`, `Left Arrow` as well as W, A, D. Used with the + * `gravity` component to simulate jumping. + * + * The key presses will move the entity in that direction by the speed passed in + * the argument. Pressing the `Up Arrow` or `W` will cause the entiy to jump. + * @see Gravity, Fourway + */ + twoway: function (speed, jump) { + + this.multiway(speed, { + RIGHT_ARROW: 0, + LEFT_ARROW: 180, + D: 0, + A: 180 + }); + + if (speed) this._speed = speed; + jump = jump || this._speed * 2; + + this.bind("EnterFrame", function () { + if (this.disableControls) return; + if (this._up) { + this.y -= jump; + this._falling = true; + } + }).bind("KeyDown", function () { + if (this.isDown("UP_ARROW") || this.isDown("W")) this._up = true; + }); + + return this; + } +}); + /**@ +* #SpriteAnimation +* @category Animation +* Used to animate sprites by changing the sprites in the sprite map. +* @trigger AnimationEnd - When the animation finishes - { reel } +* @trigger Change - On each frame +*/ +Crafty.c("SpriteAnimation", { + /**@ + * #._reels + * @comp SpriteAnimation + * + * A map consists of arrays that contains the coordinates of each frame within the sprite, e.g., + * `{"walk_left":[[96,48],[112,48],[128,48]]}` + */ + _reels: null, + _frame: null, + /**@ + * #._currentReelId + * @comp SpriteAnimation + * + * The current playing reel (one element of `this._reels`). It is `null` if no reel is playing. + */ + _currentReelId: null, + + init: function () { + this._reels = {}; + }, + + /**@ + * #.animate + * @comp SpriteAnimation + * @sign public this .animate(String reelId, Number fromX, Number y, Number toX) + * @param reelId - ID of the animation reel being created + * @param fromX - Starting `x` position (in the unit of sprite horizontal size) on the sprite map + * @param y - `y` position on the sprite map (in the unit of sprite vertical size). Remains constant through the animation. + * @param toX - End `x` position on the sprite map (in the unit of sprite horizontal size) + * @sign public this .animate(String reelId, Array frames) + * @param reelId - ID of the animation reel being created + * @param frames - Array of arrays containing the `x` and `y` values: [[x1,y1],[x2,y2],...] + * @sign public this .animate(String reelId, Number duration[, Number repeatCount]) + * @param reelId - ID of the animation reel to play + * @param duration - Play the animation within a duration (in frames) + * @param repeatCount - number of times to repeat the animation. Use -1 for infinitely + * + * Method to setup animation reels or play pre-made reels. Animation works by changing the sprites over + * a duration. Only works for sprites built with the Crafty.sprite methods. See the Tween component for animation of 2D properties. + * + * To setup an animation reel, pass the name of the reel (used to identify the reel and play it later), and either an + * array of absolute sprite positions or the start x on the sprite map, the y on the sprite map and then the end x on the sprite map. + * + * To play a reel, pass the name of the reel and the duration it should play for (in frames). If you need + * to repeat the animation, simply pass in the amount of times the animation should repeat. To repeat + * forever, pass in `-1`. + * + * @example + * ~~~ + * Crafty.sprite(16, "images/sprite.png", { + * PlayerSprite: [0,0] + * }); + * + * Crafty.e("2D, DOM, SpriteAnimation, PlayerSprite") + * .animate('PlayerRunning', 0, 0, 3) //setup animation + * .animate('PlayerRunning', 15, -1) // start animation + * + * Crafty.e("2D, DOM, SpriteAnimation, PlayerSprite") + * .animate('PlayerRunning', 0, 3, 0) //setup animation + * .animate('PlayerRunning', 15, -1) // start animation + * ~~~ + * + * @see crafty.sprite + */ + animate: function (reelId, fromx, y, tox) { + var reel, i, tile, tileh, duration, pos; + + //play a reel + //.animate('PlayerRunning', 15, -1) // start animation + if (arguments.length < 4 && typeof fromx === "number") { + duration = fromx; + + //make sure not currently animating + this._currentReelId = reelId; + + currentReel = this._reels[reelId]; + + this._frame = { + currentReel: currentReel, + numberOfFramesBetweenSlides: Math.ceil(duration / currentReel.length), + currentSlideNumber: 0, + frameNumberBetweenSlides: 0, + repeat: 0 + }; + if (arguments.length === 3 && typeof y === "number") { + //User provided repetition count + if (y === -1) this._frame.repeatInfinitly = true; + else this._frame.repeat = y; + } + + pos = this._frame.currentReel[0]; + this.__coord[0] = pos[0]; + this.__coord[1] = pos[1]; + + this.bind("EnterFrame", this.updateSprite); + return this; + } + // .animate('PlayerRunning', 0, 0, 3) //setup animation + if (typeof fromx === "number") { + // Defind in Sprite component. + tile = this.__tile + parseInt(this.__padding[0] || 0, 10); + tileh = this.__tileh + parseInt(this.__padding[1] || 0, 10); + + reel = []; + i = fromx; + if (tox > fromx) { + for (; i <= tox; i++) { + reel.push([i * tile, y * tileh]); + } + } else { + for (; i >= tox; i--) { + reel.push([i * tile, y * tileh]); + } + } + + this._reels[reelId] = reel; + } else if (typeof fromx === "object") { + // @sign public this .animate(reelId, [[x1,y1],[x2,y2],...]) + i = 0; + reel = []; + tox = fromx.length - 1; + tile = this.__tile + parseInt(this.__padding[0] || 0, 10); + tileh = this.__tileh + parseInt(this.__padding[1] || 0, 10); + + for (; i <= tox; i++) { + pos = fromx[i]; + reel.push([pos[0] * tile, pos[1] * tileh]); + } + + this._reels[reelId] = reel; + } + + return this; + }, + + /**@ + * #.updateSprite + * @comp SpriteAnimation + * @sign private void .updateSprite() + * + * This is called at every `EnterFrame` event when `.animate()` enables animation. It update the SpriteAnimation component when the slide in the sprite should be updated. + * + * @example + * ~~~ + * this.bind("EnterFrame", this.updateSprite); + * ~~~ + * + * @see crafty.sprite + */ + updateSprite: function () { + var data = this._frame; + + if (this._frame.frameNumberBetweenSlides++ === data.numberOfFramesBetweenSlides) { + var pos = data.currentReel[data.currentSlideNumber++]; + + this.__coord[0] = pos[0]; + this.__coord[1] = pos[1]; + this._frame.frameNumberBetweenSlides = 0; + } + + + if (data.currentSlideNumber === data.currentReel.length) { + data.currentSlideNumber = 0; + if (this._frame.repeatInfinitly === true || this._frame.repeat > 0) { + if (this._frame.repeat) this._frame.repeat--; + this._frame.frameNumberBetweenSlides = 0; + this._frame.currentSlideNumber = 0; + } else { + this.trigger("AnimationEnd", { reel: data.currentReel }); + this.stop(); + return; + } + } + + this.trigger("Change"); + }, + + /**@ + * #.stop + * @comp SpriteAnimation + * @sign public this .stop(void) + * Stop any animation currently playing. + */ + stop: function () { + this.unbind("EnterFrame", this.updateSprite); + this.unbind("AnimationEnd"); + this._currentReelId = null; + this._frame = null; + + return this; + }, + + /**@ + * #.reset + * @comp SpriteAnimation + * @sign public this .reset(void) + * Method will reset the entities sprite to its original. + */ + reset: function () { + if (!this._frame) return this; + + var co = this._frame.currentReel[0]; + this.__coord[0] = co[0]; + this.__coord[1] = co[1]; + this.stop(); + + return this; + }, + + /**@ + * #.isPlaying + * @comp SpriteAnimation + * @sign public Boolean .isPlaying([String reelId]) + * @param reelId - Determine if the animation reel with this reelId is playing + * Determines if an animation is currently playing. If a reel is passed, it will determine + * if the passed reel is playing. + * ~~~ + * myEntity.isPlaying() //is any animation playing + * myEntity.isPlaying('PlayerRunning') //is the PlayerRunning animation playing + * ~~~ + */ + isPlaying: function (reelId) { + if (!reelId) return !!this._interval; + return this._currentReelId === reelId; + } +}); + +/**@ +* #Tween +* @category Animation +* Component to animate the change in 2D properties over time. +* @trigger TweenEnd - when a tween finishes - String - property +*/ +Crafty.c("Tween", { + _step: null, + _numProps: 0, + + /**@ + * #.tween + * @comp Tween + * @sign public this .tween(Object properties, Number duration) + * @param properties - Object of 2D properties and what they should animate to + * @param duration - Duration to animate the properties over (in frames) + * This method will animate a 2D entities properties over the specified duration. + * These include `x`, `y`, `w`, `h`, `alpha` and `rotation`. + * + * The object passed should have the properties as keys and the value should be the resulting + * values of the properties. + * @example + * Move an object to 100,100 and fade out in 200 frames. + * ~~~ + * Crafty.e("2D, Tween") + * .attr({alpha: 1.0, x: 0, y: 0}) + * .tween({alpha: 0.0, x: 100, y: 100}, 200) + * ~~~ + */ + tween: function (props, duration) { + this.each(function () { + if (this._step == null) { + this._step = {}; + this.bind('EnterFrame', tweenEnterFrame); + this.bind('RemoveComponent', function (c) { + if (c == 'Tween') { + this.unbind('EnterFrame', tweenEnterFrame); + } + }); + } + + for (var prop in props) { + this._step[prop] = { prop: props[prop], val: (props[prop] - this[prop]) / duration, rem: duration }; + this._numProps++; + } + }); + return this; + } +}); + +function tweenEnterFrame(e) { + if (this._numProps <= 0) return; + + var prop, k; + for (k in this._step) { + prop = this._step[k]; + this[k] += prop.val; + if (--prop.rem == 0) { + // decimal numbers rounding fix + this[k] = prop.prop; + this.trigger("TweenEnd", k); + // make sure the duration wasn't changed in TweenEnd + if (this._step[k].rem <= 0) { + delete this._step[k]; + } + this._numProps--; + } + } + + if (this.has('Mouse')) { + var over = Crafty.over, + mouse = Crafty.mousePos; + if (over && over[0] == this[0] && !this.isAt(mouse.x, mouse.y)) { + this.trigger('MouseOut', Crafty.lastEvent); + Crafty.over = null; + } + else if ((!over || over[0] != this[0]) && this.isAt(mouse.x, mouse.y)) { + Crafty.over = this; + this.trigger('MouseOver', Crafty.lastEvent); + } + } +} + + /**@ +* #Sprite +* @category Graphics +* @trigger Change - when the sprites change +* Component for using tiles in a sprite map. +*/ +Crafty.c("Sprite", { + __image: '', + /* + * #.__tile + * @comp Sprite + * + * Horizontal sprite tile size. + */ + __tile: 0, + /* + * #.__tileh + * @comp Sprite + * + * Vertical sprite tile size. + */ + __tileh: 0, + __padding: null, + __trim: null, + img: null, + //ready is changed to true in Crafty.sprite + ready: false, + + init: function () { + this.__trim = [0, 0, 0, 0]; + + var draw = function (e) { + var co = e.co, + pos = e.pos, + context = e.ctx; + + if (e.type === "canvas") { + //draw the image on the canvas element + context.drawImage(this.img, //image element + co.x, //x position on sprite + co.y, //y position on sprite + co.w, //width on sprite + co.h, //height on sprite + pos._x, //x position on canvas + pos._y, //y position on canvas + pos._w, //width on canvas + pos._h //height on canvas + ); + } else if (e.type === "DOM") { + this._element.style.background = "url('" + this.__image + "') no-repeat -" + co.x + "px -" + co.y + "px"; + } + }; + + this.bind("Draw", draw).bind("RemoveComponent", function (id) { + if (id === "Sprite") this.unbind("Draw", draw); + }); + }, + + /**@ + * #.sprite + * @comp Sprite + * @sign public this .sprite(Number x, Number y, Number w, Number h) + * @param x - X cell position + * @param y - Y cell position + * @param w - Width in cells + * @param h - Height in cells + * Uses a new location on the sprite map as its sprite. + * + * Values should be in tiles or cells (not pixels). + * + * @example + * ~~~ + * Crafty.e("2D, DOM, Sprite") + * .sprite(0, 0, 2, 2); + * ~~~ + */ + + /**@ + * #.__coord + * @comp Sprite + * + * The coordinate of the slide within the sprite in the format of [x, y, w, h]. + */ + sprite: function (x, y, w, h) { + this.__coord = [x * this.__tile + this.__padding[0] + this.__trim[0], + y * this.__tileh + this.__padding[1] + this.__trim[1], + this.__trim[2] || w * this.__tile || this.__tile, + this.__trim[3] || h * this.__tileh || this.__tileh]; + + this.trigger("Change"); + return this; + }, + + /**@ + * #.crop + * @comp Sprite + * @sign public this .crop(Number x, Number y, Number w, Number h) + * @param x - Offset x position + * @param y - Offset y position + * @param w - New width + * @param h - New height + * If the entity needs to be smaller than the tile size, use this method to crop it. + * + * The values should be in pixels rather than tiles. + * + * @example + * ~~~ + * Crafty.e("2D, DOM, Sprite") + * .crop(40, 40, 22, 23); + * ~~~ + */ + crop: function (x, y, w, h) { + var old = this._mbr || this.pos(); + this.__trim = []; + this.__trim[0] = x; + this.__trim[1] = y; + this.__trim[2] = w; + this.__trim[3] = h; + + this.__coord[0] += x; + this.__coord[1] += y; + this.__coord[2] = w; + this.__coord[3] = h; + this._w = w; + this._h = h; + + this.trigger("Change", old); + return this; + } +}); + /**@ +* #Color +* @category Graphics +* Draw a solid color for the entity +*/ +Crafty.c("Color", { + _color: "", + ready: true, + + init: function () { + this.bind("Draw", function (e) { + if (e.type === "DOM") { + e.style.background = this._color; + e.style.lineHeight = 0; + } else if (e.type === "canvas") { + if (this._color) e.ctx.fillStyle = this._color; + e.ctx.fillRect(e.pos._x, e.pos._y, e.pos._w, e.pos._h); + } + }); + }, + + /**@ + * #.color + * @comp Color + * @trigger Change - when the color changes + * @sign public this .color(String color) + * @sign public String .color() + * @param color - Color of the rectangle + * Will create a rectangle of solid color for the entity, or return the color if no argument is given. + * + * The argument must be a color readable depending on which browser you + * choose to support. IE 8 and below doesn't support the rgb() syntax. + * @example + * ~~~ + * Crafty.e("2D, DOM, Color") + * .color("#969696"); + * ~~~ + */ + color: function (color) { + if (!color) return this._color; + this._color = color; + this.trigger("Change"); + return this; + } +}); + +/**@ +* #Tint +* @category Graphics +* Similar to Color by adding an overlay of semi-transparent color. +* +* *Note: Currently only works for Canvas* +*/ +Crafty.c("Tint", { + _color: null, + _strength: 1.0, + + init: function () { + var draw = function d(e) { + var context = e.ctx || Crafty.canvas.context; + + context.fillStyle = this._color || "rgb(0,0,0)"; + context.fillRect(e.pos._x, e.pos._y, e.pos._w, e.pos._h); + }; + + this.bind("Draw", draw).bind("RemoveComponent", function (id) { + if (id === "Tint") this.unbind("Draw", draw); + }); + }, + + /**@ + * #.tint + * @comp Tint + * @trigger Change - when the tint is applied + * @sign public this .tint(String color, Number strength) + * @param color - The color in hexidecimal + * @param strength - Level of opacity + * Modify the color and level opacity to give a tint on the entity. + * @example + * ~~~ + * Crafty.e("2D, Canvas, Tint") + * .tint("#969696", 0.3); + * ~~~ + */ + tint: function (color, strength) { + this._strength = strength; + this._color = Crafty.toRGB(color, this._strength); + + this.trigger("Change"); + return this; + } +}); + +/**@ +* #Image +* @category Graphics +* Draw an image with or without repeating (tiling). +*/ +Crafty.c("Image", { + _repeat: "repeat", + ready: false, + + init: function () { + var draw = function (e) { + if (e.type === "canvas") { + //skip if no image + if (!this.ready || !this._pattern) return; + + var context = e.ctx; + + context.fillStyle = this._pattern; + + //context.save(); + //context.translate(e.pos._x, e.pos._y); + context.fillRect(this._x, this._y, this._w, this._h); + //context.restore(); + } else if (e.type === "DOM") { + if (this.__image) + e.style.background = "url(" + this.__image + ") " + this._repeat; + } + }; + + this.bind("Draw", draw).bind("RemoveComponent", function (id) { + if (id === "Image") this.unbind("Draw", draw); + }); + }, + + /**@ + * #.image + * @comp Image + * @trigger Change - when the image is loaded + * @sign public this .image(String url[, String repeat]) + * @param url - URL of the image + * @param repeat - If the image should be repeated to fill the entity. + * Draw specified image. Repeat follows CSS syntax (`"no-repeat", "repeat", "repeat-x", "repeat-y"`); + * + * *Note: Default repeat is `no-repeat` which is different to standard DOM (which is `repeat`)* + * + * If the width and height are `0` and repeat is set to `no-repeat` the width and + * height will automatically assume that of the image. This is an + * easy way to create an image without needing sprites. + * @example + * Will default to no-repeat. Entity width and height will be set to the images width and height + * ~~~ + * var ent = Crafty.e("2D, DOM, Image").image("myimage.png"); + * ~~~ + * Create a repeating background. + * ~~~ + * var bg = Crafty.e("2D, DOM, Image") + * .attr({w: Crafty.viewport.width, h: Crafty.viewport.height}) + * .image("bg.png", "repeat"); + * ~~~ + * @see Crafty.sprite + */ + image: function (url, repeat) { + this.__image = url; + this._repeat = repeat || "no-repeat"; + + + this.img = Crafty.assets[url]; + if (!this.img) { + this.img = new Image(); + Crafty.assets[url] = this.img; + this.img.src = url; + var self = this; + + this.img.onload = function () { + if (self.has("Canvas")) self._pattern = Crafty.canvas.context.createPattern(self.img, self._repeat); + self.ready = true; + + if (self._repeat === "no-repeat") { + self.w = self.img.width; + self.h = self.img.height; + } + + self.trigger("Change"); + }; + + return this; + } else { + this.ready = true; + if (this.has("Canvas")) this._pattern = Crafty.canvas.context.createPattern(this.img, this._repeat); + if (this._repeat === "no-repeat") { + this.w = this.img.width; + this.h = this.img.height; + } + } + + + this.trigger("Change"); + + return this; + } +}); + +Crafty.extend({ + _scenes: [], + _current: null, + + /**@ + * #Crafty.scene + * @category Scenes, Stage + * @trigger SceneChange - when a scene is played - { oldScene:String, newScene:String } + * @sign public void Crafty.scene(String sceneName, Function init[, Function uninit]) + * @param sceneName - Name of the scene to add + * @param init - Function to execute when scene is played + * @param uninit - Function to execute before next scene is played, after entities with `2D` are destroyed + * @sign public void Crafty.scene(String sceneName) + * @param sceneName - Name of scene to play + * Method to create scenes on the stage. Pass an ID and function to register a scene. + * + * To play a scene, just pass the ID. When a scene is played, all + * entities with the `2D` component on the stage are destroyed. + * + * If you want some entities to persist over scenes (as in not be destroyed) + * simply add the component `Persist`. + * + * @example + * ~~~ + * Crafty.scene("loading", function() {}); + * + * Crafty.scene("loading", function() {}, function() {}); + * + * Crafty.scene("loading"); + * ~~~ + */ + scene: function (name, intro, outro) { + //play scene + if (arguments.length === 1) { + Crafty("2D").each(function () { + if (!this.has("Persist")) this.destroy(); + }); + // uninitialize previous scene + if (this._current !== null && 'uninitialize' in this._scenes[this._current]) { + this._scenes[this._current].uninitialize.call(this); + } + // initialize next scene + this._scenes[name].initialize.call(this); + var oldScene = this._current; + this._current = name; + Crafty.trigger("SceneChange", { oldScene: oldScene, newScene: name }); + return; + } + //add scene + this._scenes[name] = {} + this._scenes[name].initialize = intro + if (typeof outro !== 'undefined') { + this._scenes[name].uninitialize = outro; + } + return; + }, + + /**@ + * #Crafty.toRGB + * @category Graphics + * @sign public String Crafty.scene(String hex[, Number alpha]) + * @param hex - a 6 character hex number string representing RGB color + * @param alpha - The alpha value. + * Get a rgb string or rgba string (if `alpha` presents). + * @example + * ~~~ + * Crafty.toRGB("ffffff"); // rgb(255,255,255) + * Crafty.toRGB("#ffffff"); // rgb(255,255,255) + * Crafty.toRGB("ffffff", .5); // rgba(255,255,255,0.5) + * ~~~ + * @see Text.textColor + */ + toRGB: function (hex, alpha) { + var hex = (hex.charAt(0) === '#') ? hex.substr(1) : hex, + c = [], result; + + c[0] = parseInt(hex.substr(0, 2), 16); + c[1] = parseInt(hex.substr(2, 2), 16); + c[2] = parseInt(hex.substr(4, 2), 16); + + result = alpha === undefined ? 'rgb(' + c.join(',') + ')' : 'rgba(' + c.join(',') + ',' + alpha + ')'; + + return result; + } +}); + +/**@ +* #Crafty.DrawManager +* @category Graphics +* @sign Crafty.DrawManager +* An internal object manage objects to be drawn and implement +* the best method of drawing in both DOM and canvas +*/ +Crafty.DrawManager = (function () { + /** array of dirty rects on screen */ + var dirty_rects = [], + /** array of DOMs needed updating */ + dom = []; + + return { + /**@ + * #Crafty.DrawManager.total2D + * @comp Crafty.DrawManager + * Total number of the entities that have the `2D` component. + */ + total2D: Crafty("2D").length, + + /**@ + * #Crafty.DrawManager.onScreen + * @comp Crafty.DrawManager + * @sign public Crafty.DrawManager.onScreen(Object rect) + * @param rect - A rectangle with field {_x: x_val, _y: y_val, _w: w_val, _h: h_val} + * Test if a rectangle is completely in viewport + */ + onScreen: function (rect) { + return Crafty.viewport._x + rect._x + rect._w > 0 && Crafty.viewport._y + rect._y + rect._h > 0 && + Crafty.viewport._x + rect._x < Crafty.viewport.width && Crafty.viewport._y + rect._y < Crafty.viewport.height; + }, + + /**@ + * #Crafty.DrawManager.merge + * @comp Crafty.DrawManager + * @sign public Object Crafty.DrawManager.merge(Object set) + * @param set - an array of rectangular regions + * Merged into non overlapping rectangular region + * Its an optimization for the redraw regions. + */ + merge: function (set) { + do { + var newset = [], didMerge = false, i = 0, + l = set.length, current, next, merger; + + while (i < l) { + current = set[i]; + next = set[i + 1]; + + if (i < l - 1 && current._x < next._x + next._w && current._x + current._w > next._x && + current._y < next._y + next._h && current._h + current._y > next._y) { + + merger = { + _x: ~~Math.min(current._x, next._x), + _y: ~~Math.min(current._y, next._y), + _w: Math.max(current._x, next._x) + Math.max(current._w, next._w), + _h: Math.max(current._y, next._y) + Math.max(current._h, next._h) + }; + merger._w = merger._w - merger._x; + merger._h = merger._h - merger._y; + merger._w = (merger._w == ~~merger._w) ? merger._w : merger._w + 1 | 0; + merger._h = (merger._h == ~~merger._h) ? merger._h : merger._h + 1 | 0; + + newset.push(merger); + + i++; + didMerge = true; + } else newset.push(current); + i++; + } + + set = newset.length ? Crafty.clone(newset) : set; + + if (didMerge) i = 0; + } while (didMerge); + + return set; + }, + + /**@ + * #Crafty.DrawManager.add + * @comp Crafty.DrawManager + * @sign public Crafty.DrawManager.add(old, current) + * @param old - Undocumented + * @param current - Undocumented + * Calculate the bounding rect of dirty data and add to the register of dirty rectangles + */ + add: function add(old, current) { + if (!current) { + dom.push(old); + return; + } + + var rect, + before = old._mbr || old, + after = current._mbr || current; + + if (old === current) { + rect = old.mbr() || old.pos(); + } else { + rect = { + _x: ~~Math.min(before._x, after._x), + _y: ~~Math.min(before._y, after._y), + _w: Math.max(before._w, after._w) + Math.max(before._x, after._x), + _h: Math.max(before._h, after._h) + Math.max(before._y, after._y) + }; + + rect._w = (rect._w - rect._x); + rect._h = (rect._h - rect._y); + } + + if (rect._w === 0 || rect._h === 0 || !this.onScreen(rect)) { + return false; + } + + //floor/ceil + rect._x = ~~rect._x; + rect._y = ~~rect._y; + rect._w = (rect._w === ~~rect._w) ? rect._w : rect._w + 1 | 0; + rect._h = (rect._h === ~~rect._h) ? rect._h : rect._h + 1 | 0; + + //add to dirty_rects, check for merging + dirty_rects.push(rect); + + //if it got merged + return true; + }, + + /**@ + * #Crafty.DrawManager.debug + * @comp Crafty.DrawManager + * @sign public Crafty.DrawManager.debug() + * Undocumented + */ + debug: function () { + console.log(dirty_rects, dom); + }, + + /**@ + * #Crafty.DrawManager.draw + * @comp Crafty.DrawManager + * @sign public Crafty.DrawManager.draw([Object rect]) + * @param rect - a rectangular region {_x: x_val, _y: y_val, _w: w_val, _h: h_val} + * - If rect is omitted, redraw within the viewport + * - If rect is provided, redraw within the rect + */ + drawAll: function (rect) { + var rect = rect || Crafty.viewport.rect(), + q = Crafty.map.search(rect), + i = 0, + l = q.length, + ctx = Crafty.canvas.context, + current; + + ctx.clearRect(rect._x, rect._y, rect._w, rect._h); + + //sort the objects by the global Z + q.sort(function (a, b) { return a._globalZ - b._globalZ; }); + for (; i < l; i++) { + current = q[i]; + if (current._visible && current.__c.Canvas) { + current.draw(); + current._changed = false; + } + } + }, + + /**@ + * #Crafty.DrawManager.boundingRect + * @comp Crafty.DrawManager + * @sign public Crafty.DrawManager.boundingRect(set) + * @param set - Undocumented + * - Calculate the common bounding rect of multiple canvas entities. + * - Returns coords + */ + boundingRect: function (set) { + if (!set || !set.length) return; + var newset = [], i = 1, + l = set.length, current, master = set[0], tmp; + master = [master._x, master._y, master._x + master._w, master._y + master._h]; + while (i < l) { + current = set[i]; + tmp = [current._x, current._y, current._x + current._w, current._y + current._h]; + if (tmp[0] < master[0]) master[0] = tmp[0]; + if (tmp[1] < master[1]) master[1] = tmp[1]; + if (tmp[2] > master[2]) master[2] = tmp[2]; + if (tmp[3] > master[3]) master[3] = tmp[3]; + i++; + } + tmp = master; + master = { _x: tmp[0], _y: tmp[1], _w: tmp[2] - tmp[0], _h: tmp[3] - tmp[1] }; + + return master; + }, + + /**@ + * #Crafty.DrawManager.draw + * @comp Crafty.DrawManager + * @sign public Crafty.DrawManager.draw() + * - If the number of rects is over 60% of the total number of objects + * do the naive method redrawing `Crafty.DrawManager.drawAll` + * - Otherwise, clear the dirty regions, and redraw entities overlapping the dirty regions. + * @see Canvas.draw, DOM.draw + */ + draw: function draw() { + //if nothing in dirty_rects, stop + if (!dirty_rects.length && !dom.length) return; + + var i = 0, l = dirty_rects.length, k = dom.length, rect, q, + j, len, dupes, obj, ent, objs = [], ctx = Crafty.canvas.context; + + //loop over all DOM elements needing updating + for (; i < k; ++i) { + dom[i].draw()._changed = false; + } + //reset DOM array + dom.length = 0; + //again, stop if nothing in dirty_rects + if (!l) { return; } + + //if the amount of rects is over 60% of the total objects + //do the naive method redrawing + if (l / this.total2D > 0.6) { + this.drawAll(); + dirty_rects.length = 0; + return; + } + + dirty_rects = this.merge(dirty_rects); + for (i = 0; i < l; ++i) { //loop over every dirty rect + rect = dirty_rects[i]; + if (!rect) continue; + q = Crafty.map.search(rect, false); //search for ents under dirty rect + + dupes = {}; + + //loop over found objects removing dupes and adding to obj array + for (j = 0, len = q.length; j < len; ++j) { + obj = q[j]; + + if (dupes[obj[0]] || !obj._visible || !obj.__c.Canvas) + continue; + dupes[obj[0]] = true; + + objs.push({ obj: obj, rect: rect }); + } + + //clear the rect from the main canvas + ctx.clearRect(rect._x, rect._y, rect._w, rect._h); + + } + + //sort the objects by the global Z + objs.sort(function (a, b) { return a.obj._globalZ - b.obj._globalZ; }); + if (!objs.length){ return; } + + //loop over the objects + for (i = 0, l = objs.length; i < l; ++i) { + obj = objs[i]; + rect = obj.rect; + ent = obj.obj; + + var area = ent._mbr || ent, + x = (rect._x - area._x <= 0) ? 0 : ~~(rect._x - area._x), + y = (rect._y - area._y < 0) ? 0 : ~~(rect._y - area._y), + w = ~~Math.min(area._w - x, rect._w - (area._x - rect._x), rect._w, area._w), + h = ~~Math.min(area._h - y, rect._h - (area._y - rect._y), rect._h, area._h); + + //no point drawing with no width or height + if (h === 0 || w === 0) continue; + + ctx.save(); + ctx.beginPath(); + ctx.moveTo(rect._x, rect._y); + ctx.lineTo(rect._x + rect._w, rect._y); + ctx.lineTo(rect._x + rect._w, rect._h + rect._y); + ctx.lineTo(rect._x, rect._h + rect._y); + ctx.lineTo(rect._x, rect._y); + + ctx.clip(); + + ent.draw(); + ctx.closePath(); + ctx.restore(); + + //allow entity to re-dirty_rects + ent._changed = false; + } + + //empty dirty_rects + dirty_rects.length = 0; + //all merged IDs are now invalid + merged = {}; + } + }; +})(); + Crafty.extend({ + /**@ +* #Crafty.isometric +* @category 2D +* Place entities in a 45deg isometric fashion. +*/ + isometric: { + _tile: { + width: 0, + height: 0 + }, + _elements:{}, + _pos: { + x:0, + y:0 + }, + _z: 0, + /**@ +* #Crafty.isometric.size +* @comp Crafty.isometric +* @sign public this Crafty.isometric.size(Number tileSize) +* @param tileSize - The size of the tiles to place. +* Method used to initialize the size of the isometric placement. +* Recommended to use a size alues in the power of `2` (128, 64 or 32). +* This makes it easy to calculate positions and implement zooming. +* ~~~ +* var iso = Crafty.isometric.size(128); +* ~~~ +* @see Crafty.isometric.place +*/ + size: function (width, height) { + this._tile.width = width; + this._tile.height = height > 0 ? height : width/2; //Setup width/2 if height doesnt set + return this; + }, + /**@ + * #Crafty.isometric.place + * @comp Crafty.isometric + * @sign public this Crafty.isometric.size(Number x, Number y, Number z, Entity tile) + * @param x - The `x` position to place the tile + * @param y - The `y` position to place the tile + * @param z - The `z` position or height to place the tile + * @param tile - The entity that should be position in the isometric fashion + * Use this method to place an entity in an isometric grid. + * ~~~ + * var iso = Crafty.isometric.size(128); + * isos.place(2, 1, 0, Crafty.e('2D, DOM, Color').color('red').attr({w:128, h:128})); + * ~~~ + * @see Crafty.isometric.size + */ + place: function (x, y, z, obj) { + var pos = this.pos2px(x,y); + pos.top -= z * (this._tile.width / 2); + obj.attr({ + x: pos.left + Crafty.viewport._x, + y: pos.top + Crafty.viewport._y + }).z += z; + return this; + }, + /**@ + * #Crafty.isometric.pos2px + * @comp Crafty.isometric + * @sign public this Crafty.isometric.pos2px(Number x,Number y) + * @param x + * @param y + * @return Object {left Number,top Number} + * This method calculate the X and Y Coordiantes to Pixel Positions + * ~~~ + * var iso = Crafty.isometric.size(128,96); + * var position = iso.pos2px(100,100); + * console.log(position); //Object { left=12800, top=4800} + * ~~~ + */ + pos2px:function(x,y){ + return { + left:x * this._tile.width + (y & 1) * (this._tile.width / 2), + top:y * this._tile.height / 2 + } + }, + /**@ + * #Crafty.isometric.px2pos + * @comp Crafty.isometric + * @sign public this Crafty.isometric.px2pos(Number left,Number top) + * @param top + * @param left + * @return Object {x Number,y Number} + * This method calculate pixel top,left positions to x,y coordiantes + * ~~~ + * var iso = Crafty.isometric.size(128,96); + * var px = iso.pos2px(12800,4800); + * console.log(px); //Object { x=-100, y=-100} + * ~~~ + */ + px2pos:function(left,top){ + return { + x:Math.ceil(-left / this._tile.width - (top & 1)*0.5), + y:-top / this._tile.height * 2 + }; + }, + /**@ + * #Crafty.isometric.centerAt + * @comp Crafty.isometric + * @sign public this Crafty.isometric.centerAt(Number x,Number y) + * @param top + * @param left + * This method center the Viewport at x/y location or gives the current centerpoint of the viewport + * ~~~ + * var iso = Crafty.isometric.size(128,96).centerAt(10,10); //Viewport is now moved + * //After moving the viewport by another event you can get the new center point + * console.log(iso.centerAt()); + * ~~~ + */ + centerAt:function(x,y){ + if(typeof x == "number" && typeof y == "number"){ + var center = this.pos2px(x,y); + Crafty.viewport._x = -center.left+Crafty.viewport.width/2-this._tile.width/2; + Crafty.viewport._y = -center.top+Crafty.viewport.height/2-this._tile.height/2; + return this; + }else{ + return { + top:-Crafty.viewport._y+Crafty.viewport.height/2-this._tile.height/2, + left:-Crafty.viewport._x+Crafty.viewport.width/2-this._tile.width/2 + } + } + }, + /**@ + * #Crafty.isometric.area + * @comp Crafty.isometric + * @sign public this Crafty.isometric.area() + * @return Object {x:{start Number,end Number},y:{start Number,end Number}} + * This method get the Area surounding by the centerpoint depends on viewport height and width + * ~~~ + * var iso = Crafty.isometric.size(128,96).centerAt(10,10); //Viewport is now moved + * var area = iso.area(); //get the area + *for(var y = area.y.start;y <= area.y.end;y++){ + *for(var x = area.x.start ;x <= area.x.end;x++){ + * iso.place(x,y,0,Crafty.e("2D,DOM,gras")); //Display tiles in the Screen + * } + *} + * ~~~ + */ + area:function(){ + //Get the center Point in the viewport + var center = this.centerAt(); + var start = this.px2pos(-center.left+Crafty.viewport.width/2,-center.top+Crafty.viewport.height/2); + var end = this.px2pos(-center.left-Crafty.viewport.width/2,-center.top-Crafty.viewport.height/2); + return { + x:{ + start : start.x, + end : end.x + }, + y:{ + start : start.y, + end : end.y + } + }; + } + } +}); /**@ +* #Particles +* @category Graphics +* Based on Parcycle by Mr. Speaker, licensed under the MIT, Ported by Leo Koppelkamm +* **This is canvas only & won't do anything if the browser doesn't support it!** +* To see how this works take a look in https://github.com/louisstow/Crafty/blob/master/src/particles.js +*/ +Crafty.c("particles", { + init: function () { + //We need to clone it + this._Particles = Crafty.clone(this._Particles); + }, + particles: function (options) { + + if (!Crafty.support.canvas || Crafty.deactivateParticles) return this; + + //If we drew on the main canvas, we'd have to redraw + //potentially huge sections of the screen every frame + //So we create a separate canvas, where we only have to redraw + //the changed particles. + var c, ctx, relativeX, relativeY, bounding; + + c = document.createElement("canvas"); + c.width = Crafty.viewport.width; + c.height = Crafty.viewport.height; + c.style.position = 'absolute'; + + Crafty.stage.elem.appendChild(c); + + ctx = c.getContext('2d'); + + this._Particles.init(options); + + relativeX = this.x + Crafty.viewport.x; + relativeY = this.y + Crafty.viewport.y; + this._Particles.position = this._Particles.vectorHelpers.create(relativeX, relativeY); + + var oldViewport = { x: Crafty.viewport.x, y: Crafty.viewport.y }; + + this.bind('EnterFrame', function () { + relativeX = this.x + Crafty.viewport.x; + relativeY = this.y + Crafty.viewport.y; + this._Particles.viewportDelta = { x: Crafty.viewport.x - oldViewport.x, y: Crafty.viewport.y - oldViewport.y }; + + oldViewport = { x: Crafty.viewport.x, y: Crafty.viewport.y }; + + this._Particles.position = this._Particles.vectorHelpers.create(relativeX, relativeY); + + //Selective clearing + if (typeof Crafty.DrawManager.boundingRect == 'function') { + bounding = Crafty.DrawManager.boundingRect(this._Particles.register); + if (bounding) ctx.clearRect(bounding._x, bounding._y, bounding._w, bounding._h); + } else { + ctx.clearRect(0, 0, Crafty.viewport.width, Crafty.viewport.height); + } + + //This updates all particle colors & positions + this._Particles.update(); + + //This renders the updated particles + this._Particles.render(ctx); + }); + return this; + }, + _Particles: { + presets: { + maxParticles: 150, + size: 18, + sizeRandom: 4, + speed: 1, + speedRandom: 1.2, + // Lifespan in frames + lifeSpan: 29, + lifeSpanRandom: 7, + // Angle is calculated clockwise: 12pm is 0deg, 3pm is 90deg etc. + angle: 65, + angleRandom: 34, + startColour: [255, 131, 0, 1], + startColourRandom: [48, 50, 45, 0], + endColour: [245, 35, 0, 0], + endColourRandom: [60, 60, 60, 0], + // Only applies when fastMode is off, specifies how sharp the gradients are drawn + sharpness: 20, + sharpnessRandom: 10, + // Random spread from origin + spread: 10, + // How many frames should this last + duration: -1, + // Will draw squares instead of circle gradients + fastMode: false, + gravity: { x: 0, y: 0.1 }, + // sensible values are 0-3 + jitter: 0, + + //Don't modify the following + particles: [], + active: true, + particleCount: 0, + elapsedFrames: 0, + emissionRate: 0, + emitCounter: 0, + particleIndex: 0 + }, + + + init: function (options) { + this.position = this.vectorHelpers.create(0, 0); + if (typeof options == 'undefined') var options = {}; + + //Create current config by mergin given options and presets. + for (key in this.presets) { + if (typeof options[key] != 'undefined') this[key] = options[key]; + else this[key] = this.presets[key]; + } + + this.emissionRate = this.maxParticles / this.lifeSpan; + this.positionRandom = this.vectorHelpers.create(this.spread, this.spread); + }, + + addParticle: function () { + if (this.particleCount == this.maxParticles) { + return false; + } + + // Take the next particle out of the particle pool we have created and initialize it + var particle = new this.particle(this.vectorHelpers); + this.initParticle(particle); + this.particles[this.particleCount] = particle; + // Increment the particle count + this.particleCount++; + + return true; + }, + RANDM1TO1: function () { + return Math.random() * 2 - 1; + }, + initParticle: function (particle) { + particle.position.x = this.position.x + this.positionRandom.x * this.RANDM1TO1(); + particle.position.y = this.position.y + this.positionRandom.y * this.RANDM1TO1(); + + var newAngle = (this.angle + this.angleRandom * this.RANDM1TO1()) * (Math.PI / 180); // convert to radians + var vector = this.vectorHelpers.create(Math.sin(newAngle), -Math.cos(newAngle)); // Could move to lookup for speed + var vectorSpeed = this.speed + this.speedRandom * this.RANDM1TO1(); + particle.direction = this.vectorHelpers.multiply(vector, vectorSpeed); + + particle.size = this.size + this.sizeRandom * this.RANDM1TO1(); + particle.size = particle.size < 0 ? 0 : ~~particle.size; + particle.timeToLive = this.lifeSpan + this.lifeSpanRandom * this.RANDM1TO1(); + + particle.sharpness = this.sharpness + this.sharpnessRandom * this.RANDM1TO1(); + particle.sharpness = particle.sharpness > 100 ? 100 : particle.sharpness < 0 ? 0 : particle.sharpness; + // internal circle gradient size - affects the sharpness of the radial gradient + particle.sizeSmall = ~~((particle.size / 200) * particle.sharpness); //(size/2/100) + var start = [ + this.startColour[0] + this.startColourRandom[0] * this.RANDM1TO1(), + this.startColour[1] + this.startColourRandom[1] * this.RANDM1TO1(), + this.startColour[2] + this.startColourRandom[2] * this.RANDM1TO1(), + this.startColour[3] + this.startColourRandom[3] * this.RANDM1TO1() + ]; + + var end = [ + this.endColour[0] + this.endColourRandom[0] * this.RANDM1TO1(), + this.endColour[1] + this.endColourRandom[1] * this.RANDM1TO1(), + this.endColour[2] + this.endColourRandom[2] * this.RANDM1TO1(), + this.endColour[3] + this.endColourRandom[3] * this.RANDM1TO1() + ]; + + particle.colour = start; + particle.deltaColour[0] = (end[0] - start[0]) / particle.timeToLive; + particle.deltaColour[1] = (end[1] - start[1]) / particle.timeToLive; + particle.deltaColour[2] = (end[2] - start[2]) / particle.timeToLive; + particle.deltaColour[3] = (end[3] - start[3]) / particle.timeToLive; + }, + update: function () { + if (this.active && this.emissionRate > 0) { + var rate = 1 / this.emissionRate; + this.emitCounter++; + while (this.particleCount < this.maxParticles && this.emitCounter > rate) { + this.addParticle(); + this.emitCounter -= rate; + } + this.elapsedFrames++; + if (this.duration != -1 && this.duration < this.elapsedFrames) { + this.stop(); + } + } + + this.particleIndex = 0; + this.register = []; + var draw; + while (this.particleIndex < this.particleCount) { + + var currentParticle = this.particles[this.particleIndex]; + + // If the current particle is alive then update it + if (currentParticle.timeToLive > 0) { + + // Calculate the new direction based on gravity + currentParticle.direction = this.vectorHelpers.add(currentParticle.direction, this.gravity); + currentParticle.position = this.vectorHelpers.add(currentParticle.position, currentParticle.direction); + currentParticle.position = this.vectorHelpers.add(currentParticle.position, this.viewportDelta); + if (this.jitter) { + currentParticle.position.x += this.jitter * this.RANDM1TO1(); + currentParticle.position.y += this.jitter * this.RANDM1TO1(); + } + currentParticle.timeToLive--; + + // Update colours + var r = currentParticle.colour[0] += currentParticle.deltaColour[0]; + var g = currentParticle.colour[1] += currentParticle.deltaColour[1]; + var b = currentParticle.colour[2] += currentParticle.deltaColour[2]; + var a = currentParticle.colour[3] += currentParticle.deltaColour[3]; + + // Calculate the rgba string to draw. + draw = []; + draw.push("rgba(" + (r > 255 ? 255 : r < 0 ? 0 : ~~r)); + draw.push(g > 255 ? 255 : g < 0 ? 0 : ~~g); + draw.push(b > 255 ? 255 : b < 0 ? 0 : ~~b); + draw.push((a > 1 ? 1 : a < 0 ? 0 : a.toFixed(2)) + ")"); + currentParticle.drawColour = draw.join(","); + + if (!this.fastMode) { + draw[3] = "0)"; + currentParticle.drawColourEnd = draw.join(","); + } + + this.particleIndex++; + } else { + // Replace particle with the last active + if (this.particleIndex != this.particleCount - 1) { + this.particles[this.particleIndex] = this.particles[this.particleCount - 1]; + } + this.particleCount--; + } + var rect = {}; + rect._x = ~~currentParticle.position.x; + rect._y = ~~currentParticle.position.y; + rect._w = currentParticle.size; + rect._h = currentParticle.size; + + this.register.push(rect); + } + }, + + stop: function () { + this.active = false; + this.elapsedFrames = 0; + this.emitCounter = 0; + }, + + render: function (context) { + + for (var i = 0, j = this.particleCount; i < j; i++) { + var particle = this.particles[i]; + var size = particle.size; + var halfSize = size >> 1; + + if (particle.position.x + size < 0 + || particle.position.y + size < 0 + || particle.position.x - size > Crafty.viewport.width + || particle.position.y - size > Crafty.viewport.height) { + //Particle is outside + continue; + } + var x = ~~particle.position.x; + var y = ~~particle.position.y; + + if (this.fastMode) { + context.fillStyle = particle.drawColour; + } else { + var radgrad = context.createRadialGradient(x + halfSize, y + halfSize, particle.sizeSmall, x + halfSize, y + halfSize, halfSize); + radgrad.addColorStop(0, particle.drawColour); + //0.9 to avoid visible boxing + radgrad.addColorStop(0.9, particle.drawColourEnd); + context.fillStyle = radgrad; + } + context.fillRect(x, y, size, size); + } + }, + particle: function (vectorHelpers) { + this.position = vectorHelpers.create(0, 0); + this.direction = vectorHelpers.create(0, 0); + this.size = 0; + this.sizeSmall = 0; + this.timeToLive = 0; + this.colour = []; + this.drawColour = ""; + this.deltaColour = []; + this.sharpness = 0; + }, + vectorHelpers: { + create: function (x, y) { + return { + "x": x, + "y": y + }; + }, + multiply: function (vector, scaleFactor) { + vector.x *= scaleFactor; + vector.y *= scaleFactor; + return vector; + }, + add: function (vector1, vector2) { + vector1.x += vector2.x; + vector1.y += vector2.y; + return vector1; + } + } + } +}); Crafty.extend({ +/**@ + * #Crafty.audio + * @category Audio + * Add sound files and play them. Chooses best format for browser support. + * Due to the nature of HTML5 audio, three types of audio files will be + * required for cross-browser capabilities. These formats are MP3, Ogg and WAV. + * When sound was not muted on before pause, sound will be unmuted after unpause. + * When sound is muted Crafty.pause() does not have any effect on sound. + */ + audio: { + _elems: {}, + _muted: false, + + /**@ + * #Crafty.audio.MAX_CHANNELS + * @comp Crafty.audio + * Amount of Audio objects for a sound so overlapping of the + * same sound can occur. More channels means more of the same sound + * playing at the same time. + */ + MAX_CHANNELS: 5, + + type: { + 'mp3': 'audio/mpeg;', + 'ogg': 'audio/ogg; codecs="vorbis"', + 'wav': 'audio/wav; codecs="1"', + 'mp4': 'audio/mp4; codecs="mp4a.40.2"' + }, + + /**@ + * #Crafty.audio.add + * @comp Crafty.audio + * @sign public this Crafty.audio.add(String id, String url) + * @param id - A string to reffer to sounds + * @param url - A string pointing to the sound file + * @sign public this Crafty.audio.add(String id, Array urls) + * @param urls - Array of urls pointing to different format of the same sound, selecting the first that is playable + * @sign public this Crafty.audio.add(Object map) + * @param map - key-value pairs where the key is the `id` and the value is either a `url` or `urls` + * + * Loads a sound to be played. Due to the nature of HTML5 audio, + * three types of audio files will be required for cross-browser capabilities. + * These formats are MP3, Ogg and WAV. + * + * Passing an array of URLs will determine which format the browser can play and select it over any other. + * + * Accepts an object where the key is the audio name and + * either a URL or an Array of URLs (to determine which type to use). + * + * The ID you use will be how you refer to that sound when using `Crafty.audio.play`. + * + * @example + * ~~~ + * //adding audio from an object + * Crafty.audio.add({ + * shoot: ["sounds/shoot.wav", + * "sounds/shoot.mp3", + * "sounds/shoot.ogg"], + * + * coin: "sounds/coin.mp3" + * }); + * + * //adding a single sound + * Crafty.audio.add("walk", [ + * "sounds/walk.mp3", + * "sounds/walk.ogg", + * "sounds/walk.wav" + * ]); + * + * //only one format + * Crafty.audio.add("jump", "sounds/jump.mp3"); + * ~~~ + */ + add: function (id, url) { + if (!Crafty.support.audio) return this; + + var elem, + key, + audio = new Audio(), + canplay, + i = 0, + sounds = []; + + //if an object is passed + if (arguments.length === 1 && typeof id === "object") { + for (key in id) { + if (!id.hasOwnProperty(key)) continue; + + //if array passed, add fallback sources + if (typeof id[key] !== "string") { + var sources = id[key], i = 0, l = sources.length, + source; + + for (; i < l; ++i) { + source = sources[i]; + //get the file extension + ext = source.substr(source.lastIndexOf('.') + 1).toLowerCase(); + canplay = audio.canPlayType(this.type[ext]); + + //if browser can play this type, use it + if (canplay !== "" && canplay !== "no") { + url = source; + break; + } + } + } else { + url = id[key]; + } + + for (; i < this.MAX_CHANNELS; i++) { + audio = new Audio(url); + audio.preload = "auto"; + audio.load(); + sounds.push(audio); + } + this._elems[key] = sounds; + if (!Crafty.assets[url]) Crafty.assets[url] = this._elems[key][0]; + } + + return this; + } + //standard method + if (typeof url !== "string") { + var i = 0, l = url.length, + source; + + for (; i < l; ++i) { + source = url[i]; + //get the file extension + ext = source.substr(source.lastIndexOf('.') + 1); + canplay = audio.canPlayType(this.type[ext]); + + //if browser can play this type, use it + if (canplay !== "" && canplay !== "no") { + url = source; + break; + } + } + } + + //create a new Audio object and add it to assets + for (; i < this.MAX_CHANNELS; i++) { + audio = new Audio(url); + audio.preload = "auto"; + audio.load(); + sounds.push(audio); + } + this._elems[id] = sounds; + if (!Crafty.assets[url]) Crafty.assets[url] = this._elems[id][0]; + + return this; + }, + /**@ + * #Crafty.audio.play + * @comp Crafty.audio + * @sign public this Crafty.audio.play(String id) + * @sign public this Crafty.audio.play(String id, Number repeatCount) + * @param id - A string to reffer to sounds + * @param repeatCount - Repeat count for the file, where -1 stands for repeat forever. + * + * Will play a sound previously added by using the ID that was used in `Crafty.audio.add`. + * Has a default maximum of 5 channels so that the same sound can play simultaneously unless all of the channels are playing. + + * *Note that the implementation of HTML5 Audio is buggy at best.* + * + * @example + * ~~~ + * Crafty.audio.play("walk"); + * + * //play and repeat forever + * Crafty.audio.play("backgroundMusic", -1); + * ~~~ + */ + play: function (id, repeat) { + if (!Crafty.support.audio) return; + + var sounds = this._elems[id], + sound, + i = 0, l = sounds.length; + + for (; i < l; i++) { + sound = sounds[i]; + //go through the channels and play a sound that is stopped + if (sound.ended || !sound.currentTime) { + sound.play(); + break; + } else if (i === l - 1) { //if all sounds playing, try stop the last one + sound.currentTime = 0; + sound.play(); + } + } + if (typeof repeat == "number") { + var j = 0; + //i is still set to the sound we played + sounds[i].addEventListener('ended', function () { + if (repeat == -1 || j <= repeat) { + this.currentTime = 0; + this.play(); + j++; + } + }, false); + } + return this; + }, + + /**@ + * #Crafty.audio.settings + * @comp Crafty.audio + * @sign public this Crafty.audio.settings(String id, Object settings) + * @param id - The audio instance added by `Crafty.audio.add` + * @param settings - An object where the key is the setting and the value is what to modify the setting with + * Used to modify settings of the HTML5 `Audio` object. For a list of all the settings available, + * see the [Mozilla Documentation](https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIDOMHTMLMediaElement). + */ + settings: function (id, settings) { + //apply to all + if (!settings) { + for (var key in this._elems) { + this.settings(key, id); + } + return this; + } + + var sounds = this._elems[id], + sound, + setting, + i = 0, l = sounds.length; + + for (var setting in settings) { + for (; i < l; i++) { + sound = sounds[i]; + sound[setting] = settings[setting]; + } + } + + return this; + }, + + /**@ + * #Crafty.audio.mute + * @sign public this Crafty.audio.mute([Boolean mute]) + * Mute or unmute every Audio instance that is playing. Toggles between + * pausing or playing depending on the state. + * @example + * ~~~ + * //toggle mute and unmute depending on current state + * Crafty.audio.mute(); + * + * //mute or unmute no matter what the current state is + * Crafty.audio.mute(true); + * Crafty.audio.mute(false); + * ~~~ + */ + mute: function (mute) { + var sounds, sound, i, l, elem; + + if (arguments.length == 1 && typeof(mute) == "boolean") + this._muted = mute; + else + this._muted = !this._muted; + + //loop over every sound + for (sounds in this._elems) { + elem = this._elems[sounds]; + + //loop over every channel for a sound + for (i = 0, l = elem.length; i < l; ++i) { + sound = elem[i]; + + //if playing, stop + if (!sound.ended && sound.currentTime) { + if (this._muted) + sound.pause(); + else + sound.play(); + } + } + } + return this; + } + } +}); + +//When there is sound stop sound on Pause Event. +//When there was sound on Pause, enable sound on Unpause Event. +(function() { + var prev_mute_state; + Crafty.bind("Pause", function () { + prev_mute_state=Crafty.audio._muted; + Crafty.audio.mute(true); + }); + Crafty.bind("Unpause", function () { + if(!prev_mute_state) { + Crafty.audio.mute(false); + } + }); +})(); + /**@ +* #HTML +* @category Graphics +* Component allow for insertion of arbitrary HTML into an entity +*/ +Crafty.c("HTML", { + inner: '', + + init: function () { + this.requires('2D, DOM'); + }, + + /**@ + * #.replace + * @comp HTML + * @sign public this .replace(String html) + * @param html - arbitrary html + * This method will replace the content of this entity with the supplied html + * + * @example + * Create a link + * ~~~ + * Crafty.e("HTML") + * .attr({x:20, y:20, w:100, h:100}) + * .replace("Crafty.js"); + * ~~~ + */ + replace: function (new_html) { + this.inner = new_html; + this._element.innerHTML = new_html; + return this; + }, + + /**@ + * #.append + * @comp HTML + * @sign public this .append(String html) + * @param html - arbitrary html + * This method will add the supplied html in the end of the entity + * + * @example + * Create a link + * ~~~ + * Crafty.e("HTML") + * .attr({x:20, y:20, w:100, h:100}) + * .append("Crafty.js"); + * ~~~ + */ + append: function (new_html) { + this.inner += new_html; + this._element.innerHTML += new_html; + return this; + }, + + /**@ + * #.prepend + * @comp HTML + * @sign public this .prepend(String html) + * @param html - arbitrary html + * This method will add the supplied html in the beginning of the entity + * + * @example + * Create a link + * ~~~ + * Crafty.e("HTML") + * .attr({x:20, y:20, w:100, h:100}) + * .prepend("Crafty.js"); + * ~~~ + */ + prepend: function (new_html) { + this.inner = new_html + this.inner; + this._element.innerHTML = new_html + this.inner; + return this; + } +}); /**@ + * #Storage + * @category Utilities + * Utility to allow data to be saved to a permanent storage solution: IndexedDB, WebSql, localstorage or cookies + */ +/**@ + * #.open + * @comp Storage + * @sign .open(String gameName) + * @param gameName - a machine readable string to uniquely identify your game + * Opens a connection to the database. If the best they have is localstorage or lower, it does nothing + * + * @example + * Open a database + * ~~~ + * Crafty.storage.open('MyGame'); + * ~~~ + */ + +/**@ + * #.save + * @comp Storage + * @sign .save(String key, String type, Mixed data) + * @param key - A unique key for identifying this piece of data + * @param type - 'save' or 'cache' + * @param data - Some kind of data. + * Saves a piece of data to the database. Can be anything, although entities are preferred. + * For all storage methods but IndexedDB, the data will be serialized as a string + * During serialization, an entity's SaveData event will be triggered. + * Components should implement a SaveData handler and attach the necessary information to the passed object + * + * @example + * Saves an entity to the database + * ~~~ + * var ent = Crafty.e("2D, DOM") + * .attr({x: 20, y: 20, w: 100, h:100}); + * Crafty.storage.open('MyGame'); + * Crafty.storage.save('MyEntity', 'save', ent); + * ~~~ + */ + +/**@ + * #.load + * @comp Storage + * @sign .load(String key, String type) + * @param key - A unique key to search for + * @param type - 'save' or 'cache' + * @param callback - Do things with the data you get back + * Loads a piece of data from the database. + * Entities will be reconstructed from the serialized string + + * @example + * Loads an entity from the database + * ~~~ + * Crafty.storage.open('MyGame'); + * Crafty.storage.load('MyEntity', 'save', function (data) { // do things }); + * ~~~ + */ + +/**@ + * #.getAllKeys + * @comp Storage + * @sign .getAllKeys(String type) + * @param type - 'save' or 'cache' + * Gets all the keys for a given type + + * @example + * Gets all the save games saved + * ~~~ + * Crafty.storage.open('MyGame'); + * var saves = Crafty.storage.getAllKeys('save'); + * ~~~ + */ + +/**@ + * #.external + * @comp Storage + * @sign .external(String url) + * @param url - URL to an external to save games too + * Enables and sets the url for saving games to an external server + + * @example + * Save an entity to an external server + * ~~~ + * Crafty.storage.external('http://somewhere.com/server.php'); + * Crafty.storage.open('MyGame'); + * var ent = Crafty.e('2D, DOM') + * .attr({x: 20, y: 20, w: 100, h:100}); + * Crafty.storage.save('save01', 'save', ent); + * ~~~ + */ + +/**@ + * #SaveData event + * @comp Storage + * @param data - An object containing all of the data to be serialized + * @param prepare - The function to prepare an entity for serialization + * Any data a component wants to save when it's serialized should be added to this object. + * Straight attribute should be set in data.attr. + * Anything that requires a special handler should be set in a unique property. + * + * @example + * Saves the innerHTML of an entity + * ~~~ + * Crafty.e("2D DOM").bind("SaveData", function (data, prepare) { + * data.attr.x = this.x; + * data.attr.y = this.y; + * data.dom = this.element.innerHTML; + * }); + * ~~~ + */ + +/**@ + * #LoadData event + * @param data - An object containing all the data that been saved + * @param process - The function to turn a string into an entity + * Handlers for processing any data that needs more than straight assignment + * + * Note that data stord in the .attr object is automatically added to the entity. + * It does not need to be handled here + * + * @example + * ~~~ + * Sets the innerHTML from a saved entity + * Crafty.e("2D DOM").bind("LoadData", function (data, process) { + * this.element.innerHTML = data.dom; + * }); + * ~~~ + */ + +Crafty.storage = (function () { + var db = null, url, gameName, timestamps = {}; + + /* + * Processes a retrieved object. + * Creates an entity if it is one + */ + function process(obj) { + if (obj.c) { + var d = Crafty.e(obj.c) + .attr(obj.attr) + .trigger('LoadData', obj, process); + return d; + } + else if (typeof obj == 'object') { + for (var prop in obj) { + obj[prop] = process(obj[prop]); + } + } + return obj; + } + + function unserialize(str) { + if (typeof str != 'string') return null; + var data = (JSON ? JSON.parse(str) : eval('(' + str + ')')); + return process(data); + } + + /* recursive function + * searches for entities in an object and processes them for serialization + */ + function prep(obj) { + if (obj.__c) { + // object is entity + var data = { c: [], attr: {} }; + obj.trigger("SaveData", data, prep); + for (var i in obj.__c) { + data.c.push(i); + } + data.c = data.c.join(', '); + obj = data; + } + else if (typeof obj == 'object') { + // recurse and look for entities + for (var prop in obj) { + obj[prop] = prep(obj[prop]); + } + } + return obj; + } + + function serialize(e) { + if (JSON) { + var data = prep(e); + return JSON.stringify(data); + } + else { + alert("Crafty does not support saving on your browser. Please upgrade to a newer browser."); + return false; + } + } + + // for saving a game to a central server + function external(setUrl) { + url = setUrl; + } + + function openExternal() { + if (1 && typeof url == "undefined") return; + // get the timestamps for external saves and compare them to local + // if the external is newer, load it + + var xml = new XMLHttpRequest(); + xhr.open("POST", url); + xhr.onreadystatechange = function (evt) { + if (xhr.readyState == 4) { + if (xhr.status == 200) { + var data = eval("(" + xhr.responseText + ")"); + for (var i in data) { + if (Crafty.storage.check(data[i].key, data[i].timestamp)) { + loadExternal(data[i].key); + } + } + } + } + } + xhr.send("mode=timestamps&game=" + gameName); + } + + function saveExternal(key, data, ts) { + if (1 && typeof url == "undefined") return; + var xhr = new XMLHttpRequest(); + xhr.open("POST", url); + xhr.send("mode=save&key=" + key + "&data=" + encodeURIComponent(data) + "&ts=" + ts + "&game=" + gameName); + } + + function loadExternal(key) { + if (1 && typeof url == "undefined") return; + var xhr = new XMLHttpRequest(); + xhr.open("POST", url); + xhr.onreadystatechange = function (evt) { + if (xhr.readyState == 4) { + if (xhr.status == 200) { + var data = eval("(" + xhr.responseText + ")"); + Crafty.storage.save(key, 'save', data); + } + } + } + xhr.send("mode=load&key=" + key + "&game=" + gameName); + } + + /** + * get timestamp + */ + function ts() { + var d = new Date(); + return d.getTime(); + } + + // everyone names their object different. Fix that nonsense. + if (typeof indexedDB != 'object') { + if (typeof mozIndexedDB == 'object') { + window.indexedDB = mozIndexedDB; + } + if (typeof webkitIndexedDB == 'object') { + window.indexedDB = webkitIndexedDB; + window.IDBTransaction = webkitIDBTransaction; + } + } + + if (typeof indexedDB == 'object') { + + return { + open: function (gameName_n) { + gameName = gameName_n; + var stores = []; + + if (arguments.length == 1) { + stores.push('save'); + stores.push('cache'); + } + else { + stores = arguments; + stores.shift(); + stores.push('save'); + stores.push('cache'); + } + if (db == null) { + var request = indexedDB.open(gameName, "Database for " + gameName); + request.onsuccess = function (e) { + db = e.target.result; + createStores(); + getTimestamps(); + openExternal(); + }; + } + else { + createStores(); + getTimestamps(); + openExternal(); + } + + // get all the timestamps for existing keys + function getTimestamps() { + try { + var trans = db.transaction(['save'], IDBTransaction.READ), + store = trans.objectStore('save'), + request = store.getAll(); + request.onsuccess = function (e) { + var i = 0, a = event.target.result, l = a.length; + for (; i < l; i++) { + timestamps[a[i].key] = a[i].timestamp; + } + }; + } + catch (e) { + } + } + + function createStores() { + var request = db.setVersion("1.0"); + request.onsuccess = function (e) { + for (var i = 0; i < stores.length; i++) { + var st = stores[i]; + if (db.objectStoreNames.contains(st)) continue; + db.createObjectStore(st, { keyPath: "key" }); + } + }; + } + }, + + save: function (key, type, data) { + if (db == null) { + setTimeout(function () { Crafty.storage.save(key, type, data); }, 1); + return; + } + + var str = serialize(data), t = ts(); + if (type == 'save') saveExternal(key, str, t); + try { + var trans = db.transaction([type], IDBTransaction.READ_WRITE), + store = trans.objectStore(type), + request = store.put({ + "data": str, + "timestamp": t, + "key": key + }); + } + catch (e) { + console.error(e); + } + }, + + load: function (key, type, callback) { + if (db == null) { + setTimeout(function () { Crafty.storage.load(key, type, callback); }, 1); + return; + } + try { + var trans = db.transaction([type], IDBTransaction.READ), + store = trans.objectStore(type), + request = store.get(key); + request.onsuccess = function (e) { + callback(unserialize(e.target.result.data)); + }; + } + catch (e) { + console.error(e); + } + }, + + getAllKeys: function (type, callback) { + if (db == null) { + setTimeout(function () { Crafty.storage.getAllkeys(type, callback); }, 1); + } + try { + var trans = db.transaction([type], IDBTransaction.READ), + store = trans.objectStore(type), + request = store.getCursor(), + res = []; + request.onsuccess = function (e) { + var cursor = e.target.result; + if (cursor) { + res.push(cursor.key); + // 'continue' is a reserved word, so .continue() causes IE8 to completely bark with "SCRIPT1010: Expected identifier". + cursor['continue'](); + } + else { + callback(res); + } + }; + } + catch (e) { + console.error(e); + } + }, + + check: function (key, timestamp) { + return (timestamps[key] > timestamp); + }, + + external: external + }; + } + else if (typeof openDatabase == 'function') { + return { + open: function (gameName_n) { + gameName = gameName_n; + if (arguments.length == 1) { + db = { + save: openDatabase(gameName_n + '_save', '1.0', 'Saves games for ' + gameName_n, 5 * 1024 * 1024), + cache: openDatabase(gameName_n + '_cache', '1.0', 'Cache for ' + gameName_n, 5 * 1024 * 1024) + } + } + else { + // allows for any other types that can be thought of + var args = arguments, i = 0; + args.shift(); + for (; i < args.length; i++) { + if (typeof db[args[i]] == 'undefined') + db[args[i]] = openDatabase(gameName + '_' + args[i], '1.0', type, 5 * 1024 * 1024); + } + } + + db['save'].transaction(function (tx) { + tx.executeSql('SELECT key, timestamp FROM data', [], function (tx, res) { + var i = 0, a = res.rows, l = a.length; + for (; i < l; i++) { + timestamps[a.item(i).key] = a.item(i).timestamp; + } + }); + }); + }, + + save: function (key, type, data) { + if (typeof db[type] == 'undefined' && gameName != '') { + this.open(gameName, type); + } + + var str = serialize(data), t = ts(); + if (type == 'save') saveExternal(key, str, t); + db[type].transaction(function (tx) { + tx.executeSql('CREATE TABLE IF NOT EXISTS data (key unique, text, timestamp)'); + tx.executeSql('SELECT * FROM data WHERE key = ?', [key], function (tx, results) { + if (results.rows.length) { + tx.executeSql('UPDATE data SET text = ?, timestamp = ? WHERE key = ?', [str, t, key]); + } + else { + tx.executeSql('INSERT INTO data VALUES (?, ?, ?)', [key, str, t]); + } + }); + }); + }, + + load: function (key, type, callback) { + if (db[type] == null) { + setTimeout(function () { Crafty.storage.load(key, type, callback); }, 1); + return; + } + db[type].transaction(function (tx) { + tx.executeSql('SELECT text FROM data WHERE key = ?', [key], function (tx, results) { + if (results.rows.length) { + res = unserialize(results.rows.item(0).text); + callback(res); + } + }); + }); + }, + + getAllKeys: function (type, callback) { + if (db[type] == null) { + setTimeout(function () { Crafty.storage.getAllKeys(type, callback); }, 1); + return; + } + db[type].transaction(function (tx) { + tx.executeSql('SELECT key FROM data', [], function (tx, results) { + callback(results.rows); + }); + }); + }, + + check: function (key, timestamp) { + return (timestamps[key] > timestamp); + }, + + external: external + }; + } + else if (typeof window.localStorage == 'object') { + return { + open: function (gameName_n) { + gameName = gameName_n; + }, + + save: function (key, type, data) { + var k = gameName + '.' + type + '.' + key, + str = serialize(data), + t = ts(); + if (type == 'save') saveExternal(key, str, t); + window.localStorage[k] = str; + if (type == 'save') + window.localStorage[k + '.ts'] = t; + }, + + load: function (key, type, callback) { + var k = gameName + '.' + type + '.' + key, + str = window.localStorage[k]; + + callback(unserialize(str)); + }, + + getAllKeys: function (type, callback) { + var res = {}, output = [], header = gameName + '.' + type; + for (var i in window.localStorage) { + if (i.indexOf(header) != -1) { + var key = i.replace(header, '').replace('.ts', ''); + res[key] = true; + } + } + for (i in res) { + output.push(i); + } + callback(output); + }, + + check: function (key, timestamp) { + var ts = window.localStorage[gameName + '.save.' + key + '.ts']; + + return (parseInt(timestamp) > parseInt(ts)); + }, + + external: external + }; + } + else { + // default fallback to cookies + return { + open: function (gameName_n) { + gameName = gameName_n; + }, + + save: function (key, type, data) { + // cookies are very limited in space. we can only keep saves there + if (type != 'save') return; + var str = serialize(data), t = ts(); + if (type == 'save') saveExternal(key, str, t); + document.cookie = gameName + '_' + key + '=' + str + '; ' + gameName + '_' + key + '_ts=' + t + '; expires=Thur, 31 Dec 2099 23:59:59 UTC; path=/'; + }, + + load: function (key, type, callback) { + if (type != 'save') return; + var reg = new RegExp(gameName + '_' + key + '=[^;]*'), + result = reg.exec(document.cookie), + data = unserialize(result[0].replace(gameName + '_' + key + '=', '')); + + callback(data); + }, + + getAllKeys: function (type, callback) { + if (type != 'save') return; + var reg = new RegExp(gameName + '_[^_=]', 'g'), + matches = reg.exec(document.cookie), + i = 0, l = matches.length, res = {}, output = []; + for (; i < l; i++) { + var key = matches[i].replace(gameName + '_', ''); + res[key] = true; + } + for (i in res) { + output.push(i); + } + callback(output); + }, + + check: function (key, timestamp) { + var header = gameName + '_' + key + '_ts', + reg = new RegExp(header + '=[^;]'), + result = reg.exec(document.cookie), + ts = result[0].replace(header + '=', ''); + + return (parseInt(timestamp) > parseInt(ts)); + }, + + external: external + }; + } + /* template + return { + open: function (gameName) { + }, + save: function (key, type, data) { + }, + load: function (key, type, callback) { + }, + }*/ +})(); /**@ +* #Text +* @category Graphics +* @trigger Change - when the text is changed +* @requires Canvas or DOM +* Component to draw text inside the body of an entity. +*/ +Crafty.c("Text", { + _text: "", + _textFont: { + "type": "", + "weight": "", + "size": "", + "family": "" + }, + ready: true, + + init: function () { + this.requires("2D"); + + this.bind("Draw", function (e) { + var font = this._textFont["type"] + ' ' + this._textFont["weight"] + ' ' + + this._textFont["size"] + ' ' + this._textFont["family"]; + + if (e.type === "DOM") { + var el = this._element, + style = el.style; + + style.color = this._textColor; + style.font = font; + el.innerHTML = this._text; + } else if (e.type === "canvas") { + var context = e.ctx, + metrics = null; + + context.save(); + + context.fillStyle = this._textColor || "rgb(0,0,0)"; + context.font = font; + + context.translate(this.x, this.y + this.h); + context.fillText(this._text, 0, 0); + + metrics = context.measureText(this._text); + this._w = metrics.width; + + context.restore(); + } + }); + }, + + /**@ + * #.text + * @comp Text + * @sign public this .text(String text) + * @sign public this .text(Function textgenerator) + * @param text - String of text that will be inserted into the DOM or Canvas element. + * This method will update the text inside the entity. + * If you use DOM, to modify the font, use the `.css` method inherited from the DOM component. + * + * If you need to reference attributes on the entity itself you can pass a function instead of a string. + * @example + * ~~~ + * Crafty.e("2D, DOM, Text").attr({ x: 100, y: 100 }).text("Look at me!!"); + * + * Crafty.e("2D, DOM, Text").attr({ x: 100, y: 100 }) + * .text(function () { return "My position is " + this._x }); + * + * Crafty.e("2D, Canvas, Text").attr({ x: 100, y: 100 }).text("Look at me!!"); + * + * Crafty.e("2D, Canvas, Text").attr({ x: 100, y: 100 }) + * .text(function () { return "My position is " + this._x }); + * ~~~ + */ + text: function (text) { + if (!text) return this._text; + if (typeof(text) == "function") + this._text = text.call(this); + else + this._text = text; + this.trigger("Change"); + return this; + }, + + /**@ + * #.textColor + * @comp Text + * @sign public this .textColor(String color, Number strength) + * @param color - The color in hexidecimal + * @param strength - Level of opacity + * + * Modify the text color and level of opacity. + * @example + * ~~~ + * Crafty.e("2D, DOM, Text").attr({ x: 100, y: 100 }).text("Look at me!!") + * .textColor('#FF0000'); + * + * Crafty.e("2D, Canvas, Text").attr({ x: 100, y: 100 }).text('Look at me!!') + * .textColor('#FF0000', 0.6); + * ~~~ + * @see Crafty.toRGB + */ + textColor: function (color, strength) { + this._strength = strength; + this._textColor = Crafty.toRGB(color, this._strength); + this.trigger("Change"); + return this; + }, + + /**@ + * #.textFont + * @comp Text + * @sign public this .textFont(String key, * value) + * @param key - Property of the entity to modify + * @param value - Value to set the property to + * + * @sign public this .textFont(Object map) + * @param map - Object where the key is the property to modify and the value as the property value + * @triggers Change + * + * Use this method to set font property of the text entity. + * @example + * ~~~ + * Crafty.e("2D, DOM, Text").textFont({ type: 'italic', family: 'Arial' }); + * Crafty.e("2D, Canvas, Text").textFont({ size: '20px', weight: 'bold' }); + * + * Crafty.e("2D, Canvas, Text").textFont("type", "italic"); + * Crafty.e("2D, Canvas, Text").textFont("type"); // italic + * ~~~ + */ + textFont: function (key, value) { + if (arguments.length === 1) { + //if just the key, return the value + if (typeof key === "string") { + return this._textFont[key]; + } + + if (typeof key === "object") { + for (propertyKey in key) { + this._textFont[propertyKey] = key[propertyKey]; + } + } + } else { + this._textFont[key] = value; + } + + this.trigger("Change"); + return this; + } +}); + Crafty.extend({ +/**@ + * #Crafty.assets + * @category Assets + * An object containing every asset used in the current Crafty game. + * The key is the URL and the value is the `Audio` or `Image` object. + * + * If loading an asset, check that it is in this object first to avoid loading twice. + * @example + * ~~~ + * var isLoaded = !!Crafty.assets["images/sprite.png"]; + * ~~~ + * @see Crafty.loader + */ + assets: {}, + + /**@ + * #Crafty.loader + * @category Assets + * @sign public void Crafty.load(Array assets, Function onLoad[, Function onProgress, Function onError]) + * @param assets - Array of assets to load (accepts sounds and images) + * @param onLoad - Callback when the assets are loaded + * @param onProgress - Callback when an asset is loaded. Contains information about assets loaded + * @param onError - Callback when an asset fails to load + * Preloader for all assets. Takes an array of URLs and + * adds them to the `Crafty.assets` object. + * + * Files with suffixes `jpg`, `jpeg`, `gif` and `png` (case insensitive) will be loaded. + * + * If `Crafty.support.audio` is `true`, files with the following suffixes `mp3`, `wav`, `ogg` and `mp4` (case insensitive) can be loaded. + * + * The `onProgress` function will be passed on object with information about + * the progress including how many assets loaded, total of all the assets to + * load and a percentage of the progress. + * + * + * { loaded: j, total: total, percent: (j / total * 100) }) + * + * `onError` will be passed with the asset that couldn't load. + * + * When `onError` is not provided, the onLoad is loaded even some assests are not successfully loaded. Otherwise, onLoad will be called no matter whether there are errors or not. + * @example + * ~~~ + * Crafty.load(["images/sprite.png", "sounds/jump.mp3"], + * function() { + * //when loaded + * Crafty.scene("main"); //go to main scene + * }, + * + * function(e) { + * //progress + * }, + * + * function(e) { + * //uh oh, error loading + * } + * ); + * ~~~ + * @see Crafty.assets + */ + load: function (data, oncomplete, onprogress, onerror) { + var i, l = data.length, current, obj, total = l, j = 0, ext; + for (i = 0; i < l; ++i) { + current = data[i]; + ext = current.substr(current.lastIndexOf('.') + 1).toLowerCase(); + + if (Crafty.support.audio && (ext === "mp3" || ext === "wav" || ext === "ogg" || ext === "mp4")) { + obj = new Audio(current); + //Chrome doesn't trigger onload on audio, see http://code.google.com/p/chromium/issues/detail?id=77794 + if (navigator.userAgent.indexOf('Chrome') != -1) j++; + } else if (ext === "jpg" || ext === "jpeg" || ext === "gif" || ext === "png") { + obj = new Image(); + obj.src = current; + } else { + total--; + continue; //skip if not applicable + } + + //add to global asset collection + this.assets[current] = obj; + + obj.onload = function () { + ++j; + + //if progress callback, give information of assets loaded, total and percent + if (onprogress) { + onprogress.call(this, { loaded: j, total: total, percent: (j / total * 100) }); + } + if (j === total) { + if (oncomplete) oncomplete(); + } + }; + + //if there is an error, pass it in the callback (this will be the object that didn't load) + obj.onerror = function () { + if (onerror) { + onerror.call(this, { loaded: j, total: total, percent: (j / total * 100) }); + } else { + j++; + if (j === total) { + if (oncomplete) oncomplete(); + } + } + }; + } + }, + /**@ + * #Crafty.modules + * @category Assets + * @sign public void Crafty.modules([String repoLocation,] Object moduleMap[, Function onLoad]) + * @param modules - Map of name:version pairs for modules to load + * @param onLoad - Callback when the modules are loaded + * Browse the selection of modules on crafty repositories. + * Downloads and executes the javascript in the specified modules. + * If no repository is specified it defaults to http://cdn.craftycomponents.com + * + * Available repositories: + * + * - http://cdn.crafty-modules.com + * - http://cdn.craftycomponents.com + * + * + * @example + * ~~~ + * // Loading from default repository + * Crafty.modules({ moveto: 'DEV' }, function () { + * //module is ready + * Crafty.e("MoveTo, 2D, DOM"); + * }); + * + * // Loading from your own server + * Crafty.modules({ 'http://mydomain.com/js/mystuff.js': 'DEV' }, function () { + * //module is ready + * Crafty.e("MoveTo, 2D, DOM"); + * }); + * + * // Loading from alternative repository + * Crafty.modules('http://cdn.crafty-modules.com', { moveto: 'DEV' }, function () { + * //module is ready + * Crafty.e("MoveTo, 2D, DOM"); + * }); + * + * // Loading from the latest component website + * Crafty.modules( + * 'http://cdn.craftycomponents.com' + * , { MoveTo: 'release' } + * , function () { + * Crafty.e("2D, DOM, Color, MoveTo") + * .attr({x: 0, y: 0, w: 50, h: 50}) + * .color("green"); + * }); + * }); + * ~~~ + * + */ + modules: function (modulesRepository, moduleMap, oncomplete) { + + if (arguments.length === 2 && typeof modulesRepository === "object") { + oncomplete = moduleMap; + moduleMap = modulesRepository; + modulesRepository = 'http://cdn.craftycomponents.com'; + } + + /*! + * $script.js Async loader & dependency manager + * https://github.com/ded/script.js + * (c) Dustin Diaz, Jacob Thornton 2011 + * License: MIT + */ + var $script = (function () { + var win = this, doc = document + , head = doc.getElementsByTagName('head')[0] + , validBase = /^https?:\/\// + , old = win.$script, list = {}, ids = {}, delay = {}, scriptpath + , scripts = {}, s = 'string', f = false + , push = 'push', domContentLoaded = 'DOMContentLoaded', readyState = 'readyState' + , addEventListener = 'addEventListener', onreadystatechange = 'onreadystatechange' + + function every(ar, fn, i) { + for (i = 0, j = ar.length; i < j; ++i) if (!fn(ar[i])) return f + return 1 + } + function each(ar, fn) { + every(ar, function (el) { + return !fn(el) + }) + } + + if (!doc[readyState] && doc[addEventListener]) { + doc[addEventListener](domContentLoaded, function fn() { + doc.removeEventListener(domContentLoaded, fn, f) + doc[readyState] = 'complete' + }, f) + doc[readyState] = 'loading' + } + + function $script(paths, idOrDone, optDone) { + paths = paths[push] ? paths : [paths] + var idOrDoneIsDone = idOrDone && idOrDone.call + , done = idOrDoneIsDone ? idOrDone : optDone + , id = idOrDoneIsDone ? paths.join('') : idOrDone + , queue = paths.length + function loopFn(item) { + return item.call ? item() : list[item] + } + function callback() { + if (!--queue) { + list[id] = 1 + done && done() + for (var dset in delay) { + every(dset.split('|'), loopFn) && !each(delay[dset], loopFn) && (delay[dset] = []) + } + } + } + setTimeout(function () { + each(paths, function (path) { + if (scripts[path]) { + id && (ids[id] = 1) + return scripts[path] == 2 && callback() + } + scripts[path] = 1 + id && (ids[id] = 1) + create(!validBase.test(path) && scriptpath ? scriptpath + path + '.js' : path, callback) + }) + }, 0) + return $script + } + + function create(path, fn) { + var el = doc.createElement('script') + , loaded = f + el.onload = el.onerror = el[onreadystatechange] = function () { + if ((el[readyState] && !(/^c|loade/.test(el[readyState]))) || loaded) return; + el.onload = el[onreadystatechange] = null + loaded = 1 + scripts[path] = 2 + fn() + } + el.async = 1 + el.src = path + head.insertBefore(el, head.firstChild) + } + + $script.get = create + + $script.order = function (scripts, id, done) { + (function callback(s) { + s = scripts.shift() + if (!scripts.length) $script(s, id, done) + else $script(s, callback) + }()) + } + + $script.path = function (p) { + scriptpath = p + } + $script.ready = function (deps, ready, req) { + deps = deps[push] ? deps : [deps] + var missing = []; + !each(deps, function (dep) { + list[dep] || missing[push](dep); + }) && every(deps, function (dep) { return list[dep] }) ? + ready() : !function (key) { + delay[key] = delay[key] || [] + delay[key][push](ready) + req && req(missing) + }(deps.join('|')) + return $script + } + + $script.noConflict = function () { + win.$script = old; + return this + } + + return $script + })(); + + var modules = []; + for (var i in moduleMap) { + if (i.indexOf("http://") != -1) + modules.push(i) + else + modules.push(modulesRepository + '/' + i.toLowerCase() + '-' + moduleMap[i].toLowerCase() + '.js'); + } + + $script(modules, function () { + if (oncomplete) oncomplete(); + }); + } +}); + /**@ +* #Crafty.math +* @category 2D +* Static functions. +*/ +Crafty.math = { +/**@ + * #Crafty.math.abs + * @comp Crafty.math + * @sign public this Crafty.math.abs(Number n) + * @param n - Some value. + * @return Absolute value. + * Returns the absolute value. + */ + abs: function (x) { + return x < 0 ? -x : x; + }, + + /**@ + * #Crafty.math.amountOf + * @comp Crafty.math + * @sign public Number Crafty.math.amountOf(Number checkValue, Number minValue, Number maxValue) + * @param checkValue - Value that should checked with minimum and maximum. + * @param minValue - Minimum value to check. + * @param maxValue - Maximum value to check. + * @return Amount of checkValue compared to minValue and maxValue. + * Returns the amount of how much a checkValue is more like minValue (=0) + * or more like maxValue (=1) + */ + amountOf: function (checkValue, minValue, maxValue) { + if (minValue < maxValue) + return (checkValue - minValue) / (maxValue - minValue); + else + return (checkValue - maxValue) / (minValue - maxValue); + }, + + + /**@ + * #Crafty.math.clamp + * @comp Crafty.math + * @sign public Number Crafty.math.clamp(Number value, Number min, Number max) + * @param value - A value. + * @param max - Maximum that value can be. + * @param min - Minimum that value can be. + * @return The value between minimum and maximum. + * Restricts a value to be within a specified range. + */ + clamp: function (value, min, max) { + if (value > max) + return max; + else if (value < min) + return min; + else + return value; + }, + + /**@ + * Converts angle from degree to radian. + * @comp Crafty.math + * @param angleInDeg - The angle in degree. + * @return The angle in radian. + */ + degToRad: function (angleInDeg) { + return angleInDeg * Math.PI / 180; + }, + + /**@ + * #Crafty.math.distance + * @comp Crafty.math + * @sign public Number Crafty.math.distance(Number x1, Number y1, Number x2, Number y2) + * @param x1 - First x coordinate. + * @param y1 - First y coordinate. + * @param x2 - Second x coordinate. + * @param y2 - Second y coordinate. + * @return The distance between the two points. + * Distance between two points. + */ + distance: function (x1, y1, x2, y2) { + var squaredDistance = Crafty.math.squaredDistance(x1, y1, x2, y2); + return Math.sqrt(parseFloat(squaredDistance)); + }, + + /**@ + * #Crafty.math.lerp + * @comp Crafty.math + * @sign public Number Crafty.math.lerp(Number value1, Number value2, Number amount) + * @param value1 - One value. + * @param value2 - Another value. + * @param amount - Amount of value2 to value1. + * @return Linear interpolated value. + * Linear interpolation. Passing amount with a value of 0 will cause value1 to be returned, + * a value of 1 will cause value2 to be returned. + */ + lerp: function (value1, value2, amount) { + return value1 + (value2 - value1) * amount; + }, + + /**@ + * #Crafty.math.negate + * @comp Crafty.math + * @sign public Number Crafty.math.negate(Number percent) + * @param percent - If you pass 1 a -1 will be returned. If you pass 0 a 1 will be returned. + * @return 1 or -1. + * Returnes "randomly" -1. + */ + negate: function (percent) { + if (Math.random() < percent) + return -1; + else + return 1; + }, + + /**@ + * #Crafty.math.radToDeg + * @comp Crafty.math + * @sign public Number Crafty.math.radToDeg(Number angle) + * @param angleInRad - The angle in radian. + * @return The angle in degree. + * Converts angle from radian to degree. + */ + radToDeg: function (angleInRad) { + return angleInRad * 180 / Math.PI; + }, + + /**@ + * #Crafty.math.randomElementOfArray + * @comp Crafty.math + * @sign public Object Crafty.math.randomElementOfArray(Array array) + * @param array - A specific array. + * @return A random element of a specific array. + * Returns a random element of a specific array. + */ + randomElementOfArray: function (array) { + return array[Math.floor(array.length * Math.random())]; + }, + + /**@ + * #Crafty.math.randomInt + * @comp Crafty.math + * @sign public Number Crafty.math.randomInt(Number start, Number end) + * @param start - Smallest int value that can be returned. + * @param end - Biggest int value that can be returned. + * @return A random int. + * Returns a random int in within a specific range. + */ + randomInt: function (start, end) { + return start + Math.floor((1 + end - start) * Math.random()); + }, + + /**@ + * #Crafty.math.randomNumber + * @comp Crafty.math + * @sign public Number Crafty.math.randomInt(Number start, Number end) + * @param start - Smallest number value that can be returned. + * @param end - Biggest number value that can be returned. + * @return A random number. + * Returns a random number in within a specific range. + */ + randomNumber: function (start, end) { + return start + (end - start) * Math.random(); + }, + + /**@ + * #Crafty.math.squaredDistance + * @comp Crafty.math + * @sign public Number Crafty.math.squaredDistance(Number x1, Number y1, Number x2, Number y2) + * @param x1 - First x coordinate. + * @param y1 - First y coordinate. + * @param x2 - Second x coordinate. + * @param y2 - Second y coordinate. + * @return The squared distance between the two points. + * Squared distance between two points. + */ + squaredDistance: function (x1, y1, x2, y2) { + return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2); + }, + + /**@ + * #Crafty.math.squaredDistance + * @comp Crafty.math + * @sign public Boolean Crafty.math.withinRange(Number value, Number min, Number max) + * @param value - The specific value. + * @param min - Minimum value. + * @param max - Maximum value. + * @return Returns true if value is within a specific range. + * Check if a value is within a specific range. + */ + withinRange: function (value, min, max) { + return (value >= min && value <= max); + } +}; + +Crafty.math.Vector2D = (function () { + /**@ + * #Crafty.math.Vector2D + * + * @class This is a general purpose 2D vector class + * + * Vector2D uses the following form: + *+ * + * @public + * @sign public {Vector2D} Vector2D(); + * @sign public {Vector2D} Vector2D(Vector2D); + * @sign public {Vector2D} Vector2D(Number, Number); + * @param {Vector2D|Number=0} x + * @param {Number=0} y + */ + function Vector2D(x, y) { + if (x instanceof Vector2D) { + this.x = x.x; + this.y = x.y; + } else if (arguments.length === 2) { + this.x = x; + this.y = y; + } else if (arguments.length > 0) + throw "Unexpected number of arguments for Vector2D()"; + } // class Vector2D + + Vector2D.prototype.x = 0; + Vector2D.prototype.y = 0; + + /**@ + * #.add( ) + * + * Adds the passed vector to this vector + * + * @public + * @sign public {Vector2D} add(Vector2D); + * @param {vector2D} vecRH + * @returns {Vector2D} this after adding + */ + Vector2D.prototype.add = function (vecRH) { + this.x += vecRH.x; + this.y += vecRH.y; + return this; + } // add( ) + + /**@ + * #.angleBetween( ) + * + * Calculates the angle between the passed vector and this vector, using <0,0> as the point of reference. + * Angles returned have the range (−π, π]. + * + * @public + * @sign public {Number} angleBetween(Vector2D); + * @param {Vector2D} vecRH + * @returns {Number} the angle between the two vectors in radians + */ + Vector2D.prototype.angleBetween = function (vecRH) { + return Math.atan2(this.x * vecRH.y - this.y * vecRH.x, this.x * vecRH.x + this.y * vecRH.y); + } // angleBetween( ) + + /**@ + * #.angleTo( ) + * + * Calculates the angle to the passed vector from this vector, using this vector as the point of reference. + * + * @public + * @sign public {Number} angleTo(Vector2D); + * @param {Vector2D} vecRH + * @returns {Number} the angle to the passed vector in radians + */ + Vector2D.prototype.angleTo = function (vecRH) { + return Math.atan2(vecRH.y - this.y, vecRH.x - this.x); + }; + + /**@ + * #.clone( ) + * + * Creates and exact, numeric copy of this vector + * + * @public + * @sign public {Vector2D} clone(); + * @returns {Vector2D} the new vector + */ + Vector2D.prototype.clone = function () { + return new Vector2D(this); + } // clone( ) + + /**@ + * #.distance( ) + * + * Calculates the distance from this vector to the passed vector. + * + * @public + * @sign public {Number} distance(Vector2D); + * @param {Vector2D} vecRH + * @returns {Number} the distance between the two vectors + */ + Vector2D.prototype.distance = function (vecRH) { + return Math.sqrt((vecRH.x - this.x) * (vecRH.x - this.x) + (vecRH.y - this.y) * (vecRH.y - this.y)); + } // distance( ) + + /**@ + * #.distanceSq( ) + * + * Calculates the squared distance from this vector to the passed vector. + * This function avoids calculating the square root, thus being slightly faster than .distance( ). + * + * @public + * @sign public {Number} distanceSq(Vector2D); + * @param {Vector2D} vecRH + * @returns {Number} the squared distance between the two vectors + * @see Vector2D.distance( ) + */ + Vector2D.prototype.distanceSq = function (vecRH) { + return (vecRH.x - this.x) * (vecRH.x - this.x) + (vecRH.y - this.y) * (vecRH.y - this.y); + } // distanceSq( ) + + /**@ + * #.divide( ) + * + * Divides this vector by the passed vector. + * + * @public + * @sign public {Vector2D} divide(Vector2D); + * @param {Vector2D} vecRH + * @returns {Vector2D} this vector after dividing + */ + Vector2D.prototype.divide = function (vecRH) { + this.x /= vecRH.x; + this.y /= vecRH.y; + return this; + } // divide( ) + + /**@ + * #.dotProduct( ) + * + * Calculates the dot product of this and the passed vectors + * + * @public + * @sign public {Number} dotProduct(Vector2D); + * @param {Vector2D} vecRH + * @returns {Number} the resultant dot product + */ + Vector2D.prototype.dotProduct = function (vecRH) { + return this.x * vecRH.x + this.y * vecRH.y; + } // dotProduct( ) + + /**@ + * #.equals( ) + * + * Determines if this vector is numerically equivalent to the passed vector. + * + * @public + * @sign public {Boolean} equals(Vector2D); + * @param {Vector2D} vecRH + * @returns {Boolean} true if the vectors are equivalent + */ + Vector2D.prototype.equals = function (vecRH) { + return vecRH instanceof Vector2D && + this.x == vecRH.x && this.y == vecRH.y; + } // equals( ) + + /**@ + * #.getNormal( ) + * + * Calculates a new right-handed normal vector for the line created by this and the passed vectors. + * + * @public + * @sign public {Vector2D} getNormal([Vector2D]); + * @param {Vector2D=<0,0>} [vecRH] + * @returns {Vector2D} the new normal vector + */ + Vector2D.prototype.getNormal = function (vecRH) { + if (vecRH === undefined) + return new Vector2D(-this.y, this.x); // assume vecRH is <0, 0> + return new Vector2D(vecRH.y - this.y, this.x - vecRH.x).normalize(); + } // getNormal( ) + + /**@ + * #.isZero( ) + * + * Determines if this vector is equal to <0,0> + * + * @public + * @sign public {Boolean} isZero(); + * @returns {Boolean} true if this vector is equal to <0,0> + */ + Vector2D.prototype.isZero = function () { + return this.x === 0 && this.y === 0; + } // isZero( ) + + /**@ + * #.magnitude( ) + * + * Calculates the magnitude of this vector. + * Note: Function objects in JavaScript already have a 'length' member, hence the use of magnitude instead. + * + * @public + * @sign public {Number} magnitude(); + * @returns {Number} the magnitude of this vector + */ + Vector2D.prototype.magnitude = function () { + return Math.sqrt(this.x * this.x + this.y * this.y); + } // magnitude( ) + + /**@ + * #.magnitudeSq( ) + * + * Calculates the square of the magnitude of this vector. + * This function avoids calculating the square root, thus being slightly faster than .magnitude( ). + * + * @public + * @sign public {Number} magnitudeSq(); + * @returns {Number} the square of the magnitude of this vector + * @see Vector2D.magnitude( ) + */ + Vector2D.prototype.magnitudeSq = function () { + return this.x * this.x + this.y * this.y; + } // magnitudeSq( ) + + /**@ + * #.multiply( ) + * + * Multiplies this vector by the passed vector + * + * @public + * @sign public {Vector2D} multiply(Vector2D); + * @param {Vector2D} vecRH + * @returns {Vector2D} this vector after multiplying + */ + Vector2D.prototype.multiply = function (vecRH) { + this.x *= vecRH.x; + this.y *= vecRH.y; + return this; + } // multiply( ) + + /**@ + * #.negate( ) + * + * Negates this vector (ie. <-x,-y>) + * + * @public + * @sign public {Vector2D} negate(); + * @returns {Vector2D} this vector after negation + */ + Vector2D.prototype.negate = function () { + this.x = -this.x; + this.y = -this.y; + return this; + } // negate( ) + + /**@ + * #.normalize( ) + * + * Normalizes this vector (scales the vector so that its new magnitude is 1) + * For vectors where magnitude is 0, <1,0> is returned. + * + * @public + * @sign public {Vector2D} normalize(); + * @returns {Vector2D} this vector after normalization + */ + Vector2D.prototype.normalize = function () { + var lng = Math.sqrt(this.x * this.x + this.y * this.y); + + if (lng === 0) { + // default due East + this.x = 1; + this.y = 0; + } else { + this.x /= lng; + this.y /= lng; + } // else + + return this; + } // normalize( ) + + /**@ + * #.scale( ) + * + * Scales this vector by the passed amount(s) + * If scalarY is omitted, scalarX is used for both axes + * + * @public + * @sign public {Vector2D} scale(Number[, Number]); + * @param {Number} scalarX + * @param {Number} [scalarY] + * @returns {Vector2D} this after scaling + */ + Vector2D.prototype.scale = function (scalarX, scalarY) { + if (scalarY === undefined) + scalarY = scalarX; + + this.x *= scalarX; + this.y *= scalarY; + + return this; + } // scale( ) + + /**@ + * #.scaleToMagnitude( ) + * + * Scales this vector such that its new magnitude is equal to the passed value. + * + * @public + * @sign public {Vector2D} scaleToMagnitude(Number); + * @param {Number} mag + * @returns {Vector2D} this vector after scaling + */ + Vector2D.prototype.scaleToMagnitude = function (mag) { + var k = mag / this.magnitude(); + this.x *= k; + this.y *= k; + return this; + } // scaleToMagnitude( ) + + /**@ + * #.setValues( ) + * + * Sets the values of this vector using a passed vector or pair of numbers. + * + * @public + * @sign public {Vector2D} setValues(Vector2D); + * @sign public {Vector2D} setValues(Number, Number); + * @param {Number|Vector2D} x + * @param {Number} y + * @returns {Vector2D} this vector after setting of values + */ + Vector2D.prototype.setValues = function (x, y) { + if (x instanceof Vector2D) { + this.x = x.x; + this.y = x.y; + } else { + this.x = x; + this.y = y; + } // else + + return this; + } // setValues( ) + + /**@ + * #.subtract( ) + * + * Subtracts the passed vector from this vector. + * + * @public + * @sign public {Vector2D} subtract(Vector2D); + * @param {Vector2D} vecRH + * @returns {vector2D} this vector after subtracting + */ + Vector2D.prototype.subtract = function (vecRH) { + this.x -= vecRH.x; + this.y -= vecRH.y; + return this; + } // subtract( ) + + /**@ + * #.toString( ) + * + * Returns a string representation of this vector. + * + * @public + * @sign public {String} toString(); + * @returns {String} + */ + Vector2D.prototype.toString = function () { + return "Vector2D(" + this.x + ", " + this.y + ")"; + } // toString( ) + + /**@ + * #.translate( ) + * + * Translates (moves) this vector by the passed amounts. + * If dy is omitted, dx is used for both axes. + * + * @public + * @sign public {Vector2D} translate(Number[, Number]); + * @param {Number} dx + * @param {Number} [dy] + * @returns {Vector2D} this vector after translating + */ + Vector2D.prototype.translate = function (dx, dy) { + if (dy === undefined) + dy = dx; + + this.x += dx; + this.y += dy; + + return this; + } // translate( ) + + /**@ + * #.tripleProduct( ) + * + * Calculates the triple product of three vectors. + * triple vector product = b(a•c) - a(b•c) + * + * @public + * @static + * @sign public {Vector2D} tripleProduct(Vector2D, Vector2D, Vector2D); + * @param {Vector2D} a + * @param {Vector2D} b + * @param {Vector2D} c + * @return {Vector2D} the triple product as a new vector + */ + Vector2D.tripleProduct = function (a, b, c) { + var ac = a.dotProduct(c); + var bc = b.dotProduct(c); + return new Crafty.math.Vector2D(b.x * ac - a.x * bc, b.y * ac - a.y * bc); + }; + + return Vector2D; +})(); + +Crafty.math.Matrix2D = (function () { + /**@ + * #Crafty.math.Matrix2D + * + * @class This is a 2D Matrix2D class. It is 3x3 to allow for affine transformations in 2D space. + * The third row is always assumed to be [0, 0, 1]. + * + * Matrix2D uses the following form, as per the whatwg.org specifications for canvas.transform(): + * [a, c, e] + * [b, d, f] + * [0, 0, 1] + * + * @public + * @sign public {Matrix2D} new Matrix2D(); + * @sign public {Matrix2D} new Matrix2D(Matrix2D); + * @sign public {Matrix2D} new Matrix2D(Number, Number, Number, Number, Number, Number); + * @param {Matrix2D|Number=1} a + * @param {Number=0} b + * @param {Number=0} c + * @param {Number=1} d + * @param {Number=0} e + * @param {Number=0} f + */ + Matrix2D = function (a, b, c, d, e, f) { + if (a instanceof Matrix2D) { + this.a = a.a; + this.b = a.b; + this.c = a.c; + this.d = a.d; + this.e = a.e; + this.f = a.f; + } else if (arguments.length === 6) { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.e = e; + this.f = f; + } else if (arguments.length > 0) + throw "Unexpected number of arguments for Matrix2D()"; + } // class Matrix2D + + Matrix2D.prototype.a = 1; + Matrix2D.prototype.b = 0; + Matrix2D.prototype.c = 0; + Matrix2D.prototype.d = 1; + Matrix2D.prototype.e = 0; + Matrix2D.prototype.f = 0; + + /**@ + * #.apply( ) + * + * Applies the matrix transformations to the passed object + * + * @public + * @sign public {Vector2D} apply(Vector2D); + * @param {Vector2D} vecRH - vector to be transformed + * @returns {Vector2D} the passed vector object after transforming + */ + Matrix2D.prototype.apply = function (vecRH) { + // I'm not sure of the best way for this function to be implemented. Ideally + // support for other objects (rectangles, polygons, etc) should be easily + // addable in the future. Maybe a function (apply) is not the best way to do + // this...? + + var tmpX = vecRH.x; + vecRH.x = tmpX * this.a + vecRH.y * this.c + this.e; + vecRH.y = tmpX * this.b + vecRH.y * this.d + this.f; + // no need to homogenize since the third row is always [0, 0, 1] + + return vecRH; + } // apply( ) + + /**@ + * #.clone( ) + * + * Creates an exact, numeric copy of the current matrix + * + * @public + * @sign public {Matrix2D} clone(); + * @returns {Matrix2D} + */ + Matrix2D.prototype.clone = function () { + return new Matrix2D(this); + } // clone( ) + + /**@ + * #.combine( ) + * + * Multiplies this matrix with another, overriding the values of this matrix. + * The passed matrix is assumed to be on the right-hand side. + * + * @public + * @sign public {Matrix2D} combine(Matrix2D); + * @param {Matrix2D} mtrxRH + * @returns {Matrix2D} this matrix after combination + */ + Matrix2D.prototype.combine = function (mtrxRH) { + var tmp = this.a; + this.a = tmp * mtrxRH.a + this.b * mtrxRH.c; + this.b = tmp * mtrxRH.b + this.b * mtrxRH.d; + tmp = this.c; + this.c = tmp * mtrxRH.a + this.d * mtrxRH.c; + this.d = tmp * mtrxRH.b + this.d * mtrxRH.d; + tmp = this.e; + this.e = tmp * mtrxRH.a + this.f * mtrxRH.c + mtrxRH.e; + this.f = tmp * mtrxRH.b + this.f * mtrxRH.d + mtrxRH.f; + return this; + } // combine( ) + + /**@ + * #.equals( ) + * + * Checks for the numeric equality of this matrix versus another. + * + * @public + * @sign public {Boolean} equals(Matrix2D); + * @param {Matrix2D} mtrxRH + * @returns {Boolean} true if the two matrices are numerically equal + */ + Matrix2D.prototype.equals = function (mtrxRH) { + return mtrxRH instanceof Matrix2D && + this.a == mtrxRH.a && this.b == mtrxRH.b && this.c == mtrxRH.c && + this.d == mtrxRH.d && this.e == mtrxRH.e && this.f == mtrxRH.f; + } // equals( ) + + /**@ + * #.determinant( ) + * + * Calculates the determinant of this matrix + * + * @public + * @sign public {Number} determinant(); + * @returns {Number} det(this matrix) + */ + Matrix2D.prototype.determinant = function () { + return this.a * this.d - this.b * this.c; + } // determinant( ) + + /**@ + * #.invert( ) + * + * Inverts this matrix if possible + * + * @public + * @sign public {Matrix2D} invert(); + * @returns {Matrix2D} this inverted matrix or the original matrix on failure + * @see Matrix2D.isInvertible( ) + */ + Matrix2D.prototype.invert = function () { + var det = this.determinant(); + + // matrix is invertible if its determinant is non-zero + if (det !== 0) { + var old = { + a: this.a, + b: this.b, + c: this.c, + d: this.d, + e: this.e, + f: this.f + }; + this.a = old.d / det; + this.b = -old.b / det; + this.c = -old.c / det; + this.d = old.a / det; + this.e = (old.c * old.f - old.e * old.d) / det; + this.f = (old.e * old.b - old.a * old.f) / det; + } // if + + return this; + } // invert( ) + + /**@ + * #.isIdentity( ) + * + * Returns true if this matrix is the identity matrix + * + * @public + * @sign public {Boolean} isIdentity(); + * @returns {Boolean} + */ + Matrix2D.prototype.isIdentity = function () { + return this.a === 1 && this.b === 0 && this.c === 0 && this.d === 1 && this.e === 0 && this.f === 0; + } // isIdentity( ) + + /**@ + * #.isInvertible( ) + * + * Determines is this matrix is invertible. + * + * @public + * @sign public {Boolean} isInvertible(); + * @returns {Boolean} true if this matrix is invertible + * @see Matrix2D.invert( ) + */ + Matrix2D.prototype.isInvertible = function () { + return this.determinant() !== 0; + } // isInvertible( ) + + /**@ + * #.preRotate( ) + * + * Applies a counter-clockwise pre-rotation to this matrix + * + * @public + * @sign public {Matrix2D} preRotate(Number); + * @param {number} rads - angle to rotate in radians + * @returns {Matrix2D} this matrix after pre-rotation + */ + Matrix2D.prototype.preRotate = function (rads) { + var nCos = Math.cos(rads); + var nSin = Math.sin(rads); + + var tmp = this.a; + this.a = nCos * tmp - nSin * this.b; + this.b = nSin * tmp + nCos * this.b; + tmp = this.c; + this.c = nCos * tmp - nSin * this.d; + this.d = nSin * tmp + nCos * this.d; + + return this; + } // preRotate( ) + + /**@ + * #.preScale( ) + * + * Applies a pre-scaling to this matrix + * + * @public + * @sign public {Matrix2D} preScale(Number[, Number]); + * @param {Number} scalarX + * @param {Number} [scalarY] scalarX is used if scalarY is undefined + * @returns {Matrix2D} this after pre-scaling + */ + Matrix2D.prototype.preScale = function (scalarX, scalarY) { + if (scalarY === undefined) + scalarY = scalarX; + + this.a *= scalarX; + this.b *= scalarY; + this.c *= scalarX; + this.d *= scalarY; + + return this; + } // preScale( ) + + /**@ + * #.preTranslate( ) + * + * Applies a pre-translation to this matrix + * + * @public + * @sign public {Matrix2D} preTranslate(Vector2D); + * @sign public {Matrix2D} preTranslate(Number, Number); + * @param {Number|Vector2D} dx + * @param {Number} dy + * @returns {Matrix2D} this matrix after pre-translation + */ + Matrix2D.prototype.preTranslate = function (dx, dy) { + if (typeof dx === "number") { + this.e += dx; + this.f += dy; + } else { + this.e += dx.x; + this.f += dx.y; + } // else + + return this; + } // preTranslate( ) + + /**@ + * #.rotate( ) + * + * Applies a counter-clockwise post-rotation to this matrix + * + * @public + * @sign public {Matrix2D} rotate(Number); + * @param {Number} rads - angle to rotate in radians + * @returns {Matrix2D} this matrix after rotation + */ + Matrix2D.prototype.rotate = function (rads) { + var nCos = Math.cos(rads); + var nSin = Math.sin(rads); + + var tmp = this.a; + this.a = nCos * tmp - nSin * this.b; + this.b = nSin * tmp + nCos * this.b; + tmp = this.c; + this.c = nCos * tmp - nSin * this.d; + this.d = nSin * tmp + nCos * this.d; + tmp = this.e; + this.e = nCos * tmp - nSin * this.f; + this.f = nSin * tmp + nCos * this.f; + + return this; + } // rotate( ) + + /**@ + * #.scale( ) + * + * Applies a post-scaling to this matrix + * + * @public + * @sign public {Matrix2D} scale(Number[, Number]); + * @param {Number} scalarX + * @param {Number} [scalarY] scalarX is used if scalarY is undefined + * @returns {Matrix2D} this after post-scaling + */ + Matrix2D.prototype.scale = function (scalarX, scalarY) { + if (scalarY === undefined) + scalarY = scalarX; + + this.a *= scalarX; + this.b *= scalarY; + this.c *= scalarX; + this.d *= scalarY; + this.e *= scalarX; + this.f *= scalarY; + + return this; + } // scale( ) + + /**@ + * #.setValues( ) + * + * Sets the values of this matrix + * + * @public + * @sign public {Matrix2D} setValues(Matrix2D); + * @sign public {Matrix2D} setValues(Number, Number, Number, Number, Number, Number); + * @param {Matrix2D|Number} a + * @param {Number} b + * @param {Number} c + * @param {Number} d + * @param {Number} e + * @param {Number} f + * @returns {Matrix2D} this matrix containing the new values + */ + Matrix2D.prototype.setValues = function (a, b, c, d, e, f) { + if (a instanceof Matrix2D) { + this.a = a.a; + this.b = a.b; + this.c = a.c; + this.d = a.d; + this.e = a.e; + this.f = a.f; + } else { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.e = e; + this.f = f; + } // else + + return this; + } // setValues( ) + + /**@ + * #.toString( ) + * + * Returns the string representation of this matrix. + * + * @public + * @sign public {String} toString(); + * @returns {String} + */ + Matrix2D.prototype.toString = function () { + return "Matrix2D([" + this.a + ", " + this.c + ", " + this.e + + "] [" + this.b + ", " + this.d + ", " + this.f + "] [0, 0, 1])"; + } // toString( ) + + /**@ + * #.translate( ) + * + * Applies a post-translation to this matrix + * + * @public + * @sign public {Matrix2D} translate(Vector2D); + * @sign public {Matrix2D} translate(Number, Number); + * @param {Number|Vector2D} dx + * @param {Number} dy + * @returns {Matrix2D} this matrix after post-translation + */ + Matrix2D.prototype.translate = function (dx, dy) { + if (typeof dx === "number") { + this.e += this.a * dx + this.c * dy; + this.f += this.b * dx + this.d * dy; + } else { + this.e += this.a * dx.x + this.c * dx.y; + this.f += this.b * dx.x + this.d * dx.y; + } // else + + return this; + } // translate( ) + + return Matrix2D; +})(); + /**@ +* #.delay +* @comp Crafty Time +* @sign public this.delay(Function callback, Number delay) +* @param callback - Method to execute after given amount of milliseconds +* @param delay - Amount of milliseconds to execute the method +* The delay method will execute a function after a given amount of time in milliseconds. +* +* It is not a wrapper for `setTimeout`. +* +* If Crafty is paused, the delay is interrupted with the pause and then resume when unpaused +* +* If the entity is destroyed, the delay is also destroyed and will not have effect. +* +* @example +* ~~~ +* console.log("start"); +* this.delay(function() { + console.log("100ms later"); +* }, 100); +* ~~~ +*/ +Crafty.c("Delay", { + init : function() { + this._delays = []; + this.bind("EnterFrame", function() { + var now = new Date().getTime(); + for(var index in this._delays) { + var item = this._delays[index]; + if(!item.triggered && item.start + item.delay + item.pause < now) { + item.triggered=true; + item.func.call(this); + } + } + }); + this.bind("Pause", function() { + var now = new Date().getTime(); + for(var index in this._delays) { + this._delays[index].pauseBuffer = now; + } + }); + this.bind("Unpause", function() { + var now = new Date().getTime(); + for(var index in this._delays) { + var item = this._delays[index]; + item.pause += now-item.pauseBuffer; + } + }); + }, + /**@ + * Set a new delay. + * @param func the callback function + * @param delay the delay in ms + */ + delay : function(func, delay) { + return this._delays.push({ + start : new Date().getTime(), + func : func, + delay : delay, + triggered : false, + pauseBuffer: 0, + pause: 0 + }); + } +}); + })(Crafty,window,window.document); \ No newline at end of file diff --git a/assets/js/libs/jquery.js b/assets/js/libs/jquery.js new file mode 100644 index 0000000..8ccd0ea --- /dev/null +++ b/assets/js/libs/jquery.js @@ -0,0 +1,9266 @@ +/*! + * jQuery JavaScript Library v1.7.1 + * http://jquery.com/ + * + * Copyright 2011, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Mon Nov 21 21:11:03 2011 -0500 + */ +(function( window, undefined ) { + +// Use the correct document accordingly with window argument (sandbox) +var document = window.document, + navigator = window.navigator, + location = window.location; +var jQuery = (function() { + +// Define a local copy of jQuery +var jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context, rootjQuery ); + }, + + // Map over jQuery in case of overwrite + _jQuery = window.jQuery, + + // Map over the $ in case of overwrite + _$ = window.$, + + // A central reference to the root jQuery(document) + rootjQuery, + + // A simple way to check for HTML strings or ID strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/, + + // Check if a string has a non-whitespace character in it + rnotwhite = /\S/, + + // Used for trimming whitespace + trimLeft = /^\s+/, + trimRight = /\s+$/, + + // Match a standalone tag + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, + + // JSON RegExp + rvalidchars = /^[\],:{}\s]*$/, + rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, + rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, + rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, + + // Useragent RegExp + rwebkit = /(webkit)[ \/]([\w.]+)/, + ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, + rmsie = /(msie) ([\w.]+)/, + rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, + + // Matches dashed string for camelizing + rdashAlpha = /-([a-z]|[0-9])/ig, + rmsPrefix = /^-ms-/, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return ( letter + "" ).toUpperCase(); + }, + + // Keep a UserAgent string for use with jQuery.browser + userAgent = navigator.userAgent, + + // For matching the engine and version of the browser + browserMatch, + + // The deferred used on DOM ready + readyList, + + // The ready event handler + DOMContentLoaded, + + // Save a reference to some core methods + toString = Object.prototype.toString, + hasOwn = Object.prototype.hasOwnProperty, + push = Array.prototype.push, + slice = Array.prototype.slice, + trim = String.prototype.trim, + indexOf = Array.prototype.indexOf, + + // [[Class]] -> type pairs + class2type = {}; + +jQuery.fn = jQuery.prototype = { + constructor: jQuery, + init: function( selector, context, rootjQuery ) { + var match, elem, ret, doc; + + // Handle $(""), $(null), or $(undefined) + if ( !selector ) { + return this; + } + + // Handle $(DOMElement) + if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + } + + // The body element only exists once, optimize finding it + if ( selector === "body" && !context && document.body ) { + this.context = document; + this[0] = document.body; + this.selector = selector; + this.length = 1; + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + // Are we dealing with HTML string or an ID? + if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = quickExpr.exec( selector ); + } + + // Verify a match, and that no context was specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; + doc = ( context ? context.ownerDocument || context : document ); + + // If a single string is passed in and it's a single tag + // just do a createElement and skip the rest + ret = rsingleTag.exec( selector ); + + if ( ret ) { + if ( jQuery.isPlainObject( context ) ) { + selector = [ document.createElement( ret[1] ) ]; + jQuery.fn.attr.call( selector, context, true ); + + } else { + selector = [ doc.createElement( ret[1] ) ]; + } + + } else { + ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); + selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes; + } + + return jQuery.merge( this, selector ); + + // HANDLE: $("#id") + } else { + elem = document.getElementById( match[2] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || rootjQuery ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return rootjQuery.ready( selector ); + } + + if ( selector.selector !== undefined ) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }, + + // Start with an empty selector + selector: "", + + // The current version of jQuery being used + jquery: "1.7.1", + + // The default length of a jQuery object is 0 + length: 0, + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + toArray: function() { + return slice.call( this, 0 ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num == null ? + + // Return a 'clean' array + this.toArray() : + + // Return just the object + ( num < 0 ? this[ this.length + num ] : this[ num ] ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems, name, selector ) { + // Build a new jQuery matched element set + var ret = this.constructor(); + + if ( jQuery.isArray( elems ) ) { + push.apply( ret, elems ); + + } else { + jQuery.merge( ret, elems ); + } + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + ret.context = this.context; + + if ( name === "find" ) { + ret.selector = this.selector + ( this.selector ? " " : "" ) + selector; + } else if ( name ) { + ret.selector = this.selector + "." + name + "(" + selector + ")"; + } + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + ready: function( fn ) { + // Attach the listeners + jQuery.bindReady(); + + // Add the callback + readyList.add( fn ); + + return this; + }, + + eq: function( i ) { + i = +i; + return i === -1 ? + this.slice( i ) : + this.slice( i, i + 1 ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ), + "slice", slice.call(arguments).join(",") ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + end: function() { + return this.prevObject || this.constructor(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: [].sort, + splice: [].splice +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( length === i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + noConflict: function( deep ) { + if ( window.$ === jQuery ) { + window.$ = _$; + } + + if ( deep && window.jQuery === jQuery ) { + window.jQuery = _jQuery; + } + + return jQuery; + }, + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } + }, + + // Handle when the DOM is ready + ready: function( wait ) { + // Either a released hold or an DOMready/load event and not yet ready + if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready, 1 ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.fireWith( document, [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger( "ready" ).off( "ready" ); + } + } + }, + + bindReady: function() { + if ( readyList ) { + return; + } + + readyList = jQuery.Callbacks( "once memory" ); + + // Catch cases where $(document).ready() is called after the + // browser event has already occurred. + if ( document.readyState === "complete" ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + return setTimeout( jQuery.ready, 1 ); + } + + // Mozilla, Opera and webkit nightlies currently support this event + if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", jQuery.ready, false ); + + // If IE event model is used + } else if ( document.attachEvent ) { + // ensure firing before onload, + // maybe late but safe also for iframes + document.attachEvent( "onreadystatechange", DOMContentLoaded ); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", jQuery.ready ); + + // If IE and not a frame + // continually check to see if the document is ready + var toplevel = false; + + try { + toplevel = window.frameElement == null; + } catch(e) {} + + if ( document.documentElement.doScroll && toplevel ) { + doScrollCheck(); + } + } + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return jQuery.type(obj) === "function"; + }, + + isArray: Array.isArray || function( obj ) { + return jQuery.type(obj) === "array"; + }, + + // A crude way of determining if an object is a window + isWindow: function( obj ) { + return obj && typeof obj === "object" && "setInterval" in obj; + }, + + isNumeric: function( obj ) { + return !isNaN( parseFloat(obj) ) && isFinite( obj ); + }, + + type: function( obj ) { + return obj == null ? + String( obj ) : + class2type[ toString.call(obj) ] || "object"; + }, + + isPlainObject: function( obj ) { + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + try { + // Not own constructor property must be Object + if ( obj.constructor && + !hasOwn.call(obj, "constructor") && + !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + } catch ( e ) { + // IE8,9 Will throw exceptions on certain host objects #9897 + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + + var key; + for ( key in obj ) {} + + return key === undefined || hasOwn.call( obj, key ); + }, + + isEmptyObject: function( obj ) { + for ( var name in obj ) { + return false; + } + return true; + }, + + error: function( msg ) { + throw new Error( msg ); + }, + + parseJSON: function( data ) { + if ( typeof data !== "string" || !data ) { + return null; + } + + // Make sure leading/trailing whitespace is removed (IE can't handle it) + data = jQuery.trim( data ); + + // Attempt to parse using the native JSON parser first + if ( window.JSON && window.JSON.parse ) { + return window.JSON.parse( data ); + } + + // Make sure the incoming data is actual JSON + // Logic borrowed from http://json.org/json2.js + if ( rvalidchars.test( data.replace( rvalidescape, "@" ) + .replace( rvalidtokens, "]" ) + .replace( rvalidbraces, "")) ) { + + return ( new Function( "return " + data ) )(); + + } + jQuery.error( "Invalid JSON: " + data ); + }, + + // Cross-browser xml parsing + parseXML: function( data ) { + var xml, tmp; + try { + if ( window.DOMParser ) { // Standard + tmp = new DOMParser(); + xml = tmp.parseFromString( data , "text/xml" ); + } else { // IE + xml = new ActiveXObject( "Microsoft.XMLDOM" ); + xml.async = "false"; + xml.loadXML( data ); + } + } catch( e ) { + xml = undefined; + } + if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; + }, + + noop: function() {}, + + // Evaluates a script in a global context + // Workarounds based on findings by Jim Driscoll + // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context + globalEval: function( data ) { + if ( data && rnotwhite.test( data ) ) { + // We use execScript on Internet Explorer + // We use an anonymous function so that context is window + // rather than jQuery in Firefox + ( window.execScript || function( data ) { + window[ "eval" ].call( window, data ); + } )( data ); + } + }, + + // Convert dashed to camelCase; used by the css and data modules + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); + }, + + // args is for internal usage only + each: function( object, callback, args ) { + var name, i = 0, + length = object.length, + isObj = length === undefined || jQuery.isFunction( object ); + + if ( args ) { + if ( isObj ) { + for ( name in object ) { + if ( callback.apply( object[ name ], args ) === false ) { + break; + } + } + } else { + for ( ; i < length; ) { + if ( callback.apply( object[ i++ ], args ) === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isObj ) { + for ( name in object ) { + if ( callback.call( object[ name ], name, object[ name ] ) === false ) { + break; + } + } + } else { + for ( ; i < length; ) { + if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) { + break; + } + } + } + } + + return object; + }, + + // Use native String.trim function wherever possible + trim: trim ? + function( text ) { + return text == null ? + "" : + trim.call( text ); + } : + + // Otherwise use our own trimming functionality + function( text ) { + return text == null ? + "" : + text.toString().replace( trimLeft, "" ).replace( trimRight, "" ); + }, + + // results is for internal usage only + makeArray: function( array, results ) { + var ret = results || []; + + if ( array != null ) { + // The window, strings (and functions) also have 'length' + // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930 + var type = jQuery.type( array ); + + if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) { + push.call( ret, array ); + } else { + jQuery.merge( ret, array ); + } + } + + return ret; + }, + + inArray: function( elem, array, i ) { + var len; + + if ( array ) { + if ( indexOf ) { + return indexOf.call( array, elem, i ); + } + + len = array.length; + i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; + + for ( ; i < len; i++ ) { + // Skip accessing in sparse arrays + if ( i in array && array[ i ] === elem ) { + return i; + } + } + } + + return -1; + }, + + merge: function( first, second ) { + var i = first.length, + j = 0; + + if ( typeof second.length === "number" ) { + for ( var l = second.length; j < l; j++ ) { + first[ i++ ] = second[ j ]; + } + + } else { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, inv ) { + var ret = [], retVal; + inv = !!inv; + + // Go through the array, only saving the items + // that pass the validator function + for ( var i = 0, length = elems.length; i < length; i++ ) { + retVal = !!callback( elems[ i ], i ); + if ( inv !== retVal ) { + ret.push( elems[ i ] ); + } + } + + return ret; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var value, key, ret = [], + i = 0, + length = elems.length, + // jquery objects are treated as arrays + isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ; + + // Go through the array, translating each of the items to their + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + + // Go through every key on the object, + } else { + for ( key in elems ) { + value = callback( elems[ key ], key, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + } + + // Flatten any nested arrays + return ret.concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + if ( typeof context === "string" ) { + var tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + var args = slice.call( arguments, 2 ), + proxy = function() { + return fn.apply( context, args.concat( slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; + + return proxy; + }, + + // Mutifunctional method to get and set values to a collection + // The value/s can optionally be executed if it's a function + access: function( elems, key, value, exec, fn, pass ) { + var length = elems.length; + + // Setting many attributes + if ( typeof key === "object" ) { + for ( var k in key ) { + jQuery.access( elems, k, key[k], exec, fn, value ); + } + return elems; + } + + // Setting one attribute + if ( value !== undefined ) { + // Optionally, function values get executed if exec is true + exec = !pass && exec && jQuery.isFunction(value); + + for ( var i = 0; i < length; i++ ) { + fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); + } + + return elems; + } + + // Getting an attribute + return length ? fn( elems[0], key ) : undefined; + }, + + now: function() { + return ( new Date() ).getTime(); + }, + + // Use of jQuery.browser is frowned upon. + // More details: http://docs.jquery.com/Utilities/jQuery.browser + uaMatch: function( ua ) { + ua = ua.toLowerCase(); + + var match = rwebkit.exec( ua ) || + ropera.exec( ua ) || + rmsie.exec( ua ) || + ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) || + []; + + return { browser: match[1] || "", version: match[2] || "0" }; + }, + + sub: function() { + function jQuerySub( selector, context ) { + return new jQuerySub.fn.init( selector, context ); + } + jQuery.extend( true, jQuerySub, this ); + jQuerySub.superclass = this; + jQuerySub.fn = jQuerySub.prototype = this(); + jQuerySub.fn.constructor = jQuerySub; + jQuerySub.sub = this.sub; + jQuerySub.fn.init = function init( selector, context ) { + if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) { + context = jQuerySub( context ); + } + + return jQuery.fn.init.call( this, selector, context, rootjQuerySub ); + }; + jQuerySub.fn.init.prototype = jQuerySub.fn; + var rootjQuerySub = jQuerySub(document); + return jQuerySub; + }, + + browser: {} +}); + +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +browserMatch = jQuery.uaMatch( userAgent ); +if ( browserMatch.browser ) { + jQuery.browser[ browserMatch.browser ] = true; + jQuery.browser.version = browserMatch.version; +} + +// Deprecated, use jQuery.browser.webkit instead +if ( jQuery.browser.webkit ) { + jQuery.browser.safari = true; +} + +// IE doesn't match non-breaking spaces with \s +if ( rnotwhite.test( "\xA0" ) ) { + trimLeft = /^[\s\xA0]+/; + trimRight = /[\s\xA0]+$/; +} + +// All jQuery objects should point back to these +rootjQuery = jQuery(document); + +// Cleanup functions for the document ready method +if ( document.addEventListener ) { + DOMContentLoaded = function() { + document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + jQuery.ready(); + }; + +} else if ( document.attachEvent ) { + DOMContentLoaded = function() { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( document.readyState === "complete" ) { + document.detachEvent( "onreadystatechange", DOMContentLoaded ); + jQuery.ready(); + } + }; +} + +// The DOM ready check for Internet Explorer +function doScrollCheck() { + if ( jQuery.isReady ) { + return; + } + + try { + // If IE is used, use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + document.documentElement.doScroll("left"); + } catch(e) { + setTimeout( doScrollCheck, 1 ); + return; + } + + // and execute any waiting functions + jQuery.ready(); +} + +return jQuery; + +})(); + + +// String to Object flags format cache +var flagsCache = {}; + +// Convert String-formatted flags into Object-formatted ones and store in cache +function createFlags( flags ) { + var object = flagsCache[ flags ] = {}, + i, length; + flags = flags.split( /\s+/ ); + for ( i = 0, length = flags.length; i < length; i++ ) { + object[ flags[i] ] = true; + } + return object; +} + +/* + * Create a callback list using the following parameters: + * + * flags: an optional list of space-separated flags that will change how + * the callback list behaves + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible flags: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( flags ) { + + // Convert flags from String-formatted to Object-formatted + // (we check in cache first) + flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {}; + + var // Actual callback list + list = [], + // Stack of fire calls for repeatable lists + stack = [], + // Last fire value (for non-forgettable lists) + memory, + // Flag to know if list is currently firing + firing, + // First callback to fire (used internally by add and fireWith) + firingStart, + // End of the loop when firing + firingLength, + // Index of currently firing callback (modified by remove if needed) + firingIndex, + // Add one or several callbacks to the list + add = function( args ) { + var i, + length, + elem, + type, + actual; + for ( i = 0, length = args.length; i < length; i++ ) { + elem = args[ i ]; + type = jQuery.type( elem ); + if ( type === "array" ) { + // Inspect recursively + add( elem ); + } else if ( type === "function" ) { + // Add if not in unique mode and callback is not in + if ( !flags.unique || !self.has( elem ) ) { + list.push( elem ); + } + } + } + }, + // Fire callbacks + fire = function( context, args ) { + args = args || []; + memory = !flags.memory || [ context, args ]; + firing = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + for ( ; list && firingIndex < firingLength; firingIndex++ ) { + if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) { + memory = true; // Mark as halted + break; + } + } + firing = false; + if ( list ) { + if ( !flags.once ) { + if ( stack && stack.length ) { + memory = stack.shift(); + self.fireWith( memory[ 0 ], memory[ 1 ] ); + } + } else if ( memory === true ) { + self.disable(); + } else { + list = []; + } + } + }, + // Actual Callbacks object + self = { + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + var length = list.length; + add( arguments ); + // Do we need to add the callbacks to the + // current firing batch? + if ( firing ) { + firingLength = list.length; + // With memory, if we're not firing then + // we should call right away, unless previous + // firing was halted (stopOnFalse) + } else if ( memory && memory !== true ) { + firingStart = length; + fire( memory[ 0 ], memory[ 1 ] ); + } + } + return this; + }, + // Remove a callback from the list + remove: function() { + if ( list ) { + var args = arguments, + argIndex = 0, + argLength = args.length; + for ( ; argIndex < argLength ; argIndex++ ) { + for ( var i = 0; i < list.length; i++ ) { + if ( args[ argIndex ] === list[ i ] ) { + // Handle firingIndex and firingLength + if ( firing ) { + if ( i <= firingLength ) { + firingLength--; + if ( i <= firingIndex ) { + firingIndex--; + } + } + } + // Remove the element + list.splice( i--, 1 ); + // If we have some unicity property then + // we only need to do this once + if ( flags.unique ) { + break; + } + } + } + } + } + return this; + }, + // Control if a given callback is in the list + has: function( fn ) { + if ( list ) { + var i = 0, + length = list.length; + for ( ; i < length; i++ ) { + if ( fn === list[ i ] ) { + return true; + } + } + } + return false; + }, + // Remove all callbacks from the list + empty: function() { + list = []; + return this; + }, + // Have the list do nothing anymore + disable: function() { + list = stack = memory = undefined; + return this; + }, + // Is it disabled? + disabled: function() { + return !list; + }, + // Lock the list in its current state + lock: function() { + stack = undefined; + if ( !memory || memory === true ) { + self.disable(); + } + return this; + }, + // Is it locked? + locked: function() { + return !stack; + }, + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( stack ) { + if ( firing ) { + if ( !flags.once ) { + stack.push( [ context, args ] ); + } + } else if ( !( flags.once && memory ) ) { + fire( context, args ); + } + } + return this; + }, + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + // To know if the callbacks have already been called at least once + fired: function() { + return !!memory; + } + }; + + return self; +}; + + + + +var // Static reference to slice + sliceDeferred = [].slice; + +jQuery.extend({ + + Deferred: function( func ) { + var doneList = jQuery.Callbacks( "once memory" ), + failList = jQuery.Callbacks( "once memory" ), + progressList = jQuery.Callbacks( "memory" ), + state = "pending", + lists = { + resolve: doneList, + reject: failList, + notify: progressList + }, + promise = { + done: doneList.add, + fail: failList.add, + progress: progressList.add, + + state: function() { + return state; + }, + + // Deprecated + isResolved: doneList.fired, + isRejected: failList.fired, + + then: function( doneCallbacks, failCallbacks, progressCallbacks ) { + deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks ); + return this; + }, + always: function() { + deferred.done.apply( deferred, arguments ).fail.apply( deferred, arguments ); + return this; + }, + pipe: function( fnDone, fnFail, fnProgress ) { + return jQuery.Deferred(function( newDefer ) { + jQuery.each( { + done: [ fnDone, "resolve" ], + fail: [ fnFail, "reject" ], + progress: [ fnProgress, "notify" ] + }, function( handler, data ) { + var fn = data[ 0 ], + action = data[ 1 ], + returned; + if ( jQuery.isFunction( fn ) ) { + deferred[ handler ](function() { + returned = fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise().then( newDefer.resolve, newDefer.reject, newDefer.notify ); + } else { + newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] ); + } + }); + } else { + deferred[ handler ]( newDefer[ action ] ); + } + }); + }).promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + if ( obj == null ) { + obj = promise; + } else { + for ( var key in promise ) { + obj[ key ] = promise[ key ]; + } + } + return obj; + } + }, + deferred = promise.promise({}), + key; + + for ( key in lists ) { + deferred[ key ] = lists[ key ].fire; + deferred[ key + "With" ] = lists[ key ].fireWith; + } + + // Handle state + deferred.done( function() { + state = "resolved"; + }, failList.disable, progressList.lock ).fail( function() { + state = "rejected"; + }, doneList.disable, progressList.lock ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( firstParam ) { + var args = sliceDeferred.call( arguments, 0 ), + i = 0, + length = args.length, + pValues = new Array( length ), + count = length, + pCount = length, + deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ? + firstParam : + jQuery.Deferred(), + promise = deferred.promise(); + function resolveFunc( i ) { + return function( value ) { + args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; + if ( !( --count ) ) { + deferred.resolveWith( deferred, args ); + } + }; + } + function progressFunc( i ) { + return function( value ) { + pValues[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; + deferred.notifyWith( promise, pValues ); + }; + } + if ( length > 1 ) { + for ( ; i < length; i++ ) { + if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) { + args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) ); + } else { + --count; + } + } + if ( !count ) { + deferred.resolveWith( deferred, args ); + } + } else if ( deferred !== firstParam ) { + deferred.resolveWith( deferred, length ? [ firstParam ] : [] ); + } + return promise; + } +}); + + + + +jQuery.support = (function() { + + var support, + all, + a, + select, + opt, + input, + marginDiv, + fragment, + tds, + events, + eventName, + i, + isSupported, + div = document.createElement( "div" ), + documentElement = document.documentElement; + + // Preliminary tests + div.setAttribute("className", "t"); + div.innerHTML = " a"; + + all = div.getElementsByTagName( "*" ); + a = div.getElementsByTagName( "a" )[ 0 ]; + + // Can't get basic test support + if ( !all || !all.length || !a ) { + return {}; + } + + // First batch of supports tests + select = document.createElement( "select" ); + opt = select.appendChild( document.createElement("option") ); + input = div.getElementsByTagName( "input" )[ 0 ]; + + support = { + // IE strips leading whitespace when .innerHTML is used + leadingWhitespace: ( div.firstChild.nodeType === 3 ), + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + tbody: !div.getElementsByTagName("tbody").length, + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + htmlSerialize: !!div.getElementsByTagName("link").length, + + // Get the style information from getAttribute + // (IE uses .cssText instead) + style: /top/.test( a.getAttribute("style") ), + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + hrefNormalized: ( a.getAttribute("href") === "/a" ), + + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + opacity: /^0.55/.test( a.style.opacity ), + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + cssFloat: !!a.style.cssFloat, + + // Make sure that if no value is specified for a checkbox + // that it defaults to "on". + // (WebKit defaults to "" instead) + checkOn: ( input.value === "on" ), + + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + optSelected: opt.selected, + + // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) + getSetAttribute: div.className !== "t", + + // Tests for enctype support on a form(#6743) + enctype: !!document.createElement("form").enctype, + + // Makes sure cloning an html5 element does not cause problems + // Where outerHTML is undefined, this still works + html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav>", + + // Will be defined later + submitBubbles: true, + changeBubbles: true, + focusinBubbles: false, + deleteExpando: true, + noCloneEvent: true, + inlineBlockNeedsLayout: false, + shrinkWrapBlocks: false, + reliableMarginRight: true + }; + + // Make sure checked status is properly cloned + input.checked = true; + support.noCloneChecked = input.cloneNode( true ).checked; + + // Make sure that the options inside disabled selects aren't marked as disabled + // (WebKit marks them as disabled) + select.disabled = true; + support.optDisabled = !opt.disabled; + + // Test to see if it's possible to delete an expando from an element + // Fails in Internet Explorer + try { + delete div.test; + } catch( e ) { + support.deleteExpando = false; + } + + if ( !div.addEventListener && div.attachEvent && div.fireEvent ) { + div.attachEvent( "onclick", function() { + // Cloning a node shouldn't copy over any + // bound event handlers (IE does this) + support.noCloneEvent = false; + }); + div.cloneNode( true ).fireEvent( "onclick" ); + } + + // Check if a radio maintains its value + // after being appended to the DOM + input = document.createElement("input"); + input.value = "t"; + input.setAttribute("type", "radio"); + support.radioValue = input.value === "t"; + + input.setAttribute("checked", "checked"); + div.appendChild( input ); + fragment = document.createDocumentFragment(); + fragment.appendChild( div.lastChild ); + + // WebKit doesn't clone checked state correctly in fragments + support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Check if a disconnected checkbox will retain its checked + // value of true after appended to the DOM (IE6/7) + support.appendChecked = input.checked; + + fragment.removeChild( input ); + fragment.appendChild( div ); + + div.innerHTML = ""; + + // Check if div with explicit width and no margin-right incorrectly + // gets computed margin-right based on width of container. For more + // info see bug #3333 + // Fails in WebKit before Feb 2011 nightlies + // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right + if ( window.getComputedStyle ) { + marginDiv = document.createElement( "div" ); + marginDiv.style.width = "0"; + marginDiv.style.marginRight = "0"; + div.style.width = "2px"; + div.appendChild( marginDiv ); + support.reliableMarginRight = + ( parseInt( ( window.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0; + } + + // Technique from Juriy Zaytsev + // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/ + // We only care about the case where non-standard event systems + // are used, namely in IE. Short-circuiting here helps us to + // avoid an eval call (in setAttribute) which can cause CSP + // to go haywire. See: https://developer.mozilla.org/en/Security/CSP + if ( div.attachEvent ) { + for( i in { + submit: 1, + change: 1, + focusin: 1 + }) { + eventName = "on" + i; + isSupported = ( eventName in div ); + if ( !isSupported ) { + div.setAttribute( eventName, "return;" ); + isSupported = ( typeof div[ eventName ] === "function" ); + } + support[ i + "Bubbles" ] = isSupported; + } + } + + fragment.removeChild( div ); + + // Null elements to avoid leaks in IE + fragment = select = opt = marginDiv = div = input = null; + + // Run tests that need a body at doc ready + jQuery(function() { + var container, outer, inner, table, td, offsetSupport, + conMarginTop, ptlm, vb, style, html, + body = document.getElementsByTagName("body")[0]; + + if ( !body ) { + // Return for frameset docs that don't have a body + return; + } + + conMarginTop = 1; + ptlm = "position:absolute;top:0;left:0;width:1px;height:1px;margin:0;"; + vb = "visibility:hidden;border:0;"; + style = "style='" + ptlm + "border:5px solid #000;padding:0;'"; + html = "
" + + "" + + "
"; + + container = document.createElement("div"); + container.style.cssText = vb + "width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px"; + body.insertBefore( container, body.firstChild ); + + // Construct the test element + div = document.createElement("div"); + container.appendChild( div ); + + // Check if table cells still have offsetWidth/Height when they are set + // to display:none and there are still other visible table cells in a + // table row; if so, offsetWidth/Height are not reliable for use when + // determining if an element has been hidden directly using + // display:none (it is still safe to use offsets if a parent element is + // hidden; don safety goggles and see bug #4512 for more information). + // (only IE 8 fails this test) + div.innerHTML = ""; + tds = div.getElementsByTagName( "td" ); + isSupported = ( tds[ 0 ].offsetHeight === 0 ); + + tds[ 0 ].style.display = ""; + tds[ 1 ].style.display = "none"; + + // Check if empty table cells still have offsetWidth/Height + // (IE <= 8 fail this test) + support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); + + // Figure out if the W3C box model works as expected + div.innerHTML = ""; + div.style.width = div.style.paddingLeft = "1px"; + jQuery.boxModel = support.boxModel = div.offsetWidth === 2; + + if ( typeof div.style.zoom !== "undefined" ) { + // Check if natively block-level elements act like inline-block + // elements when setting their display to 'inline' and giving + // them layout + // (IE < 8 does this) + div.style.display = "inline"; + div.style.zoom = 1; + support.inlineBlockNeedsLayout = ( div.offsetWidth === 2 ); + + // Check if elements with layout shrink-wrap their children + // (IE 6 does this) + div.style.display = ""; + div.innerHTML = ""; + support.shrinkWrapBlocks = ( div.offsetWidth !== 2 ); + } + + div.style.cssText = ptlm + vb; + div.innerHTML = html; + + outer = div.firstChild; + inner = outer.firstChild; + td = outer.nextSibling.firstChild.firstChild; + + offsetSupport = { + doesNotAddBorder: ( inner.offsetTop !== 5 ), + doesAddBorderForTableAndCells: ( td.offsetTop === 5 ) + }; + + inner.style.position = "fixed"; + inner.style.top = "20px"; + + // safari subtracts parent border width here which is 5px + offsetSupport.fixedPosition = ( inner.offsetTop === 20 || inner.offsetTop === 15 ); + inner.style.position = inner.style.top = ""; + + outer.style.overflow = "hidden"; + outer.style.position = "relative"; + + offsetSupport.subtractsBorderForOverflowNotVisible = ( inner.offsetTop === -5 ); + offsetSupport.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== conMarginTop ); + + body.removeChild( container ); + div = container = null; + + jQuery.extend( support, offsetSupport ); + }); + + return support; +})(); + + + + +var rbrace = /^(?:\{.*\}|\[.*\])$/, + rmultiDash = /([A-Z])/g; + +jQuery.extend({ + cache: {}, + + // Please use with caution + uuid: 0, + + // Unique for each copy of jQuery on the page + // Non-digits removed to match rinlinejQuery + expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ), + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData: { + "embed": true, + // Ban all objects except for Flash (which handle expandos) + "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", + "applet": true + }, + + hasData: function( elem ) { + elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; + return !!elem && !isEmptyDataObject( elem ); + }, + + data: function( elem, name, data, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var privateCache, thisCache, ret, + internalKey = jQuery.expando, + getByName = typeof name === "string", + + // We have to handle DOM nodes and JS objects differently because IE6-7 + // can't GC object references properly across the DOM-JS boundary + isNode = elem.nodeType, + + // Only DOM nodes need the global jQuery cache; JS object data is + // attached directly to the object so GC can occur automatically + cache = isNode ? jQuery.cache : elem, + + // Only defining an ID for JS objects if its cache already exists allows + // the code to shortcut on the same path as a DOM node with no cache + id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey, + isEvents = name === "events"; + + // Avoid doing any more work than we need to when trying to get data on an + // object that has no data at all + if ( (!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) { + return; + } + + if ( !id ) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if ( isNode ) { + elem[ internalKey ] = id = ++jQuery.uuid; + } else { + id = internalKey; + } + } + + if ( !cache[ id ] ) { + cache[ id ] = {}; + + // Avoids exposing jQuery metadata on plain JS objects when the object + // is serialized using JSON.stringify + if ( !isNode ) { + cache[ id ].toJSON = jQuery.noop; + } + } + + // An object can be passed to jQuery.data instead of a key/value pair; this gets + // shallow copied over onto the existing cache + if ( typeof name === "object" || typeof name === "function" ) { + if ( pvt ) { + cache[ id ] = jQuery.extend( cache[ id ], name ); + } else { + cache[ id ].data = jQuery.extend( cache[ id ].data, name ); + } + } + + privateCache = thisCache = cache[ id ]; + + // jQuery data() is stored in a separate object inside the object's internal data + // cache in order to avoid key collisions between internal data and user-defined + // data. + if ( !pvt ) { + if ( !thisCache.data ) { + thisCache.data = {}; + } + + thisCache = thisCache.data; + } + + if ( data !== undefined ) { + thisCache[ jQuery.camelCase( name ) ] = data; + } + + // Users should not attempt to inspect the internal events object using jQuery.data, + // it is undocumented and subject to change. But does anyone listen? No. + if ( isEvents && !thisCache[ name ] ) { + return privateCache.events; + } + + // Check for both converted-to-camel and non-converted data property names + // If a data property was specified + if ( getByName ) { + + // First Try to find as-is property data + ret = thisCache[ name ]; + + // Test for null|undefined property data + if ( ret == null ) { + + // Try to find the camelCased property + ret = thisCache[ jQuery.camelCase( name ) ]; + } + } else { + ret = thisCache; + } + + return ret; + }, + + removeData: function( elem, name, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var thisCache, i, l, + + // Reference to internal data cache key + internalKey = jQuery.expando, + + isNode = elem.nodeType, + + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + + // See jQuery.data for more information + id = isNode ? elem[ internalKey ] : internalKey; + + // If there is already no cache entry for this object, there is no + // purpose in continuing + if ( !cache[ id ] ) { + return; + } + + if ( name ) { + + thisCache = pvt ? cache[ id ] : cache[ id ].data; + + if ( thisCache ) { + + // Support array or space separated string names for data keys + if ( !jQuery.isArray( name ) ) { + + // try the string as a key before any manipulation + if ( name in thisCache ) { + name = [ name ]; + } else { + + // split the camel cased version by spaces unless a key with the spaces exists + name = jQuery.camelCase( name ); + if ( name in thisCache ) { + name = [ name ]; + } else { + name = name.split( " " ); + } + } + } + + for ( i = 0, l = name.length; i < l; i++ ) { + delete thisCache[ name[i] ]; + } + + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) { + return; + } + } + } + + // See jQuery.data for more information + if ( !pvt ) { + delete cache[ id ].data; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if ( !isEmptyDataObject(cache[ id ]) ) { + return; + } + } + + // Browsers that fail expando deletion also refuse to delete expandos on + // the window, but it will allow it on all other JS objects; other browsers + // don't care + // Ensure that `cache` is not a window object #10080 + if ( jQuery.support.deleteExpando || !cache.setInterval ) { + delete cache[ id ]; + } else { + cache[ id ] = null; + } + + // We destroyed the cache and need to eliminate the expando on the node to avoid + // false lookups in the cache for entries that no longer exist + if ( isNode ) { + // IE does not allow us to delete expando properties from nodes, + // nor does it have a removeAttribute function on Document nodes; + // we must handle all of these cases + if ( jQuery.support.deleteExpando ) { + delete elem[ internalKey ]; + } else if ( elem.removeAttribute ) { + elem.removeAttribute( internalKey ); + } else { + elem[ internalKey ] = null; + } + } + }, + + // For internal use only. + _data: function( elem, name, data ) { + return jQuery.data( elem, name, data, true ); + }, + + // A method for determining if a DOM node can handle the data expando + acceptData: function( elem ) { + if ( elem.nodeName ) { + var match = jQuery.noData[ elem.nodeName.toLowerCase() ]; + + if ( match ) { + return !(match === true || elem.getAttribute("classid") !== match); + } + } + + return true; + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var parts, attr, name, + data = null; + + if ( typeof key === "undefined" ) { + if ( this.length ) { + data = jQuery.data( this[0] ); + + if ( this[0].nodeType === 1 && !jQuery._data( this[0], "parsedAttrs" ) ) { + attr = this[0].attributes; + for ( var i = 0, l = attr.length; i < l; i++ ) { + name = attr[i].name; + + if ( name.indexOf( "data-" ) === 0 ) { + name = jQuery.camelCase( name.substring(5) ); + + dataAttr( this[0], name, data[ name ] ); + } + } + jQuery._data( this[0], "parsedAttrs", true ); + } + } + + return data; + + } else if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + parts = key.split("."); + parts[1] = parts[1] ? "." + parts[1] : ""; + + if ( value === undefined ) { + data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); + + // Try to fetch any internally stored data first + if ( data === undefined && this.length ) { + data = jQuery.data( this[0], key ); + data = dataAttr( this[0], key, data ); + } + + return data === undefined && parts[1] ? + this.data( parts[0] ) : + data; + + } else { + return this.each(function() { + var self = jQuery( this ), + args = [ parts[0], value ]; + + self.triggerHandler( "setData" + parts[1] + "!", args ); + jQuery.data( this, key, value ); + self.triggerHandler( "changeData" + parts[1] + "!", args ); + }); + } + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); + +function dataAttr( elem, key, data ) { + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + + var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); + + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + jQuery.isNumeric( data ) ? parseFloat( data ) : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + jQuery.data( elem, key, data ); + + } else { + data = undefined; + } + } + + return data; +} + +// checks a cache object for emptiness +function isEmptyDataObject( obj ) { + for ( var name in obj ) { + + // if the public data object is empty, the private is still empty + if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { + continue; + } + if ( name !== "toJSON" ) { + return false; + } + } + + return true; +} + + + + +function handleQueueMarkDefer( elem, type, src ) { + var deferDataKey = type + "defer", + queueDataKey = type + "queue", + markDataKey = type + "mark", + defer = jQuery._data( elem, deferDataKey ); + if ( defer && + ( src === "queue" || !jQuery._data(elem, queueDataKey) ) && + ( src === "mark" || !jQuery._data(elem, markDataKey) ) ) { + // Give room for hard-coded callbacks to fire first + // and eventually mark/queue something else on the element + setTimeout( function() { + if ( !jQuery._data( elem, queueDataKey ) && + !jQuery._data( elem, markDataKey ) ) { + jQuery.removeData( elem, deferDataKey, true ); + defer.fire(); + } + }, 0 ); + } +} + +jQuery.extend({ + + _mark: function( elem, type ) { + if ( elem ) { + type = ( type || "fx" ) + "mark"; + jQuery._data( elem, type, (jQuery._data( elem, type ) || 0) + 1 ); + } + }, + + _unmark: function( force, elem, type ) { + if ( force !== true ) { + type = elem; + elem = force; + force = false; + } + if ( elem ) { + type = type || "fx"; + var key = type + "mark", + count = force ? 0 : ( (jQuery._data( elem, key ) || 1) - 1 ); + if ( count ) { + jQuery._data( elem, key, count ); + } else { + jQuery.removeData( elem, key, true ); + handleQueueMarkDefer( elem, type, "mark" ); + } + } + }, + + queue: function( elem, type, data ) { + var q; + if ( elem ) { + type = ( type || "fx" ) + "queue"; + q = jQuery._data( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !q || jQuery.isArray(data) ) { + q = jQuery._data( elem, type, jQuery.makeArray(data) ); + } else { + q.push( data ); + } + } + return q || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + fn = queue.shift(), + hooks = {}; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + } + + if ( fn ) { + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + jQuery._data( elem, type + ".run", hooks ); + fn.call( elem, function() { + jQuery.dequeue( elem, type ); + }, hooks ); + } + + if ( !queue.length ) { + jQuery.removeData( elem, type + "queue " + type + ".run", true ); + handleQueueMarkDefer( elem, type, "queue" ); + } + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + } + + if ( data === undefined ) { + return jQuery.queue( this[0], type ); + } + return this.each(function() { + var queue = jQuery.queue( this, type, data ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + // Based off of the plugin by Clint Helfers, with permission. + // http://blindsignals.com/index.php/2009/07/jquery-delay/ + delay: function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = setTimeout( next, time ); + hooks.stop = function() { + clearTimeout( timeout ); + }; + }); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, object ) { + if ( typeof type !== "string" ) { + object = type; + type = undefined; + } + type = type || "fx"; + var defer = jQuery.Deferred(), + elements = this, + i = elements.length, + count = 1, + deferDataKey = type + "defer", + queueDataKey = type + "queue", + markDataKey = type + "mark", + tmp; + function resolve() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + } + while( i-- ) { + if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) || + ( jQuery.data( elements[ i ], queueDataKey, undefined, true ) || + jQuery.data( elements[ i ], markDataKey, undefined, true ) ) && + jQuery.data( elements[ i ], deferDataKey, jQuery.Callbacks( "once memory" ), true ) )) { + count++; + tmp.add( resolve ); + } + } + resolve(); + return defer.promise(); + } +}); + + + + +var rclass = /[\n\t\r]/g, + rspace = /\s+/, + rreturn = /\r/g, + rtype = /^(?:button|input)$/i, + rfocusable = /^(?:button|input|object|select|textarea)$/i, + rclickable = /^a(?:rea)?$/i, + rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i, + getSetAttribute = jQuery.support.getSetAttribute, + nodeHook, boolHook, fixSpecified; + +jQuery.fn.extend({ + attr: function( name, value ) { + return jQuery.access( this, name, value, true, jQuery.attr ); + }, + + removeAttr: function( name ) { + return this.each(function() { + jQuery.removeAttr( this, name ); + }); + }, + + prop: function( name, value ) { + return jQuery.access( this, name, value, true, jQuery.prop ); + }, + + removeProp: function( name ) { + name = jQuery.propFix[ name ] || name; + return this.each(function() { + // try/catch handles cases where IE balks (such as removing a property on window) + try { + this[ name ] = undefined; + delete this[ name ]; + } catch( e ) {} + }); + }, + + addClass: function( value ) { + var classNames, i, l, elem, + setClass, c, cl; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).addClass( value.call(this, j, this.className) ); + }); + } + + if ( value && typeof value === "string" ) { + classNames = value.split( rspace ); + + for ( i = 0, l = this.length; i < l; i++ ) { + elem = this[ i ]; + + if ( elem.nodeType === 1 ) { + if ( !elem.className && classNames.length === 1 ) { + elem.className = value; + + } else { + setClass = " " + elem.className + " "; + + for ( c = 0, cl = classNames.length; c < cl; c++ ) { + if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) { + setClass += classNames[ c ] + " "; + } + } + elem.className = jQuery.trim( setClass ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classNames, i, l, elem, className, c, cl; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).removeClass( value.call(this, j, this.className) ); + }); + } + + if ( (value && typeof value === "string") || value === undefined ) { + classNames = ( value || "" ).split( rspace ); + + for ( i = 0, l = this.length; i < l; i++ ) { + elem = this[ i ]; + + if ( elem.nodeType === 1 && elem.className ) { + if ( value ) { + className = (" " + elem.className + " ").replace( rclass, " " ); + for ( c = 0, cl = classNames.length; c < cl; c++ ) { + className = className.replace(" " + classNames[ c ] + " ", " "); + } + elem.className = jQuery.trim( className ); + + } else { + elem.className = ""; + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isBool = typeof stateVal === "boolean"; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( i ) { + jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal ); + }); + } + + return this.each(function() { + if ( type === "string" ) { + // toggle individual class names + var className, + i = 0, + self = jQuery( this ), + state = stateVal, + classNames = value.split( rspace ); + + while ( (className = classNames[ i++ ]) ) { + // check each className given, space seperated list + state = isBool ? state : !self.hasClass( className ); + self[ state ? "addClass" : "removeClass" ]( className ); + } + + } else if ( type === "undefined" || type === "boolean" ) { + if ( this.className ) { + // store className if set + jQuery._data( this, "__className__", this.className ); + } + + // toggle whole className + this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; + } + }); + }, + + hasClass: function( selector ) { + var className = " " + selector + " ", + i = 0, + l = this.length; + for ( ; i < l; i++ ) { + if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { + return true; + } + } + + return false; + }, + + val: function( value ) { + var hooks, ret, isFunction, + elem = this[0]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.nodeName.toLowerCase() ] || jQuery.valHooks[ elem.type ]; + + if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { + return ret; + } + + ret = elem.value; + + return typeof ret === "string" ? + // handle most common string cases + ret.replace(rreturn, "") : + // handle cases where value is null/undef or number + ret == null ? "" : ret; + } + + return; + } + + isFunction = jQuery.isFunction( value ); + + return this.each(function( i ) { + var self = jQuery(this), val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call( this, i, self.val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + } else if ( typeof val === "number" ) { + val += ""; + } else if ( jQuery.isArray( val ) ) { + val = jQuery.map(val, function ( value ) { + return value == null ? "" : value + ""; + }); + } + + hooks = jQuery.valHooks[ this.nodeName.toLowerCase() ] || jQuery.valHooks[ this.type ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + }); + } +}); + +jQuery.extend({ + valHooks: { + option: { + get: function( elem ) { + // attributes.value is undefined in Blackberry 4.7 but + // uses .value. See #6932 + var val = elem.attributes.value; + return !val || val.specified ? elem.value : elem.text; + } + }, + select: { + get: function( elem ) { + var value, i, max, option, + index = elem.selectedIndex, + values = [], + options = elem.options, + one = elem.type === "select-one"; + + // Nothing was selected + if ( index < 0 ) { + return null; + } + + // Loop through all the selected options + i = one ? index : 0; + max = one ? index + 1 : options.length; + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Don't return options that are disabled or in a disabled optgroup + if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && + (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + // Fixes Bug #2551 -- select.val() broken in IE after form.reset() + if ( one && !values.length && options.length ) { + return jQuery( options[ index ] ).val(); + } + + return values; + }, + + set: function( elem, value ) { + var values = jQuery.makeArray( value ); + + jQuery(elem).find("option").each(function() { + this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; + }); + + if ( !values.length ) { + elem.selectedIndex = -1; + } + return values; + } + } + }, + + attrFn: { + val: true, + css: true, + html: true, + text: true, + data: true, + width: true, + height: true, + offset: true + }, + + attr: function( elem, name, value, pass ) { + var ret, hooks, notxml, + nType = elem.nodeType; + + // don't get/set attributes on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( pass && name in jQuery.attrFn ) { + return jQuery( elem )[ name ]( value ); + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + // All attributes are lowercase + // Grab necessary hook if one is defined + if ( notxml ) { + name = name.toLowerCase(); + hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook ); + } + + if ( value !== undefined ) { + + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + + } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + elem.setAttribute( name, "" + value ); + return value; + } + + } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + + ret = elem.getAttribute( name ); + + // Non-existent attributes return null, we normalize to undefined + return ret === null ? + undefined : + ret; + } + }, + + removeAttr: function( elem, value ) { + var propName, attrNames, name, l, + i = 0; + + if ( value && elem.nodeType === 1 ) { + attrNames = value.toLowerCase().split( rspace ); + l = attrNames.length; + + for ( ; i < l; i++ ) { + name = attrNames[ i ]; + + if ( name ) { + propName = jQuery.propFix[ name ] || name; + + // See #9699 for explanation of this approach (setting first, then removal) + jQuery.attr( elem, name, "" ); + elem.removeAttribute( getSetAttribute ? name : propName ); + + // Set corresponding property to false for boolean attributes + if ( rboolean.test( name ) && propName in elem ) { + elem[ propName ] = false; + } + } + } + } + }, + + attrHooks: { + type: { + set: function( elem, value ) { + // We can't allow the type property to be changed (since it causes problems in IE) + if ( rtype.test( elem.nodeName ) && elem.parentNode ) { + jQuery.error( "type property can't be changed" ); + } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) { + // Setting the type on a radio button after the value resets the value in IE6-9 + // Reset value to it's default in case type is set after value + // This is for element creation + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + }, + // Use the value property for back compat + // Use the nodeHook for button elements in IE6/7 (#1954) + value: { + get: function( elem, name ) { + if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { + return nodeHook.get( elem, name ); + } + return name in elem ? + elem.value : + null; + }, + set: function( elem, value, name ) { + if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { + return nodeHook.set( elem, value, name ); + } + // Does not return so that setAttribute is also used + elem.value = value; + } + } + }, + + propFix: { + tabindex: "tabIndex", + readonly: "readOnly", + "for": "htmlFor", + "class": "className", + maxlength: "maxLength", + cellspacing: "cellSpacing", + cellpadding: "cellPadding", + rowspan: "rowSpan", + colspan: "colSpan", + usemap: "useMap", + frameborder: "frameBorder", + contenteditable: "contentEditable" + }, + + prop: function( elem, name, value ) { + var ret, hooks, notxml, + nType = elem.nodeType; + + // don't get/set properties on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + if ( notxml ) { + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + return ( elem[ name ] = value ); + } + + } else { + if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + return elem[ name ]; + } + } + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + var attributeNode = elem.getAttributeNode("tabindex"); + + return attributeNode && attributeNode.specified ? + parseInt( attributeNode.value, 10 ) : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; + } + } + } +}); + +// Add the tabIndex propHook to attrHooks for back-compat (different case is intentional) +jQuery.attrHooks.tabindex = jQuery.propHooks.tabIndex; + +// Hook for boolean attributes +boolHook = { + get: function( elem, name ) { + // Align boolean attributes with corresponding properties + // Fall back to attribute presence where some booleans are not supported + var attrNode, + property = jQuery.prop( elem, name ); + return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ? + name.toLowerCase() : + undefined; + }, + set: function( elem, value, name ) { + var propName; + if ( value === false ) { + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + // value is true since we know at this point it's type boolean and not false + // Set boolean attributes to the same name and set the DOM property + propName = jQuery.propFix[ name ] || name; + if ( propName in elem ) { + // Only set the IDL specifically if it already exists on the element + elem[ propName ] = true; + } + + elem.setAttribute( name, name.toLowerCase() ); + } + return name; + } +}; + +// IE6/7 do not support getting/setting some attributes with get/setAttribute +if ( !getSetAttribute ) { + + fixSpecified = { + name: true, + id: true + }; + + // Use this for any attribute in IE6/7 + // This fixes almost every IE6/7 issue + nodeHook = jQuery.valHooks.button = { + get: function( elem, name ) { + var ret; + ret = elem.getAttributeNode( name ); + return ret && ( fixSpecified[ name ] ? ret.nodeValue !== "" : ret.specified ) ? + ret.nodeValue : + undefined; + }, + set: function( elem, value, name ) { + // Set the existing or create a new attribute node + var ret = elem.getAttributeNode( name ); + if ( !ret ) { + ret = document.createAttribute( name ); + elem.setAttributeNode( ret ); + } + return ( ret.nodeValue = value + "" ); + } + }; + + // Apply the nodeHook to tabindex + jQuery.attrHooks.tabindex.set = nodeHook.set; + + // Set width and height to auto instead of 0 on empty string( Bug #8150 ) + // This is for removals + jQuery.each([ "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + set: function( elem, value ) { + if ( value === "" ) { + elem.setAttribute( name, "auto" ); + return value; + } + } + }); + }); + + // Set contenteditable to false on removals(#10429) + // Setting to empty string throws an error as an invalid value + jQuery.attrHooks.contenteditable = { + get: nodeHook.get, + set: function( elem, value, name ) { + if ( value === "" ) { + value = "false"; + } + nodeHook.set( elem, value, name ); + } + }; +} + + +// Some attributes require a special call on IE +if ( !jQuery.support.hrefNormalized ) { + jQuery.each([ "href", "src", "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + get: function( elem ) { + var ret = elem.getAttribute( name, 2 ); + return ret === null ? undefined : ret; + } + }); + }); +} + +if ( !jQuery.support.style ) { + jQuery.attrHooks.style = { + get: function( elem ) { + // Return undefined in the case of empty string + // Normalize to lowercase since IE uppercases css property names + return elem.style.cssText.toLowerCase() || undefined; + }, + set: function( elem, value ) { + return ( elem.style.cssText = "" + value ); + } + }; +} + +// Safari mis-reports the default selected property of an option +// Accessing the parent's selectedIndex property fixes it +if ( !jQuery.support.optSelected ) { + jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, { + get: function( elem ) { + var parent = elem.parentNode; + + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + return null; + } + }); +} + +// IE6/7 call enctype encoding +if ( !jQuery.support.enctype ) { + jQuery.propFix.enctype = "encoding"; +} + +// Radios and checkboxes getter/setter +if ( !jQuery.support.checkOn ) { + jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + get: function( elem ) { + // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified + return elem.getAttribute("value") === null ? "on" : elem.value; + } + }; + }); +} +jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], { + set: function( elem, value ) { + if ( jQuery.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 ); + } + } + }); +}); + + + + +var rformElems = /^(?:textarea|input|select)$/i, + rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/, + rhoverHack = /\bhover(\.\S+)?\b/, + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|contextmenu)|click/, + rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + rquickIs = /^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/, + quickParse = function( selector ) { + var quick = rquickIs.exec( selector ); + if ( quick ) { + // 0 1 2 3 + // [ _, tag, id, class ] + quick[1] = ( quick[1] || "" ).toLowerCase(); + quick[3] = quick[3] && new RegExp( "(?:^|\\s)" + quick[3] + "(?:\\s|$)" ); + } + return quick; + }, + quickIs = function( elem, m ) { + var attrs = elem.attributes || {}; + return ( + (!m[1] || elem.nodeName.toLowerCase() === m[1]) && + (!m[2] || (attrs.id || {}).value === m[2]) && + (!m[3] || m[3].test( (attrs[ "class" ] || {}).value )) + ); + }, + hoverHack = function( events ) { + return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" ); + }; + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + add: function( elem, types, handler, data, selector ) { + + var elemData, eventHandle, events, + t, tns, type, namespaces, handleObj, + handleObjIn, quick, handlers, special; + + // Don't attach events to noData or text/comment nodes (allow plain objects tho) + if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + events = elemData.events; + if ( !events ) { + elemData.events = events = {}; + } + eventHandle = elemData.handle; + if ( !eventHandle ) { + elemData.handle = eventHandle = function( e ) { + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? + jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : + undefined; + }; + // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events + eventHandle.elem = elem; + } + + // Handle multiple events separated by a space + // jQuery(...).bind("mouseover mouseout", fn); + types = jQuery.trim( hoverHack(types) ).split( " " ); + for ( t = 0; t < types.length; t++ ) { + + tns = rtypenamespace.exec( types[t] ) || []; + type = tns[1]; + namespaces = ( tns[2] || "" ).split( "." ).sort(); + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend({ + type: type, + origType: tns[1], + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + quick: quickParse( selector ), + namespace: namespaces.join(".") + }, handleObjIn ); + + // Init the event handler queue if we're the first + handlers = events[ type ]; + if ( !handlers ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener/attachEvent if the special events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + global: {}, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var elemData = jQuery.hasData( elem ) && jQuery._data( elem ), + t, tns, type, origType, namespaces, origCount, + j, events, special, handle, eventType, handleObj; + + if ( !elemData || !(events = elemData.events) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = jQuery.trim( hoverHack( types || "" ) ).split(" "); + for ( t = 0; t < types.length; t++ ) { + tns = rtypenamespace.exec( types[t] ) || []; + type = origType = tns[1]; + namespaces = tns[2]; + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector? special.delegateType : special.bindType ) || type; + eventType = events[ type ] || []; + origCount = eventType.length; + namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.)?") + "(\\.|$)") : null; + + // Remove matching events + for ( j = 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !namespaces || namespaces.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { + eventType.splice( j--, 1 ); + + if ( handleObj.selector ) { + eventType.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( eventType.length === 0 && origCount !== eventType.length ) { + if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + handle = elemData.handle; + if ( handle ) { + handle.elem = null; + } + + // removeData also checks for emptiness and clears the expando if empty + // so use it instead of delete + jQuery.removeData( elem, [ "events", "handle" ], true ); + } + }, + + // Events that are safe to short-circuit if no handlers are attached. + // Native DOM events should not be added, they may have inline handlers. + customEvent: { + "getData": true, + "setData": true, + "changeData": true + }, + + trigger: function( event, data, elem, onlyHandlers ) { + // Don't do events on text and comment nodes + if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) { + return; + } + + // Event object or event type + var type = event.type || event, + namespaces = [], + cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType; + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "!" ) >= 0 ) { + // Exclusive events trigger only for the exact event (no namespaces) + type = type.slice(0, -1); + exclusive = true; + } + + if ( type.indexOf( "." ) >= 0 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + + if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) { + // No jQuery handlers for this event type, and it can't have inline handlers + return; + } + + // Caller can pass in an Event, Object, or just an event type string + event = typeof event === "object" ? + // jQuery.Event object + event[ jQuery.expando ] ? event : + // Object literal + new jQuery.Event( type, event ) : + // Just the event type (string) + new jQuery.Event( type ); + + event.type = type; + event.isTrigger = true; + event.exclusive = exclusive; + event.namespace = namespaces.join( "." ); + event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") : null; + ontype = type.indexOf( ":" ) < 0 ? "on" + type : ""; + + // Handle a global trigger + if ( !elem ) { + + // TODO: Stop taunting the data cache; remove global events and always attach to document + cache = jQuery.cache; + for ( i in cache ) { + if ( cache[ i ].events && cache[ i ].events[ type ] ) { + jQuery.event.trigger( event, data, cache[ i ].handle.elem, true ); + } + } + return; + } + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data != null ? jQuery.makeArray( data ) : []; + data.unshift( event ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + eventPath = [[ elem, special.bindType || type ]]; + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode; + old = null; + for ( ; cur; cur = cur.parentNode ) { + eventPath.push([ cur, bubbleType ]); + old = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( old && old === elem.ownerDocument ) { + eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]); + } + } + + // Fire handlers on the event path + for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) { + + cur = eventPath[i][0]; + event.type = eventPath[i][1]; + + handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + // Note that this is a bare JS function and not a jQuery handler + handle = ontype && cur[ ontype ]; + if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) { + event.preventDefault(); + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) && + !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name name as the event. + // Can't use an .isFunction() check here because IE6/7 fails that test. + // Don't do default actions on window, that's where global variables be (#6170) + // IE<9 dies on focus/blur to hidden element (#1486) + if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + old = elem[ ontype ]; + + if ( old ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + elem[ type ](); + jQuery.event.triggered = undefined; + + if ( old ) { + elem[ ontype ] = old; + } + } + } + } + + return event.result; + }, + + dispatch: function( event ) { + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( event || window.event ); + + var handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []), + delegateCount = handlers.delegateCount, + args = [].slice.call( arguments, 0 ), + run_all = !event.exclusive && !event.namespace, + handlerQueue = [], + i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[0] = event; + event.delegateTarget = this; + + // Determine handlers that should run if there are delegated events + // Avoid disabled elements in IE (#6911) and non-left-click bubbling in Firefox (#3861) + if ( delegateCount && !event.target.disabled && !(event.button && event.type === "click") ) { + + // Pregenerate a single jQuery object for reuse with .is() + jqcur = jQuery(this); + jqcur.context = this.ownerDocument || this; + + for ( cur = event.target; cur != this; cur = cur.parentNode || this ) { + selMatch = {}; + matches = []; + jqcur[0] = cur; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + sel = handleObj.selector; + + if ( selMatch[ sel ] === undefined ) { + selMatch[ sel ] = ( + handleObj.quick ? quickIs( cur, handleObj.quick ) : jqcur.is( sel ) + ); + } + if ( selMatch[ sel ] ) { + matches.push( handleObj ); + } + } + if ( matches.length ) { + handlerQueue.push({ elem: cur, matches: matches }); + } + } + } + + // Add the remaining (directly-bound) handlers + if ( handlers.length > delegateCount ) { + handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) }); + } + + // Run delegates first; they may want to stop propagation beneath us + for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) { + matched = handlerQueue[ i ]; + event.currentTarget = matched.elem; + + for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) { + handleObj = matched.matches[ j ]; + + // Triggered event must either 1) be non-exclusive and have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). + if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) { + + event.data = handleObj.data; + event.handleObj = handleObj; + + ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) + .apply( matched.elem, args ); + + if ( ret !== undefined ) { + event.result = ret; + if ( ret === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + return event.result; + }, + + // Includes some event props shared by KeyEvent and MouseEvent + // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 *** + props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + + fixHooks: {}, + + keyHooks: { + props: "char charCode key keyCode".split(" "), + filter: function( event, original ) { + + // Add which for key events + if ( event.which == null ) { + event.which = original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + } + }, + + mouseHooks: { + props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), + filter: function( event, original ) { + var eventDoc, doc, body, + button = original.button, + fromElement = original.fromElement; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && original.clientX != null ) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); + event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && fromElement ) { + event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && button !== undefined ) { + event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); + } + + return event; + } + }, + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // Create a writable copy of the event object and normalize some properties + var i, prop, + originalEvent = event, + fixHook = jQuery.event.fixHooks[ event.type ] || {}, + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + + event = jQuery.Event( originalEvent ); + + for ( i = copy.length; i; ) { + prop = copy[ --i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2) + if ( !event.target ) { + event.target = originalEvent.srcElement || document; + } + + // Target should not be a text node (#504, Safari) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // For mouse/key events; add metaKey if it's not there (#3368, IE6/7/8) + if ( event.metaKey === undefined ) { + event.metaKey = event.ctrlKey; + } + + return fixHook.filter? fixHook.filter( event, originalEvent ) : event; + }, + + special: { + ready: { + // Make sure the ready event is setup + setup: jQuery.bindReady + }, + + load: { + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + + focus: { + delegateType: "focusin" + }, + blur: { + delegateType: "focusout" + }, + + beforeunload: { + setup: function( data, namespaces, eventHandle ) { + // We only want to do this special case on windows + if ( jQuery.isWindow( this ) ) { + this.onbeforeunload = eventHandle; + } + }, + + teardown: function( namespaces, eventHandle ) { + if ( this.onbeforeunload === eventHandle ) { + this.onbeforeunload = null; + } + } + } + }, + + simulate: function( type, elem, event, bubble ) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + var e = jQuery.extend( + new jQuery.Event(), + event, + { type: type, + isSimulated: true, + originalEvent: {} + } + ); + if ( bubble ) { + jQuery.event.trigger( e, null, elem ); + } else { + jQuery.event.dispatch.call( elem, e ); + } + if ( e.isDefaultPrevented() ) { + event.preventDefault(); + } + } +}; + +// Some plugins are using, but it's undocumented/deprecated and will be removed. +// The 1.7 special event interface should provide all the hooks needed now. +jQuery.event.handle = jQuery.event.dispatch; + +jQuery.removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } + } : + function( elem, type, handle ) { + if ( elem.detachEvent ) { + elem.detachEvent( "on" + type, handle ); + } + }; + +jQuery.Event = function( src, props ) { + // Allow instantiation without the 'new' keyword + if ( !(this instanceof jQuery.Event) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false || + src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +function returnFalse() { + return false; +} +function returnTrue() { + return true; +} + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + preventDefault: function() { + this.isDefaultPrevented = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + + // if preventDefault exists run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + + // otherwise set the returnValue property of the original event to false (IE) + } else { + e.returnValue = false; + } + }, + stopPropagation: function() { + this.isPropagationStopped = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + // if stopPropagation exists run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + // otherwise set the cancelBubble property of the original event to true (IE) + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + }, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse +}; + +// Create mouseenter/leave events using mouseover/out and event-time checks +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var target = this, + related = event.relatedTarget, + handleObj = event.handleObj, + selector = handleObj.selector, + ret; + + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || (related !== target && !jQuery.contains( target, related )) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +}); + +// IE submit delegation +if ( !jQuery.support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Lazy-add a submit handler when a descendant form may potentially be submitted + jQuery.event.add( this, "click._submit keypress._submit", function( e ) { + // Node name check avoids a VML-related crash in IE (#9807) + var elem = e.target, + form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; + if ( form && !form._submit_attached ) { + jQuery.event.add( form, "submit._submit", function( event ) { + // If form was submitted by the user, bubble the event up the tree + if ( this.parentNode && !event.isTrigger ) { + jQuery.event.simulate( "submit", this.parentNode, event, true ); + } + }); + form._submit_attached = true; + } + }); + // return undefined since we don't need an event listener + }, + + teardown: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Remove delegated handlers; cleanData eventually reaps submit handlers attached above + jQuery.event.remove( this, "._submit" ); + } + }; +} + +// IE change delegation and checkbox/radio fix +if ( !jQuery.support.changeBubbles ) { + + jQuery.event.special.change = { + + setup: function() { + + if ( rformElems.test( this.nodeName ) ) { + // IE doesn't fire change on a check/radio until blur; trigger it on click + // after a propertychange. Eat the blur-change in special.change.handle. + // This still fires onchange a second time for check/radio after blur. + if ( this.type === "checkbox" || this.type === "radio" ) { + jQuery.event.add( this, "propertychange._change", function( event ) { + if ( event.originalEvent.propertyName === "checked" ) { + this._just_changed = true; + } + }); + jQuery.event.add( this, "click._change", function( event ) { + if ( this._just_changed && !event.isTrigger ) { + this._just_changed = false; + jQuery.event.simulate( "change", this, event, true ); + } + }); + } + return false; + } + // Delegated event; lazy-add a change handler on descendant inputs + jQuery.event.add( this, "beforeactivate._change", function( e ) { + var elem = e.target; + + if ( rformElems.test( elem.nodeName ) && !elem._change_attached ) { + jQuery.event.add( elem, "change._change", function( event ) { + if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { + jQuery.event.simulate( "change", this.parentNode, event, true ); + } + }); + elem._change_attached = true; + } + }); + }, + + handle: function( event ) { + var elem = event.target; + + // Swallow native change events from checkbox/radio, we already triggered them above + if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { + return event.handleObj.handler.apply( this, arguments ); + } + }, + + teardown: function() { + jQuery.event.remove( this, "._change" ); + + return rformElems.test( this.nodeName ); + } + }; +} + +// Create "bubbling" focus and blur events +if ( !jQuery.support.focusinBubbles ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler while someone wants focusin/focusout + var attaches = 0, + handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + if ( attaches++ === 0 ) { + document.addEventListener( orig, handler, true ); + } + }, + teardown: function() { + if ( --attaches === 0 ) { + document.removeEventListener( orig, handler, true ); + } + } + }; + }); +} + +jQuery.fn.extend({ + + on: function( types, selector, data, fn, /*INTERNAL*/ one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + // ( types-Object, data ) + data = selector; + selector = undefined; + } + for ( type in types ) { + this.on( type, selector, data, types[ type ], one ); + } + return this; + } + + if ( data == null && fn == null ) { + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return this; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return this.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + }); + }, + one: function( types, selector, data, fn ) { + return this.on.call( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + if ( types && types.preventDefault && types.handleObj ) { + // ( event ) dispatched jQuery.Event + var handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace? handleObj.type + "." + handleObj.namespace : handleObj.type, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + // ( types-object [, selector] ) + for ( var type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each(function() { + jQuery.event.remove( this, types, fn, selector ); + }); + }, + + bind: function( types, data, fn ) { + return this.on( types, null, data, fn ); + }, + unbind: function( types, fn ) { + return this.off( types, null, fn ); + }, + + live: function( types, data, fn ) { + jQuery( this.context ).on( types, this.selector, data, fn ); + return this; + }, + die: function( types, fn ) { + jQuery( this.context ).off( types, this.selector || "**", fn ); + return this; + }, + + delegate: function( selector, types, data, fn ) { + return this.on( types, selector, data, fn ); + }, + undelegate: function( selector, types, fn ) { + // ( namespace ) or ( selector, types [, fn] ) + return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector, fn ); + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + triggerHandler: function( type, data ) { + if ( this[0] ) { + return jQuery.event.trigger( type, data, this[0], true ); + } + }, + + toggle: function( fn ) { + // Save reference to arguments for access in closure + var args = arguments, + guid = fn.guid || jQuery.guid++, + i = 0, + toggler = function( event ) { + // Figure out which function to execute + var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i; + jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 ); + + // Make sure that clicks stop + event.preventDefault(); + + // and execute the function + return args[ lastToggle ].apply( this, arguments ) || false; + }; + + // link all the functions, so any of them can unbind this click handler + toggler.guid = guid; + while ( i < args.length ) { + args[ i++ ].guid = guid; + } + + return this.click( toggler ); + }, + + hover: function( fnOver, fnOut ) { + return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); + } +}); + +jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + + "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) { + + // Handle event binding + jQuery.fn[ name ] = function( data, fn ) { + if ( fn == null ) { + fn = data; + data = null; + } + + return arguments.length > 0 ? + this.on( name, null, data, fn ) : + this.trigger( name ); + }; + + if ( jQuery.attrFn ) { + jQuery.attrFn[ name ] = true; + } + + if ( rkeyEvent.test( name ) ) { + jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks; + } + + if ( rmouseEvent.test( name ) ) { + jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks; + } +}); + + + +/*! + * Sizzle CSS Selector Engine + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + expando = "sizcache" + (Math.random() + '').replace('.', ''), + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true, + rBackslash = /\\/g, + rReturn = /\r\n/g, + rNonWord = /\W/; + +// Here we check if the JavaScript engine is using some sort of +// optimization where it does not always call our comparision +// function. If that is the case, discard the hasDuplicate value. +// Thus far that includes Google Chrome. +[0, 0].sort(function() { + baseHasDuplicate = false; + return 0; +}); + +var Sizzle = function( selector, context, results, seed ) { + results = results || []; + context = context || document; + + var origContext = context; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var m, set, checkSet, extra, ret, cur, pop, i, + prune = true, + contextXML = Sizzle.isXML( context ), + parts = [], + soFar = selector; + + // Reset the position of the chunker regexp (start from head) + do { + chunker.exec( "" ); + m = chunker.exec( soFar ); + + if ( m ) { + soFar = m[3]; + + parts.push( m[1] ); + + if ( m[2] ) { + extra = m[3]; + break; + } + } + } while ( m ); + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context, seed ); + + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); + + if ( Expr.relative[ selector ] ) { + selector += parts.shift(); + } + + set = posProcess( selector, set, seed ); + } + } + + } else { + // Take a shortcut and set the context if the root selector is an ID + // (but not if it'll be faster if the inner selector is an ID) + if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + + ret = Sizzle.find( parts.shift(), context, contextXML ); + context = ret.expr ? + Sizzle.filter( ret.expr, ret.set )[0] : + ret.set[0]; + } + + if ( context ) { + ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); + + set = ret.expr ? + Sizzle.filter( ret.expr, ret.set ) : + ret.set; + + if ( parts.length > 0 ) { + checkSet = makeArray( set ); + + } else { + prune = false; + } + + while ( parts.length ) { + cur = parts.pop(); + pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, contextXML ); + } + + } else { + checkSet = parts = []; + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + Sizzle.error( cur || selector ); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + + } else if ( context && context.nodeType === 1 ) { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + + } else { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, origContext, results, seed ); + Sizzle.uniqueSort( results ); + } + + return results; +}; + +Sizzle.uniqueSort = function( results ) { + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort( sortOrder ); + + if ( hasDuplicate ) { + for ( var i = 1; i < results.length; i++ ) { + if ( results[i] === results[ i - 1 ] ) { + results.splice( i--, 1 ); + } + } + } + } + + return results; +}; + +Sizzle.matches = function( expr, set ) { + return Sizzle( expr, null, null, set ); +}; + +Sizzle.matchesSelector = function( node, expr ) { + return Sizzle( expr, null, null, [node] ).length > 0; +}; + +Sizzle.find = function( expr, context, isXML ) { + var set, i, len, match, type, left; + + if ( !expr ) { + return []; + } + + for ( i = 0, len = Expr.order.length; i < len; i++ ) { + type = Expr.order[i]; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + left = match[1]; + match.splice( 1, 1 ); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace( rBackslash, "" ); + set = Expr.find[ type ]( match, context, isXML ); + + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = typeof context.getElementsByTagName !== "undefined" ? + context.getElementsByTagName( "*" ) : + []; + } + + return { set: set, expr: expr }; +}; + +Sizzle.filter = function( expr, set, inplace, not ) { + var match, anyFound, + type, found, item, filter, left, + i, pass, + old = expr, + result = [], + curLoop = set, + isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); + + while ( expr && set.length ) { + for ( type in Expr.filter ) { + if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { + filter = Expr.filter[ type ]; + left = match[1]; + + anyFound = false; + + match.splice(1,1); + + if ( left.substr( left.length - 1 ) === "\\" ) { + continue; + } + + if ( curLoop === result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + + if ( !match ) { + anyFound = found = true; + + } else if ( match === true ) { + continue; + } + } + + if ( match ) { + for ( i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + pass = not ^ found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + + } else { + curLoop[i] = false; + } + + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; + } + + break; + } + } + } + + // Improper expression + if ( expr === old ) { + if ( anyFound == null ) { + Sizzle.error( expr ); + + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Utility function for retreiving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +var getText = Sizzle.getText = function( elem ) { + var i, node, + nodeType = elem.nodeType, + ret = ""; + + if ( nodeType ) { + if ( nodeType === 1 || nodeType === 9 ) { + // Use textContent || innerText for elements + if ( typeof elem.textContent === 'string' ) { + return elem.textContent; + } else if ( typeof elem.innerText === 'string' ) { + // Replace IE's carriage returns + return elem.innerText.replace( rReturn, '' ); + } else { + // Traverse it's children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + } else { + + // If no nodeType, this is expected to be an array + for ( i = 0; (node = elem[i]); i++ ) { + // Do not traverse comment nodes + if ( node.nodeType !== 8 ) { + ret += getText( node ); + } + } + } + return ret; +}; + +var Expr = Sizzle.selectors = { + order: [ "ID", "NAME", "TAG" ], + + match: { + ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ + }, + + leftMatch: {}, + + attrMap: { + "class": "className", + "for": "htmlFor" + }, + + attrHandle: { + href: function( elem ) { + return elem.getAttribute( "href" ); + }, + type: function( elem ) { + return elem.getAttribute( "type" ); + } + }, + + relative: { + "+": function(checkSet, part){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !rNonWord.test( part ), + isPartStrNotTag = isPartStr && !isTag; + + if ( isTag ) { + part = part.toLowerCase(); + } + + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? + elem || false : + elem === part; + } + } + + if ( isPartStrNotTag ) { + Sizzle.filter( part, checkSet, true ); + } + }, + + ">": function( checkSet, part ) { + var elem, + isPartStr = typeof part === "string", + i = 0, + l = checkSet.length; + + if ( isPartStr && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + + for ( ; i < l; i++ ) { + elem = checkSet[i]; + + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; + } + } + + } else { + for ( ; i < l; i++ ) { + elem = checkSet[i]; + + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( isPartStr ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + + "": function(checkSet, part, isXML){ + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if ( typeof part === "string" && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); + }, + + "~": function( checkSet, part, isXML ) { + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if ( typeof part === "string" && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); + } + }, + + find: { + ID: function( match, context, isXML ) { + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [m] : []; + } + }, + + NAME: function( match, context ) { + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], + results = context.getElementsByName( match[1] ); + + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); + } + } + + return ret.length === 0 ? null : ret; + } + }, + + TAG: function( match, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( match[1] ); + } + } + }, + preFilter: { + CLASS: function( match, curLoop, inplace, result, not, isXML ) { + match = " " + match[1].replace( rBackslash, "" ) + " "; + + if ( isXML ) { + return match; + } + + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) { + if ( !inplace ) { + result.push( elem ); + } + + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + + ID: function( match ) { + return match[1].replace( rBackslash, "" ); + }, + + TAG: function( match, curLoop ) { + return match[1].replace( rBackslash, "" ).toLowerCase(); + }, + + CHILD: function( match ) { + if ( match[1] === "nth" ) { + if ( !match[2] ) { + Sizzle.error( match[0] ); + } + + match[2] = match[2].replace(/^\+|\s*/g, ''); + + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' + var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( + match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + // calculate the numbers (first)n+(last) including if they are negative + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + else if ( match[2] ) { + Sizzle.error( match[0] ); + } + + // TODO: Move to normal caching system + match[0] = done++; + + return match; + }, + + ATTR: function( match, curLoop, inplace, result, not, isXML ) { + var name = match[1] = match[1].replace( rBackslash, "" ); + + if ( !isXML && Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + // Handle if an un-quoted value was used + match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" ); + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + + PSEUDO: function( match, curLoop, inplace, result, not ) { + if ( match[1] === "not" ) { + // If we're dealing with a complex expression, or a simple one + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = Sizzle(match[3], null, null, curLoop); + + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + + if ( !inplace ) { + result.push.apply( result, ret ); + } + + return false; + } + + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { + return true; + } + + return match; + }, + + POS: function( match ) { + match.unshift( true ); + + return match; + } + }, + + filters: { + enabled: function( elem ) { + return elem.disabled === false && elem.type !== "hidden"; + }, + + disabled: function( elem ) { + return elem.disabled === true; + }, + + checked: function( elem ) { + return elem.checked === true; + }, + + selected: function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + parent: function( elem ) { + return !!elem.firstChild; + }, + + empty: function( elem ) { + return !elem.firstChild; + }, + + has: function( elem, i, match ) { + return !!Sizzle( match[3], elem ).length; + }, + + header: function( elem ) { + return (/h\d/i).test( elem.nodeName ); + }, + + text: function( elem ) { + var attr = elem.getAttribute( "type" ), type = elem.type; + // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) + // use getAttribute instead to test this case + return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null ); + }, + + radio: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type; + }, + + checkbox: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type; + }, + + file: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "file" === elem.type; + }, + + password: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "password" === elem.type; + }, + + submit: function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && "submit" === elem.type; + }, + + image: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "image" === elem.type; + }, + + reset: function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && "reset" === elem.type; + }, + + button: function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && "button" === elem.type || name === "button"; + }, + + input: function( elem ) { + return (/input|select|textarea|button/i).test( elem.nodeName ); + }, + + focus: function( elem ) { + return elem === elem.ownerDocument.activeElement; + } + }, + setFilters: { + first: function( elem, i ) { + return i === 0; + }, + + last: function( elem, i, match, array ) { + return i === array.length - 1; + }, + + even: function( elem, i ) { + return i % 2 === 0; + }, + + odd: function( elem, i ) { + return i % 2 === 1; + }, + + lt: function( elem, i, match ) { + return i < match[3] - 0; + }, + + gt: function( elem, i, match ) { + return i > match[3] - 0; + }, + + nth: function( elem, i, match ) { + return match[3] - 0 === i; + }, + + eq: function( elem, i, match ) { + return match[3] - 0 === i; + } + }, + filter: { + PSEUDO: function( elem, match, i, array ) { + var name = match[1], + filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0; + + } else if ( name === "not" ) { + var not = match[3]; + + for ( var j = 0, l = not.length; j < l; j++ ) { + if ( not[j] === elem ) { + return false; + } + } + + return true; + + } else { + Sizzle.error( name ); + } + }, + + CHILD: function( elem, match ) { + var first, last, + doneName, parent, cache, + count, diff, + type = match[1], + node = elem; + + switch ( type ) { + case "only": + case "first": + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + + if ( type === "first" ) { + return true; + } + + node = elem; + + case "last": + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + + return true; + + case "nth": + first = match[2]; + last = match[3]; + + if ( first === 1 && last === 0 ) { + return true; + } + + doneName = match[0]; + parent = elem.parentNode; + + if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) { + count = 0; + + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + + parent[ expando ] = doneName; + } + + diff = elem.nodeIndex - last; + + if ( first === 0 ) { + return diff === 0; + + } else { + return ( diff % first === 0 && diff / first >= 0 ); + } + } + }, + + ID: function( elem, match ) { + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + + TAG: function( elem, match ) { + return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match; + }, + + CLASS: function( elem, match ) { + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + + ATTR: function( elem, match ) { + var name = match[1], + result = Sizzle.attr ? + Sizzle.attr( elem, name ) : + Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + !type && Sizzle.attr ? + result != null : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value !== check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + + POS: function( elem, match, i, array ) { + var name = match[2], + filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } +}; + +var origPOS = Expr.match.POS, + fescape = function(all, num){ + return "\\" + (num - 0 + 1); + }; + +for ( var type in Expr.match ) { + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); +} + +var makeArray = function( array, results ) { + array = Array.prototype.slice.call( array, 0 ); + + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; + +// Perform a simple check to determine if the browser is capable of +// converting a NodeList to an array using builtin methods. +// Also verifies that the returned array holds DOM nodes +// (which is not the case in the Blackberry browser) +try { + Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; + +// Provide a fallback method if it does not work +} catch( e ) { + makeArray = function( array, results ) { + var i = 0, + ret = results || []; + + if ( toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + + } else { + if ( typeof array.length === "number" ) { + for ( var l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + + } else { + for ( ; array[i]; i++ ) { + ret.push( array[i] ); + } + } + } + + return ret; + }; +} + +var sortOrder, siblingCheck; + +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + return a.compareDocumentPosition ? -1 : 1; + } + + return a.compareDocumentPosition(b) & 4 ? -1 : 1; + }; + +} else { + sortOrder = function( a, b ) { + // The nodes are identical, we can exit early + if ( a === b ) { + hasDuplicate = true; + return 0; + + // Fallback to using sourceIndex (in IE) if it's available on both nodes + } else if ( a.sourceIndex && b.sourceIndex ) { + return a.sourceIndex - b.sourceIndex; + } + + var al, bl, + ap = [], + bp = [], + aup = a.parentNode, + bup = b.parentNode, + cur = aup; + + // If the nodes are siblings (or identical) we can do a quick check + if ( aup === bup ) { + return siblingCheck( a, b ); + + // If no parents were found then the nodes are disconnected + } else if ( !aup ) { + return -1; + + } else if ( !bup ) { + return 1; + } + + // Otherwise they're somewhere else in the tree so we need + // to build up a full list of the parentNodes for comparison + while ( cur ) { + ap.unshift( cur ); + cur = cur.parentNode; + } + + cur = bup; + + while ( cur ) { + bp.unshift( cur ); + cur = cur.parentNode; + } + + al = ap.length; + bl = bp.length; + + // Start walking down the tree looking for a discrepancy + for ( var i = 0; i < al && i < bl; i++ ) { + if ( ap[i] !== bp[i] ) { + return siblingCheck( ap[i], bp[i] ); + } + } + + // We ended someplace up the tree so do a sibling check + return i === al ? + siblingCheck( a, bp[i], -1 ) : + siblingCheck( ap[i], b, 1 ); + }; + + siblingCheck = function( a, b, ret ) { + if ( a === b ) { + return ret; + } + + var cur = a.nextSibling; + + while ( cur ) { + if ( cur === b ) { + return -1; + } + + cur = cur.nextSibling; + } + + return 1; + }; +} + +// Check to see if the browser returns elements by name when +// querying by getElementById (and provide a workaround) +(function(){ + // We're going to inject a fake input element with a specified name + var form = document.createElement("div"), + id = "script" + (new Date()).getTime(), + root = document.documentElement; + + form.innerHTML = ""; + + // Inject it into the root element, check its status, and remove it quickly + root.insertBefore( form, root.firstChild ); + + // The workaround has to do additional checks after a getElementById + // Which slows things down for other browsers (hence the branching) + if ( document.getElementById( id ) ) { + Expr.find.ID = function( match, context, isXML ) { + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + + return m ? + m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? + [m] : + undefined : + []; + } + }; + + Expr.filter.ID = function( elem, match ) { + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); + + // release memory in IE + root = form = null; +})(); + +(function(){ + // Check to see if the browser returns only elements + // when doing getElementsByTagName("*") + + // Create a fake element + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + // Make sure no comments are found + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function( match, context ) { + var results = context.getElementsByTagName( match[1] ); + + // Filter out possible comments + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; + } + + return results; + }; + } + + // Check to see if an attribute returns normalized href attributes + div.innerHTML = ""; + + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#" ) { + + Expr.attrHandle.href = function( elem ) { + return elem.getAttribute( "href", 2 ); + }; + } + + // release memory in IE + div = null; +})(); + +if ( document.querySelectorAll ) { + (function(){ + var oldSizzle = Sizzle, + div = document.createElement("div"), + id = "__sizzle__"; + + div.innerHTML = ""; + + // Safari can't handle uppercase or unicode characters when + // in quirks mode. + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; + } + + Sizzle = function( query, context, extra, seed ) { + context = context || document; + + // Only use querySelectorAll on non-XML documents + // (ID selectors don't work in non-HTML documents) + if ( !seed && !Sizzle.isXML(context) ) { + // See if we find a selector to speed up + var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query ); + + if ( match && (context.nodeType === 1 || context.nodeType === 9) ) { + // Speed-up: Sizzle("TAG") + if ( match[1] ) { + return makeArray( context.getElementsByTagName( query ), extra ); + + // Speed-up: Sizzle(".CLASS") + } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) { + return makeArray( context.getElementsByClassName( match[2] ), extra ); + } + } + + if ( context.nodeType === 9 ) { + // Speed-up: Sizzle("body") + // The body element only exists once, optimize finding it + if ( query === "body" && context.body ) { + return makeArray( [ context.body ], extra ); + + // Speed-up: Sizzle("#ID") + } else if ( match && match[3] ) { + var elem = context.getElementById( match[3] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id === match[3] ) { + return makeArray( [ elem ], extra ); + } + + } else { + return makeArray( [], extra ); + } + } + + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(qsaError) {} + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + var oldContext = context, + old = context.getAttribute( "id" ), + nid = old || id, + hasParent = context.parentNode, + relativeHierarchySelector = /^\s*[+~]/.test( query ); + + if ( !old ) { + context.setAttribute( "id", nid ); + } else { + nid = nid.replace( /'/g, "\\$&" ); + } + if ( relativeHierarchySelector && hasParent ) { + context = context.parentNode; + } + + try { + if ( !relativeHierarchySelector || hasParent ) { + return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); + } + + } catch(pseudoError) { + } finally { + if ( !old ) { + oldContext.removeAttribute( "id" ); + } + } + } + } + + return oldSizzle(query, context, extra, seed); + }; + + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + // release memory in IE + div = null; + })(); +} + +(function(){ + var html = document.documentElement, + matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector; + + if ( matches ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9 fails this) + var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ), + pseudoWorks = false; + + try { + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( document.documentElement, "[test!='']:sizzle" ); + + } catch( pseudoError ) { + pseudoWorks = true; + } + + Sizzle.matchesSelector = function( node, expr ) { + // Make sure that attribute selectors are quoted + expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); + + if ( !Sizzle.isXML( node ) ) { + try { + if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { + var ret = matches.call( node, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || !disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9, so check for that + node.document && node.document.nodeType !== 11 ) { + return ret; + } + } + } catch(e) {} + } + + return Sizzle(expr, null, null, [node]).length > 0; + }; + } +})(); + +(function(){ + var div = document.createElement("div"); + + div.innerHTML = ""; + + // Opera can't find a second classname (in 9.6) + // Also, make sure that getElementsByClassName actually exists + if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { + return; + } + + // Safari caches class attributes, doesn't catch changes (in 3.2) + div.lastChild.className = "e"; + + if ( div.getElementsByClassName("e").length === 1 ) { + return; + } + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function( match, context, isXML ) { + if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { + return context.getElementsByClassName(match[1]); + } + }; + + // release memory in IE + div = null; +})(); + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + + if ( elem ) { + var match = false; + + elem = elem[dir]; + + while ( elem ) { + if ( elem[ expando ] === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 && !isXML ){ + elem[ expando ] = doneName; + elem.sizset = i; + } + + if ( elem.nodeName.toLowerCase() === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + + if ( elem ) { + var match = false; + + elem = elem[dir]; + + while ( elem ) { + if ( elem[ expando ] === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem[ expando ] = doneName; + elem.sizset = i; + } + + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +if ( document.documentElement.contains ) { + Sizzle.contains = function( a, b ) { + return a !== b && (a.contains ? a.contains(b) : true); + }; + +} else if ( document.documentElement.compareDocumentPosition ) { + Sizzle.contains = function( a, b ) { + return !!(a.compareDocumentPosition(b) & 16); + }; + +} else { + Sizzle.contains = function() { + return false; + }; +} + +Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; + + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +var posProcess = function( selector, context, seed ) { + var match, + tmpSet = [], + later = "", + root = context.nodeType ? [context] : context; + + // Position selectors must be done after the filter + // And so must :not(positional) so we move all PSEUDOs to the end + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet, seed ); + } + + return Sizzle.filter( later, tmpSet ); +}; + +// EXPOSE +// Override sizzle attribute retrieval +Sizzle.attr = jQuery.attr; +Sizzle.selectors.attrMap = {}; +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.filters; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + +})(); + + +var runtil = /Until$/, + rparentsprev = /^(?:parents|prevUntil|prevAll)/, + // Note: This RegExp should be improved, or likely pulled from Sizzle + rmultiselector = /,/, + isSimple = /^.[^:#\[\.,]*$/, + slice = Array.prototype.slice, + POS = jQuery.expr.match.POS, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend({ + find: function( selector ) { + var self = this, + i, l; + + if ( typeof selector !== "string" ) { + return jQuery( selector ).filter(function() { + for ( i = 0, l = self.length; i < l; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + }); + } + + var ret = this.pushStack( "", "find", selector ), + length, n, r; + + for ( i = 0, l = this.length; i < l; i++ ) { + length = ret.length; + jQuery.find( selector, this[i], ret ); + + if ( i > 0 ) { + // Make sure that the results are unique + for ( n = length; n < ret.length; n++ ) { + for ( r = 0; r < length; r++ ) { + if ( ret[r] === ret[n] ) { + ret.splice(n--, 1); + break; + } + } + } + } + } + + return ret; + }, + + has: function( target ) { + var targets = jQuery( target ); + return this.filter(function() { + for ( var i = 0, l = targets.length; i < l; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + not: function( selector ) { + return this.pushStack( winnow(this, selector, false), "not", selector); + }, + + filter: function( selector ) { + return this.pushStack( winnow(this, selector, true), "filter", selector ); + }, + + is: function( selector ) { + return !!selector && ( + typeof selector === "string" ? + // If this is a positional selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + POS.test( selector ) ? + jQuery( selector, this.context ).index( this[0] ) >= 0 : + jQuery.filter( selector, this ).length > 0 : + this.filter( selector ).length > 0 ); + }, + + closest: function( selectors, context ) { + var ret = [], i, l, cur = this[0]; + + // Array (deprecated as of jQuery 1.7) + if ( jQuery.isArray( selectors ) ) { + var level = 1; + + while ( cur && cur.ownerDocument && cur !== context ) { + for ( i = 0; i < selectors.length; i++ ) { + + if ( jQuery( cur ).is( selectors[ i ] ) ) { + ret.push({ selector: selectors[ i ], elem: cur, level: level }); + } + } + + cur = cur.parentNode; + level++; + } + + return ret; + } + + // String + var pos = POS.test( selectors ) || typeof selectors !== "string" ? + jQuery( selectors, context || this.context ) : + 0; + + for ( i = 0, l = this.length; i < l; i++ ) { + cur = this[i]; + + while ( cur ) { + if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { + ret.push( cur ); + break; + + } else { + cur = cur.parentNode; + if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) { + break; + } + } + } + } + + ret = ret.length > 1 ? jQuery.unique( ret ) : ret; + + return this.pushStack( ret, "closest", selectors ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1; + } + + // index in selector + if ( typeof elem === "string" ) { + return jQuery.inArray( this[0], jQuery( elem ) ); + } + + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + var set = typeof selector === "string" ? + jQuery( selector, context ) : + jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ), + all = jQuery.merge( this.get(), set ); + + return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? + all : + jQuery.unique( all ) ); + }, + + andSelf: function() { + return this.add( this.prevObject ); + } +}); + +// A painfully simple check to see if an element is disconnected +// from a document (should be improved, where feasible). +function isDisconnected( node ) { + return !node || !node.parentNode || node.parentNode.nodeType === 11; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return jQuery.nth( elem, 2, "nextSibling" ); + }, + prev: function( elem ) { + return jQuery.nth( elem, 2, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( elem.parentNode.firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.makeArray( elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ); + + if ( !runtil.test( name ) ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; + + if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + + return this.pushStack( ret, name, slice.call( arguments ).join(",") ); + }; +}); + +jQuery.extend({ + filter: function( expr, elems, not ) { + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 ? + jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : + jQuery.find.matches(expr, elems); + }, + + dir: function( elem, dir, until ) { + var matched = [], + cur = elem[ dir ]; + + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + nth: function( cur, result, dir, elem ) { + result = result || 1; + var num = 0; + + for ( ; cur; cur = cur[dir] ) { + if ( cur.nodeType === 1 && ++num === result ) { + break; + } + } + + return cur; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, keep ) { + + // Can't pass null or undefined to indexOf in Firefox 4 + // Set to 0 to skip string check + qualifier = qualifier || 0; + + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep(elements, function( elem, i ) { + var retVal = !!qualifier.call( elem, i, elem ); + return retVal === keep; + }); + + } else if ( qualifier.nodeType ) { + return jQuery.grep(elements, function( elem, i ) { + return ( elem === qualifier ) === keep; + }); + + } else if ( typeof qualifier === "string" ) { + var filtered = jQuery.grep(elements, function( elem ) { + return elem.nodeType === 1; + }); + + if ( isSimple.test( qualifier ) ) { + return jQuery.filter(qualifier, filtered, !keep); + } else { + qualifier = jQuery.filter( qualifier, filtered ); + } + } + + return jQuery.grep(elements, function( elem, i ) { + return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep; + }); +} + + + + +function createSafeFragment( document ) { + var list = nodeNames.split( "|" ), + safeFrag = document.createDocumentFragment(); + + if ( safeFrag.createElement ) { + while ( list.length ) { + safeFrag.createElement( + list.pop() + ); + } + } + return safeFrag; +} + +var nodeNames = "abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|" + + "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", + rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, + rleadingWhitespace = /^\s+/, + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, + rtagName = /<([\w:]+)/, + rtbody = /", "" ], + legend: [ 1, "" ], + thead: [ 1, "
t ", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "" ], + col: [ 2, "
", " " ], + area: [ 1, "" ], + _default: [ 0, "", "" ] + }, + safeFragment = createSafeFragment( document ); + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// IE can't serialize and +
", "