[Jifty-commit] r2734 - in jifty: . branches/schema-plugins branches/schema-plugins/bin branches/schema-plugins/debian branches/schema-plugins/doc branches/schema-plugins/doc/examples branches/schema-plugins/doc/examples/CounterDemo branches/schema-plugins/doc/examples/CounterDemo/bin branches/schema-plugins/doc/examples/CounterDemo/doc branches/schema-plugins/doc/examples/CounterDemo/etc branches/schema-plugins/doc/examples/CounterDemo/lib branches/schema-plugins/doc/examples/CounterDemo/lib/CounterDemo branches/schema-plugins/doc/examples/CounterDemo/lib/CounterDemo/Action branches/schema-plugins/doc/examples/CounterDemo/lib/CounterDemo/Model branches/schema-plugins/doc/examples/CounterDemo/log branches/schema-plugins/doc/examples/CounterDemo/share branches/schema-plugins/doc/examples/CounterDemo/share/po branches/schema-plugins/doc/examples/CounterDemo/share/web branches/schema-plugins/doc/examples/CounterDemo/share/web/static branches/schema-plugins/doc/examples/CounterDemo/share/web/templates branches/schema-plugins/doc/examples/CounterDemo/t branches/schema-plugins/doc/examples/CounterDemo/var branches/schema-plugins/doc/examples/CounterDemo/var/mason branches/schema-plugins/doc/examples/CounterDemo/var/mason/cache branches/schema-plugins/doc/examples/CounterDemo/var/mason/obj branches/schema-plugins/doc/pubsub branches/schema-plugins/doc/talks branches/schema-plugins/doc/talks/npw.2006 branches/schema-plugins/doc/talks/npw.2006/Blog branches/schema-plugins/doc/talks/npw.2006/Blog/bin branches/schema-plugins/doc/talks/npw.2006/Blog/doc branches/schema-plugins/doc/talks/npw.2006/Blog/etc branches/schema-plugins/doc/talks/npw.2006/Blog/lib branches/schema-plugins/doc/talks/npw.2006/Blog/lib/Blog branches/schema-plugins/doc/talks/npw.2006/Blog/lib/Blog/Action branches/schema-plugins/doc/talks/npw.2006/Blog/lib/Blog/Model branches/schema-plugins/doc/talks/npw.2006/Blog/log branches/schema-plugins/doc/talks/npw.2006/Blog/share branches/schema-plugins/doc/talks/npw.2006/Blog/share/po branches/schema-plugins/doc/talks/npw.2006/Blog/share/web branches/schema-plugins/doc/talks/npw.2006/Blog/share/web/static branches/schema-plugins/doc/talks/npw.2006/Blog/share/web/templates branches/schema-plugins/doc/talks/npw.2006/Blog/t branches/schema-plugins/doc/talks/npw.2006/Blog/var branches/schema-plugins/etc branches/schema-plugins/examples branches/schema-plugins/examples/Chat branches/schema-plugins/examples/Chat/bin branches/schema-plugins/examples/Chat/doc branches/schema-plugins/examples/Chat/etc branches/schema-plugins/examples/Chat/lib branches/schema-plugins/examples/Chat/lib/Chat branches/schema-plugins/examples/Chat/lib/Chat/Action branches/schema-plugins/examples/Chat/lib/Chat/Event branches/schema-plugins/examples/Chat/lib/Chat/Model branches/schema-plugins/examples/Chat/log branches/schema-plugins/examples/Chat/share branches/schema-plugins/examples/Chat/share/po branches/schema-plugins/examples/Chat/share/web branches/schema-plugins/examples/Chat/share/web/static branches/schema-plugins/examples/Chat/share/web/templates branches/schema-plugins/examples/Chat/share/web/templates/fragments branches/schema-plugins/examples/Chat/t branches/schema-plugins/examples/Chat/var branches/schema-plugins/examples/Clock branches/schema-plugins/examples/Clock/bin branches/schema-plugins/examples/Clock/doc branches/schema-plugins/examples/Clock/etc branches/schema-plugins/examples/Clock/lib branches/schema-plugins/examples/Clock/lib/Clock branches/schema-plugins/examples/Clock/lib/Clock/Action branches/schema-plugins/examples/Clock/lib/Clock/Model branches/schema-plugins/examples/Clock/log branches/schema-plugins/examples/Clock/share branches/schema-plugins/examples/Clock/share/po branches/schema-plugins/examples/Clock/share/web branches/schema-plugins/examples/Clock/share/web/templates branches/schema-plugins/examples/Clock/share/web/templates/fragments branches/schema-plugins/examples/Clock/t branches/schema-plugins/examples/Clock/var branches/schema-plugins/examples/MyWeblog branches/schema-plugins/examples/MyWeblog/bin branches/schema-plugins/examples/MyWeblog/doc branches/schema-plugins/examples/MyWeblog/etc branches/schema-plugins/examples/MyWeblog/lib branches/schema-plugins/examples/MyWeblog/lib/MyWeblog branches/schema-plugins/examples/MyWeblog/lib/MyWeblog/Action branches/schema-plugins/examples/MyWeblog/lib/MyWeblog/Model branches/schema-plugins/examples/MyWeblog/share branches/schema-plugins/examples/MyWeblog/share/po branches/schema-plugins/examples/MyWeblog/share/web branches/schema-plugins/examples/MyWeblog/share/web/static branches/schema-plugins/examples/MyWeblog/share/web/templates branches/schema-plugins/examples/MyWeblog/share/web/templates/_elements branches/schema-plugins/examples/MyWeblog/share/web/templates/fragments branches/schema-plugins/examples/MyWeblog/t branches/schema-plugins/examples/MyWeblog/var branches/schema-plugins/examples/MyWeblog/var/mason branches/schema-plugins/examples/MyWeblog/var/mason/cache branches/schema-plugins/examples/Ping branches/schema-plugins/examples/Ping/bin branches/schema-plugins/examples/Ping/doc branches/schema-plugins/examples/Ping/etc branches/schema-plugins/examples/Ping/lib branches/schema-plugins/examples/Ping/lib/Ping branches/schema-plugins/examples/Ping/lib/Ping/Action branches/schema-plugins/examples/Ping/lib/Ping/Event branches/schema-plugins/examples/Ping/lib/Ping/Model branches/schema-plugins/examples/Ping/log branches/schema-plugins/examples/Ping/share branches/schema-plugins/examples/Ping/share/po branches/schema-plugins/examples/Ping/share/web branches/schema-plugins/examples/Ping/share/web/static branches/schema-plugins/examples/Ping/share/web/templates branches/schema-plugins/examples/Ping/share/web/templates/fragments branches/schema-plugins/examples/Ping/t branches/schema-plugins/inc branches/schema-plugins/inc/Module branches/schema-plugins/inc/Module/Install branches/schema-plugins/lib branches/schema-plugins/lib/Email branches/schema-plugins/lib/Email/Send branches/schema-plugins/lib/Email/Send/Jifty branches/schema-plugins/lib/Jifty branches/schema-plugins/lib/Jifty/Action branches/schema-plugins/lib/Jifty/Action/Devel branches/schema-plugins/lib/Jifty/Action/Record branches/schema-plugins/lib/Jifty/Event branches/schema-plugins/lib/Jifty/Filter branches/schema-plugins/lib/Jifty/Manual branches/schema-plugins/lib/Jifty/Mason branches/schema-plugins/lib/Jifty/Model branches/schema-plugins/lib/Jifty/Module branches/schema-plugins/lib/Jifty/Param branches/schema-plugins/lib/Jifty/Plugin branches/schema-plugins/lib/Jifty/Plugin/REST branches/schema-plugins/lib/Jifty/Request branches/schema-plugins/lib/Jifty/Script branches/schema-plugins/lib/Jifty/Subs branches/schema-plugins/lib/Jifty/Test branches/schema-plugins/lib/Jifty/Test/WWW branches/schema-plugins/lib/Jifty/Upgrade branches/schema-plugins/lib/Jifty/View branches/schema-plugins/lib/Jifty/View/Mason branches/schema-plugins/lib/Jifty/View/Static branches/schema-plugins/lib/Jifty/Web branches/schema-plugins/lib/Jifty/Web/Form branches/schema-plugins/lib/Jifty/Web/Form/Field branches/schema-plugins/lib/Jifty/Web/Session branches/schema-plugins/lib/auto branches/schema-plugins/plugins branches/schema-plugins/plugins/AuthCASLogin branches/schema-plugins/plugins/AuthCASLogin/debian branches/schema-plugins/plugins/AuthCASLogin/doc branches/schema-plugins/plugins/AuthCASLogin/lib branches/schema-plugins/plugins/AuthCASLogin/lib/Jifty branches/schema-plugins/plugins/AuthCASLogin/lib/Jifty/Plugin branches/schema-plugins/plugins/AuthCASLogin/lib/Jifty/Plugin/AuthCASLogin branches/schema-plugins/plugins/AuthCASLogin/lib/Jifty/Plugin/AuthCASLogin/Action branches/schema-plugins/plugins/AuthCASLogin/lib/Jifty/Plugin/AuthCASLogin/Model branches/schema-plugins/plugins/AuthCASLogin/share branches/schema-plugins/plugins/AuthCASLogin/share/po branches/schema-plugins/plugins/AuthCASLogin/share/web branches/schema-plugins/plugins/AuthCASLogin/share/web/static branches/schema-plugins/plugins/AuthCASLogin/share/web/templates branches/schema-plugins/plugins/AuthCASLogin/t branches/schema-plugins/plugins/AuthCASOnly branches/schema-plugins/plugins/AuthCASOnly/debian branches/schema-plugins/plugins/AuthCASOnly/doc branches/schema-plugins/plugins/AuthCASOnly/lib branches/schema-plugins/plugins/AuthCASOnly/lib/Jifty branches/schema-plugins/plugins/AuthCASOnly/lib/Jifty/Plugin branches/schema-plugins/plugins/AuthCASOnly/lib/Jifty/Plugin/AuthCASOnly branches/schema-plugins/plugins/AuthCASOnly/lib/Jifty/Plugin/AuthCASOnly/Action branches/schema-plugins/plugins/AuthCASOnly/lib/Jifty/Plugin/AuthCASOnly/Model branches/schema-plugins/plugins/AuthCASOnly/share branches/schema-plugins/plugins/AuthCASOnly/share/po branches/schema-plugins/plugins/AuthCASOnly/share/web branches/schema-plugins/plugins/AuthCASOnly/share/web/static branches/schema-plugins/plugins/AuthCASOnly/share/web/templates branches/schema-plugins/plugins/AuthCASOnly/t branches/schema-plugins/plugins/AuthLDAPLogin branches/schema-plugins/plugins/AuthLDAPLogin/debian branches/schema-plugins/plugins/AuthLDAPLogin/doc branches/schema-plugins/plugins/AuthLDAPLogin/lib branches/schema-plugins/plugins/AuthLDAPLogin/lib/Jifty branches/schema-plugins/plugins/AuthLDAPLogin/lib/Jifty/Plugin branches/schema-plugins/plugins/AuthLDAPLogin/lib/Jifty/Plugin/AuthLDAPLogin branches/schema-plugins/plugins/AuthLDAPLogin/lib/Jifty/Plugin/AuthLDAPLogin/Action branches/schema-plugins/plugins/AuthLDAPLogin/lib/Jifty/Plugin/AuthLDAPLogin/Model branches/schema-plugins/plugins/AuthLDAPLogin/share branches/schema-plugins/plugins/AuthLDAPLogin/share/po branches/schema-plugins/plugins/AuthLDAPLogin/share/web branches/schema-plugins/plugins/AuthLDAPLogin/share/web/static branches/schema-plugins/plugins/AuthLDAPLogin/share/web/templates branches/schema-plugins/plugins/AuthLDAPLogin/t branches/schema-plugins/plugins/AuthLDAPOnly branches/schema-plugins/plugins/AuthLDAPOnly/debian branches/schema-plugins/plugins/AuthLDAPOnly/doc branches/schema-plugins/plugins/AuthLDAPOnly/lib branches/schema-plugins/plugins/AuthLDAPOnly/lib/Jifty branches/schema-plugins/plugins/AuthLDAPOnly/lib/Jifty/Plugin branches/schema-plugins/plugins/AuthLDAPOnly/lib/Jifty/Plugin/AuthLDAPOnly branches/schema-plugins/plugins/AuthLDAPOnly/lib/Jifty/Plugin/AuthLDAPOnly/Action branches/schema-plugins/plugins/AuthLDAPOnly/lib/Jifty/Plugin/AuthLDAPOnly/Model branches/schema-plugins/plugins/AuthLDAPOnly/share branches/schema-plugins/plugins/AuthLDAPOnly/share/po branches/schema-plugins/plugins/AuthLDAPOnly/share/web branches/schema-plugins/plugins/AuthLDAPOnly/share/web/static branches/schema-plugins/plugins/AuthLDAPOnly/share/web/templates branches/schema-plugins/plugins/AuthLDAPOnly/t branches/schema-plugins/plugins/AuthzLDAP branches/schema-plugins/plugins/AuthzLDAP/doc branches/schema-plugins/plugins/AuthzLDAP/lib branches/schema-plugins/plugins/AuthzLDAP/lib/Jifty branches/schema-plugins/plugins/AuthzLDAP/lib/Jifty/Plugin branches/schema-plugins/plugins/AuthzLDAP/lib/Jifty/Plugin/AuthzLDAP branches/schema-plugins/plugins/AuthzLDAP/lib/Jifty/Plugin/AuthzLDAP/Action branches/schema-plugins/plugins/AuthzLDAP/lib/Jifty/Plugin/AuthzLDAP/Model branches/schema-plugins/plugins/AuthzLDAP/share branches/schema-plugins/plugins/AuthzLDAP/share/po branches/schema-plugins/plugins/AuthzLDAP/share/web branches/schema-plugins/plugins/AuthzLDAP/share/web/static branches/schema-plugins/plugins/AuthzLDAP/share/web/templates branches/schema-plugins/plugins/AuthzLDAP/share/web/templates/error branches/schema-plugins/plugins/AuthzLDAP/t branches/schema-plugins/plugins/EditInPlace branches/schema-plugins/plugins/EditInPlace/debian branches/schema-plugins/plugins/EditInPlace/inc branches/schema-plugins/plugins/EditInPlace/inc/Module branches/schema-plugins/plugins/EditInPlace/inc/Module/Install branches/schema-plugins/plugins/EditInPlace/inc/Module/Install/Makefile branches/schema-plugins/plugins/EditInPlace/lib branches/schema-plugins/plugins/EditInPlace/lib/Jifty branches/schema-plugins/plugins/EditInPlace/lib/Jifty/Plugin branches/schema-plugins/plugins/EditInPlace/lib/Jifty/Plugin/EditInPlace branches/schema-plugins/plugins/EditInPlace/lib/Jifty/Plugin/EditInPlace/Action branches/schema-plugins/plugins/EditInPlace/share branches/schema-plugins/plugins/EditInPlace/share/web branches/schema-plugins/plugins/EditInPlace/share/web/templates branches/schema-plugins/plugins/EditInPlace/share/web/templates/__jifty branches/schema-plugins/plugins/EmailErrors branches/schema-plugins/plugins/EmailErrors/doc branches/schema-plugins/plugins/EmailErrors/lib branches/schema-plugins/plugins/EmailErrors/lib/Jifty branches/schema-plugins/plugins/EmailErrors/lib/Jifty/Plugin branches/schema-plugins/plugins/EmailErrors/lib/Jifty/Plugin/EmailErrors branches/schema-plugins/plugins/EmailErrors/lib/Jifty/Plugin/EmailErrors/Action branches/schema-plugins/plugins/EmailErrors/lib/Jifty/Plugin/EmailErrors/Model branches/schema-plugins/plugins/EmailErrors/lib/Jifty/Plugin/EmailErrors/Notification branches/schema-plugins/plugins/EmailErrors/share branches/schema-plugins/plugins/EmailErrors/share/web branches/schema-plugins/plugins/EmailErrors/share/web/templates branches/schema-plugins/plugins/LetMe branches/schema-plugins/plugins/LetMe/doc branches/schema-plugins/plugins/LetMe/lib branches/schema-plugins/plugins/LetMe/lib/Jifty branches/schema-plugins/plugins/LetMe/lib/Jifty/Plugin branches/schema-plugins/plugins/LetMe/lib/Jifty/Plugin/LetMe branches/schema-plugins/plugins/LetMe/lib/Jifty/Plugin/LetMe/Action branches/schema-plugins/plugins/LetMe/lib/Jifty/Plugin/LetMe/Model branches/schema-plugins/plugins/LetMe/share branches/schema-plugins/plugins/LetMe/share/po branches/schema-plugins/plugins/LetMe/share/web branches/schema-plugins/plugins/LetMe/share/web/static branches/schema-plugins/plugins/LetMe/share/web/templates branches/schema-plugins/plugins/LetMe/t branches/schema-plugins/plugins/Login branches/schema-plugins/plugins/Login/debian branches/schema-plugins/plugins/Login/doc branches/schema-plugins/plugins/Login/inc branches/schema-plugins/plugins/Login/inc/Module branches/schema-plugins/plugins/Login/inc/Module/Install branches/schema-plugins/plugins/Login/lib branches/schema-plugins/plugins/Login/lib/Jifty branches/schema-plugins/plugins/Login/lib/Jifty/Plugin branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Action branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Model branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Notification branches/schema-plugins/plugins/Login/share branches/schema-plugins/plugins/Login/share/po branches/schema-plugins/plugins/Login/share/web branches/schema-plugins/plugins/Login/share/web/static branches/schema-plugins/plugins/Login/share/web/templates branches/schema-plugins/plugins/Login/share/web/templates/let branches/schema-plugins/plugins/Login/t branches/schema-plugins/plugins/Nothing branches/schema-plugins/plugins/Nothing/doc branches/schema-plugins/plugins/Nothing/lib branches/schema-plugins/plugins/Nothing/lib/Jifty branches/schema-plugins/plugins/Nothing/lib/Jifty/Plugin branches/schema-plugins/plugins/Nothing/lib/Jifty/Plugin/Nothing branches/schema-plugins/plugins/Nothing/lib/Jifty/Plugin/Nothing/Action branches/schema-plugins/plugins/Nothing/lib/Jifty/Plugin/Nothing/Model branches/schema-plugins/plugins/Nothing/share branches/schema-plugins/plugins/Nothing/share/po branches/schema-plugins/plugins/Nothing/share/web branches/schema-plugins/plugins/Nothing/share/web/static branches/schema-plugins/plugins/Nothing/share/web/templates branches/schema-plugins/plugins/ProfileBehaviour branches/schema-plugins/plugins/ProfileBehaviour/doc branches/schema-plugins/plugins/ProfileBehaviour/lib branches/schema-plugins/plugins/ProfileBehaviour/lib/Jifty branches/schema-plugins/plugins/ProfileBehaviour/lib/Jifty/Plugin branches/schema-plugins/plugins/ProfileBehaviour/lib/Jifty/Plugin/ProfileBehaviour branches/schema-plugins/plugins/ProfileBehaviour/lib/Jifty/Plugin/ProfileBehaviour/Action branches/schema-plugins/plugins/ProfileBehaviour/lib/Jifty/Plugin/ProfileBehaviour/Model branches/schema-plugins/plugins/ProfileBehaviour/share branches/schema-plugins/plugins/ProfileBehaviour/share/po branches/schema-plugins/plugins/ProfileBehaviour/share/web branches/schema-plugins/plugins/ProfileBehaviour/share/web/static branches/schema-plugins/plugins/ProfileBehaviour/share/web/static/css branches/schema-plugins/plugins/ProfileBehaviour/share/web/static/js branches/schema-plugins/plugins/ProfileBehaviour/share/web/templates branches/schema-plugins/plugins/ProfileBehaviour/t branches/schema-plugins/plugins/Users branches/schema-plugins/plugins/Users-Identity-File branches/schema-plugins/plugins/Users-Identity-File/doc branches/schema-plugins/plugins/Users-Identity-File/lib branches/schema-plugins/plugins/Users-Identity-File/lib/Jifty branches/schema-plugins/plugins/Users-Identity-File/lib/Jifty/Plugin branches/schema-plugins/plugins/Users-Identity-File/lib/Jifty/Plugin/Users branches/schema-plugins/plugins/Users-Identity-File/lib/Jifty/Plugin/Users/Identity branches/schema-plugins/plugins/Users-Identity-File/lib/Jifty/Plugin/Users/Identity/File branches/schema-plugins/plugins/Users-Identity-File/lib/Jifty/Plugin/Users/Identity/File/Action branches/schema-plugins/plugins/Users-Identity-File/lib/Jifty/Plugin/Users/Identity/File/Model branches/schema-plugins/plugins/Users-Identity-File/share branches/schema-plugins/plugins/Users-Identity-File/share/po branches/schema-plugins/plugins/Users-Identity-File/share/web branches/schema-plugins/plugins/Users-Identity-File/share/web/static branches/schema-plugins/plugins/Users-Identity-File/share/web/templates branches/schema-plugins/plugins/Users-Identity-File/t branches/schema-plugins/plugins/Users/doc branches/schema-plugins/plugins/Users/lib branches/schema-plugins/plugins/Users/lib/Jifty branches/schema-plugins/plugins/Users/lib/Jifty/Plugin branches/schema-plugins/plugins/Users/lib/Jifty/Plugin/Users branches/schema-plugins/plugins/Users/lib/Jifty/Plugin/Users/Action branches/schema-plugins/plugins/Users/lib/Jifty/Plugin/Users/Model branches/schema-plugins/plugins/Users/share branches/schema-plugins/plugins/Users/share/po branches/schema-plugins/plugins/Users/share/web branches/schema-plugins/plugins/Users/share/web/static branches/schema-plugins/plugins/Users/share/web/templates branches/schema-plugins/plugins/Users/t branches/schema-plugins/share branches/schema-plugins/share/dtd branches/schema-plugins/share/po branches/schema-plugins/share/web branches/schema-plugins/share/web/static branches/schema-plugins/share/web/static/css branches/schema-plugins/share/web/static/css/yui branches/schema-plugins/share/web/static/css/yui/calendar branches/schema-plugins/share/web/static/css/yui/tabview branches/schema-plugins/share/web/static/images branches/schema-plugins/share/web/static/images/css branches/schema-plugins/share/web/static/images/iepngfix branches/schema-plugins/share/web/static/images/silk branches/schema-plugins/share/web/static/images/yui branches/schema-plugins/share/web/static/images/yui/us branches/schema-plugins/share/web/static/images/yui/us/my branches/schema-plugins/share/web/static/images/yui/us/my/bn branches/schema-plugins/share/web/static/images/yui/us/tr branches/schema-plugins/share/web/static/js branches/schema-plugins/share/web/static/js/cssquery branches/schema-plugins/share/web/static/js/jsan branches/schema-plugins/share/web/static/js/jsan/DOM branches/schema-plugins/share/web/static/js/jsan/Upgrade branches/schema-plugins/share/web/static/js/jsan/Upgrade/Array branches/schema-plugins/share/web/static/js/jsan/Upgrade/Function branches/schema-plugins/share/web/static/js/scriptaculous branches/schema-plugins/share/web/static/js/yui branches/schema-plugins/share/web/templates branches/schema-plugins/share/web/templates/= branches/schema-plugins/share/web/templates/__jifty branches/schema-plugins/share/web/templates/__jifty/admin branches/schema-plugins/share/web/templates/__jifty/admin/_elements branches/schema-plugins/share/web/templates/__jifty/admin/action branches/schema-plugins/share/web/templates/__jifty/admin/fragments branches/schema-plugins/share/web/templates/__jifty/admin/fragments/list branches/schema-plugins/share/web/templates/__jifty/admin/model branches/schema-plugins/share/web/templates/__jifty/css branches/schema-plugins/share/web/templates/__jifty/error branches/schema-plugins/share/web/templates/__jifty/error/_elements branches/schema-plugins/share/web/templates/__jifty/js branches/schema-plugins/share/web/templates/__jifty/online_docs branches/schema-plugins/share/web/templates/__jifty/webservices branches/schema-plugins/share/web/templates/_elements branches/schema-plugins/share/web/templates/helpers branches/schema-plugins/t branches/schema-plugins/t/Continuations branches/schema-plugins/t/Continuations/bin branches/schema-plugins/t/Continuations/lib branches/schema-plugins/t/Continuations/lib/Continuations branches/schema-plugins/t/Continuations/lib/Continuations/Action branches/schema-plugins/t/Continuations/share branches/schema-plugins/t/Continuations/share/web branches/schema-plugins/t/Continuations/share/web/templates branches/schema-plugins/t/Continuations/t branches/schema-plugins/t/Mapper branches/schema-plugins/t/Mapper/bin branches/schema-plugins/t/Mapper/lib branches/schema-plugins/t/Mapper/lib/Mapper branches/schema-plugins/t/Mapper/lib/Mapper/Action branches/schema-plugins/t/Mapper/share branches/schema-plugins/t/Mapper/share/web branches/schema-plugins/t/Mapper/share/web/templates branches/schema-plugins/t/Mapper/t branches/schema-plugins/t/TestApp branches/schema-plugins/t/TestApp-Plugin-REST branches/schema-plugins/t/TestApp-Plugin-REST/bin branches/schema-plugins/t/TestApp-Plugin-REST/etc branches/schema-plugins/t/TestApp-Plugin-REST/lib branches/schema-plugins/t/TestApp-Plugin-REST/lib/TestApp branches/schema-plugins/t/TestApp-Plugin-REST/lib/TestApp/Plugin branches/schema-plugins/t/TestApp-Plugin-REST/lib/TestApp/Plugin/REST branches/schema-plugins/t/TestApp-Plugin-REST/lib/TestApp/Plugin/REST/Action branches/schema-plugins/t/TestApp-Plugin-REST/lib/TestApp/Plugin/REST/Model branches/schema-plugins/t/TestApp-Plugin-REST/share branches/schema-plugins/t/TestApp-Plugin-REST/t branches/schema-plugins/t/TestApp/bin branches/schema-plugins/t/TestApp/lib branches/schema-plugins/t/TestApp/lib/TestApp branches/schema-plugins/t/TestApp/lib/TestApp/Action branches/schema-plugins/t/TestApp/lib/TestApp/Model branches/schema-plugins/t/TestApp/share branches/schema-plugins/t/TestApp/share/web branches/schema-plugins/t/TestApp/share/web/static branches/schema-plugins/t/TestApp/share/web/static/images branches/schema-plugins/t/TestApp/share/web/templates branches/schema-plugins/t/TestApp/share/web/templates/dispatch branches/schema-plugins/t/TestApp/share/web/templates/regions branches/schema-plugins/t/TestApp/share/web/templates/somedir branches/schema-plugins/t/TestApp/t branches/schema-plugins/t/lib branches/schema-plugins/t/lib/Jifty

jifty-commit at lists.jifty.org jifty-commit at lists.jifty.org
Mon Jan 29 21:05:31 EST 2007


Author: sterling
Date: Mon Jan 29 21:05:02 2007
New Revision: 2734

Added:
   jifty/branches/schema-plugins/   (props changed)
   jifty/branches/schema-plugins/AUTHORS
   jifty/branches/schema-plugins/Changelog
   jifty/branches/schema-plugins/MANIFEST
   jifty/branches/schema-plugins/MANIFEST.SKIP
   jifty/branches/schema-plugins/META.yml
   jifty/branches/schema-plugins/Makefile.PL
   jifty/branches/schema-plugins/README
   jifty/branches/schema-plugins/SIGNATURE
   jifty/branches/schema-plugins/bin/
   jifty/branches/schema-plugins/bin/build_par
   jifty/branches/schema-plugins/bin/jifty   (contents, props changed)
   jifty/branches/schema-plugins/bin/runcover   (contents, props changed)
   jifty/branches/schema-plugins/bin/service   (contents, props changed)
   jifty/branches/schema-plugins/bin/xgettext
   jifty/branches/schema-plugins/debian/
   jifty/branches/schema-plugins/debian/README
   jifty/branches/schema-plugins/debian/changelog
   jifty/branches/schema-plugins/debian/compat
   jifty/branches/schema-plugins/debian/control
   jifty/branches/schema-plugins/debian/rules   (contents, props changed)
   jifty/branches/schema-plugins/doc/
   jifty/branches/schema-plugins/doc/ajax-upgraded-links
   jifty/branches/schema-plugins/doc/building_a_par
   jifty/branches/schema-plugins/doc/client_side_continuations
   jifty/branches/schema-plugins/doc/command_naming
   jifty/branches/schema-plugins/doc/declarative-test-design
   jifty/branches/schema-plugins/doc/edit-in-place
   jifty/branches/schema-plugins/doc/examples/
   jifty/branches/schema-plugins/doc/examples/CounterDemo/
   jifty/branches/schema-plugins/doc/examples/CounterDemo/Makefile.PL
   jifty/branches/schema-plugins/doc/examples/CounterDemo/bin/
   jifty/branches/schema-plugins/doc/examples/CounterDemo/bin/jifty   (contents, props changed)
   jifty/branches/schema-plugins/doc/examples/CounterDemo/doc/
   jifty/branches/schema-plugins/doc/examples/CounterDemo/etc/
   jifty/branches/schema-plugins/doc/examples/CounterDemo/etc/config.yml
   jifty/branches/schema-plugins/doc/examples/CounterDemo/lib/
   jifty/branches/schema-plugins/doc/examples/CounterDemo/lib/CounterDemo/
   jifty/branches/schema-plugins/doc/examples/CounterDemo/lib/CounterDemo/Action/
   jifty/branches/schema-plugins/doc/examples/CounterDemo/lib/CounterDemo/Model/
   jifty/branches/schema-plugins/doc/examples/CounterDemo/log/
   jifty/branches/schema-plugins/doc/examples/CounterDemo/share/
   jifty/branches/schema-plugins/doc/examples/CounterDemo/share/po/
   jifty/branches/schema-plugins/doc/examples/CounterDemo/share/web/
   jifty/branches/schema-plugins/doc/examples/CounterDemo/share/web/static/
   jifty/branches/schema-plugins/doc/examples/CounterDemo/share/web/templates/
   jifty/branches/schema-plugins/doc/examples/CounterDemo/share/web/templates/index.html
   jifty/branches/schema-plugins/doc/examples/CounterDemo/t/
   jifty/branches/schema-plugins/doc/examples/CounterDemo/t/00-counter-test.t   (contents, props changed)
   jifty/branches/schema-plugins/doc/examples/CounterDemo/var/
   jifty/branches/schema-plugins/doc/examples/CounterDemo/var/mason/
   jifty/branches/schema-plugins/doc/examples/CounterDemo/var/mason/cache/
   jifty/branches/schema-plugins/doc/examples/CounterDemo/var/mason/obj/
   jifty/branches/schema-plugins/doc/jifty-action-record-search
   jifty/branches/schema-plugins/doc/jifty-dispatcher.graffle
   jifty/branches/schema-plugins/doc/jifty-dispatcher.svg
   jifty/branches/schema-plugins/doc/jifty-model-svk
   jifty/branches/schema-plugins/doc/jifty-plugins-2.0
   jifty/branches/schema-plugins/doc/jifty-web-form-etc
   jifty/branches/schema-plugins/doc/packaging
   jifty/branches/schema-plugins/doc/plugin-requirements
   jifty/branches/schema-plugins/doc/plugin-syntax
   jifty/branches/schema-plugins/doc/plugins-restated-assumptions
   jifty/branches/schema-plugins/doc/pubsub/
   jifty/branches/schema-plugins/doc/pubsub/backend_message_types
   jifty/branches/schema-plugins/doc/pubsub/subscriptions
   jifty/branches/schema-plugins/doc/pubsub/system_architecture.graffle
   jifty/branches/schema-plugins/doc/pubsub/system_architecture.png   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/
   jifty/branches/schema-plugins/doc/talks/amazon-jifty.pdf   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/blogdemo.png   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/component-tree.png   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/edit.png   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/error.png   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/euroscon.css
   jifty/branches/schema-plugins/doc/talks/halo-overview.png   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/halo.png   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/lpw_intro_jifty_2006.key.tgz   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/lpw_intro_jifty_2006.pdf   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/lpw_scary_jifty.key.tgz   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/lpw_scary_jifty.pdf   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/new-entry.png   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/npw.2006/
   jifty/branches/schema-plugins/doc/talks/npw.2006.xul   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/
   jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/Makefile.PL
   jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/bin/
   jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/bin/jifty   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/doc/
   jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/etc/
   jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/etc/config.yml
   jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/lib/
   jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/lib/Blog/
   jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/lib/Blog/Action/
   jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/lib/Blog/Dispatcher.pm
   jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/lib/Blog/Model/
   jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/lib/Blog/Model/Entry.pm
   jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/log/
   jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/share/
   jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/share/po/
   jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/share/web/
   jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/share/web/static/
   jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/share/web/templates/
   jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/share/web/templates/index.html
   jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/share/web/templates/new_entry
   jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/t/
   jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/t/00-model-Entry.t
   jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/var/
   jifty/branches/schema-plugins/doc/talks/npw.2006/blog.png   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/npw.2006/canonicalize_entry.png   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/npw.2006/halo.png   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/npw.2006/halo2.png   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/npw.2006/hello.png   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/npw.2006/hello_jesse.png   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/npw.2006/jifty_3_files.png   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/npw.2006/jifty_app.png   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/npw.2006/jifty_edit_model.png   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/npw.2006/jifty_model.png   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/npw.2006/jifty_schema_setup.png   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/npw.2006/jifty_server.png   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/npw.2006/jifty_web_1.png   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/npw.2006/jifty_web_crud.png   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/npw.2006/jifty_web_crud_create_2.png   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/npw.2006/jifty_web_crud_edit.png   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/npw.2006/jifty_web_crud_edited.png   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/npw.2006/jifty_web_crud_new.png   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/npw.2006/jifty_web_dbadmin.png   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/npw.2006/makefile_pl.png   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/npw.2006/new_entry.png   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/npw.2006/online_docs.png   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/npw.2006/tests.png   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/npw.2006/validate_entry.png   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/npw2005.xul   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/oscon-europe.2006.xul   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/oscon.2006.xul   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/pony.jpg   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/pony.png   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/takahashi.css
   jifty/branches/schema-plugins/doc/talks/takahashi.js
   jifty/branches/schema-plugins/doc/talks/yapc.asia.2006.xul   (contents, props changed)
   jifty/branches/schema-plugins/doc/talks/yapc.na.2006.xul   (contents, props changed)
   jifty/branches/schema-plugins/etc/
   jifty/branches/schema-plugins/etc/config.yml
   jifty/branches/schema-plugins/etc/site_config.yml
   jifty/branches/schema-plugins/examples/
   jifty/branches/schema-plugins/examples/Chat/
   jifty/branches/schema-plugins/examples/Chat/Makefile.PL
   jifty/branches/schema-plugins/examples/Chat/bin/
   jifty/branches/schema-plugins/examples/Chat/bin/jifty   (contents, props changed)
   jifty/branches/schema-plugins/examples/Chat/doc/
   jifty/branches/schema-plugins/examples/Chat/etc/
   jifty/branches/schema-plugins/examples/Chat/etc/config.yml
   jifty/branches/schema-plugins/examples/Chat/lib/
   jifty/branches/schema-plugins/examples/Chat/lib/Chat/
   jifty/branches/schema-plugins/examples/Chat/lib/Chat/Action/
   jifty/branches/schema-plugins/examples/Chat/lib/Chat/Action/Send.pm
   jifty/branches/schema-plugins/examples/Chat/lib/Chat/Event/
   jifty/branches/schema-plugins/examples/Chat/lib/Chat/Event/Message.pm
   jifty/branches/schema-plugins/examples/Chat/lib/Chat/Model/
   jifty/branches/schema-plugins/examples/Chat/lib/Chat/Server.pm
   jifty/branches/schema-plugins/examples/Chat/log/
   jifty/branches/schema-plugins/examples/Chat/share/
   jifty/branches/schema-plugins/examples/Chat/share/po/
   jifty/branches/schema-plugins/examples/Chat/share/web/
   jifty/branches/schema-plugins/examples/Chat/share/web/static/
   jifty/branches/schema-plugins/examples/Chat/share/web/templates/
   jifty/branches/schema-plugins/examples/Chat/share/web/templates/fragments/
   jifty/branches/schema-plugins/examples/Chat/share/web/templates/fragments/message
   jifty/branches/schema-plugins/examples/Chat/share/web/templates/fragments/sender
   jifty/branches/schema-plugins/examples/Chat/share/web/templates/index.html
   jifty/branches/schema-plugins/examples/Chat/t/
   jifty/branches/schema-plugins/examples/Chat/t/00compile.t
   jifty/branches/schema-plugins/examples/Chat/t/01startup.t
   jifty/branches/schema-plugins/examples/Chat/var/
   jifty/branches/schema-plugins/examples/Clock/
   jifty/branches/schema-plugins/examples/Clock/Makefile.PL
   jifty/branches/schema-plugins/examples/Clock/bin/
   jifty/branches/schema-plugins/examples/Clock/bin/jifty   (contents, props changed)
   jifty/branches/schema-plugins/examples/Clock/clockserv.pl
   jifty/branches/schema-plugins/examples/Clock/doc/
   jifty/branches/schema-plugins/examples/Clock/etc/
   jifty/branches/schema-plugins/examples/Clock/etc/config.yml
   jifty/branches/schema-plugins/examples/Clock/lib/
   jifty/branches/schema-plugins/examples/Clock/lib/Clock/
   jifty/branches/schema-plugins/examples/Clock/lib/Clock/Action/
   jifty/branches/schema-plugins/examples/Clock/lib/Clock/Event/
   jifty/branches/schema-plugins/examples/Clock/lib/Clock/Event/Tick.pm
   jifty/branches/schema-plugins/examples/Clock/lib/Clock/Model/
   jifty/branches/schema-plugins/examples/Clock/lib/Clock/Server.pm
   jifty/branches/schema-plugins/examples/Clock/log/
   jifty/branches/schema-plugins/examples/Clock/share/
   jifty/branches/schema-plugins/examples/Clock/share/po/
   jifty/branches/schema-plugins/examples/Clock/share/web/
   jifty/branches/schema-plugins/examples/Clock/share/web/templates/
   jifty/branches/schema-plugins/examples/Clock/share/web/templates/fragments/
   jifty/branches/schema-plugins/examples/Clock/share/web/templates/fragments/time
   jifty/branches/schema-plugins/examples/Clock/share/web/templates/index.html
   jifty/branches/schema-plugins/examples/Clock/t/
   jifty/branches/schema-plugins/examples/Clock/var/
   jifty/branches/schema-plugins/examples/MyWeblog/
   jifty/branches/schema-plugins/examples/MyWeblog/Makefile.PL
   jifty/branches/schema-plugins/examples/MyWeblog/bin/
   jifty/branches/schema-plugins/examples/MyWeblog/bin/jifty   (contents, props changed)
   jifty/branches/schema-plugins/examples/MyWeblog/doc/
   jifty/branches/schema-plugins/examples/MyWeblog/etc/
   jifty/branches/schema-plugins/examples/MyWeblog/etc/config.yml
   jifty/branches/schema-plugins/examples/MyWeblog/lib/
   jifty/branches/schema-plugins/examples/MyWeblog/lib/MyWeblog/
   jifty/branches/schema-plugins/examples/MyWeblog/lib/MyWeblog/Action/
   jifty/branches/schema-plugins/examples/MyWeblog/lib/MyWeblog/Model/
   jifty/branches/schema-plugins/examples/MyWeblog/lib/MyWeblog/Model/Post.pm
   jifty/branches/schema-plugins/examples/MyWeblog/share/
   jifty/branches/schema-plugins/examples/MyWeblog/share/po/
   jifty/branches/schema-plugins/examples/MyWeblog/share/web/
   jifty/branches/schema-plugins/examples/MyWeblog/share/web/static/
   jifty/branches/schema-plugins/examples/MyWeblog/share/web/templates/
   jifty/branches/schema-plugins/examples/MyWeblog/share/web/templates/_elements/
   jifty/branches/schema-plugins/examples/MyWeblog/share/web/templates/_elements/nav
   jifty/branches/schema-plugins/examples/MyWeblog/share/web/templates/fragments/
   jifty/branches/schema-plugins/examples/MyWeblog/share/web/templates/fragments/page_of_posts
   jifty/branches/schema-plugins/examples/MyWeblog/share/web/templates/index.html
   jifty/branches/schema-plugins/examples/MyWeblog/share/web/templates/post
   jifty/branches/schema-plugins/examples/MyWeblog/t/
   jifty/branches/schema-plugins/examples/MyWeblog/t/00-model-Post.t
   jifty/branches/schema-plugins/examples/MyWeblog/var/
   jifty/branches/schema-plugins/examples/MyWeblog/var/mason/
   jifty/branches/schema-plugins/examples/MyWeblog/var/mason/cache/
   jifty/branches/schema-plugins/examples/Ping/
   jifty/branches/schema-plugins/examples/Ping/Makefile.PL
   jifty/branches/schema-plugins/examples/Ping/bin/
   jifty/branches/schema-plugins/examples/Ping/bin/jifty   (contents, props changed)
   jifty/branches/schema-plugins/examples/Ping/doc/
   jifty/branches/schema-plugins/examples/Ping/etc/
   jifty/branches/schema-plugins/examples/Ping/etc/config.yml
   jifty/branches/schema-plugins/examples/Ping/lib/
   jifty/branches/schema-plugins/examples/Ping/lib/Ping/
   jifty/branches/schema-plugins/examples/Ping/lib/Ping/Action/
   jifty/branches/schema-plugins/examples/Ping/lib/Ping/Action/AddPing.pm
   jifty/branches/schema-plugins/examples/Ping/lib/Ping/Action/CancelPing.pm
   jifty/branches/schema-plugins/examples/Ping/lib/Ping/Event/
   jifty/branches/schema-plugins/examples/Ping/lib/Ping/Event/Pong.pm
   jifty/branches/schema-plugins/examples/Ping/lib/Ping/Model/
   jifty/branches/schema-plugins/examples/Ping/lib/Ping/PingServer.pm
   jifty/branches/schema-plugins/examples/Ping/lib/Ping/Server.pm
   jifty/branches/schema-plugins/examples/Ping/log/
   jifty/branches/schema-plugins/examples/Ping/share/
   jifty/branches/schema-plugins/examples/Ping/share/po/
   jifty/branches/schema-plugins/examples/Ping/share/web/
   jifty/branches/schema-plugins/examples/Ping/share/web/static/
   jifty/branches/schema-plugins/examples/Ping/share/web/templates/
   jifty/branches/schema-plugins/examples/Ping/share/web/templates/fragments/
   jifty/branches/schema-plugins/examples/Ping/share/web/templates/fragments/pong
   jifty/branches/schema-plugins/examples/Ping/share/web/templates/index.html
   jifty/branches/schema-plugins/examples/Ping/t/
   jifty/branches/schema-plugins/examples/Ping/t/00compile.t
   jifty/branches/schema-plugins/examples/Ping/t/01startup.t
   jifty/branches/schema-plugins/inc/   (props changed)
   jifty/branches/schema-plugins/inc/Module/
   jifty/branches/schema-plugins/inc/Module/AutoInstall.pm
   jifty/branches/schema-plugins/inc/Module/Install/
   jifty/branches/schema-plugins/inc/Module/Install.pm
   jifty/branches/schema-plugins/inc/Module/Install/AutoInstall.pm
   jifty/branches/schema-plugins/inc/Module/Install/Base.pm
   jifty/branches/schema-plugins/inc/Module/Install/Can.pm
   jifty/branches/schema-plugins/inc/Module/Install/Fetch.pm
   jifty/branches/schema-plugins/inc/Module/Install/Include.pm
   jifty/branches/schema-plugins/inc/Module/Install/Makefile.pm
   jifty/branches/schema-plugins/inc/Module/Install/Metadata.pm
   jifty/branches/schema-plugins/inc/Module/Install/Scripts.pm
   jifty/branches/schema-plugins/inc/Module/Install/Share.pm
   jifty/branches/schema-plugins/inc/Module/Install/Win32.pm
   jifty/branches/schema-plugins/inc/Module/Install/WriteAll.pm
   jifty/branches/schema-plugins/lib/
   jifty/branches/schema-plugins/lib/Email/
   jifty/branches/schema-plugins/lib/Email/Send/
   jifty/branches/schema-plugins/lib/Email/Send/Jifty/
   jifty/branches/schema-plugins/lib/Email/Send/Jifty/Test.pm
   jifty/branches/schema-plugins/lib/Jifty/
   jifty/branches/schema-plugins/lib/Jifty.pm
   jifty/branches/schema-plugins/lib/Jifty/API.pm
   jifty/branches/schema-plugins/lib/Jifty/Action/
   jifty/branches/schema-plugins/lib/Jifty/Action.pm
   jifty/branches/schema-plugins/lib/Jifty/Action/Autocomplete.pm
   jifty/branches/schema-plugins/lib/Jifty/Action/Devel/
   jifty/branches/schema-plugins/lib/Jifty/Action/Record/
   jifty/branches/schema-plugins/lib/Jifty/Action/Record.pm
   jifty/branches/schema-plugins/lib/Jifty/Action/Record/Create.pm
   jifty/branches/schema-plugins/lib/Jifty/Action/Record/Delete.pm
   jifty/branches/schema-plugins/lib/Jifty/Action/Record/Search.pm
   jifty/branches/schema-plugins/lib/Jifty/Action/Record/Update.pm
   jifty/branches/schema-plugins/lib/Jifty/Action/Redirect.pm
   jifty/branches/schema-plugins/lib/Jifty/Bootstrap.pm
   jifty/branches/schema-plugins/lib/Jifty/ClassLoader.pm
   jifty/branches/schema-plugins/lib/Jifty/Client.pm
   jifty/branches/schema-plugins/lib/Jifty/Collection.pm
   jifty/branches/schema-plugins/lib/Jifty/Config.pm
   jifty/branches/schema-plugins/lib/Jifty/Continuation.pm
   jifty/branches/schema-plugins/lib/Jifty/CurrentUser.pm
   jifty/branches/schema-plugins/lib/Jifty/DateTime.pm
   jifty/branches/schema-plugins/lib/Jifty/Dispatcher.pm
   jifty/branches/schema-plugins/lib/Jifty/Event/
   jifty/branches/schema-plugins/lib/Jifty/Event.pm
   jifty/branches/schema-plugins/lib/Jifty/Event/Model.pm
   jifty/branches/schema-plugins/lib/Jifty/Everything.pm
   jifty/branches/schema-plugins/lib/Jifty/Filter/
   jifty/branches/schema-plugins/lib/Jifty/Filter/DateTime.pm
   jifty/branches/schema-plugins/lib/Jifty/Handle.pm
   jifty/branches/schema-plugins/lib/Jifty/Handler.pm
   jifty/branches/schema-plugins/lib/Jifty/I18N.pm
   jifty/branches/schema-plugins/lib/Jifty/JSON.pm
   jifty/branches/schema-plugins/lib/Jifty/LetMe.pm   (contents, props changed)
   jifty/branches/schema-plugins/lib/Jifty/Logger.pm
   jifty/branches/schema-plugins/lib/Jifty/Manual/
   jifty/branches/schema-plugins/lib/Jifty/Manual/AccessControl.pod
   jifty/branches/schema-plugins/lib/Jifty/Manual/Actions.pod
   jifty/branches/schema-plugins/lib/Jifty/Manual/Continuations.pod
   jifty/branches/schema-plugins/lib/Jifty/Manual/Cookbook.pod
   jifty/branches/schema-plugins/lib/Jifty/Manual/FAQ.pod
   jifty/branches/schema-plugins/lib/Jifty/Manual/Glossary.pod
   jifty/branches/schema-plugins/lib/Jifty/Manual/Models.pod
   jifty/branches/schema-plugins/lib/Jifty/Manual/ObjectModel.pod
   jifty/branches/schema-plugins/lib/Jifty/Manual/PageRegions.pod
   jifty/branches/schema-plugins/lib/Jifty/Manual/RequestHandling.pod
   jifty/branches/schema-plugins/lib/Jifty/Manual/Style.pod
   jifty/branches/schema-plugins/lib/Jifty/Manual/Tutorial.pod
   jifty/branches/schema-plugins/lib/Jifty/Manual/Tutorial_de.pod
   jifty/branches/schema-plugins/lib/Jifty/Manual/Tutorial_ja.pod
   jifty/branches/schema-plugins/lib/Jifty/Manual/Upgrading.pod
   jifty/branches/schema-plugins/lib/Jifty/Manual/UsingCSSandJS.pod
   jifty/branches/schema-plugins/lib/Jifty/Mason/
   jifty/branches/schema-plugins/lib/Jifty/Mason/Halo.pm
   jifty/branches/schema-plugins/lib/Jifty/Model/
   jifty/branches/schema-plugins/lib/Jifty/Model/Metadata.pm
   jifty/branches/schema-plugins/lib/Jifty/Model/Session.pm
   jifty/branches/schema-plugins/lib/Jifty/Model/SessionCollection.pm
   jifty/branches/schema-plugins/lib/Jifty/Module/
   jifty/branches/schema-plugins/lib/Jifty/Module/Pluggable.pm
   jifty/branches/schema-plugins/lib/Jifty/Notification.pm
   jifty/branches/schema-plugins/lib/Jifty/Object.pm
   jifty/branches/schema-plugins/lib/Jifty/Param/
   jifty/branches/schema-plugins/lib/Jifty/Param.pm
   jifty/branches/schema-plugins/lib/Jifty/Param/Schema.pm
   jifty/branches/schema-plugins/lib/Jifty/Plugin/
   jifty/branches/schema-plugins/lib/Jifty/Plugin.pm
   jifty/branches/schema-plugins/lib/Jifty/Plugin/ClassLoader.pm
   jifty/branches/schema-plugins/lib/Jifty/Plugin/REST/
   jifty/branches/schema-plugins/lib/Jifty/Plugin/REST.pm
   jifty/branches/schema-plugins/lib/Jifty/Plugin/REST/Dispatcher.pm
   jifty/branches/schema-plugins/lib/Jifty/Record.pm
   jifty/branches/schema-plugins/lib/Jifty/Request/
   jifty/branches/schema-plugins/lib/Jifty/Request.pm
   jifty/branches/schema-plugins/lib/Jifty/Request/Mapper.pm
   jifty/branches/schema-plugins/lib/Jifty/Response.pm
   jifty/branches/schema-plugins/lib/Jifty/Result.pm
   jifty/branches/schema-plugins/lib/Jifty/RightsFrom.pm
   jifty/branches/schema-plugins/lib/Jifty/Script/
   jifty/branches/schema-plugins/lib/Jifty/Script.pm
   jifty/branches/schema-plugins/lib/Jifty/Script/Action.pm
   jifty/branches/schema-plugins/lib/Jifty/Script/App.pm
   jifty/branches/schema-plugins/lib/Jifty/Script/Deps.pm
   jifty/branches/schema-plugins/lib/Jifty/Script/FastCGI.pm   (contents, props changed)
   jifty/branches/schema-plugins/lib/Jifty/Script/Help.pm
   jifty/branches/schema-plugins/lib/Jifty/Script/Model.pm
   jifty/branches/schema-plugins/lib/Jifty/Script/Plugin.pm
   jifty/branches/schema-plugins/lib/Jifty/Script/Po.pm
   jifty/branches/schema-plugins/lib/Jifty/Script/Schema.pm   (contents, props changed)
   jifty/branches/schema-plugins/lib/Jifty/Script/Server.pm   (contents, props changed)
   jifty/branches/schema-plugins/lib/Jifty/Server.pm
   jifty/branches/schema-plugins/lib/Jifty/Subs/
   jifty/branches/schema-plugins/lib/Jifty/Subs.pm   (contents, props changed)
   jifty/branches/schema-plugins/lib/Jifty/Subs/Render.pm   (contents, props changed)
   jifty/branches/schema-plugins/lib/Jifty/Test/
   jifty/branches/schema-plugins/lib/Jifty/Test.pm
   jifty/branches/schema-plugins/lib/Jifty/Test/WWW/
   jifty/branches/schema-plugins/lib/Jifty/Test/WWW/Mechanize.pm
   jifty/branches/schema-plugins/lib/Jifty/TestServer.pm   (contents, props changed)
   jifty/branches/schema-plugins/lib/Jifty/Upgrade/
   jifty/branches/schema-plugins/lib/Jifty/Upgrade.pm
   jifty/branches/schema-plugins/lib/Jifty/Upgrade/Internal.pm
   jifty/branches/schema-plugins/lib/Jifty/Util.pm
   jifty/branches/schema-plugins/lib/Jifty/View/
   jifty/branches/schema-plugins/lib/Jifty/View/Mason/
   jifty/branches/schema-plugins/lib/Jifty/View/Mason/Handler.pm
   jifty/branches/schema-plugins/lib/Jifty/View/Static/
   jifty/branches/schema-plugins/lib/Jifty/View/Static/Handler.pm
   jifty/branches/schema-plugins/lib/Jifty/Web/
   jifty/branches/schema-plugins/lib/Jifty/Web.pm
   jifty/branches/schema-plugins/lib/Jifty/Web/Form/
   jifty/branches/schema-plugins/lib/Jifty/Web/Form.pm
   jifty/branches/schema-plugins/lib/Jifty/Web/Form/Clickable.pm
   jifty/branches/schema-plugins/lib/Jifty/Web/Form/Element.pm
   jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/
   jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field.pm
   jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/Button.pm
   jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/Checkbox.pm
   jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/Combobox.pm
   jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/Date.pm
   jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/Hidden.pm
   jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/InlineButton.pm
   jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/Password.pm
   jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/Radio.pm
   jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/ResetButton.pm
   jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/Select.pm
   jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/Text.pm
   jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/Textarea.pm
   jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/Unrendered.pm
   jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/Upload.pm
   jifty/branches/schema-plugins/lib/Jifty/Web/Form/Link.pm
   jifty/branches/schema-plugins/lib/Jifty/Web/Menu.pm
   jifty/branches/schema-plugins/lib/Jifty/Web/PageRegion.pm
   jifty/branches/schema-plugins/lib/Jifty/Web/Session/
   jifty/branches/schema-plugins/lib/Jifty/Web/Session.pm
   jifty/branches/schema-plugins/lib/Jifty/Web/Session/ClientSide.pm
   jifty/branches/schema-plugins/lib/Jifty/YAML.pm
   jifty/branches/schema-plugins/lib/auto/
   jifty/branches/schema-plugins/plugins/
   jifty/branches/schema-plugins/plugins/AuthCASLogin/
   jifty/branches/schema-plugins/plugins/AuthCASLogin/MANIFEST
   jifty/branches/schema-plugins/plugins/AuthCASLogin/Makefile.PL
   jifty/branches/schema-plugins/plugins/AuthCASLogin/debian/
   jifty/branches/schema-plugins/plugins/AuthCASLogin/debian/changelog
   jifty/branches/schema-plugins/plugins/AuthCASLogin/debian/compat
   jifty/branches/schema-plugins/plugins/AuthCASLogin/debian/control
   jifty/branches/schema-plugins/plugins/AuthCASLogin/debian/copyright
   jifty/branches/schema-plugins/plugins/AuthCASLogin/debian/files
   jifty/branches/schema-plugins/plugins/AuthCASLogin/debian/rules   (contents, props changed)
   jifty/branches/schema-plugins/plugins/AuthCASLogin/doc/
   jifty/branches/schema-plugins/plugins/AuthCASLogin/lib/
   jifty/branches/schema-plugins/plugins/AuthCASLogin/lib/Jifty/
   jifty/branches/schema-plugins/plugins/AuthCASLogin/lib/Jifty/Plugin/
   jifty/branches/schema-plugins/plugins/AuthCASLogin/lib/Jifty/Plugin/AuthCASLogin/
   jifty/branches/schema-plugins/plugins/AuthCASLogin/lib/Jifty/Plugin/AuthCASLogin.pm
   jifty/branches/schema-plugins/plugins/AuthCASLogin/lib/Jifty/Plugin/AuthCASLogin/Action/
   jifty/branches/schema-plugins/plugins/AuthCASLogin/lib/Jifty/Plugin/AuthCASLogin/Action/CASLogin.pm
   jifty/branches/schema-plugins/plugins/AuthCASLogin/lib/Jifty/Plugin/AuthCASLogin/Action/CASLogout.pm
   jifty/branches/schema-plugins/plugins/AuthCASLogin/lib/Jifty/Plugin/AuthCASLogin/Dispatcher.pm
   jifty/branches/schema-plugins/plugins/AuthCASLogin/lib/Jifty/Plugin/AuthCASLogin/Model/
   jifty/branches/schema-plugins/plugins/AuthCASLogin/share/
   jifty/branches/schema-plugins/plugins/AuthCASLogin/share/po/
   jifty/branches/schema-plugins/plugins/AuthCASLogin/share/web/
   jifty/branches/schema-plugins/plugins/AuthCASLogin/share/web/static/
   jifty/branches/schema-plugins/plugins/AuthCASLogin/share/web/templates/
   jifty/branches/schema-plugins/plugins/AuthCASLogin/share/web/templates/caslogin
   jifty/branches/schema-plugins/plugins/AuthCASLogin/share/web/templates/caslogout
   jifty/branches/schema-plugins/plugins/AuthCASLogin/t/
   jifty/branches/schema-plugins/plugins/AuthCASLogin/t/00-load.t
   jifty/branches/schema-plugins/plugins/AuthCASOnly/
   jifty/branches/schema-plugins/plugins/AuthCASOnly/MANIFEST
   jifty/branches/schema-plugins/plugins/AuthCASOnly/MANIFEST.bak
   jifty/branches/schema-plugins/plugins/AuthCASOnly/META.yml
   jifty/branches/schema-plugins/plugins/AuthCASOnly/Makefile
   jifty/branches/schema-plugins/plugins/AuthCASOnly/Makefile.PL
   jifty/branches/schema-plugins/plugins/AuthCASOnly/debian/
   jifty/branches/schema-plugins/plugins/AuthCASOnly/debian/changelog
   jifty/branches/schema-plugins/plugins/AuthCASOnly/debian/compat
   jifty/branches/schema-plugins/plugins/AuthCASOnly/debian/control
   jifty/branches/schema-plugins/plugins/AuthCASOnly/debian/copyright
   jifty/branches/schema-plugins/plugins/AuthCASOnly/debian/files
   jifty/branches/schema-plugins/plugins/AuthCASOnly/debian/rules   (contents, props changed)
   jifty/branches/schema-plugins/plugins/AuthCASOnly/doc/
   jifty/branches/schema-plugins/plugins/AuthCASOnly/lib/
   jifty/branches/schema-plugins/plugins/AuthCASOnly/lib/Jifty/
   jifty/branches/schema-plugins/plugins/AuthCASOnly/lib/Jifty/Plugin/
   jifty/branches/schema-plugins/plugins/AuthCASOnly/lib/Jifty/Plugin/AuthCASOnly/
   jifty/branches/schema-plugins/plugins/AuthCASOnly/lib/Jifty/Plugin/AuthCASOnly.pm
   jifty/branches/schema-plugins/plugins/AuthCASOnly/lib/Jifty/Plugin/AuthCASOnly/Action/
   jifty/branches/schema-plugins/plugins/AuthCASOnly/lib/Jifty/Plugin/AuthCASOnly/Action/CASLogin.pm
   jifty/branches/schema-plugins/plugins/AuthCASOnly/lib/Jifty/Plugin/AuthCASOnly/Action/CASLogout.pm
   jifty/branches/schema-plugins/plugins/AuthCASOnly/lib/Jifty/Plugin/AuthCASOnly/CurrentUser.pm
   jifty/branches/schema-plugins/plugins/AuthCASOnly/lib/Jifty/Plugin/AuthCASOnly/Dispatcher.pm
   jifty/branches/schema-plugins/plugins/AuthCASOnly/lib/Jifty/Plugin/AuthCASOnly/Model/
   jifty/branches/schema-plugins/plugins/AuthCASOnly/lib/Jifty/Plugin/AuthCASOnly/Model/CASUser.pm
   jifty/branches/schema-plugins/plugins/AuthCASOnly/share/
   jifty/branches/schema-plugins/plugins/AuthCASOnly/share/po/
   jifty/branches/schema-plugins/plugins/AuthCASOnly/share/web/
   jifty/branches/schema-plugins/plugins/AuthCASOnly/share/web/static/
   jifty/branches/schema-plugins/plugins/AuthCASOnly/share/web/templates/
   jifty/branches/schema-plugins/plugins/AuthCASOnly/share/web/templates/caslogin
   jifty/branches/schema-plugins/plugins/AuthCASOnly/share/web/templates/caslogout
   jifty/branches/schema-plugins/plugins/AuthCASOnly/t/
   jifty/branches/schema-plugins/plugins/AuthCASOnly/t/00-load.t
   jifty/branches/schema-plugins/plugins/AuthLDAPLogin/
   jifty/branches/schema-plugins/plugins/AuthLDAPLogin/MANIFEST
   jifty/branches/schema-plugins/plugins/AuthLDAPLogin/Makefile.PL
   jifty/branches/schema-plugins/plugins/AuthLDAPLogin/debian/
   jifty/branches/schema-plugins/plugins/AuthLDAPLogin/debian/changelog
   jifty/branches/schema-plugins/plugins/AuthLDAPLogin/debian/compat
   jifty/branches/schema-plugins/plugins/AuthLDAPLogin/debian/control
   jifty/branches/schema-plugins/plugins/AuthLDAPLogin/debian/copyright
   jifty/branches/schema-plugins/plugins/AuthLDAPLogin/debian/rules   (contents, props changed)
   jifty/branches/schema-plugins/plugins/AuthLDAPLogin/doc/
   jifty/branches/schema-plugins/plugins/AuthLDAPLogin/lib/
   jifty/branches/schema-plugins/plugins/AuthLDAPLogin/lib/Jifty/
   jifty/branches/schema-plugins/plugins/AuthLDAPLogin/lib/Jifty/Plugin/
   jifty/branches/schema-plugins/plugins/AuthLDAPLogin/lib/Jifty/Plugin/AuthLDAPLogin/
   jifty/branches/schema-plugins/plugins/AuthLDAPLogin/lib/Jifty/Plugin/AuthLDAPLogin.pm
   jifty/branches/schema-plugins/plugins/AuthLDAPLogin/lib/Jifty/Plugin/AuthLDAPLogin/Action/
   jifty/branches/schema-plugins/plugins/AuthLDAPLogin/lib/Jifty/Plugin/AuthLDAPLogin/Action/LDAPLogin.pm
   jifty/branches/schema-plugins/plugins/AuthLDAPLogin/lib/Jifty/Plugin/AuthLDAPLogin/Action/LDAPLogout.pm
   jifty/branches/schema-plugins/plugins/AuthLDAPLogin/lib/Jifty/Plugin/AuthLDAPLogin/Dispatcher.pm
   jifty/branches/schema-plugins/plugins/AuthLDAPLogin/lib/Jifty/Plugin/AuthLDAPLogin/Model/
   jifty/branches/schema-plugins/plugins/AuthLDAPLogin/share/
   jifty/branches/schema-plugins/plugins/AuthLDAPLogin/share/po/
   jifty/branches/schema-plugins/plugins/AuthLDAPLogin/share/po/en.po
   jifty/branches/schema-plugins/plugins/AuthLDAPLogin/share/po/fr.po
   jifty/branches/schema-plugins/plugins/AuthLDAPLogin/share/web/
   jifty/branches/schema-plugins/plugins/AuthLDAPLogin/share/web/static/
   jifty/branches/schema-plugins/plugins/AuthLDAPLogin/share/web/templates/
   jifty/branches/schema-plugins/plugins/AuthLDAPLogin/share/web/templates/ldaplogin
   jifty/branches/schema-plugins/plugins/AuthLDAPLogin/share/web/templates/ldaplogout
   jifty/branches/schema-plugins/plugins/AuthLDAPLogin/t/
   jifty/branches/schema-plugins/plugins/AuthLDAPLogin/t/00-load.t
   jifty/branches/schema-plugins/plugins/AuthLDAPOnly/
   jifty/branches/schema-plugins/plugins/AuthLDAPOnly/MANIFEST
   jifty/branches/schema-plugins/plugins/AuthLDAPOnly/Makefile.PL
   jifty/branches/schema-plugins/plugins/AuthLDAPOnly/debian/
   jifty/branches/schema-plugins/plugins/AuthLDAPOnly/debian/changelog
   jifty/branches/schema-plugins/plugins/AuthLDAPOnly/debian/compat
   jifty/branches/schema-plugins/plugins/AuthLDAPOnly/debian/control
   jifty/branches/schema-plugins/plugins/AuthLDAPOnly/debian/copyright
   jifty/branches/schema-plugins/plugins/AuthLDAPOnly/debian/rules   (contents, props changed)
   jifty/branches/schema-plugins/plugins/AuthLDAPOnly/doc/
   jifty/branches/schema-plugins/plugins/AuthLDAPOnly/lib/
   jifty/branches/schema-plugins/plugins/AuthLDAPOnly/lib/Jifty/
   jifty/branches/schema-plugins/plugins/AuthLDAPOnly/lib/Jifty/Plugin/
   jifty/branches/schema-plugins/plugins/AuthLDAPOnly/lib/Jifty/Plugin/AuthLDAPOnly/
   jifty/branches/schema-plugins/plugins/AuthLDAPOnly/lib/Jifty/Plugin/AuthLDAPOnly.pm
   jifty/branches/schema-plugins/plugins/AuthLDAPOnly/lib/Jifty/Plugin/AuthLDAPOnly/Action/
   jifty/branches/schema-plugins/plugins/AuthLDAPOnly/lib/Jifty/Plugin/AuthLDAPOnly/Action/LDAPLogin.pm
   jifty/branches/schema-plugins/plugins/AuthLDAPOnly/lib/Jifty/Plugin/AuthLDAPOnly/Action/LDAPLogout.pm
   jifty/branches/schema-plugins/plugins/AuthLDAPOnly/lib/Jifty/Plugin/AuthLDAPOnly/CurrentUser.pm
   jifty/branches/schema-plugins/plugins/AuthLDAPOnly/lib/Jifty/Plugin/AuthLDAPOnly/Dispatcher.pm
   jifty/branches/schema-plugins/plugins/AuthLDAPOnly/lib/Jifty/Plugin/AuthLDAPOnly/Model/
   jifty/branches/schema-plugins/plugins/AuthLDAPOnly/lib/Jifty/Plugin/AuthLDAPOnly/Model/LDAPUser.pm
   jifty/branches/schema-plugins/plugins/AuthLDAPOnly/share/
   jifty/branches/schema-plugins/plugins/AuthLDAPOnly/share/po/
   jifty/branches/schema-plugins/plugins/AuthLDAPOnly/share/po/en.po
   jifty/branches/schema-plugins/plugins/AuthLDAPOnly/share/po/fr.po
   jifty/branches/schema-plugins/plugins/AuthLDAPOnly/share/web/
   jifty/branches/schema-plugins/plugins/AuthLDAPOnly/share/web/static/
   jifty/branches/schema-plugins/plugins/AuthLDAPOnly/share/web/templates/
   jifty/branches/schema-plugins/plugins/AuthLDAPOnly/share/web/templates/ldaplogin
   jifty/branches/schema-plugins/plugins/AuthLDAPOnly/share/web/templates/ldaplogout
   jifty/branches/schema-plugins/plugins/AuthLDAPOnly/t/
   jifty/branches/schema-plugins/plugins/AuthLDAPOnly/t/00-load.t
   jifty/branches/schema-plugins/plugins/AuthzLDAP/
   jifty/branches/schema-plugins/plugins/AuthzLDAP/MANIFEST
   jifty/branches/schema-plugins/plugins/AuthzLDAP/Makefile.PL
   jifty/branches/schema-plugins/plugins/AuthzLDAP/doc/
   jifty/branches/schema-plugins/plugins/AuthzLDAP/lib/
   jifty/branches/schema-plugins/plugins/AuthzLDAP/lib/Jifty/
   jifty/branches/schema-plugins/plugins/AuthzLDAP/lib/Jifty/Plugin/
   jifty/branches/schema-plugins/plugins/AuthzLDAP/lib/Jifty/Plugin/AuthzLDAP/
   jifty/branches/schema-plugins/plugins/AuthzLDAP/lib/Jifty/Plugin/AuthzLDAP.pm
   jifty/branches/schema-plugins/plugins/AuthzLDAP/lib/Jifty/Plugin/AuthzLDAP/Action/
   jifty/branches/schema-plugins/plugins/AuthzLDAP/lib/Jifty/Plugin/AuthzLDAP/Action/LDAPValidate.pm
   jifty/branches/schema-plugins/plugins/AuthzLDAP/lib/Jifty/Plugin/AuthzLDAP/Model/
   jifty/branches/schema-plugins/plugins/AuthzLDAP/lib/Jifty/Plugin/AuthzLDAP/Model/LDAPFilter.pm
   jifty/branches/schema-plugins/plugins/AuthzLDAP/share/
   jifty/branches/schema-plugins/plugins/AuthzLDAP/share/po/
   jifty/branches/schema-plugins/plugins/AuthzLDAP/share/po/en.po
   jifty/branches/schema-plugins/plugins/AuthzLDAP/share/po/fr.po
   jifty/branches/schema-plugins/plugins/AuthzLDAP/share/web/
   jifty/branches/schema-plugins/plugins/AuthzLDAP/share/web/static/
   jifty/branches/schema-plugins/plugins/AuthzLDAP/share/web/templates/
   jifty/branches/schema-plugins/plugins/AuthzLDAP/share/web/templates/error/
   jifty/branches/schema-plugins/plugins/AuthzLDAP/share/web/templates/error/AccessDenied
   jifty/branches/schema-plugins/plugins/AuthzLDAP/t/
   jifty/branches/schema-plugins/plugins/AuthzLDAP/t/00-load.t
   jifty/branches/schema-plugins/plugins/EditInPlace/
   jifty/branches/schema-plugins/plugins/EditInPlace/Makefile.PL
   jifty/branches/schema-plugins/plugins/EditInPlace/debian/
   jifty/branches/schema-plugins/plugins/EditInPlace/debian/changelog
   jifty/branches/schema-plugins/plugins/EditInPlace/debian/compat
   jifty/branches/schema-plugins/plugins/EditInPlace/debian/control
   jifty/branches/schema-plugins/plugins/EditInPlace/debian/rules   (contents, props changed)
   jifty/branches/schema-plugins/plugins/EditInPlace/inc/
   jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/
   jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/AutoInstall.pm
   jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/Install/
   jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/Install.pm
   jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/Install/AutoInstall.pm
   jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/Install/Base.pm
   jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/Install/Can.pm
   jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/Install/Fetch.pm
   jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/Install/Include.pm
   jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/Install/Makefile/
   jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/Install/Makefile.pm
   jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/Install/Makefile/Version.pm
   jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/Install/Metadata.pm
   jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/Install/Share.pm
   jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/Install/Win32.pm
   jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/Install/WriteAll.pm
   jifty/branches/schema-plugins/plugins/EditInPlace/lib/
   jifty/branches/schema-plugins/plugins/EditInPlace/lib/Jifty/
   jifty/branches/schema-plugins/plugins/EditInPlace/lib/Jifty/Plugin/
   jifty/branches/schema-plugins/plugins/EditInPlace/lib/Jifty/Plugin/EditInPlace/
   jifty/branches/schema-plugins/plugins/EditInPlace/lib/Jifty/Plugin/EditInPlace.pm
   jifty/branches/schema-plugins/plugins/EditInPlace/lib/Jifty/Plugin/EditInPlace/Action/
   jifty/branches/schema-plugins/plugins/EditInPlace/lib/Jifty/Plugin/EditInPlace/Action/FileEditor.pm
   jifty/branches/schema-plugins/plugins/EditInPlace/lib/Jifty/Plugin/EditInPlace/Dispatcher.pm
   jifty/branches/schema-plugins/plugins/EditInPlace/share/
   jifty/branches/schema-plugins/plugins/EditInPlace/share/web/
   jifty/branches/schema-plugins/plugins/EditInPlace/share/web/templates/
   jifty/branches/schema-plugins/plugins/EditInPlace/share/web/templates/__jifty/
   jifty/branches/schema-plugins/plugins/EditInPlace/share/web/templates/__jifty/create_file_inline
   jifty/branches/schema-plugins/plugins/EditInPlace/share/web/templates/__jifty/edit_file
   jifty/branches/schema-plugins/plugins/EditInPlace/share/web/templates/__jifty/edit_file_inline
   jifty/branches/schema-plugins/plugins/EmailErrors/
   jifty/branches/schema-plugins/plugins/EmailErrors/Makefile.PL
   jifty/branches/schema-plugins/plugins/EmailErrors/doc/
   jifty/branches/schema-plugins/plugins/EmailErrors/doc/site_config.yml
   jifty/branches/schema-plugins/plugins/EmailErrors/lib/
   jifty/branches/schema-plugins/plugins/EmailErrors/lib/Jifty/
   jifty/branches/schema-plugins/plugins/EmailErrors/lib/Jifty/Plugin/
   jifty/branches/schema-plugins/plugins/EmailErrors/lib/Jifty/Plugin/EmailErrors/
   jifty/branches/schema-plugins/plugins/EmailErrors/lib/Jifty/Plugin/EmailErrors.pm
   jifty/branches/schema-plugins/plugins/EmailErrors/lib/Jifty/Plugin/EmailErrors/Action/
   jifty/branches/schema-plugins/plugins/EmailErrors/lib/Jifty/Plugin/EmailErrors/Dispatcher.pm
   jifty/branches/schema-plugins/plugins/EmailErrors/lib/Jifty/Plugin/EmailErrors/Model/
   jifty/branches/schema-plugins/plugins/EmailErrors/lib/Jifty/Plugin/EmailErrors/Notification/
   jifty/branches/schema-plugins/plugins/EmailErrors/lib/Jifty/Plugin/EmailErrors/Notification/EmailError.pm
   jifty/branches/schema-plugins/plugins/EmailErrors/share/
   jifty/branches/schema-plugins/plugins/EmailErrors/share/web/
   jifty/branches/schema-plugins/plugins/EmailErrors/share/web/templates/
   jifty/branches/schema-plugins/plugins/EmailErrors/share/web/templates/.file
   jifty/branches/schema-plugins/plugins/LetMe/
   jifty/branches/schema-plugins/plugins/LetMe/Makefile.PL
   jifty/branches/schema-plugins/plugins/LetMe/doc/
   jifty/branches/schema-plugins/plugins/LetMe/lib/
   jifty/branches/schema-plugins/plugins/LetMe/lib/Jifty/
   jifty/branches/schema-plugins/plugins/LetMe/lib/Jifty/Plugin/
   jifty/branches/schema-plugins/plugins/LetMe/lib/Jifty/Plugin/LetMe/
   jifty/branches/schema-plugins/plugins/LetMe/lib/Jifty/Plugin/LetMe.pm
   jifty/branches/schema-plugins/plugins/LetMe/lib/Jifty/Plugin/LetMe/Action/
   jifty/branches/schema-plugins/plugins/LetMe/lib/Jifty/Plugin/LetMe/Dispatcher.pm
   jifty/branches/schema-plugins/plugins/LetMe/lib/Jifty/Plugin/LetMe/Model/
   jifty/branches/schema-plugins/plugins/LetMe/share/
   jifty/branches/schema-plugins/plugins/LetMe/share/po/
   jifty/branches/schema-plugins/plugins/LetMe/share/web/
   jifty/branches/schema-plugins/plugins/LetMe/share/web/static/
   jifty/branches/schema-plugins/plugins/LetMe/share/web/templates/
   jifty/branches/schema-plugins/plugins/LetMe/t/
   jifty/branches/schema-plugins/plugins/Login/
   jifty/branches/schema-plugins/plugins/Login/MANIFEST
   jifty/branches/schema-plugins/plugins/Login/META.yml
   jifty/branches/schema-plugins/plugins/Login/Makefile.PL
   jifty/branches/schema-plugins/plugins/Login/debian/
   jifty/branches/schema-plugins/plugins/Login/debian/changelog
   jifty/branches/schema-plugins/plugins/Login/debian/compat
   jifty/branches/schema-plugins/plugins/Login/debian/control
   jifty/branches/schema-plugins/plugins/Login/debian/rules   (contents, props changed)
   jifty/branches/schema-plugins/plugins/Login/doc/
   jifty/branches/schema-plugins/plugins/Login/inc/
   jifty/branches/schema-plugins/plugins/Login/inc/Module/
   jifty/branches/schema-plugins/plugins/Login/inc/Module/Install/
   jifty/branches/schema-plugins/plugins/Login/inc/Module/Install.pm
   jifty/branches/schema-plugins/plugins/Login/inc/Module/Install/Base.pm
   jifty/branches/schema-plugins/plugins/Login/inc/Module/Install/Can.pm
   jifty/branches/schema-plugins/plugins/Login/inc/Module/Install/Fetch.pm
   jifty/branches/schema-plugins/plugins/Login/inc/Module/Install/Makefile.pm
   jifty/branches/schema-plugins/plugins/Login/inc/Module/Install/Metadata.pm
   jifty/branches/schema-plugins/plugins/Login/inc/Module/Install/Share.pm
   jifty/branches/schema-plugins/plugins/Login/inc/Module/Install/Win32.pm
   jifty/branches/schema-plugins/plugins/Login/inc/Module/Install/WriteAll.pm
   jifty/branches/schema-plugins/plugins/Login/lib/
   jifty/branches/schema-plugins/plugins/Login/lib/Jifty/
   jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/
   jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/
   jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login.pm
   jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Action/
   jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Action/ChangePassword.pm
   jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Action/ConfirmEmail.pm
   jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Action/Login.pm
   jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Action/Logout.pm
   jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Action/RecoverPassword.pm
   jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Action/ResetLostPassword.pm
   jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Action/SendAccountConfirmation.pm
   jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Action/SendPasswordReminder.pm
   jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Action/Signup.pm
   jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/CurrentUser.pm
   jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Dispatcher.pm
   jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Model/
   jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Model/User.pm
   jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Notification/
   jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Notification/ConfirmAddress.pm   (contents, props changed)
   jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Notification/ConfirmLostPassword.pm
   jifty/branches/schema-plugins/plugins/Login/share/
   jifty/branches/schema-plugins/plugins/Login/share/po/
   jifty/branches/schema-plugins/plugins/Login/share/po/en.po
   jifty/branches/schema-plugins/plugins/Login/share/po/fr.po
   jifty/branches/schema-plugins/plugins/Login/share/web/
   jifty/branches/schema-plugins/plugins/Login/share/web/static/
   jifty/branches/schema-plugins/plugins/Login/share/web/templates/
   jifty/branches/schema-plugins/plugins/Login/share/web/templates/chgpasswd
   jifty/branches/schema-plugins/plugins/Login/share/web/templates/let/
   jifty/branches/schema-plugins/plugins/Login/share/web/templates/let/confirm_email
   jifty/branches/schema-plugins/plugins/Login/share/web/templates/let/reset_lost_password
   jifty/branches/schema-plugins/plugins/Login/share/web/templates/login
   jifty/branches/schema-plugins/plugins/Login/share/web/templates/passwordreminder
   jifty/branches/schema-plugins/plugins/Login/share/web/templates/signup
   jifty/branches/schema-plugins/plugins/Login/t/
   jifty/branches/schema-plugins/plugins/Nothing/
   jifty/branches/schema-plugins/plugins/Nothing/Makefile.PL
   jifty/branches/schema-plugins/plugins/Nothing/doc/
   jifty/branches/schema-plugins/plugins/Nothing/lib/
   jifty/branches/schema-plugins/plugins/Nothing/lib/Jifty/
   jifty/branches/schema-plugins/plugins/Nothing/lib/Jifty/Plugin/
   jifty/branches/schema-plugins/plugins/Nothing/lib/Jifty/Plugin/Nothing/
   jifty/branches/schema-plugins/plugins/Nothing/lib/Jifty/Plugin/Nothing.pm
   jifty/branches/schema-plugins/plugins/Nothing/lib/Jifty/Plugin/Nothing/Action/
   jifty/branches/schema-plugins/plugins/Nothing/lib/Jifty/Plugin/Nothing/Dispatcher.pm
   jifty/branches/schema-plugins/plugins/Nothing/lib/Jifty/Plugin/Nothing/Model/
   jifty/branches/schema-plugins/plugins/Nothing/share/
   jifty/branches/schema-plugins/plugins/Nothing/share/po/
   jifty/branches/schema-plugins/plugins/Nothing/share/web/
   jifty/branches/schema-plugins/plugins/Nothing/share/web/static/
   jifty/branches/schema-plugins/plugins/Nothing/share/web/templates/
   jifty/branches/schema-plugins/plugins/ProfileBehaviour/
   jifty/branches/schema-plugins/plugins/ProfileBehaviour/Makefile.PL
   jifty/branches/schema-plugins/plugins/ProfileBehaviour/doc/
   jifty/branches/schema-plugins/plugins/ProfileBehaviour/lib/
   jifty/branches/schema-plugins/plugins/ProfileBehaviour/lib/Jifty/
   jifty/branches/schema-plugins/plugins/ProfileBehaviour/lib/Jifty/Plugin/
   jifty/branches/schema-plugins/plugins/ProfileBehaviour/lib/Jifty/Plugin/ProfileBehaviour/
   jifty/branches/schema-plugins/plugins/ProfileBehaviour/lib/Jifty/Plugin/ProfileBehaviour.pm
   jifty/branches/schema-plugins/plugins/ProfileBehaviour/lib/Jifty/Plugin/ProfileBehaviour/Action/
   jifty/branches/schema-plugins/plugins/ProfileBehaviour/lib/Jifty/Plugin/ProfileBehaviour/Dispatcher.pm
   jifty/branches/schema-plugins/plugins/ProfileBehaviour/lib/Jifty/Plugin/ProfileBehaviour/Model/
   jifty/branches/schema-plugins/plugins/ProfileBehaviour/share/
   jifty/branches/schema-plugins/plugins/ProfileBehaviour/share/po/
   jifty/branches/schema-plugins/plugins/ProfileBehaviour/share/web/
   jifty/branches/schema-plugins/plugins/ProfileBehaviour/share/web/static/
   jifty/branches/schema-plugins/plugins/ProfileBehaviour/share/web/static/css/
   jifty/branches/schema-plugins/plugins/ProfileBehaviour/share/web/static/css/behaviour-profile.css
   jifty/branches/schema-plugins/plugins/ProfileBehaviour/share/web/static/js/
   jifty/branches/schema-plugins/plugins/ProfileBehaviour/share/web/static/js/behaviour.js
   jifty/branches/schema-plugins/plugins/ProfileBehaviour/share/web/templates/
   jifty/branches/schema-plugins/plugins/ProfileBehaviour/t/
   jifty/branches/schema-plugins/plugins/Users/
   jifty/branches/schema-plugins/plugins/Users-Identity-File/
   jifty/branches/schema-plugins/plugins/Users-Identity-File/Makefile.PL
   jifty/branches/schema-plugins/plugins/Users-Identity-File/doc/
   jifty/branches/schema-plugins/plugins/Users-Identity-File/lib/
   jifty/branches/schema-plugins/plugins/Users-Identity-File/lib/Jifty/
   jifty/branches/schema-plugins/plugins/Users-Identity-File/lib/Jifty/Plugin/
   jifty/branches/schema-plugins/plugins/Users-Identity-File/lib/Jifty/Plugin/Users/
   jifty/branches/schema-plugins/plugins/Users-Identity-File/lib/Jifty/Plugin/Users/Identity/
   jifty/branches/schema-plugins/plugins/Users-Identity-File/lib/Jifty/Plugin/Users/Identity/File/
   jifty/branches/schema-plugins/plugins/Users-Identity-File/lib/Jifty/Plugin/Users/Identity/File.pm
   jifty/branches/schema-plugins/plugins/Users-Identity-File/lib/Jifty/Plugin/Users/Identity/File/Action/
   jifty/branches/schema-plugins/plugins/Users-Identity-File/lib/Jifty/Plugin/Users/Identity/File/Action/Login.pm
   jifty/branches/schema-plugins/plugins/Users-Identity-File/lib/Jifty/Plugin/Users/Identity/File/Dispatcher.pm
   jifty/branches/schema-plugins/plugins/Users-Identity-File/lib/Jifty/Plugin/Users/Identity/File/Model/
   jifty/branches/schema-plugins/plugins/Users-Identity-File/share/
   jifty/branches/schema-plugins/plugins/Users-Identity-File/share/po/
   jifty/branches/schema-plugins/plugins/Users-Identity-File/share/web/
   jifty/branches/schema-plugins/plugins/Users-Identity-File/share/web/static/
   jifty/branches/schema-plugins/plugins/Users-Identity-File/share/web/templates/
   jifty/branches/schema-plugins/plugins/Users-Identity-File/share/web/templates/login
   jifty/branches/schema-plugins/plugins/Users-Identity-File/t/
   jifty/branches/schema-plugins/plugins/Users/Makefile.PL
   jifty/branches/schema-plugins/plugins/Users/doc/
   jifty/branches/schema-plugins/plugins/Users/lib/
   jifty/branches/schema-plugins/plugins/Users/lib/Jifty/
   jifty/branches/schema-plugins/plugins/Users/lib/Jifty/Plugin/
   jifty/branches/schema-plugins/plugins/Users/lib/Jifty/Plugin/Users/
   jifty/branches/schema-plugins/plugins/Users/lib/Jifty/Plugin/Users.pm
   jifty/branches/schema-plugins/plugins/Users/lib/Jifty/Plugin/Users/Action/
   jifty/branches/schema-plugins/plugins/Users/lib/Jifty/Plugin/Users/CurrentUser.pm
   jifty/branches/schema-plugins/plugins/Users/lib/Jifty/Plugin/Users/Dispatcher.pm
   jifty/branches/schema-plugins/plugins/Users/lib/Jifty/Plugin/Users/Model/
   jifty/branches/schema-plugins/plugins/Users/lib/Jifty/Plugin/Users/Model/User.pm
   jifty/branches/schema-plugins/plugins/Users/share/
   jifty/branches/schema-plugins/plugins/Users/share/po/
   jifty/branches/schema-plugins/plugins/Users/share/web/
   jifty/branches/schema-plugins/plugins/Users/share/web/static/
   jifty/branches/schema-plugins/plugins/Users/share/web/templates/
   jifty/branches/schema-plugins/plugins/Users/t/
   jifty/branches/schema-plugins/share/
   jifty/branches/schema-plugins/share/dtd/
   jifty/branches/schema-plugins/share/dtd/xhtml-lat1.ent
   jifty/branches/schema-plugins/share/dtd/xhtml-special.ent
   jifty/branches/schema-plugins/share/dtd/xhtml-symbol.ent
   jifty/branches/schema-plugins/share/dtd/xhtml1-strict.dtd
   jifty/branches/schema-plugins/share/po/
   jifty/branches/schema-plugins/share/po/en.po
   jifty/branches/schema-plugins/share/po/fr.po
   jifty/branches/schema-plugins/share/po/ja.po
   jifty/branches/schema-plugins/share/po/zh_cn.po
   jifty/branches/schema-plugins/share/po/zh_tw.po
   jifty/branches/schema-plugins/share/web/
   jifty/branches/schema-plugins/share/web/static/
   jifty/branches/schema-plugins/share/web/static/css/
   jifty/branches/schema-plugins/share/web/static/css/app-base.css
   jifty/branches/schema-plugins/share/web/static/css/app.css
   jifty/branches/schema-plugins/share/web/static/css/autocomplete.css
   jifty/branches/schema-plugins/share/web/static/css/autohandler
   jifty/branches/schema-plugins/share/web/static/css/base.css
   jifty/branches/schema-plugins/share/web/static/css/calendar.css
   jifty/branches/schema-plugins/share/web/static/css/combobox.css   (contents, props changed)
   jifty/branches/schema-plugins/share/web/static/css/context-menus.css
   jifty/branches/schema-plugins/share/web/static/css/forms.css
   jifty/branches/schema-plugins/share/web/static/css/halos.css
   jifty/branches/schema-plugins/share/web/static/css/keybindings.css
   jifty/branches/schema-plugins/share/web/static/css/main.css
   jifty/branches/schema-plugins/share/web/static/css/nav.css   (contents, props changed)
   jifty/branches/schema-plugins/share/web/static/css/notices.css
   jifty/branches/schema-plugins/share/web/static/css/yui/
   jifty/branches/schema-plugins/share/web/static/css/yui/calendar/
   jifty/branches/schema-plugins/share/web/static/css/yui/calendar/calendar.css
   jifty/branches/schema-plugins/share/web/static/css/yui/tabview/
   jifty/branches/schema-plugins/share/web/static/css/yui/tabview/border_tabs.css
   jifty/branches/schema-plugins/share/web/static/css/yui/tabview/tabs.css
   jifty/branches/schema-plugins/share/web/static/favicon.ico   (contents, props changed)
   jifty/branches/schema-plugins/share/web/static/images/
   jifty/branches/schema-plugins/share/web/static/images/css/
   jifty/branches/schema-plugins/share/web/static/images/css/bullet_arrow_down.png   (contents, props changed)
   jifty/branches/schema-plugins/share/web/static/images/css/bullet_arrow_up.png   (contents, props changed)
   jifty/branches/schema-plugins/share/web/static/images/css/fieldbg-autocomplete.gif   (contents, props changed)
   jifty/branches/schema-plugins/share/web/static/images/css/fieldbg.gif   (contents, props changed)
   jifty/branches/schema-plugins/share/web/static/images/iepngfix/
   jifty/branches/schema-plugins/share/web/static/images/iepngfix/blank.gif   (contents, props changed)
   jifty/branches/schema-plugins/share/web/static/images/pony.jpg   (contents, props changed)
   jifty/branches/schema-plugins/share/web/static/images/silk/
   jifty/branches/schema-plugins/share/web/static/images/silk/bullet_arrow_down.png   (contents, props changed)
   jifty/branches/schema-plugins/share/web/static/images/silk/bullet_arrow_up.png   (contents, props changed)
   jifty/branches/schema-plugins/share/web/static/images/silk/calendar.png   (contents, props changed)
   jifty/branches/schema-plugins/share/web/static/images/silk/cancel.png   (contents, props changed)
   jifty/branches/schema-plugins/share/web/static/images/silk/cancel_grey.png   (contents, props changed)
   jifty/branches/schema-plugins/share/web/static/images/silk/error.png   (contents, props changed)
   jifty/branches/schema-plugins/share/web/static/images/silk/information.png   (contents, props changed)
   jifty/branches/schema-plugins/share/web/static/images/silk/pencil.png   (contents, props changed)
   jifty/branches/schema-plugins/share/web/static/images/silk/pencil_add.png   (contents, props changed)
   jifty/branches/schema-plugins/share/web/static/images/yui/
   jifty/branches/schema-plugins/share/web/static/images/yui/us/
   jifty/branches/schema-plugins/share/web/static/images/yui/us/my/
   jifty/branches/schema-plugins/share/web/static/images/yui/us/my/bn/
   jifty/branches/schema-plugins/share/web/static/images/yui/us/my/bn/x_d.gif   (contents, props changed)
   jifty/branches/schema-plugins/share/web/static/images/yui/us/tr/
   jifty/branches/schema-plugins/share/web/static/images/yui/us/tr/callt.gif   (contents, props changed)
   jifty/branches/schema-plugins/share/web/static/images/yui/us/tr/calrt.gif   (contents, props changed)
   jifty/branches/schema-plugins/share/web/static/js/
   jifty/branches/schema-plugins/share/web/static/js/app.js
   jifty/branches/schema-plugins/share/web/static/js/app_behaviour.js
   jifty/branches/schema-plugins/share/web/static/js/behaviour.js
   jifty/branches/schema-plugins/share/web/static/js/bps_util.js   (contents, props changed)
   jifty/branches/schema-plugins/share/web/static/js/calendar.js
   jifty/branches/schema-plugins/share/web/static/js/combobox.js
   jifty/branches/schema-plugins/share/web/static/js/context_menu.js
   jifty/branches/schema-plugins/share/web/static/js/css_browser_selector.js   (contents, props changed)
   jifty/branches/schema-plugins/share/web/static/js/cssquery/
   jifty/branches/schema-plugins/share/web/static/js/cssquery/cssQuery-level2.js
   jifty/branches/schema-plugins/share/web/static/js/cssquery/cssQuery-level3.js
   jifty/branches/schema-plugins/share/web/static/js/cssquery/cssQuery-standard.js
   jifty/branches/schema-plugins/share/web/static/js/cssquery/cssQuery.js
   jifty/branches/schema-plugins/share/web/static/js/dom-drag.js
   jifty/branches/schema-plugins/share/web/static/js/formatDate.js   (contents, props changed)
   jifty/branches/schema-plugins/share/web/static/js/halo.js
   jifty/branches/schema-plugins/share/web/static/js/iepngfix.htc
   jifty/branches/schema-plugins/share/web/static/js/jifty.js
   jifty/branches/schema-plugins/share/web/static/js/jifty_smoothscroll.js
   jifty/branches/schema-plugins/share/web/static/js/jifty_subs.js
   jifty/branches/schema-plugins/share/web/static/js/jifty_utils.js
   jifty/branches/schema-plugins/share/web/static/js/jsTrace.js
   jifty/branches/schema-plugins/share/web/static/js/jsan/
   jifty/branches/schema-plugins/share/web/static/js/jsan/DOM/
   jifty/branches/schema-plugins/share/web/static/js/jsan/DOM/Events.js
   jifty/branches/schema-plugins/share/web/static/js/jsan/JSAN.js
   jifty/branches/schema-plugins/share/web/static/js/jsan/Push.js
   jifty/branches/schema-plugins/share/web/static/js/jsan/Upgrade/
   jifty/branches/schema-plugins/share/web/static/js/jsan/Upgrade.js
   jifty/branches/schema-plugins/share/web/static/js/jsan/Upgrade/Array/
   jifty/branches/schema-plugins/share/web/static/js/jsan/Upgrade/Array/push.js
   jifty/branches/schema-plugins/share/web/static/js/jsan/Upgrade/Function/
   jifty/branches/schema-plugins/share/web/static/js/jsan/Upgrade/Function/apply.js
   jifty/branches/schema-plugins/share/web/static/js/json.js
   jifty/branches/schema-plugins/share/web/static/js/key_bindings.js   (contents, props changed)
   jifty/branches/schema-plugins/share/web/static/js/prototype.js
   jifty/branches/schema-plugins/share/web/static/js/rico.js
   jifty/branches/schema-plugins/share/web/static/js/scriptaculous/
   jifty/branches/schema-plugins/share/web/static/js/scriptaculous/builder.js   (contents, props changed)
   jifty/branches/schema-plugins/share/web/static/js/scriptaculous/controls.js   (contents, props changed)
   jifty/branches/schema-plugins/share/web/static/js/scriptaculous/dragdrop.js   (contents, props changed)
   jifty/branches/schema-plugins/share/web/static/js/scriptaculous/effects.js   (contents, props changed)
   jifty/branches/schema-plugins/share/web/static/js/scriptaculous/scriptaculous.js   (contents, props changed)
   jifty/branches/schema-plugins/share/web/static/js/scriptaculous/slider.js   (contents, props changed)
   jifty/branches/schema-plugins/share/web/static/js/scriptaculous/unittest.js   (contents, props changed)
   jifty/branches/schema-plugins/share/web/static/js/setup_jsan.js
   jifty/branches/schema-plugins/share/web/static/js/yui/
   jifty/branches/schema-plugins/share/web/static/js/yui/calendar.js
   jifty/branches/schema-plugins/share/web/static/js/yui/container.js
   jifty/branches/schema-plugins/share/web/static/js/yui/dom.js
   jifty/branches/schema-plugins/share/web/static/js/yui/event.js
   jifty/branches/schema-plugins/share/web/static/js/yui/tabview.js
   jifty/branches/schema-plugins/share/web/static/js/yui/yahoo.js
   jifty/branches/schema-plugins/share/web/templates/
   jifty/branches/schema-plugins/share/web/templates/=/
   jifty/branches/schema-plugins/share/web/templates/=/subs
   jifty/branches/schema-plugins/share/web/templates/__jifty/
   jifty/branches/schema-plugins/share/web/templates/__jifty/admin/
   jifty/branches/schema-plugins/share/web/templates/__jifty/admin/_elements/
   jifty/branches/schema-plugins/share/web/templates/__jifty/admin/_elements/nav   (contents, props changed)
   jifty/branches/schema-plugins/share/web/templates/__jifty/admin/action/
   jifty/branches/schema-plugins/share/web/templates/__jifty/admin/action/dhandler
   jifty/branches/schema-plugins/share/web/templates/__jifty/admin/autohandler
   jifty/branches/schema-plugins/share/web/templates/__jifty/admin/fragments/
   jifty/branches/schema-plugins/share/web/templates/__jifty/admin/fragments/list/
   jifty/branches/schema-plugins/share/web/templates/__jifty/admin/fragments/list/header
   jifty/branches/schema-plugins/share/web/templates/__jifty/admin/fragments/list/list
   jifty/branches/schema-plugins/share/web/templates/__jifty/admin/fragments/list/new_item
   jifty/branches/schema-plugins/share/web/templates/__jifty/admin/fragments/list/search
   jifty/branches/schema-plugins/share/web/templates/__jifty/admin/fragments/list/update
   jifty/branches/schema-plugins/share/web/templates/__jifty/admin/fragments/list/view
   jifty/branches/schema-plugins/share/web/templates/__jifty/admin/index.html
   jifty/branches/schema-plugins/share/web/templates/__jifty/admin/model/
   jifty/branches/schema-plugins/share/web/templates/__jifty/admin/model/dhandler
   jifty/branches/schema-plugins/share/web/templates/__jifty/autocomplete.xml
   jifty/branches/schema-plugins/share/web/templates/__jifty/css/
   jifty/branches/schema-plugins/share/web/templates/__jifty/css/dhandler
   jifty/branches/schema-plugins/share/web/templates/__jifty/empty
   jifty/branches/schema-plugins/share/web/templates/__jifty/error/
   jifty/branches/schema-plugins/share/web/templates/__jifty/error/_elements/
   jifty/branches/schema-plugins/share/web/templates/__jifty/error/_elements/error_text
   jifty/branches/schema-plugins/share/web/templates/__jifty/error/_elements/wrapper
   jifty/branches/schema-plugins/share/web/templates/__jifty/error/autohandler
   jifty/branches/schema-plugins/share/web/templates/__jifty/error/dhandler
   jifty/branches/schema-plugins/share/web/templates/__jifty/error/error.css
   jifty/branches/schema-plugins/share/web/templates/__jifty/error/mason_internal_error
   jifty/branches/schema-plugins/share/web/templates/__jifty/halo
   jifty/branches/schema-plugins/share/web/templates/__jifty/js/
   jifty/branches/schema-plugins/share/web/templates/__jifty/js/dhandler
   jifty/branches/schema-plugins/share/web/templates/__jifty/online_docs/
   jifty/branches/schema-plugins/share/web/templates/__jifty/online_docs/autohandler   (contents, props changed)
   jifty/branches/schema-plugins/share/web/templates/__jifty/online_docs/content.html
   jifty/branches/schema-plugins/share/web/templates/__jifty/online_docs/index.html   (contents, props changed)
   jifty/branches/schema-plugins/share/web/templates/__jifty/online_docs/toc.html
   jifty/branches/schema-plugins/share/web/templates/__jifty/validator.xml
   jifty/branches/schema-plugins/share/web/templates/__jifty/webservices/
   jifty/branches/schema-plugins/share/web/templates/__jifty/webservices/json
   jifty/branches/schema-plugins/share/web/templates/__jifty/webservices/xml
   jifty/branches/schema-plugins/share/web/templates/__jifty/webservices/yaml
   jifty/branches/schema-plugins/share/web/templates/_elements/
   jifty/branches/schema-plugins/share/web/templates/_elements/header
   jifty/branches/schema-plugins/share/web/templates/_elements/keybindings
   jifty/branches/schema-plugins/share/web/templates/_elements/menu
   jifty/branches/schema-plugins/share/web/templates/_elements/nav
   jifty/branches/schema-plugins/share/web/templates/_elements/page_nav
   jifty/branches/schema-plugins/share/web/templates/_elements/sidebar
   jifty/branches/schema-plugins/share/web/templates/_elements/wrapper
   jifty/branches/schema-plugins/share/web/templates/autohandler
   jifty/branches/schema-plugins/share/web/templates/dhandler
   jifty/branches/schema-plugins/share/web/templates/helpers/
   jifty/branches/schema-plugins/share/web/templates/helpers/calendar.html
   jifty/branches/schema-plugins/share/web/templates/index.html
   jifty/branches/schema-plugins/t/
   jifty/branches/schema-plugins/t/00-load.t
   jifty/branches/schema-plugins/t/01-dependencies.t
   jifty/branches/schema-plugins/t/01-test-web.t
   jifty/branches/schema-plugins/t/01-version_checks.t
   jifty/branches/schema-plugins/t/02-connect.t
   jifty/branches/schema-plugins/t/03-form-protocol.t
   jifty/branches/schema-plugins/t/03-is_passing-no_plan.t   (contents, props changed)
   jifty/branches/schema-plugins/t/03-is_passing.t   (contents, props changed)
   jifty/branches/schema-plugins/t/03-test-mailbox.t   (contents, props changed)
   jifty/branches/schema-plugins/t/04-test_file.t   (contents, props changed)
   jifty/branches/schema-plugins/t/05-dispatcher.t
   jifty/branches/schema-plugins/t/06-forms.t
   jifty/branches/schema-plugins/t/07-limit-actions.t
   jifty/branches/schema-plugins/t/08-client.t
   jifty/branches/schema-plugins/t/09-url.t   (contents, props changed)
   jifty/branches/schema-plugins/t/10-i18n.t
   jifty/branches/schema-plugins/t/11-config-files.t
   jifty/branches/schema-plugins/t/12-param-schema.t   (contents, props changed)
   jifty/branches/schema-plugins/t/99-pod-coverage.t
   jifty/branches/schema-plugins/t/99-pod.t
   jifty/branches/schema-plugins/t/Continuations/   (props changed)
   jifty/branches/schema-plugins/t/Continuations/bin/
   jifty/branches/schema-plugins/t/Continuations/bin/jifty   (contents, props changed)
   jifty/branches/schema-plugins/t/Continuations/lib/
   jifty/branches/schema-plugins/t/Continuations/lib/Continuations/
   jifty/branches/schema-plugins/t/Continuations/lib/Continuations/Action/
   jifty/branches/schema-plugins/t/Continuations/lib/Continuations/Action/CrossBridge.pm
   jifty/branches/schema-plugins/t/Continuations/lib/Continuations/Action/GetGrail.pm
   jifty/branches/schema-plugins/t/Continuations/lib/Continuations/Dispatcher.pm
   jifty/branches/schema-plugins/t/Continuations/share/
   jifty/branches/schema-plugins/t/Continuations/share/web/
   jifty/branches/schema-plugins/t/Continuations/share/web/templates/
   jifty/branches/schema-plugins/t/Continuations/share/web/templates/autohandler
   jifty/branches/schema-plugins/t/Continuations/share/web/templates/black-knight-color.html
   jifty/branches/schema-plugins/t/Continuations/share/web/templates/black-knight-name.html
   jifty/branches/schema-plugins/t/Continuations/share/web/templates/black-knight-quest.html
   jifty/branches/schema-plugins/t/Continuations/share/web/templates/black-knight.html
   jifty/branches/schema-plugins/t/Continuations/share/web/templates/help-help.html
   jifty/branches/schema-plugins/t/Continuations/share/web/templates/index-help.html
   jifty/branches/schema-plugins/t/Continuations/share/web/templates/index.html
   jifty/branches/schema-plugins/t/Continuations/t/
   jifty/branches/schema-plugins/t/Continuations/t/00-prototype.t
   jifty/branches/schema-plugins/t/Continuations/t/01-raw-api.t
   jifty/branches/schema-plugins/t/Continuations/t/02-api.t
   jifty/branches/schema-plugins/t/Continuations/t/03-gc.t
   jifty/branches/schema-plugins/t/Continuations/t/04-before-blocks.t
   jifty/branches/schema-plugins/t/DateTime.t
   jifty/branches/schema-plugins/t/Jifty.pm
   jifty/branches/schema-plugins/t/Mapper/   (props changed)
   jifty/branches/schema-plugins/t/Mapper/bin/
   jifty/branches/schema-plugins/t/Mapper/bin/jifty   (contents, props changed)
   jifty/branches/schema-plugins/t/Mapper/lib/
   jifty/branches/schema-plugins/t/Mapper/lib/Mapper/
   jifty/branches/schema-plugins/t/Mapper/lib/Mapper/Action/
   jifty/branches/schema-plugins/t/Mapper/lib/Mapper/Action/CrossBridge.pm
   jifty/branches/schema-plugins/t/Mapper/lib/Mapper/Action/GetGrail.pm
   jifty/branches/schema-plugins/t/Mapper/share/
   jifty/branches/schema-plugins/t/Mapper/share/web/
   jifty/branches/schema-plugins/t/Mapper/share/web/templates/
   jifty/branches/schema-plugins/t/Mapper/share/web/templates/autohandler
   jifty/branches/schema-plugins/t/Mapper/share/web/templates/index.html
   jifty/branches/schema-plugins/t/Mapper/t/
   jifty/branches/schema-plugins/t/Mapper/t/00-prototype.t
   jifty/branches/schema-plugins/t/Mapper/t/01-raw-api.t
   jifty/branches/schema-plugins/t/Mapper/t/02-api.t
   jifty/branches/schema-plugins/t/TestApp/   (props changed)
   jifty/branches/schema-plugins/t/TestApp-Plugin-REST/
   jifty/branches/schema-plugins/t/TestApp-Plugin-REST/bin/
   jifty/branches/schema-plugins/t/TestApp-Plugin-REST/bin/jifty   (contents, props changed)
   jifty/branches/schema-plugins/t/TestApp-Plugin-REST/etc/
   jifty/branches/schema-plugins/t/TestApp-Plugin-REST/etc/config.yml
   jifty/branches/schema-plugins/t/TestApp-Plugin-REST/lib/
   jifty/branches/schema-plugins/t/TestApp-Plugin-REST/lib/TestApp/
   jifty/branches/schema-plugins/t/TestApp-Plugin-REST/lib/TestApp/Plugin/
   jifty/branches/schema-plugins/t/TestApp-Plugin-REST/lib/TestApp/Plugin/REST/
   jifty/branches/schema-plugins/t/TestApp-Plugin-REST/lib/TestApp/Plugin/REST/Action/
   jifty/branches/schema-plugins/t/TestApp-Plugin-REST/lib/TestApp/Plugin/REST/Action/DoSomething.pm
   jifty/branches/schema-plugins/t/TestApp-Plugin-REST/lib/TestApp/Plugin/REST/Dispatcher.pm
   jifty/branches/schema-plugins/t/TestApp-Plugin-REST/lib/TestApp/Plugin/REST/Model/
   jifty/branches/schema-plugins/t/TestApp-Plugin-REST/lib/TestApp/Plugin/REST/Model/Group.pm
   jifty/branches/schema-plugins/t/TestApp-Plugin-REST/lib/TestApp/Plugin/REST/Model/User.pm
   jifty/branches/schema-plugins/t/TestApp-Plugin-REST/share/
   jifty/branches/schema-plugins/t/TestApp-Plugin-REST/t/
   jifty/branches/schema-plugins/t/TestApp-Plugin-REST/t/00-model-User.t   (contents, props changed)
   jifty/branches/schema-plugins/t/TestApp-Plugin-REST/t/00-prototype.t   (contents, props changed)
   jifty/branches/schema-plugins/t/TestApp-Plugin-REST/t/01-config.t   (contents, props changed)
   jifty/branches/schema-plugins/t/TestApp-Plugin-REST/t/02-basic-use.t   (contents, props changed)
   jifty/branches/schema-plugins/t/TestApp/bin/
   jifty/branches/schema-plugins/t/TestApp/bin/jifty   (contents, props changed)
   jifty/branches/schema-plugins/t/TestApp/lib/
   jifty/branches/schema-plugins/t/TestApp/lib/TestApp/
   jifty/branches/schema-plugins/t/TestApp/lib/TestApp/Action/
   jifty/branches/schema-plugins/t/TestApp/lib/TestApp/Action/DoSomething.pm
   jifty/branches/schema-plugins/t/TestApp/lib/TestApp/Action/DoSomethingElse.pm
   jifty/branches/schema-plugins/t/TestApp/lib/TestApp/CurrentUser.pm
   jifty/branches/schema-plugins/t/TestApp/lib/TestApp/Dispatcher.pm
   jifty/branches/schema-plugins/t/TestApp/lib/TestApp/Model/
   jifty/branches/schema-plugins/t/TestApp/lib/TestApp/Model/User.pm
   jifty/branches/schema-plugins/t/TestApp/share/
   jifty/branches/schema-plugins/t/TestApp/share/web/
   jifty/branches/schema-plugins/t/TestApp/share/web/static/
   jifty/branches/schema-plugins/t/TestApp/share/web/static/images/
   jifty/branches/schema-plugins/t/TestApp/share/web/static/images/pony.jpg   (contents, props changed)
   jifty/branches/schema-plugins/t/TestApp/share/web/templates/
   jifty/branches/schema-plugins/t/TestApp/share/web/templates/concrete.html
   jifty/branches/schema-plugins/t/TestApp/share/web/templates/currentuser
   jifty/branches/schema-plugins/t/TestApp/share/web/templates/dispatch/
   jifty/branches/schema-plugins/t/TestApp/share/web/templates/dispatch/basic
   jifty/branches/schema-plugins/t/TestApp/share/web/templates/dispatch/basic-show
   jifty/branches/schema-plugins/t/TestApp/share/web/templates/dosomethingelse
   jifty/branches/schema-plugins/t/TestApp/share/web/templates/editform
   jifty/branches/schema-plugins/t/TestApp/share/web/templates/index.html
   jifty/branches/schema-plugins/t/TestApp/share/web/templates/manual_redirect
   jifty/branches/schema-plugins/t/TestApp/share/web/templates/regions/
   jifty/branches/schema-plugins/t/TestApp/share/web/templates/regions/list
   jifty/branches/schema-plugins/t/TestApp/share/web/templates/regions/long
   jifty/branches/schema-plugins/t/TestApp/share/web/templates/regions/short
   jifty/branches/schema-plugins/t/TestApp/share/web/templates/somedir/
   jifty/branches/schema-plugins/t/TestApp/share/web/templates/somedir/dhandler
   jifty/branches/schema-plugins/t/TestApp/t/
   jifty/branches/schema-plugins/t/TestApp/t/00-model-User.t
   jifty/branches/schema-plugins/t/TestApp/t/00-prototype.t
   jifty/branches/schema-plugins/t/TestApp/t/01-config.t
   jifty/branches/schema-plugins/t/TestApp/t/02-dispatch-show-rule-in-wrong-ruleset.t
   jifty/branches/schema-plugins/t/TestApp/t/02-dispatch.t
   jifty/branches/schema-plugins/t/TestApp/t/03-static.t
   jifty/branches/schema-plugins/t/TestApp/t/04-sessions.t
   jifty/branches/schema-plugins/t/TestApp/t/05-actions-before-redirect.pm
   jifty/branches/schema-plugins/t/TestApp/t/05-editactions-Cachable.t
   jifty/branches/schema-plugins/t/TestApp/t/05-editactions-Record.t
   jifty/branches/schema-plugins/t/TestApp/t/06-validation.t
   jifty/branches/schema-plugins/t/TestApp/t/07-sandboxing.t
   jifty/branches/schema-plugins/t/TestApp/t/08-notifications.t
   jifty/branches/schema-plugins/t/TestApp/t/09-redirect.t   (contents, props changed)
   jifty/branches/schema-plugins/t/TestApp/t/10-compress.t   (contents, props changed)
   jifty/branches/schema-plugins/t/TestApp/t/11-current_user.t   (contents, props changed)
   jifty/branches/schema-plugins/t/TestApp/t/12-search.t   (contents, props changed)
   jifty/branches/schema-plugins/t/TestApp/t/13-page-regions.t
   jifty/branches/schema-plugins/t/TestApp/t/config-Cachable
   jifty/branches/schema-plugins/t/TestApp/t/config-Record
   jifty/branches/schema-plugins/t/TestApp/t/i18n-standalone.t
   jifty/branches/schema-plugins/t/TestApp/t/instance_id.t
   jifty/branches/schema-plugins/t/TestApp/t/regex_meta_in_path_info.t
   jifty/branches/schema-plugins/t/lib/
   jifty/branches/schema-plugins/t/lib/Jifty/
   jifty/branches/schema-plugins/t/lib/Jifty/SubTest.pm   (contents, props changed)
Modified:
   jifty/   (props changed)

Log:
 r2740 at riddle:  andrew | 2007-01-29 12:06:20 -0600
 Creating a new branch to tinker with model/action schema editing plugins.


Added: jifty/branches/schema-plugins/AUTHORS
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/AUTHORS	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,24 @@
+In order of first commit:
+Jesse Vincent <jesse at bestpractical.com>
+David Glasser <glasser at bestpractical.com>
+Alex Vandiver <alexmv at bestpractical.com>
+Thomas Sibley <trs at bestpractical.com>
+Kevin Riggle <kevinr at bestpractical.com>
+Audrey Tang <audreyt at audreyt.org>
+Eric Wilhelm <ewilhelm at cpan.org>
+Nelson Elhage <nelhage at mit.edu>
+Sean E Millichamp <sean at bruenor.org>
+Kenichi Ishigaki <ishigaki at tcool.org>
+Chia-Liang Kao <clkao at clkao.org>
+Dobrica Pavlinusic <dpavlin at rot13.org>
+Bart Bunting <bart at bunting.net.au>
+Norman Nunley, Jr. <nnunley at cpan.org>
+Mark Fowler <mark at twoshortplanks.com>
+Liang-Bin Hsueh <hlb at bestpractical.com>
+Wolfgang Kinkeldei <wolfgang at kinkeldei.de>
+Paul Fenwick <pjf at perltraining.com.au>
+Edmund von der Burg <evdb at ecclestoad.co.uk>
+Yves Agostini <agostini at univ-metz.fr>
+Agent Zhang <agentzh at gmail.com>
+Pawel Murias <pmurias at woobling.org>
+Andrew Sterling Hanenkamp <sterling at hanenkamp.com>

Added: jifty/branches/schema-plugins/Changelog
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/Changelog	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,988 @@
+Jifty 0.70117
+   Dependencies: 
+     * Bumped the minimum required version of Jifty::DBI to 0.30
+
+Jifty 0.70116
+
+   New authors
+     * Add yves in authors, for localisation, debian packaging -yves
+     * Added PJF to AUTHORS file. -pjf
+     * Added evdb as a new author -evdb
+     * agentz
+     * pmurias
+
+   PubSub (audreyt, clkao, jesse)
+     * Audrey, Jesse and CL hacked out a PubSub message bus and a
+       preliminary Comet implementation.
+     * Doc for how subscription stuff works. a bit of refactoring toward
+       a second transport
+     * Added sample apps and design docs from the PubSub/Comet hackathon
+     * upgrade schema to work on mysql -jesse
+
+   Database
+     * Initial support for prefetched collections. -jesse
+     * Added support for SQL::ReservedWords to the schema tool, to stop
+       me from building applications for Postgres on SQLite and hurting
+       myself later -jesse
+     * Add a CheckSchema option to the config file to govern the SQL
+       Schema keyword checking -jesse
+     * Classify databases failing an SQL::ReservedWords check for nicer
+       output -gaal
+     * Better debugging information when running actions -jesse
+
+   Documentation
+     * Updated tutorial to remove mention of the deprecated download
+       area. -pjf
+     * Provided extra tips on how to install Jifty using Perl's standard
+       CPAN module on both Unix-like and Win32 systems. -pjf
+     * Documented PageRegions usage -wolfgang
+     * Show an example of a canonicalizer (lifted from the jifty
+       presentations). -falcone
+     * Document the ability to frob other pieces of the action from a
+       canonicalizer -falcone
+     * Jifty::Manual::UsingCSSandJS - Fix the misspelled "app.css" to the
+       correct "app.js"; minor reformatting. -audreyt
+     * Jifty::Manual::Models - It mentions <> as the SQL inequality
+       operator, but all internal code uses != instead; change the doc to
+       match reality. -audreyt
+     * Jifty::Manual::Cookbook - Trivial typo. -audreyt
+     * Jifty::Manual::AccessControl, Jifty::Manual::RequestHandling -
+       Change C< objEmeth > to the more readable C<< obj->meth >> POD
+       syntax. -audreyt
+     * expanded AccessControl.pod to reflect changes in Login plugin
+       -wolfgang
+     * added a pod describing Jifty's request handling process -wolfgang
+     * added a german translation for Tutorial.pod -wolfgang
+     * Minor fixes to pod -evdb
+     * added explanations about Login Plugin to AccessControl.pod
+       -wolfgang
+     * updated Models.pod to reflect recent changes -wolfgang
+     * added a section on 'limit' to 'Models.pod' -wolfgang
+     * Supply documentation for all of the methods which had been missing
+       it -jesse
+     * Jifty::Param::Schema merge algorithm rescued from the obscurity of
+       commit logs. -jesse
+     * Corrected Jifty::Record::current_user_can: The right is 'update',
+       not 'edit' -jesse
+     * Jifty::Record::current_user_can: "admin" should have been "delete"
+       -jesse
+     * Added the time and date filters to the cookbook. -nelhage
+     * Improved docs for Jifty::RightsFrom -jesse
+     * Improved docs for Jifty::Record (Access control related
+       functionality) -jesse
+     * Added a documentation fileon CSS and JS -wolfgang
+     * Jifty::Param::Schema - Trivial doc fix to s/Wifty/MyApp/. -audreyt
+     * Jifty::Action - add documentation for the automatic moniker
+       generation algorithm. -audreyt
+     * Minor documentation updates -gaal
+     * Jifty::Script::FastCGI doc - it's share/web/ not web/ nowadays.
+       -audreyt
+     * Two simple POD typos, one spotted by Gaal Yahas. -audreyt
+     * Minor spelling corrections. -jpeacock
+     * Fixed a cookbook typo, and add some sentences describing an issue
+       when defer{} failed to dwim. -gugod
+     * A cookbook recipe to do ajax canonicalization. -gugod
+     * Jifty::Action POD: Copy-n-paste the synopsis from
+       Jifty::Param::Schema and correctly L<> there. -audreyt
+     * Tell people where the docs for Jifty::Action::button really live -jesse
+     * [Manual/Actions.pod] added more explanation for the "return values" of Actions
+       made the $id args optional in the sample code.
+       added internal links to L</monikers>.
+       a few additions to the explanation
+       a few cleanups in the sample code -agentz
+     * Manual/Cookbook.pod: typo fixes, code cleanup -agentz
+     * document Jifty::Web::Form::Field::preamble -agentz
+     * Jifty::Manual::(Upgrading|RequestHandling|AccessControl|UsingCSSandJS|Actions|Continuations) 
+       typo and wording fixes -agentz
+     * Jifty::Manual::PageRegions examples and cleanup -agentz
+     * Jifty::Manual::Models - mentioned the build_select_query method,
+       paging, and Jifty::Manual::Upgrading. -agentz
+     * Added a note about the fact that mason needs to be flush left to the
+       tutorial -jesse
+     * add full MyWeblog example -jesse
+     * limit handling corrected in Jifty::Manual::Models -wolfgang
+     * Cookbook typo fixed. thanks to tokuhirom -jesse
+     * Jifty::Manual::Cookbook - applied the "edit" link patch from Peter Wise. -agentz
+     * plugin and declarative test design docs -nelhage
+     * ldap autocomplete example -yves
+     * Tutorial_de retitled to not conflict with the english one. -jesse
+
+   Plugins
+     * First release for plugins AuthLDAPOnly and AuthLDAPLogin, all
+       comments are welcome -yves
+     * Login plugin : Add missing Notification::ConfirmLostPassword,
+       dispatcher for passwordreminder, let to reset lost password -yves
+     * Added an action to let the user change his/her password in the
+       login plugin -yves
+     * AuthLDAP plugins: minor doc and debian rules fix -yves
+     * You can now run actions and get back arbitrary data formats from
+       the REST dispatcher -nelhage
+     * REST dispatcher cleanups. -jesse
+     * First cut at XML webservices in the REST plugin -jesse
+     * The first bit of major refactoring of the REST plugin. -jesse
+     * we loves our ACLs, we do. The REST plugin was violating too much
+       encapsuplation -jesse
+     * Added Module::Install files for plugins EditInPlace and Login;
+       some of them were in the MANIFEST but not the repository, so we
+       were getting false warnings of missing files when running 'make
+       distclean'. -jpeacock
+     * Add license to Login plugin's Makefile.PL. NOTE: remember to
+       increment the plugin's $VERSION strings before releasing to CPAN
+       (else updates won't get installed). -jpeacock
+     * test and debian updates for the LDAP plugin -yves
+     * Usable AuthzLDAP Plugin, see man Jifty::Plugin::AuthzLDAP
+       while thinking on new more generic Jifty::Plugin::Authz::XYZ , 
+       Jifty::Plugin::Authentication:XYZ -yves
+     * Login: Don't delete arguments that you don't know about /a priori/.  
+       Don't display fields that shouldn't be displayed 
+       (using the Unrendered attribute). -jpeacock
+     * Duplicate code removed from the plugin classloader -jesse
+     * Better handling of autocreated modules from plugins.
+       Print debug statements when autogenerating packages. -jpeacock
+     * reworking of Login plugin -jpeacock
+     * Some CAS authentification plugins -yves
+
+   Internals
+     * Jifty.pm: Load I18N after plugins, but before the main
+       classloader, so the main classes has access to _(). - audreyt
+     * Removed support for Devel::Gladiator. It was very, very beta and
+       caused server processes to end up as zombies -jesse
+     * qw'' is just weird. Change all instances to qw(). -schwern
+     * Jifty::Action::Record 'use'd DateManip but never uses it. -schwern
+     * It also used UNIVERSAL::require but did all of its requires via
+       Jifty::Util. -schwern
+     * Yet another fix to the URI-from-env feature, fixes a failing test
+       (reported by alexmv++). -gaal
+     * Guessing request schemes from the environment is fragile, so make
+       the fallback on BaseURL more reliable. -gaal
+     * when inferring a scheme for the application, look at REQUEST_URI
+       instead of assuming http://. Fixes tangent() on non-http:// apps.
+       -gaal
+     * Use Jifty->app_class whenever possible -alexmv
+     * Code cleanups in Jifty/Subs.pm -alexmv
+     * Fix for when Jifty->web->url is called with query parameters
+       -alexmv
+     * Added a Module::Pluggable subclass to get our own (somewhat
+       improved) require behaviour -jesse
+     * We were not properly removing blank values on record create -jesse
+     * Better handling of current_user when used as a class method -jesse
+     * use ApplicationClass, not Application Name in the login plugin
+       -clkao
+     * Add _is_readable in Jifty::Record, which means the record should
+       bypass current_user_can in check_read_rights. -clkao
+     * Add results_are_readable argument to collection to mark records
+       with _is_readable. -clkao
+     * Minor refactoring to enable non-cookie based session storage
+       -jesse
+     * Use Jifty->app_class to construct app-space class names. -clkao
+     * Jifty::JSON - Turn on $ImplicitUnicode so unicode strings
+       can be reliably serialized into .js and back.
+       (This is crucial for e.g. JavaScript confirm hooks.) -audreyt
+     * avoid warnings in LetMe -jesse
+     * DateTime: DateManip can get confused if someone else calls Date_Init 
+       earlier in the process.  Tell it "no, really, GMT please" -falcone
+     * DateTime: we're always setting the timezone to the user's timezone, even if
+       new is explicitly called with a timezone.  This breaks DateTime->from_epoch
+       which wants data back in UTC -falcone
+     * provide json webservices -audreyt && pmurias
+     * Jifty::ClassLoader - Defining MyApp::Action::Record::* now works. -audreyt
+     * Jifty.pm: Before we call Data::UUID->new, be sure to load it. -audreyt
+     * Jifty::Param::Schema allows "hints are 'type stuff'" but our Model syntax
+       uses 'are' to build an arrayref, so "hints are 'type stuff'" in a model would
+       result in displaying ARRAY(0x123456).   -falcone
+     * implement if-modified-since for static view handler. -clkao
+     * Module::Pluggable does't include empty intermediate classes now -alexmv
+
+   Web UI
+     * Links and Form titles needed to be better escaped -jesse
+     * Add delete option in admin view (for Jamalle - private joke) -yves
+     * Jifty::Param - It's no longer a Jifty::Web::Form::Field subclass.
+       -audreyt
+     * Add the ability to send "notes" to users from your canonicalizer.
+       This is separate from the warning and error spans used by the
+       validator -falcone
+     * validator.xml - allow us to update action data just by changing
+       it. Without this you had to set ajax_canonicalizes on a field in
+       order to change it and have it propagated back. --falcone
+     * Some refactoring of form field rendering, and adding a focus =>
+       argument to form fields to focus them on page load. -nelhage
+     * Switch our implementation of autofocus to use behavior, rather
+       than a custom onload event -jesse
+     * Fix calendar div & IE select box problem -ishigaki
+     * Do a slightly more generic dereferencing on the user object's
+       friendly name in the sidebar -jesse
+     * Fixing autocomplete so we render the autocomplete div *before
+     * the javascript, so the JS can hook it. -nelhage
+     * Remove useless check in buttonToLink to get a javascript
+       performance boost. -hlb
+     * Make render_messages sort on result moniker as well. -audreyt
+     * Jifty::Response: Ensure consistent ordering from monikers.
+       -audreyt
+     * Hacked the yui calendar component to allow selection of out-of-month
+       dates -jesse
+     * Even more I18N+L10N, this time for admin crud pages and calendar.html -audreyt
+     * adjusted the output format of render_preamble by removing a redundant space. -agentz
+     * The "length" attribute Web::Form::Field now also means HTML "maxlength"
+       in addition to "size". -audreyt
+     * calendar.js - Fire off canonicalization/validation methods upon clicking a
+       date. -audreyt
+     * Update YUI to 0.12 and port local changes -trs
+     * Administration manage model : 
+       add sortable capabilities in header table
+       bug fix in delete item
+       begining of modularity
+       other cosmetic changes
+       silk icons, wai tags thanks to Jamalle -yves
+     * Jifty::Web::Form::Field and ::Select - Label display was rendered using
+       the latin1-biased escaping in HTML::Entities; switch to the proper UTF-8
+       escaping in Jifty->web->escape. -audreyt
+     * This helps passing xhtml validation.
+       <input> cannot be direclty under <form>, there should be a
+       block-level element in-between. -gugod
+     * made wait-message look consistent in both firefox and IE -agentz
+     * admin/model/dhandler: localization hooks added, page title added -agentz
+     * add yui/tabview and its assets -hlb
+     * Indicate mandatory fields visually -trs
+     * Only emit mandatory field warnings with Ajax when the field starts with
+       data. -trs
+     * Close <li>s we open in the admin interface -alexmv
+     * remove extra </div>s -gugod
+     * upgrade yui library & add yui/container.js -hlb
+     * Fix broken Jifty.Utils.isMSIE -trs
+     * Fix buttonToLink to deal with normal, non-ajaxy buttons that we want to turn
+       into links -trs
+     * Use Jifty::JSON::objToJson to properly escape JS values (in particular
+       single quotes in button labels were causing problems) -trs
+     * Add a key_binding_label attribute so that key binding labels can be set
+       independently of the normal label -trs
+
+   Jifty Actions
+     * Jifty::Action: Generate stable auto-monikers for actions based on
+       the caller stack. -audreyt
+     * Negative searching for Search actions -jesse
+     * Added an option to search the contents of any text field to jifty
+       search actions -nelhage
+     * Jifty::Action::Record: Allow the same for user-generated param vs
+       CRUD actions. -audreyt
+     * Jifty::Action - Autoincrement the per-request stash counter for
+       the case of looped action creation. -audreyt
+     * Fixed 'mandatory' validation misbehavior -- 'mandatory' now
+       handled correctly -wolftang
+     * Jifty::Param::Schema: Allow partial override of superclass's
+       PARAMS by simply declaring a sub "param" and fill them with the
+       fields you'd override. -audreyt
+     * Jifty::Web / Jifty::Action: Stickiness now works on autogenerated
+       monikers. -audreyt
+     * Modified Jifty::Action::Record::Update so that empty strings for integer and
+       boolean columns will be interpreted as NULLs. This may break apps that
+       assume that an empty value string will be a no-op. -jesse
+     * Jifty::Action::Record::Search - Consider "float" and "double" fields
+       as numeric for comparison.  Also consider "char" as textual. -audreyt
+     * Allow a canonicalization note to be set, even if you don't change the 
+       value of the action parameter - falcone
+     * L10N for Action::Record::Search -audreyt
+     * Jifty::Manual::Actions include example with available are defer { ... }
+       syntax which doesn't work because ref on variables deferred with Scalar::Defer
+       return 0 instead of ARRAY -dpavlin
+     * Add an "(any)" label to Action::Record::Search when render as radio. -audreyt
+     * Jifty::Action::Record - Support for "is autocompleted" annotation. -audreyt
+     * Jifty::Action::Record::Search - "numeric" and "decimal" fields are also
+       numeric. -audreyt
+     * Jifty::Action::Record - "is autocompleted" choices should not consider
+       null/empty fields. -audreyt
+     * Action::Record::Search - When there is just one choice, don't bother
+       displaying '(any)' for Radio. -audreyt
+     * Jifty::Action::Record::Search - First cut at a _dwim field for numeric
+       fields that supports > >= < <= == = != ! <> operators. -audreyt
+     * Jifty::Action::Record - Autocompletion now lists only the parts
+       that matches the user-input as prefix. -audreyt
+     * Jifty::Action::Record::Search - Add _before/_since for dates, as well as
+       the equivalent _ge and _le for numbers. -audreyt
+     * Jifty::Action::Record::Search - Add hints to _dwim. -audreyt
+     * Adding a note about canonicalizers being idempotent -nelhage
+     * add max_length alias to fix Object::Declare and Jifty::Param::Schema
+       not handling length properly -alexmv
+
+   bin/jifty
+     * Change Jifty::Util's probe of bin/jifty from -x to -r for poor
+       people on filesystems that does not have a executable bit. (The
+       maybe_command is still needed for the .bat case.) -audrety
+     * Fix the bin/jifty detection logick: The .bat extension exists for
+       MSWin32, cygwin and os2, so use MM->maybe_command for those three
+       platforms. -audreyt
+     * Also, the -e check is redundant after -x, and in Win32 we can use
+       bin/jifty.bat alone without bin/jifty, so make the check respect
+       that case. Reported by: Stephen at s-team -audreyt
+     * bin/jifty covered sigterm in a way that could cause zombie processes
+       under fastcgi -jesse
+     * Be explicit about the paths we're creating -alexmv
+     * Jifty::Script::Server - Remedy for the edge error case where var/
+       is missing, which used to cause mysterious error messages. -audreyt
+
+   Building apps
+     * Small error string change to suggest looking for missing use lines
+       in models where refer_to is used -bartb
+     * Ongoing work to pass through Class::ReturnValue errors all the way
+       from Jifty::DBI to the view layer -jesse
+     * create scaffolding actions with the new Jifty::Param::Schema
+       syntax --falcone
+     * Jifty::Manual::Upgrading - Remove the now-obsoleted claim that one has
+       to "use" model classes before renaming it. -audreyt
+     * Jifty::Upgrade - rename() now works with SQLite too, woot! -audreyt
+
+   Dependencies and installation
+     * Makefile.PL typo. Spotted by David Adler -jesse
+     * Files for debian packaging, now rather for actual cpan release
+       than for svn. -yves
+     * Update MANIFEST.SKIP and run 'make manifest' to ensure that new
+       files get added properly. -jpeacock
+     * Added a dependency on libextutils-command-perl to debian control
+       file -bartb
+     * Older DBI versions didn't provide the API we're using. (0.22 is
+       known bad) -jesse
+     * Remove PerlIO::gzip as a Jifty dependency. -audreyt
+     * Older XML::Writer versions failed tests. Dependency bumped -
+       Thanks to Jonathan Stowe -jesse
+     * Reverting to dumping using YAML.pm *again*, because YAML::Syck
+       generates XML that makes YAML segfault :/ -nelhage
+     * Fixed missing dependency on Module::CoreList -- Thanks to Henry
+       Baragar -jesse
+     * Makefile.PL - Add dependency on Test::Base and Module::Refresh.
+       Reported by: Andreas Koenig -audreyt
+     * Makefile.PL - Bump JSON::Syck dependency to 0.15 to handle
+       single quote + unicode strings. -audreyt
+     * SQLite is required to test properly -jesse
+     * debian packaging updates -yves
+     * Skip html files when looking for dependencies. This may cause us to
+       miss some modules used only from within mason, but it will stop falsely
+       detecting lines that start with the word "use" in docs. -jesse
+     * add Test::MockModule and Test::MockObject for J::W::F::F testing -agentz
+     * made Test::MockModule and Test::MockObject optional by
+       putting them into development dependency list -agentz
+     * Makefile.PL: remove Test::HTTP::Server::Simple dependency when $^O eq
+       'MSWin32' -ishigaki
+     * debian packages : add libtest-mockobject-perl libtest-mockmodule-perl in
+       recommands packages (for new snapshot on jiftysvn repository) -yves
+     * cleaned up debian readme -bartb
+     * Clean up MANIFEST (mostly sqlite files) -alexmv
+     * hlb++ reported that we really want HTTP::Server::Simple 0.26+,
+       not 0.20+, as 0.20 and 0.21's critical URI-path-processing
+       bug makes us non utf8 friendly. -audreyt
+     * Added Data::UUID to the Makefile.PL and made sure we use'd it in 
+       Jifty.pm -kevinr
+
+   Internationalization
+     * The ubiquitous "There was an error completing the request. Please
+       try again later.") error message should be localised. -audreyt
+     * We now default the location of the jifty siteconfig file -jesse
+     * For some reason, loc('') started spewing out PO metadata. This
+       seems to be b0rkeness on the Locale::Maketext layer, but for now
+       generalize the undef detection logic in Jifty::I18N to recognize
+       that case. -audreyt
+     * "You need to fill in this field" needs to be localized. -audreyt
+     * Stopped the internationalization system from exploding if a plugin
+       doesn't have a module_dir -alexmv
+     * The internationalization system now extracts messages from
+       TemplateRoot, not share. -clkao
+     * Plugin internationalization and french po files -yves
+     * Jifty::I18N: New ->refresh method so .po files are reloaded
+       properly when DevelMode is on. -audreyt
+     * Even more l10n on Jifty::Action::Record. -audreyt
+     * jifty po shouldn't check/update files under .svn directories -ishigaki
+     * zh_cn and zh_tw translations -audreyt
+     * "jifty po": Ignore _svn/ directories (Win32), as well as foo~ files. -audreyt
+     * L10N for the new Search action fields. -audreyt
+     * update fr.po -yves
+     * update zh_cn and zh_tw and use traditional characters in zh_tw -agentz
+     * update german J::Manual::Tutorial_de to match english version's 
+       changes -wolfgang
+     * Jifty::Script::Po: shouldn't update other catalogs if we specify target
+       language with -l -ishigaki
+
+   Testing
+     * Jifty::Test: canonpath-ed for Win32 -ishigaki
+     * make sure to skip 04memcached.t if you don't have Cache::Memcached
+       -ishigaki
+     * skip all the live tests (that call 'start_ok') on Win32 -ishigaki
+     * shut up warnings when tests have no plan (t/Continuations/03-gc.t)
+       -ishigaki
+     * Added Jifty::Test->web to allow using Jifty->web in tests without
+       a bunch of scaffolding.
+     * Converted search tests to using Jifty::Test->web -schwern
+     * Basic compile and startup tests for the Chat sample -schwern
+     * add tests for Jifty::Web::Form::Field's render methods -agentz
+     * adjusted t/06-forms.t to skip related tests when
+       these two modules are not installed. -agentz
+     * fix t/TestApp-Plugin-REST/t/02-basic-use.t because ClassLoader
+       creates 4 new actions -falcone
+     * add tests for Jifty::Param::Schema -agentz
+     * TODO tests attempting to test if we get ajax validation errors for
+       mandatory values after the sticky_value has been deleted -trs
+
+   Email notifications
+     * When sending email notifications, encode the message body -clkao
+     * MIME-encode notification subjects. -clkao
+     * Content-transfer-encoding needs to be 8bit. -clkao
+     * Don't set notification transfer_encoding to 8bit if it's actually
+       multipart. -clkao
+
+   Dispatcher
+     * avoid undef warnings in the Dispatcher -jesse
+     * Jifty::Dispatcher - Alternation in extended shell globbing syntax
+       now admits zero characters as well:
+       on 'foo{,.zip}'     # matches 'foo' and 'foo.zip' -audreyt
+     * Jifty::Dispatcher - NUMBER SIGN (#) now captures one or more digit
+       characters in the extended shellglob condition syntax.
+       Suggested by: Sebastian Riedel -audreyt
+
+Jifty 0.60912
+
+Testing
+
+ * force to use Jifty::TestServer on Win32, though both JTS and THSS doesn't work properly at the moment. 
+ * Give a description for get_html_ok so that you know what URLs fail/succeed in test output
+ * Small stylistic cleanup to t/01-dependencies
+ * Honour coverage options.
+ * make sure to remove remnant test db before we test (Jifty on Win32 fails to unlink them right now)
+ * Add Jifty::Test->test_in_isolation
+ * Document Jifty::Test->is_done.
+ * Added Jifty::Test->is_passing and is_done
+ * Basic SYNOPSIS for Jifty::Test as well as mentioning the Test::More passthrough.
+ * Add Jifty::Test->teardown_mailbox to mirror setup_mailbox
+ * use safer File::Spec->rel2abs for SubTest
+ * TestServer: use File::Spec->rel2abs; it's safer than Cwd::abs_path which croaks
+ * Jifty::Test->test_file() accepting and returning a list causes problems
+   because people will try to do:  my $file = Jifty::Test->test_file($file) and
+   it ain't gonna DWIM.
+ * Mention Shell::Command and Jifty::Test->temp_file in the style guide.
+ * Add Jifty::Test->test_file() to declare files created only for testing and
+   which should be cleaned up.
+ * Fixing tests when using JDBI::Record::Memcached and setting things in the database from test scripts
+ * Added explicit tests for Jifty::Action::Redirect
+ * Script for running client and server side combined code coverage.
+ * Give a description for get_html_ok so that you know what URLs fail/succeed in test output
+ * Ignore "fluff" errors in HTML validation since they cause non-W3C attributes like "autocomplete" to be warned about
+ * Add html_ok method for checking the mech's current content so tests can use it while we retain control over Test::HTML::Lint
+
+Models
+
+ * jifty model --name now uses the new schema {} sub.
+ * Made the display of a friendly string for picking a record from a list a lot more flexible. 
+ * canonicalization wasn't being properly run on models before validation
+ * Added Jifty::Filter::DateTime, a JDBI filter that promotes DateTime
+   objects to Jifty::DateTime objects on read, setting the time zone
+   appropriately.
+ * Added a concept of "virtual" arguments to actions. These won't be passed on to Record classes, even if they're sumbitted. We use this for Password confirmation arguments, so that we don't pass password_confirm on to the database, which is kinda useless (and breaks the db ;)
+ * Added a as_superuser method to Jifty::Record to make it easier for
+ * code to briefly dodge around ACLs when needed.
+
+
+Admin UI
+
+ * __jifty/admin: use ->models reflection to build the nav bar.
+ * Integer C<gt> or C<lt> searching.
+ * Added substring search and date comparison to J::A::R::Search
+ * Basic search in admin mode using Jifty::Action::Record::Search. Still buggy, especially UI-wise, but functional.
+ * Initial version of Jifty::Action::Record::Search. It only supports
+ * exact positive searches on fields at the moment.
+ * There's no point in rendering confirm fields for passwords when we're
+   viewing records in admin mode.
+ * Don't create _gt and _lt search fields for magic _id refers_to fields.
+ * Make the admin UI look slightly less crappy.
+ * Make admin mode DTRT with columns that end in _id and refer to another model.
+
+REST
+
+ * REST Dispatcher: model list reflection
+ * Basic placeholder for REST plugin tests
+ * REST Dispatcher skeleton that actually works
+* Jifty::Plugin::REST::Dispatcher - /=/action/ now works across HTML+HTTP.
+* J::P::REST::Dispatcher - all GET model URLs work, with 404s.
+* Jifty::Plugin::REST::Dispatcher - model fetch actually works!
+
+
+Actions
+
+ * No longer generating arguments on C<Jifty::Action::Record> for fields
+   that C<refer_to> C<Jifty::DBI::Collection>s, since we can't do
+   anything useful with them right now anyways.
+ * Get the _confirm items on passwords to respect sort ordering
+ * Debugging improvements to stop stupid developer mistakes like passing the wrong sort of object to a Jifty::Action::Record. 
+ * Now we do proper escaping of values in select-one lists.
+ * Canonicalise {onclick}{submit} using the accessor wrapper.
+ * Only call moniker when {onclick}{submit} isa Jifty::Action.
+
+Documentation
+
+ * Wolfgang Kinkeldei: added a pod on models 
+ * Patch from Todd Chapman to fix tutorial
+ * The beginnings of a Jifty code style guide.
+ * Jifty::Manual::Continuations: reflect tangent() in the manual.
+ * Developer documentation for the Jifty::Web::Form::Field::* hierarchy.
+ * Add "How do I Add Atom/RSS Feeds" to Cookbook.
+ * Add a recipe about running fastcgi server, which, in fact, only points
+   to 'jifty help fastcgi'.
+ * lib/Jifty/Manual/FAQ.pod - a start on an FAQ
+ * Standardizing on referring to share/web everywhere in the tutorial.
+ * Todd Chapman noticed a typo in the docs about autocompleters
+ * Tutorial patch from rindolf++
+ * documented logger_component argument
+ * Add a small bit of doc about creating your own classes that are normally created by J::ClassLoader
+ * A start at some docs on upgrading, needs reviewing and some more examples
+ 
+
+Perfomance and optimization
+
+ * Don't try to lowercase session information on postgres
+ * Don't bother with session when serving static files.
+ * Don't need ExtUtils::MakeMaker in Jifty::Util.  This is about 7% of total compile time loading jifty.
+ * Kill Jifty::Web::Menu circular references.
+ * Transform actions in {onclick}{submit} to their monikers, to avoid
+   circular references.
+
+Javascript and HTML
+
+ * Fix AJAX canonicalization of date fields
+ * Some browsers don't like trailing commas in JS arrays and hashes.
+ * You can now pass confirm => 'question?' to javascript hooks (i.e. onclick) and get a confirm dialog in the browser. This doesn't work without javascript yet.
+ * Use a local copy of the icons in our calendar widget, rather than the version that yahoo points to on akamai
+ * fix the unexpected behavior in context menu for IE users.
+ * Fix bug that didn't allow calendar months to be changed
+ * Show calendar widget on focus and hide it on blur
+ * If we don't have XMLHttpRequest, fall back on page loads
+ * More thorough normalization of the submit parameter to Javascript handlers
+ * Accesskey support added to buttons and links. It just uses the same keys as our javascript key bindings.
+ * A little more Element/Clickable refactoring, and implementing a
+   C<disable> option to onclick handlers that toggles whether or not to
+   disable form fields for an action.
+ * We now write out state variables at the start of forms, instead of at the end. 
+ * Moved "Dismiss" buttons on messages and errors into Behaviour, so they only show up in javascripty contexts where they'd be useful
+ * Added the ability to support target attributes in menu items and clickables
+ * IE doesn't support element.setAttribute("class", "foo"), so use element.className instead
+ * Explicitly specify a radix of 10 (decimal) for the parseInt calls used in Yahoo's calendar widget when parsing the date to initially display
+ * Don't attempt to disable hidden inputs, since this sometimes causes IE to die
+ * Use our own "enter" handler to select the button to click, since Safari sometimes gets it wrong with complex fields
+ * The setAttribute call doesn't work for "class" in IE
+ * Fixing the calendar widget to create a new calendar every time, so
+   that the calendar reflects any changes the user makes in the text
+   field.
+ * Support turning off autocomplete on a per-form basis.  We still need per-field, but that's for later.
+
+Distribution
+
+ * debian packaging files for jifty
+ * Removed duplicated share/web (it was copied to lib/auto/Jifty)
+ * CGI.pm 3.17 (and possibly earier) had a bug where regex metacharacters in
+   the PATH_INFO would cause it to puke.  We now depend on CGI 3.19 which fixed
+   that bug.
+ * Update Module::Install to 0.64.  The important thing here is it gets us
+   a fixed Module::AutoInstall which works when you run Makefile.PL from the
+   command line without CPANPLUS installed.
+ * Don't index the t directory
+ * add "use Jifty::YAML" before all uses.
+ * Moved some modules to feature sections in Makefile.PL, updated 01-dependencies.t to recognise recommends sections as well as requires
+ * Win32 requires File::ShareDir 0.04
+ * Removed Text::Autoformat dependency and usage.
+
+Dispatcher
+
+ * Dispatcher: Support tangent($url) as sugar for Jifty->web->tangent(url=>$url).
+ * Dispatcher: Allow "**" in glob pattern to mean anychar including slash.
+ * Dispatcher did not have a ->{cgi}, so ->method certainly could not
+   work.  Use the env variable for now.
+ * Jifty::Dispatcher: abort(404) now works as the doc promised.
+
+Internals
+
+ * Jifty->web->return in void context is now an immediate return.
+ * Jifty::ClassLoader - Make Jifty::Handle a CL'ed module as well,
+   so MyApp::Handle can implement scary magick of its own.
+ * Don't blow up when trying to check if action mixins are autogenerated
+ * Let's be better about not redirect-looping when calling continuations
+   to paths that contain multibyte characters. This solution is a hack,
+   but it's better than looping.
+ * Fix placeholders on browser forward/back
+ * Replace hard tabs with spaces for consistency
+ * Jifty::Web::state_variables no longer prefixes keys with J:V- before
+   returning them, and Clickables now serialize the *outgoing* state
+   vars, instead of the previous request's.
+ * When rendering a page region, mark actions as inactive, don't remove
+   them, so that their arguments are available inside fragments.
+ * If we receive an action's arguments, but it's not in J:ACTIONS, or
+   we don't run it for some other reason, don't consider it to have
+   failed for the purposes of stickiness.
+ * moniker_for and action_form now behave more cleanly with forms which
+   have no non-continuation fields other than their submit buttons.
+ * No longer lose if you do a Jifty::Action::Redirect to the same page
+   you're already on. Also, add the ability to force Web to redirect,
+   even if it's to the current page.
+ * Jifty.pm: Change all __PACKAGE__ to Jifty.
+ * Jifty::ClassLoader: provide ->models accessor to list the model classes.
+ * added Jifty::Request::clear_state_variables
+ * Form::Clickable: Don't mix self accessors and args.
+ * Refactor the constructors for Jifty::Web::Form::*, which takes initial
+   hash and values to be overridden with accessors.
+ 
+Internationalization
+
+ * LetMes' escaping should be utf8 aware
+ * Email addresses probably shouldn't ever be utf8, but using the utf8 escaper is more Right(tm)
+ * Properly UTF-safeing Jifty::LetMe
+ Sean E. Millichamp and clkao both pointed out that Locale::Maketext could choke on overloaded objects like DateTimes. This change makes sure that doesn't happen any more.
+ * Locale::Maketext doesn't always do the right thing with user-generated strings. So let's do that for it.
+ * Properly encode arguments when generating LetMe URLs.
+
+Email
+
+ * add UTF-8 charset to message body on notifications
+ * added infrastructure to do mime mails
+ * make Jifty notifications be UTF-8
+
+Plugins
+
+ * Jifty::Web::Session::ClientSide - Client-side sessions.
+ * Added a ProfileBehaviour plugin to aid profiling Javascript
+   Behaviours (see app_behaviour.js in the Jifty source for some more
+   information)
+ * Some Behaviour profiling UI fixes.
+ * Removing profiling code from behaviour.js
+ * Get the signup form in the login plugin to respect locally defined schema for the user class
+ * Added information about the environment to the EmailErrors plugin
+ * Making the Login plugin play nice with admin mode.
+ * Plugin static roots should take precendence over jifty's
+
+
+Win32
+
+ * Win32 complains when you try to unlink open DB
+0.60722
+
+* Dispatcher fixes to deal with the better canonicalization we started doing in
+   0.60707
+* changed all instances of '/usr/bin/perl' to '/usr/bin/env perl'
+* added a path option to Jifty::Web->url
+* added url tests
+* Jifty::Manual::Actions -- update the worldview to reflect the
+  parameters/arguments concept split.
+* Introduce aliases.  See Jifty::Param::Schema for the table.
+* Declarative Jifty Parameters.
+* See Jifty::Param and Jifty::Param::Schema for the new syntax.
+* Also added dependencies for Jifty::Script::Deps and declarative parameters.
+* Also updated test applications to use declarative parameters. 
+* Adding the CSS browser selector trick from http://rafael.adm.br/css_browser_selector/ to Jifty
+* After autocomplete, trigger a validation.
+* Upping JSON::Syck version dependency. 0.14 fixes escaping in single-quoted strings.
+* Add a tooltip to the dismiss link and hide the dotted border
+* Trailing commas are not good for JS in Safari
+* Let's not blow up if we have placeholders on an input without a form
+* Don't fade autocomplete in and out, just show and hide it
+* Not submitting placeholder values when we submit forms or AJAX
+* Add the class before we set the text, so that it appears grayed-out, rather than appearing and *then* graying out
+* Support multi-line placeholders
+* textareas can have placeholders, too; Style them appropriately as well
+* Adding support for placeholders, grayed-out text in form fields that is written in with JS and vanishes on focus
+* Auto-accept cpan's wisdom about prompts (Jifty::Deps)
+* 0th sketch at "jifty deps"
+* packaging plan updates
+* J::Web::redirect can take a Clickable as arg, so make goto do that, instead of passing a URL with parameters, which doesn't work right
+* Proper port support on urls. Thanks to jpeacock.
+*  Resolve inconsistent filenames vs packages (Plugins)
+* Switched Jifty::Web::url to use uri.pm rather than "heuristics"
+* Let's not blow up if an action has a result that's an unblessed reference.
+* Don't upgrade the database versions with schema --print. I'm not sure
+  if there's a good way to persuade JDBI to give us the SQL to print, so
+  we're just spitting out a warning for now, but that's better than the
+  old behavior.
+* Stop notification from flipping out if you use a scalar as the To:
+* basic smoke test for jifty's notifications 
+* Only set active child on create if we have a request
+* More serious failure message, and don't imply that it's necessarily the server's fault.
+* update .po files
+* Jifty::Dispatcher - there's no call_next, remove it from POD, fnord.
+* Refactoring message rendering slightly
+* Fix Jifty-Win32 by having canonicalize_path always returning
+  /-separated paths, never \-separated paths.
+* Let classes be set on menu items
+* Adding a warning about our slow Rico.Corner.round
+* Documenting how to write sane Behaviours that don't leak memory (leak
+  less memory, probably) in IE and aren't dog-slow.
+* Fix JS memory leak in IE
+* Make jifty tests respect the current given @INC, so it doesn't
+  use lib when you are supposed to use blib during make test.
+* Make the subtest system less painful.
+* Use Jifty::Script to invoke test server, so it doesn't depend on
+  bin/jifty.
+* Fix a Safari display bug
+* Fix calendar positioning bug and make sure it works when the date field is within a relative or absolutely positioned element
+* Test file for Jifty::Client
+* First pass at a Jifty client module.
+*  English orthography fixes.
+* Trailing commas in Perl are good.  In Javascript, they aren't, and sometimes cause IE to barf (like this one).
+*  Hide focus border
+* Lowering the autocomplete delay
+* first-pass editorial run over Continuations.pod.
+* Sketchy sketchy handwavy descriptions of page region backend.
+  Ramblings totally not expected to be interpretable.
+* Wrap the popup notification div in dropshadow wrapper hooks
+* Mention webservices in Actions doc
+* Double fallback goes the way of the dodo
+* Continuation manual
+* Set up the output API for mapping of request parameters (input API
+  already existed)
+* Change method of getting results out of response on continuation RETURN
+* Fewer calls to ->arguments, though they might be cached already.
+* Not all CurrentUser classes may have a "nobody"
+* Jifty::Web::Form:Clickable - provide a bit more info on how to use
+  the "returns" field.
+* jifty-dispatcher.graffle that shows the dispatch chain.
+* Nicer "server down" message
+* Re-enable form inputs after failure
+* toggleable page region clickables weren't doing the right thing when used in non-ajax mode
+* Fix the validation and autocomplete race condition
+* Skip nobody and superuser when we do notifications
+
+
+060707
+
+  * Minor build fixes
+
+060706
+  * SECURITY UPDATE: Previous versions of Jifty did not 
+  protect users against a class of remote data access vulnerability. If an
+  attacker knew the structure of your local filesystem and you were using 
+  the "standalone" webserver in production, the attacker could gain read
+  only access to local files. 
+  
+  We found this vulnerability on 6 July 2006 during an internal Security 
+  scan. We've added new tests to ensure that these and other similar
+  vulnerabilities don't recur.
+
+  We recommend that ALL users of Jifty UPGRADE to 0.60706 IMMEDIATELY.
+
+
+060616
+  * The last CPAN release was broken. No real changes.
+
+060615 (15 June 2006)
+
+The following incompatible changes were made to Jifty. For a complete changelog,
+please see the "Changes" file.
+
+ * Removed the ActionBasePath and CurrentUserClass configuration
+   variables.  This breaks backwards compatibility.
+
+ * If you've overridden /_elements/javascript in your app, you'll want to 
+   take a look at the new stock version to see what has changed.
+
+ * The JS method document.getElementsBySelector() no longer exists.  Use 
+   cssQuery() instead.
+
+ * Jifty::Record now returns empty objects for related objects that can't be 
+   loaded, rather than undef
+
+
+ * The keybinding JS code is now a more proper library 
+   (Jifty.KeyBindings) and less intrusive.  If you've overridden the 
+   default /_elements/wrapper in your app, you'll want to make sure you get 
+   rid of the keybinding javascript (see the stock wrapper).  Just include 
+   <& /_elements/keybindings &> wherever you want the keybindings for a 
+   page to appear.
+
+ * This shouldn't be the cause of breakage, but the included rico.js is 
+   no longer a stock Rico build.  See the comments at the top of the file 
+   if you're interested.
+
+ * 'last_rule' now aborts only the current stage -- thus, last_rule                         
+   from a 'before' block will *NOT* prevent the RUN stage from happening!
+   This is a *BACKWARDS-INCOMPATIBILE CHANGE*, but fixes the dispatcher
+   to agree with its docs.
+ * 'abort' is the correct call to skip straight to the cleanup block.                       
+ * This allows the edit in place plugin to 'claim' its path and                             
+   protect it from access control in the app's 'before' blocks                                                                                                                              
+
+
+
+0.60507 (7 May 2006)
+
+  * Pod fixes from Eric Wilhelm
+    lib/Jifty/Object.pm - removed incorrect '=for' directive
+    lib/Jifty/Web/Form/Field.pm - removed incorrect '=for' directive
+    lib/Jifty/Web/Form.pm - removed incorrect '=for' directive
+
+  * Better failure messages on when schema upgrades with SQLite fail
+    from Alex Vandiver
+
+  * Be a little more explicit about SQLite's limitation, and a possible
+    (painful) workaround
+
+  * Update the inc tree to 0.62 for various fixes,
+    in particular improved share_dir compatibility. --Audrey Tang
+
+  * We were inconsistently using Jifty::Util::make_path as a
+    subroutine. It's a class method.  This could break the stub
+    generators and tutorial.  Thanks to Sean E. Millichamp
+
+0.60505 - Cinco de Jifty! (5 May 2006)
+
+  * Native support for times and timezones.
+
+  * Bug fixes (Many contributors)
+
+  * Documentation updates (Many contributors)
+
+  * Win32 Support (Audrey Tang)
+
+  * New Session layer based on Jifty instead of Apache::Session.
+    Designed for AJAX and Continations (alexmv)
+
+  * Jifty internal metadata store (The begining of an internal configuration
+    management system (alexmv)
+
+  * Form fields no longer automatically insert the field name by itself
+    as a class.  Instead, the class has changed to "argument-<fieldname>"
+	to avoid conflicts with generic class names (such as date).
+
+  * Move allow and deny'ing of actions into Jifty::API; this breaks
+    backwards compatibility.
+
+  * Don't allow applications to be named "Jifty" by default.  They are
+    forced to be named "JiftyApp" now, for namespace reasons.
+
+  * Remove Jifty->web->actions method; you should be using
+    Jifty::Form's actions method.
+
+  * Beginning of localization support.
+
+  * Notifications can now take a user object or an email address
+
+  * "sort order for arguments" patch, as suggested by miyagawa.  This
+    makes use of sort_order column property of Jifty::DBI
+
+  * YAML -> Jifty::YAML
+
+  * Switch from Time::ParseDate to Date::Manip, since the former isn't
+    win32 compatible
+
+  * Shuffle the Mason and static handlers into Jifty::View namespace
+
+  * Jifty no longer attempts to AJAX submit file upload fields
+
+  * We no longer write DefaultStaticRoot and DefaultTemplateRoot into
+    config files
+
+  * Added a "LogLevel" option to the Jifty config file, so you can
+    more easily enable debug logging.
+
+
+0.60321
+
+  * 'return if already_run' in after rules so they run only once
+
+  * Overhauled the static server to try really hard to force caching
+    by clients.  It also gzips its content, if possible.
+
+  * More stylish forms
+
+  * Allow Jifty->web->return( to => "..default path..", ...)
+
+  * Actually accept region names to refresh
+
+  * Halo improvements
+
+  * chromatic supplied a patch to switch from UNIVERSAL::isa to ->isa.
+
+  * Trivial webservices hack
+
+  * It's now possible to override Jifty::Record's baseclass in your
+    app
+
+
+0.60221
+
+  * Use Jifty::Util->require to log require errors
+
+  * Auto-generate Bootstrap class
+
+  * Give us a way to get the CurrentUser from a Mech object during
+    testing
+
+  * When calling a continuation, try to make sure that the urls are
+    really different, not just differently canonicalized, lest we get
+    an infinite loop
+
+  * Default to not showing debug logs
+
+  * Fragments in JS land now know about their parents, and pass their
+    superstructure in the fragment request.  This lets $region->parent
+    have full information.
+
+  * 'refresh => region' mode for replacement
+
+  * Better docs on region replacement
+
+  * add_* calls on Jifty::Request now return the object added, not the
+    request
+
+
+0.60213
+
+  * Jifty::Dispatcher written
+
+  * Jifty::Handler is now an object, not a utility. It has the power
+    to "run" a request.
+
+  * Call chain is now Handler to Dispatcher, which calls Jifty::Web
+    and Mason, instead of the other way around.
+
+  * Better Documentation coverage.
+
+  * Paint on some really bitchin 'go faster' stripes on the server.
+
+  * Refactored the dispatcher to use exceptions rather than LABELS: so
+    that Devel::DProf doesn't fall over
+
+  * Added a new "DevelMode" flag, to toggle the peformance-killing
+    development aids.
+
+  * Only drop tables if the tests all succeeded
+
+  * Fragments now go through the dispatcher
+
+  * `jifty schema` overhaul
+
+  * Mandatory form fields now have a css marker.
+
+  * Do away with setup_actions mason method
+
+  * Stop using mason notes
+
+  * Jifty::Script::Schema support for basic mysql love
+
+  * lighttpd support.
+
+  * Move autocomplete.xml and validator.xml to __jifty/.
+
+  * Upgrade to Scriptaculous 1.5.1.
+
+  * Delete is its own action now, instead of being part of Update.
+
+
+0.51228
+
+ * Jifty::Action->argument_names should sort keys lexographically, not
+   random hash-ordering.
+
+ * Remove last vestiges of ::Delete from ::Update
+
+ * Refactored Jifty::Config to allow the application class to be
+   specified when calling Config->guess
+
+ * Refactored Jifty::Script::App into bite-sized morsels
+
+ * Made Jifty::Script::App generate a config file
+
+ * Made Jifty::Script::App and Jifty::Config::Guess happy with
+   multi-level package names
+
+
+0.51225 - Initial release

Added: jifty/branches/schema-plugins/MANIFEST
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/MANIFEST	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,588 @@
+AUTHORS
+bin/build_par
+bin/jifty
+bin/runcover
+bin/service
+bin/xgettext
+Changelog
+debian/changelog
+debian/compat
+debian/control
+debian/README
+debian/rules
+doc/ajax-upgraded-links
+doc/building_a_par
+doc/client_side_continuations
+doc/command_naming
+doc/declarative-test-design
+doc/edit-in-place
+doc/examples/CounterDemo/bin/jifty
+doc/examples/CounterDemo/etc/config.yml
+doc/examples/CounterDemo/Makefile.PL
+doc/examples/CounterDemo/share/web/templates/index.html
+doc/examples/CounterDemo/t/00-counter-test.t
+doc/jifty-action-record-search
+doc/jifty-dispatcher.graffle
+doc/jifty-dispatcher.svg
+doc/jifty-plugins-2.0
+doc/jifty-web-form-etc
+doc/packaging
+doc/plugin-requirements
+doc/plugin-syntax
+doc/plugins-restated-assumptions
+doc/pubsub/backend_message_types
+doc/pubsub/subscriptions
+doc/pubsub/system_architecture.graffle
+doc/pubsub/system_architecture.png
+etc/config.yml
+etc/site_config.yml
+examples/Chat/bin/jifty
+examples/Chat/etc/config.yml
+examples/Chat/lib/Chat/Action/Send.pm
+examples/Chat/lib/Chat/Event/Message.pm
+examples/Chat/lib/Chat/Server.pm
+examples/Chat/Makefile.PL
+examples/Chat/share/web/templates/fragments/message
+examples/Chat/share/web/templates/fragments/sender
+examples/Chat/share/web/templates/index.html
+examples/Chat/t/00compile.t
+examples/Chat/t/01startup.t
+examples/Clock/bin/jifty
+examples/Clock/clockserv.pl
+examples/Clock/etc/config.yml
+examples/Clock/lib/Clock/Event/Tick.pm
+examples/Clock/lib/Clock/Server.pm
+examples/Clock/Makefile.PL
+examples/Clock/share/web/templates/fragments/time
+examples/Clock/share/web/templates/index.html
+examples/MyWeblog/bin/jifty
+examples/MyWeblog/etc/config.yml
+examples/MyWeblog/lib/MyWeblog/Model/Post.pm
+examples/MyWeblog/Makefile.PL
+examples/MyWeblog/share/web/templates/_elements/nav
+examples/MyWeblog/share/web/templates/fragments/page_of_posts
+examples/MyWeblog/share/web/templates/index.html
+examples/MyWeblog/share/web/templates/post
+examples/MyWeblog/t/00-model-Post.t
+examples/Ping/bin/jifty
+examples/Ping/etc/config.yml
+examples/Ping/lib/Ping/Action/AddPing.pm
+examples/Ping/lib/Ping/Action/CancelPing.pm
+examples/Ping/lib/Ping/Event/Pong.pm
+examples/Ping/lib/Ping/PingServer.pm
+examples/Ping/lib/Ping/Server.pm
+examples/Ping/Makefile.PL
+examples/Ping/share/web/templates/fragments/pong
+examples/Ping/share/web/templates/index.html
+examples/Ping/t/00compile.t
+examples/Ping/t/01startup.t
+inc/Module/AutoInstall.pm
+inc/Module/Install.pm
+inc/Module/Install/AutoInstall.pm
+inc/Module/Install/Base.pm
+inc/Module/Install/Can.pm
+inc/Module/Install/Fetch.pm
+inc/Module/Install/Include.pm
+inc/Module/Install/Makefile.pm
+inc/Module/Install/Metadata.pm
+inc/Module/Install/Scripts.pm
+inc/Module/Install/Share.pm
+inc/Module/Install/Win32.pm
+inc/Module/Install/WriteAll.pm
+lib/Email/Send/Jifty/Test.pm
+lib/Jifty.pm
+lib/Jifty/Action.pm
+lib/Jifty/Action/Autocomplete.pm
+lib/Jifty/Action/Record.pm
+lib/Jifty/Action/Record/Create.pm
+lib/Jifty/Action/Record/Delete.pm
+lib/Jifty/Action/Record/Search.pm
+lib/Jifty/Action/Record/Update.pm
+lib/Jifty/Action/Redirect.pm
+lib/Jifty/API.pm
+lib/Jifty/Bootstrap.pm
+lib/Jifty/ClassLoader.pm
+lib/Jifty/Client.pm
+lib/Jifty/Collection.pm
+lib/Jifty/Config.pm
+lib/Jifty/Continuation.pm
+lib/Jifty/CurrentUser.pm
+lib/Jifty/DateTime.pm
+lib/Jifty/Dispatcher.pm
+lib/Jifty/Event.pm
+lib/Jifty/Event/Model.pm
+lib/Jifty/Everything.pm
+lib/Jifty/Filter/DateTime.pm
+lib/Jifty/Handle.pm
+lib/Jifty/Handler.pm
+lib/Jifty/I18N.pm
+lib/Jifty/JSON.pm
+lib/Jifty/LetMe.pm
+lib/Jifty/Logger.pm
+lib/Jifty/Manual/AccessControl.pod
+lib/Jifty/Manual/Actions.pod
+lib/Jifty/Manual/Continuations.pod
+lib/Jifty/Manual/Cookbook.pod
+lib/Jifty/Manual/FAQ.pod
+lib/Jifty/Manual/Glossary.pod
+lib/Jifty/Manual/Models.pod
+lib/Jifty/Manual/ObjectModel.pod
+lib/Jifty/Manual/PageRegions.pod
+lib/Jifty/Manual/RequestHandling.pod
+lib/Jifty/Manual/Style.pod
+lib/Jifty/Manual/Tutorial.pod
+lib/Jifty/Manual/Tutorial_de.pod
+lib/Jifty/Manual/Tutorial_ja.pod
+lib/Jifty/Manual/Upgrading.pod
+lib/Jifty/Manual/UsingCSSandJS.pod
+lib/Jifty/Mason/Halo.pm
+lib/Jifty/Model/Metadata.pm
+lib/Jifty/Model/Session.pm
+lib/Jifty/Model/SessionCollection.pm
+lib/Jifty/Module/Pluggable.pm
+lib/Jifty/Notification.pm
+lib/Jifty/Object.pm
+lib/Jifty/Param.pm
+lib/Jifty/Param/Schema.pm
+lib/Jifty/Plugin.pm
+lib/Jifty/Plugin/ClassLoader.pm
+lib/Jifty/Plugin/REST.pm
+lib/Jifty/Plugin/REST/Dispatcher.pm
+lib/Jifty/Record.pm
+lib/Jifty/Request.pm
+lib/Jifty/Request/Mapper.pm
+lib/Jifty/Response.pm
+lib/Jifty/Result.pm
+lib/Jifty/RightsFrom.pm
+lib/Jifty/Script.pm
+lib/Jifty/Script/Action.pm
+lib/Jifty/Script/App.pm
+lib/Jifty/Script/Deps.pm
+lib/Jifty/Script/FastCGI.pm
+lib/Jifty/Script/Help.pm
+lib/Jifty/Script/Model.pm
+lib/Jifty/Script/Plugin.pm
+lib/Jifty/Script/Po.pm
+lib/Jifty/Script/Schema.pm
+lib/Jifty/Script/Server.pm
+lib/Jifty/Server.pm
+lib/Jifty/Subs.pm
+lib/Jifty/Subs/Render.pm
+lib/Jifty/Test.pm
+lib/Jifty/Test/WWW/Mechanize.pm
+lib/Jifty/TestServer.pm
+lib/Jifty/Upgrade.pm
+lib/Jifty/Upgrade/Internal.pm
+lib/Jifty/Util.pm
+lib/Jifty/View/Mason/Handler.pm
+lib/Jifty/View/Static/Handler.pm
+lib/Jifty/Web.pm
+lib/Jifty/Web/Form.pm
+lib/Jifty/Web/Form/Clickable.pm
+lib/Jifty/Web/Form/Element.pm
+lib/Jifty/Web/Form/Field.pm
+lib/Jifty/Web/Form/Field/Button.pm
+lib/Jifty/Web/Form/Field/Checkbox.pm
+lib/Jifty/Web/Form/Field/Combobox.pm
+lib/Jifty/Web/Form/Field/Date.pm
+lib/Jifty/Web/Form/Field/Hidden.pm
+lib/Jifty/Web/Form/Field/InlineButton.pm
+lib/Jifty/Web/Form/Field/Password.pm
+lib/Jifty/Web/Form/Field/Radio.pm
+lib/Jifty/Web/Form/Field/ResetButton.pm
+lib/Jifty/Web/Form/Field/Select.pm
+lib/Jifty/Web/Form/Field/Text.pm
+lib/Jifty/Web/Form/Field/Textarea.pm
+lib/Jifty/Web/Form/Field/Unrendered.pm
+lib/Jifty/Web/Form/Field/Upload.pm
+lib/Jifty/Web/Form/Link.pm
+lib/Jifty/Web/Menu.pm
+lib/Jifty/Web/PageRegion.pm
+lib/Jifty/Web/Session.pm
+lib/Jifty/Web/Session/ClientSide.pm
+lib/Jifty/YAML.pm
+Makefile.PL
+MANIFEST			This list of files
+MANIFEST.SKIP
+META.yml
+plugins/AuthCASLogin/debian/changelog
+plugins/AuthCASLogin/debian/compat
+plugins/AuthCASLogin/debian/control
+plugins/AuthCASLogin/debian/copyright
+plugins/AuthCASLogin/debian/files
+plugins/AuthCASLogin/debian/rules
+plugins/AuthCASLogin/lib/Jifty/Plugin/AuthCASLogin.pm
+plugins/AuthCASLogin/lib/Jifty/Plugin/AuthCASLogin/Action/CASLogin.pm
+plugins/AuthCASLogin/lib/Jifty/Plugin/AuthCASLogin/Action/CASLogout.pm
+plugins/AuthCASLogin/lib/Jifty/Plugin/AuthCASLogin/Dispatcher.pm
+plugins/AuthCASLogin/Makefile.PL
+plugins/AuthCASLogin/MANIFEST
+plugins/AuthCASLogin/share/web/templates/caslogin
+plugins/AuthCASLogin/share/web/templates/caslogout
+plugins/AuthCASLogin/t/00-load.t
+plugins/AuthCASOnly/debian/changelog
+plugins/AuthCASOnly/debian/compat
+plugins/AuthCASOnly/debian/control
+plugins/AuthCASOnly/debian/copyright
+plugins/AuthCASOnly/debian/files
+plugins/AuthCASOnly/debian/rules
+plugins/AuthCASOnly/lib/Jifty/Plugin/AuthCASOnly.pm
+plugins/AuthCASOnly/lib/Jifty/Plugin/AuthCASOnly/Action/CASLogin.pm
+plugins/AuthCASOnly/lib/Jifty/Plugin/AuthCASOnly/Action/CASLogout.pm
+plugins/AuthCASOnly/lib/Jifty/Plugin/AuthCASOnly/CurrentUser.pm
+plugins/AuthCASOnly/lib/Jifty/Plugin/AuthCASOnly/Dispatcher.pm
+plugins/AuthCASOnly/lib/Jifty/Plugin/AuthCASOnly/Model/CASUser.pm
+plugins/AuthCASOnly/Makefile.PL
+plugins/AuthCASOnly/MANIFEST
+plugins/AuthCASOnly/META.yml
+plugins/AuthCASOnly/share/web/templates/caslogin
+plugins/AuthCASOnly/share/web/templates/caslogout
+plugins/AuthCASOnly/t/00-load.t
+plugins/AuthLDAPLogin/debian/changelog
+plugins/AuthLDAPLogin/debian/compat
+plugins/AuthLDAPLogin/debian/control
+plugins/AuthLDAPLogin/debian/copyright
+plugins/AuthLDAPLogin/debian/rules
+plugins/AuthLDAPLogin/lib/Jifty/Plugin/AuthLDAPLogin.pm
+plugins/AuthLDAPLogin/lib/Jifty/Plugin/AuthLDAPLogin/Action/LDAPLogin.pm
+plugins/AuthLDAPLogin/lib/Jifty/Plugin/AuthLDAPLogin/Action/LDAPLogout.pm
+plugins/AuthLDAPLogin/lib/Jifty/Plugin/AuthLDAPLogin/Dispatcher.pm
+plugins/AuthLDAPLogin/Makefile.PL
+plugins/AuthLDAPLogin/MANIFEST
+plugins/AuthLDAPLogin/share/po/en.po
+plugins/AuthLDAPLogin/share/po/fr.po
+plugins/AuthLDAPLogin/share/web/templates/ldaplogin
+plugins/AuthLDAPLogin/share/web/templates/ldaplogout
+plugins/AuthLDAPLogin/t/00-load.t
+plugins/AuthLDAPOnly/debian/changelog
+plugins/AuthLDAPOnly/debian/compat
+plugins/AuthLDAPOnly/debian/control
+plugins/AuthLDAPOnly/debian/copyright
+plugins/AuthLDAPOnly/debian/rules
+plugins/AuthLDAPOnly/lib/Jifty/Plugin/AuthLDAPOnly.pm
+plugins/AuthLDAPOnly/lib/Jifty/Plugin/AuthLDAPOnly/Action/LDAPLogin.pm
+plugins/AuthLDAPOnly/lib/Jifty/Plugin/AuthLDAPOnly/Action/LDAPLogout.pm
+plugins/AuthLDAPOnly/lib/Jifty/Plugin/AuthLDAPOnly/CurrentUser.pm
+plugins/AuthLDAPOnly/lib/Jifty/Plugin/AuthLDAPOnly/Dispatcher.pm
+plugins/AuthLDAPOnly/lib/Jifty/Plugin/AuthLDAPOnly/Model/LDAPUser.pm
+plugins/AuthLDAPOnly/Makefile.PL
+plugins/AuthLDAPOnly/MANIFEST
+plugins/AuthLDAPOnly/share/po/en.po
+plugins/AuthLDAPOnly/share/po/fr.po
+plugins/AuthLDAPOnly/share/web/templates/ldaplogin
+plugins/AuthLDAPOnly/share/web/templates/ldaplogout
+plugins/AuthLDAPOnly/t/00-load.t
+plugins/AuthzLDAP/lib/Jifty/Plugin/AuthzLDAP.pm
+plugins/AuthzLDAP/lib/Jifty/Plugin/AuthzLDAP/Action/LDAPValidate.pm
+plugins/AuthzLDAP/lib/Jifty/Plugin/AuthzLDAP/Model/LDAPFilter.pm
+plugins/AuthzLDAP/Makefile.PL
+plugins/AuthzLDAP/MANIFEST
+plugins/AuthzLDAP/share/po/en.po
+plugins/AuthzLDAP/share/po/fr.po
+plugins/AuthzLDAP/share/web/templates/error/AccessDenied
+plugins/AuthzLDAP/t/00-load.t
+plugins/EditInPlace/debian/changelog
+plugins/EditInPlace/debian/compat
+plugins/EditInPlace/debian/control
+plugins/EditInPlace/debian/rules
+plugins/EditInPlace/inc/Module/AutoInstall.pm
+plugins/EditInPlace/inc/Module/Install.pm
+plugins/EditInPlace/inc/Module/Install/AutoInstall.pm
+plugins/EditInPlace/inc/Module/Install/Base.pm
+plugins/EditInPlace/inc/Module/Install/Can.pm
+plugins/EditInPlace/inc/Module/Install/Fetch.pm
+plugins/EditInPlace/inc/Module/Install/Include.pm
+plugins/EditInPlace/inc/Module/Install/Makefile.pm
+plugins/EditInPlace/inc/Module/Install/Makefile/Version.pm
+plugins/EditInPlace/inc/Module/Install/Metadata.pm
+plugins/EditInPlace/inc/Module/Install/Share.pm
+plugins/EditInPlace/inc/Module/Install/Win32.pm
+plugins/EditInPlace/inc/Module/Install/WriteAll.pm
+plugins/EditInPlace/lib/Jifty/Plugin/EditInPlace.pm
+plugins/EditInPlace/lib/Jifty/Plugin/EditInPlace/Action/FileEditor.pm
+plugins/EditInPlace/lib/Jifty/Plugin/EditInPlace/Dispatcher.pm
+plugins/EditInPlace/Makefile.PL
+plugins/EditInPlace/share/web/templates/__jifty/create_file_inline
+plugins/EditInPlace/share/web/templates/__jifty/edit_file
+plugins/EditInPlace/share/web/templates/__jifty/edit_file_inline
+plugins/EmailErrors/doc/site_config.yml
+plugins/EmailErrors/lib/Jifty/Plugin/EmailErrors.pm
+plugins/EmailErrors/lib/Jifty/Plugin/EmailErrors/Dispatcher.pm
+plugins/EmailErrors/lib/Jifty/Plugin/EmailErrors/Notification/EmailError.pm
+plugins/EmailErrors/Makefile.PL
+plugins/EmailErrors/share/web/templates/.file
+plugins/LetMe/lib/Jifty/Plugin/LetMe.pm
+plugins/LetMe/lib/Jifty/Plugin/LetMe/Dispatcher.pm
+plugins/LetMe/Makefile.PL
+plugins/Login/debian/changelog
+plugins/Login/debian/compat
+plugins/Login/debian/control
+plugins/Login/debian/rules
+plugins/Login/inc/Module/Install.pm
+plugins/Login/inc/Module/Install/Base.pm
+plugins/Login/inc/Module/Install/Can.pm
+plugins/Login/inc/Module/Install/Fetch.pm
+plugins/Login/inc/Module/Install/Makefile.pm
+plugins/Login/inc/Module/Install/Metadata.pm
+plugins/Login/inc/Module/Install/Share.pm
+plugins/Login/inc/Module/Install/Win32.pm
+plugins/Login/inc/Module/Install/WriteAll.pm
+plugins/Login/lib/Jifty/Plugin/Login.pm
+plugins/Login/lib/Jifty/Plugin/Login/Action/ChangePassword.pm
+plugins/Login/lib/Jifty/Plugin/Login/Action/ConfirmEmail.pm
+plugins/Login/lib/Jifty/Plugin/Login/Action/Login.pm
+plugins/Login/lib/Jifty/Plugin/Login/Action/Logout.pm
+plugins/Login/lib/Jifty/Plugin/Login/Action/RecoverPassword.pm
+plugins/Login/lib/Jifty/Plugin/Login/Action/ResetLostPassword.pm
+plugins/Login/lib/Jifty/Plugin/Login/Action/SendAccountConfirmation.pm
+plugins/Login/lib/Jifty/Plugin/Login/Action/SendPasswordReminder.pm
+plugins/Login/lib/Jifty/Plugin/Login/Action/Signup.pm
+plugins/Login/lib/Jifty/Plugin/Login/CurrentUser.pm
+plugins/Login/lib/Jifty/Plugin/Login/Dispatcher.pm
+plugins/Login/lib/Jifty/Plugin/Login/Model/User.pm
+plugins/Login/lib/Jifty/Plugin/Login/Notification/ConfirmAddress.pm
+plugins/Login/lib/Jifty/Plugin/Login/Notification/ConfirmLostPassword.pm
+plugins/Login/Makefile.PL
+plugins/Login/MANIFEST
+plugins/Login/META.yml
+plugins/Login/share/po/en.po
+plugins/Login/share/po/fr.po
+plugins/Login/share/web/templates/chgpasswd
+plugins/Login/share/web/templates/let/confirm_email
+plugins/Login/share/web/templates/let/reset_lost_password
+plugins/Login/share/web/templates/login
+plugins/Login/share/web/templates/passwordreminder
+plugins/Login/share/web/templates/signup
+plugins/Nothing/lib/Jifty/Plugin/Nothing.pm
+plugins/Nothing/lib/Jifty/Plugin/Nothing/Dispatcher.pm
+plugins/Nothing/Makefile.PL
+plugins/ProfileBehaviour/lib/Jifty/Plugin/ProfileBehaviour.pm
+plugins/ProfileBehaviour/lib/Jifty/Plugin/ProfileBehaviour/Dispatcher.pm
+plugins/ProfileBehaviour/Makefile.PL
+plugins/ProfileBehaviour/share/web/static/css/behaviour-profile.css
+plugins/ProfileBehaviour/share/web/static/js/behaviour.js
+README
+share/dtd/xhtml-lat1.ent
+share/dtd/xhtml-special.ent
+share/dtd/xhtml-symbol.ent
+share/dtd/xhtml1-strict.dtd
+share/po/en.po
+share/po/fr.po
+share/po/ja.po
+share/po/zh_cn.po
+share/po/zh_tw.po
+share/web/static/css/app-base.css
+share/web/static/css/app.css
+share/web/static/css/autocomplete.css
+share/web/static/css/autohandler
+share/web/static/css/base.css
+share/web/static/css/calendar.css
+share/web/static/css/combobox.css
+share/web/static/css/context-menus.css
+share/web/static/css/forms.css
+share/web/static/css/halos.css
+share/web/static/css/keybindings.css
+share/web/static/css/main.css
+share/web/static/css/nav.css
+share/web/static/css/notices.css
+share/web/static/css/yui/calendar/calendar.css
+share/web/static/css/yui/tabview/border_tabs.css
+share/web/static/css/yui/tabview/tabs.css
+share/web/static/favicon.ico
+share/web/static/images/css/bullet_arrow_down.png
+share/web/static/images/css/bullet_arrow_up.png
+share/web/static/images/css/fieldbg-autocomplete.gif
+share/web/static/images/css/fieldbg.gif
+share/web/static/images/pony.jpg
+share/web/static/images/silk/bullet_arrow_down.png
+share/web/static/images/silk/bullet_arrow_up.png
+share/web/static/images/silk/calendar.png
+share/web/static/images/silk/cancel.png
+share/web/static/images/silk/cancel_grey.png
+share/web/static/images/silk/error.png
+share/web/static/images/silk/information.png
+share/web/static/images/silk/pencil.png
+share/web/static/images/silk/pencil_add.png
+share/web/static/images/yui/us/my/bn/x_d.gif
+share/web/static/images/yui/us/tr/callt.gif
+share/web/static/images/yui/us/tr/calrt.gif
+share/web/static/js/app.js
+share/web/static/js/app_behaviour.js
+share/web/static/js/behaviour.js
+share/web/static/js/bps_util.js
+share/web/static/js/calendar.js
+share/web/static/js/combobox.js
+share/web/static/js/context_menu.js
+share/web/static/js/css_browser_selector.js
+share/web/static/js/cssquery/cssQuery-level2.js
+share/web/static/js/cssquery/cssQuery-level3.js
+share/web/static/js/cssquery/cssQuery-standard.js
+share/web/static/js/cssquery/cssQuery.js
+share/web/static/js/dom-drag.js
+share/web/static/js/formatDate.js
+share/web/static/js/halo.js
+share/web/static/js/jifty.js
+share/web/static/js/jifty_smoothscroll.js
+share/web/static/js/jifty_subs.js
+share/web/static/js/jifty_utils.js
+share/web/static/js/jsan/DOM/Events.js
+share/web/static/js/jsan/JSAN.js
+share/web/static/js/jsan/Push.js
+share/web/static/js/jsan/Upgrade.js
+share/web/static/js/jsan/Upgrade/Array/push.js
+share/web/static/js/jsan/Upgrade/Function/apply.js
+share/web/static/js/json.js
+share/web/static/js/jsTrace.js
+share/web/static/js/key_bindings.js
+share/web/static/js/prototype.js
+share/web/static/js/rico.js
+share/web/static/js/scriptaculous/builder.js
+share/web/static/js/scriptaculous/controls.js
+share/web/static/js/scriptaculous/dragdrop.js
+share/web/static/js/scriptaculous/effects.js
+share/web/static/js/scriptaculous/scriptaculous.js
+share/web/static/js/scriptaculous/slider.js
+share/web/static/js/scriptaculous/unittest.js
+share/web/static/js/setup_jsan.js
+share/web/static/js/yui/calendar.js
+share/web/static/js/yui/container.js
+share/web/static/js/yui/dom.js
+share/web/static/js/yui/event.js
+share/web/static/js/yui/tabview.js
+share/web/static/js/yui/yahoo.js
+share/web/templates/=/subs
+share/web/templates/__jifty/admin/_elements/nav
+share/web/templates/__jifty/admin/action/dhandler
+share/web/templates/__jifty/admin/autohandler
+share/web/templates/__jifty/admin/fragments/list/header
+share/web/templates/__jifty/admin/fragments/list/list
+share/web/templates/__jifty/admin/fragments/list/new_item
+share/web/templates/__jifty/admin/fragments/list/search
+share/web/templates/__jifty/admin/fragments/list/update
+share/web/templates/__jifty/admin/fragments/list/view
+share/web/templates/__jifty/admin/index.html
+share/web/templates/__jifty/admin/model/dhandler
+share/web/templates/__jifty/autocomplete.xml
+share/web/templates/__jifty/css/dhandler
+share/web/templates/__jifty/empty
+share/web/templates/__jifty/error/_elements/error_text
+share/web/templates/__jifty/error/_elements/wrapper
+share/web/templates/__jifty/error/autohandler
+share/web/templates/__jifty/error/dhandler
+share/web/templates/__jifty/error/error.css
+share/web/templates/__jifty/error/mason_internal_error
+share/web/templates/__jifty/halo
+share/web/templates/__jifty/js/dhandler
+share/web/templates/__jifty/online_docs/autohandler
+share/web/templates/__jifty/online_docs/content.html
+share/web/templates/__jifty/online_docs/index.html
+share/web/templates/__jifty/online_docs/toc.html
+share/web/templates/__jifty/validator.xml
+share/web/templates/__jifty/webservices/json
+share/web/templates/__jifty/webservices/xml
+share/web/templates/__jifty/webservices/yaml
+share/web/templates/_elements/header
+share/web/templates/_elements/keybindings
+share/web/templates/_elements/menu
+share/web/templates/_elements/nav
+share/web/templates/_elements/page_nav
+share/web/templates/_elements/sidebar
+share/web/templates/_elements/wrapper
+share/web/templates/autohandler
+share/web/templates/dhandler
+share/web/templates/helpers/calendar.html
+share/web/templates/index.html
+SIGNATURE
+t/00-load.t
+t/01-dependencies.t
+t/01-test-web.t
+t/01-version_checks.t
+t/02-connect.t
+t/03-form-protocol.t
+t/03-is_passing-no_plan.t
+t/03-is_passing.t
+t/03-test-mailbox.t
+t/04-test_file.t
+t/05-dispatcher.t
+t/06-forms.t
+t/07-limit-actions.t
+t/08-client.t
+t/09-url.t
+t/10-i18n.t
+t/11-config-files.t
+t/12-param-schema.t
+t/99-pod-coverage.t
+t/99-pod.t
+t/Continuations/bin/jifty
+t/Continuations/lib/Continuations/Action/CrossBridge.pm
+t/Continuations/lib/Continuations/Action/GetGrail.pm
+t/Continuations/lib/Continuations/Dispatcher.pm
+t/Continuations/share/web/templates/autohandler
+t/Continuations/share/web/templates/black-knight-color.html
+t/Continuations/share/web/templates/black-knight-name.html
+t/Continuations/share/web/templates/black-knight-quest.html
+t/Continuations/share/web/templates/black-knight.html
+t/Continuations/share/web/templates/help-help.html
+t/Continuations/share/web/templates/index-help.html
+t/Continuations/share/web/templates/index.html
+t/Continuations/t/00-prototype.t
+t/Continuations/t/01-raw-api.t
+t/Continuations/t/02-api.t
+t/Continuations/t/03-gc.t
+t/Continuations/t/04-before-blocks.t
+t/DateTime.t
+t/Jifty.pm
+t/lib/Jifty/SubTest.pm
+t/Mapper/bin/jifty
+t/Mapper/lib/Mapper/Action/CrossBridge.pm
+t/Mapper/lib/Mapper/Action/GetGrail.pm
+t/Mapper/share/web/templates/autohandler
+t/Mapper/share/web/templates/index.html
+t/Mapper/t/00-prototype.t
+t/Mapper/t/01-raw-api.t
+t/Mapper/t/02-api.t
+t/TestApp-Plugin-REST/bin/jifty
+t/TestApp-Plugin-REST/etc/config.yml
+t/TestApp-Plugin-REST/lib/TestApp/Plugin/REST/Action/DoSomething.pm
+t/TestApp-Plugin-REST/lib/TestApp/Plugin/REST/Dispatcher.pm
+t/TestApp-Plugin-REST/lib/TestApp/Plugin/REST/Model/Group.pm
+t/TestApp-Plugin-REST/lib/TestApp/Plugin/REST/Model/User.pm
+t/TestApp-Plugin-REST/t/00-model-User.t
+t/TestApp-Plugin-REST/t/00-prototype.t
+t/TestApp-Plugin-REST/t/01-config.t
+t/TestApp-Plugin-REST/t/02-basic-use.t
+t/TestApp/bin/jifty
+t/TestApp/lib/TestApp/Action/DoSomething.pm
+t/TestApp/lib/TestApp/Action/DoSomethingElse.pm
+t/TestApp/lib/TestApp/CurrentUser.pm
+t/TestApp/lib/TestApp/Dispatcher.pm
+t/TestApp/lib/TestApp/Model/User.pm
+t/TestApp/share/web/static/images/pony.jpg
+t/TestApp/share/web/templates/currentuser
+t/TestApp/share/web/templates/dispatch/basic
+t/TestApp/share/web/templates/dispatch/basic-show
+t/TestApp/share/web/templates/dosomethingelse
+t/TestApp/share/web/templates/editform
+t/TestApp/share/web/templates/index.html
+t/TestApp/share/web/templates/manual_redirect
+t/TestApp/share/web/templates/somedir/dhandler
+t/TestApp/t/00-model-User.t
+t/TestApp/t/00-prototype.t
+t/TestApp/t/01-config.t
+t/TestApp/t/02-dispatch.t
+t/TestApp/t/03-static.t
+t/TestApp/t/04-sessions.t
+t/TestApp/t/05-actions-before-redirect.pm
+t/TestApp/t/05-editactions-Cachable.t
+t/TestApp/t/05-editactions-Record.t
+t/TestApp/t/06-validation.t
+t/TestApp/t/07-sandboxing.t
+t/TestApp/t/08-notifications.t
+t/TestApp/t/09-redirect.t
+t/TestApp/t/10-compress.t
+t/TestApp/t/11-current_user.t
+t/TestApp/t/12-search.t
+t/TestApp/t/config-Cachable
+t/TestApp/t/config-Record
+t/TestApp/t/instance_id.t
+t/TestApp/t/regex_meta_in_path_info.t

Added: jifty/branches/schema-plugins/MANIFEST.SKIP
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/MANIFEST.SKIP	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,31 @@
+\.ppd$
+\bCVS\b
+\b.svn\b
+~$
+t/lib/File/
+t/Big-Dummy
+t/Problem-Module
+.gz$
+.bak$
+Makefile$
+MANIFEST.perl$
+\.old$
+merge_bleadperl$
+^blib/
+^pm_to_blib
+.DS_Store
+\#
+^bleadperl\.patch$
+.*.swp
+var/*
+t/*/var/*
+doc/talks/*
+^\.
+/\._
+t/mailbox$
+jiftyapptest$
+\.orig$
+\.rej$
+\.tmp$
+\.diff$
+blib

Added: jifty/branches/schema-plugins/META.yml
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/META.yml	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,103 @@
+build_requires: 
+  ExtUtils::MakeMaker: 6.11
+distribution_type: module
+generated_by: Module::Install version 0.640
+license: Perl
+name: Jifty
+no_index: 
+  directory: 
+    - share
+    - t
+    - doc
+    - inc
+    - t
+  package: 
+    - DB
+    - inc
+recommends: 
+  Class::Accessor::Named: 0
+  DBD::SQLite: 0
+  Devel::Cover: 0
+  Module::CoreList: 0
+  Module::Install::Admin: 0.50
+  Module::Refresh: 0.09
+  PAR::Dist::FromCPAN: 0
+  Test::Base: 0.44
+  Test::HTML::Lint: 0
+  Test::HTTP::Server::Simple: 0.02
+  Test::MockModule: 0.05
+  Test::MockObject: 1.07
+  Test::More: 0.62
+  Test::Pod::Coverage: 0
+  Test::WWW::Mechanize: 1.04
+  WWW::Mechanize: 1.12
+requires: 
+  App::CLI: 0.03
+  CGI: 3.19
+  CGI::Cookie::Splitter: 0
+  CSS::Squish: 0.05
+  Cache::Cache: 0
+  Calendar::Simple: 0
+  Class::Accessor: 0
+  Class::Container: 0
+  Class::Data::Inheritable: 0
+  Compress::Zlib: 0
+  Crypt::CBC: 0
+  Crypt::Rijndael: 0
+  DBD::SQLite: 1.11
+  Data::Page: 0
+  Data::UUID: 0
+  Date::Manip: 0
+  DateTime: 0
+  Email::Folder: 0
+  Email::LocalDelivery: 0
+  Email::MIME: 0
+  Email::MIME::ContentType: 0
+  Email::MIME::Creator: 0
+  Email::Send: 1.99_01
+  Email::Simple: 0
+  Email::Simple::Creator: 0
+  Exporter::Lite: 0
+  File::Find::Rule: 0
+  File::MMagic: 0
+  File::ShareDir: 0.04
+  HTML::Entities: 0
+  HTML::Lint: 0
+  HTML::Mason: 1.3101
+  HTML::Mason::Plugin: 0
+  HTTP::Cookies: 0
+  HTTP::Date: 0
+  HTTP::Server::Simple: 0.26
+  HTTP::Server::Simple::Recorder: 0
+  Hash::Merge: 0
+  Hook::LexWrap: 0
+  IPC::PubSub: 0.22
+  JSON::Syck: 0.15
+  Jifty::DBI: 0.31
+  LWP::UserAgent: 0
+  Locale::Maketext::Extract: 0.20
+  Locale::Maketext::Lexicon: 0.60
+  Log::Log4perl: 0
+  MIME::Types: 0
+  Module::CoreList: 0
+  Module::Pluggable: 3.1
+  Module::Refresh: 0
+  Module::ScanDeps: 0
+  Object::Declare: 0.13
+  Params::Validate: 0
+  Pod::Simple: 0
+  SQL::ReservedWords: 0
+  Scalar::Defer: 0.06
+  Shell::Command: 0
+  String::Koremutake: 0
+  Test::Base: 0
+  UNIVERSAL::require: 0
+  URI: 0
+  XML::Simple: 0
+  XML::Writer: 0.601
+  XML::XPath: 0
+  YAML::Syck: 0.71
+  perl: 5.8.3
+  version: 0
+tests: t/*.t t/*/t/*.t
+version: 0.70117

Added: jifty/branches/schema-plugins/Makefile.PL
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/Makefile.PL	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,133 @@
+use inc::Module::Install 0.46;
+name('Jifty');
+license('Perl');
+requires(perl => '5.8.3');
+requires('App::CLI' => 0.03 ); # App::CLI::Command::Help App::CLI::Command
+requires('Cache::Cache'); #Cache::FileCache
+requires('Calendar::Simple');
+requires('Class::Accessor'); # Class::Accessor::Fast
+requires('Class::Container');
+requires('Class::Data::Inheritable');
+requires('CGI' => '3.19');
+requires('CGI::Cookie::Splitter');
+requires('Crypt::CBC');
+requires('Crypt::Rijndael');
+requires('Compress::Zlib');
+requires('CSS::Squish' => 0.05 );
+requires('DBD::SQLite' => 1.11 );
+requires('Data::Page');
+requires('Data::UUID');
+requires('DateTime');
+requires('Date::Manip');
+requires('Email::Folder');
+requires('Email::LocalDelivery');
+requires('Email::MIME');
+requires('Email::MIME::Creator');
+requires('Email::MIME::ContentType');
+requires('Email::Send' => '1.99_01'); # Email::Send::Jifty::Test
+requires('Email::Simple');
+requires('Email::Simple::Creator');
+requires('Exporter::Lite');
+requires('File::Find::Rule');
+requires('File::MMagic');
+requires('File::ShareDir' => '0.04');
+requires('HTML::Entities');
+requires('HTML::Lint');
+requires('HTML::Mason' => 1.3101);           # HTML::Mason::Exceptions HTML::Mason::FakeApache HTML::Mason::MethodMaker HTML::Mason::Request HTML::Mason::Utils
+requires('HTML::Mason::Plugin');
+requires('HTTP::Cookies');
+requires('HTTP::Date');
+requires('HTTP::Server::Simple' => '0.26');  # HTTP::Server::Simple::CGI
+requires('HTTP::Server::Simple::Recorder');
+requires('Hash::Merge');
+requires('Hook::LexWrap');
+requires('IPC::PubSub' => '0.22' );
+requires('Jifty::DBI' => '0.31' );            # Jifty::DBI::Collection Jifty::DBI::Handle Jifty::DBI::Record::Cachable Jifty::DBI::SchemaGenerator
+requires('Locale::Maketext::Extract' => '0.20');
+requires('Locale::Maketext::Lexicon' => '0.60');
+requires('Log::Log4perl');
+requires('LWP::UserAgent'); # Net::HTTP
+requires('MIME::Types');
+requires('Module::Pluggable' => '3.1'); # Module::Pluggable::Object
+requires('Module::Pluggable::Object');
+requires('Module::CoreList');
+requires('Module::Refresh');
+requires('Module::ScanDeps');
+requires('Object::Declare' => '0.13');
+requires('Params::Validate');
+requires('Scalar::Defer' => '0.06');
+requires('Shell::Command');
+requires('String::Koremutake');
+requires('SQL::ReservedWords');
+requires('Test::Base');
+requires('UNIVERSAL::require');
+requires('URI');
+requires('XML::Writer' => '0.601');
+requires('XML::Simple');
+requires('XML::XPath');
+requires('version');
+
+if (can_cc()) {
+    # Always require the Syck bindings if a C compiler is available
+    requires('YAML::Syck' => 0.71);
+    requires('JSON::Syck' => 0.15);
+}
+else {
+    requires('YAML' => 0.35) unless can_use('YAML::Syck' => 0.71);
+    requires('JSON' => 0.01) unless can_use('JSON::Syck' => 0.15);
+}
+
+features(
+    'Administrative Interface (web)' => [ 
+        -default => 1,
+       requires( 'Pod::Simple') # Pod::Simple::Text Pod::Simple::HTML
+   ],
+    'Development of a jifty application' => [
+        -default => 1,
+        recommends('DBD::SQLite' => 1.11 ),
+        recommends('Test::Base' => 0.44 ),            # Test::Base::Filter
+        recommends('Module::Install::Admin' => '0.50'),
+        ($^O ne 'MSWin32' ? recommends('Test::HTTP::Server::Simple' => '0.02' ) : ()),
+        recommends('Test::HTML::Lint'),
+        recommends('Test::More' => 0.62 ),
+        recommends('Test::MockModule' => '0.05'),
+        recommends('Test::MockObject' => '1.07'),
+        recommends('Test::Pod::Coverage'),
+        recommends('Test::WWW::Mechanize' => 1.04 ),
+        recommends('WWW::Mechanize' => 1.12 ),
+        recommends('Module::Refresh' => '0.09')
+    ],
+    'Development of the jifty framework' => [
+        -default => 0,
+        recommends('DBD::SQLite'),
+        recommends('Class::Accessor::Named'),
+        recommends('Devel::Cover'),
+        recommends('Module::CoreList'),
+        recommends('Module::Install::Admin' => '0.50')
+     ],
+    'Experimental features' => [
+        -default => 0,
+        recommends('PAR::Dist::FromCPAN'),
+    ],
+);
+
+
+no_index directory => 'share';
+no_index directory => 't';
+no_index package => 'DB';
+no_index package => 'inc';
+no_index directory => 'doc';
+
+version_from('lib/Jifty.pm');
+#&auto_bundle_deps();
+&auto_install();
+#&auto_include_dependent_dists();
+
+install_script('bin/jifty');
+install_share;
+
+# Test all of our sub-dist tests too
+tests('t/*.t t/*/t/*.t');
+
+WriteAll;
+

Added: jifty/branches/schema-plugins/README
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/README	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,79 @@
+NAME
+    Jifty -- Just Do It
+
+DESCRIPTION
+    Yet another web framework.
+
+  What's cool about Jifty? (Buzzwords)
+    DRY (Don't Repeat Yourself)
+        Jifty tries not to make you say things more than once.
+
+    Full-stack
+        Out of the proverbial box, Jifty comes with one way to do everything
+        you should need to do: One database mapper, one templating system,
+        one web services layer, one AJAX toolkit, one set of handlers for
+        standalone or FastCGI servers. We work hard to make all the bits
+        play well together, so you don't have to.
+
+    Continuations
+        With Jifty, it's easy to let the user go off and do something else,
+        like fill out a wizard, look something up in the help system or go
+        twiddle their preferences and come right back to where they were.
+
+    Form-based dispatch
+        This is one of the things that Jifty does that we've not seen
+        anywhere else. Jifty owns your form rendering and processing. This
+        means you never need to write form handling logic. All you say is "I
+        want an input for this argument here" and Jifty takes care of the
+        rest. (Even autocomplete and validation)
+
+    A Pony
+        Jifty is the only web application framework that comes with a pony.
+
+  Introduction
+    If this is your first time using Jifty, Jifty::Manual::Tutorial is
+    probably a better place to start.
+
+METHODS
+  new PARAMHASH
+    This class method instantiates a new "Jifty" object. This object deals
+    with configuration files, logging and database handles for the system.
+    Most of the time, the server will call this for you to set up your
+    "Jifty" object. If you are writing command-line programs htat want to
+    use your libraries (as opposed to web services) you will need to call
+    this yourself.
+
+    See Jifty::Config for details on how to configure your Jifty
+    application.
+
+   Arguments
+    no_handle
+        If this is set to true, Jifty will not create a Jifty::Handle and
+        connect to a database. Only use this if you're about to drop the
+        database or do something extreme like that; most of Jifty expects
+        the handle to exist. Defaults to false.
+
+  config
+    An accessor for the Jifty::Config object that stores the configuration
+    for the Jifty application.
+
+  logger
+    An accessor for our Jifty::Logger object for the application.
+
+  handle
+    An accessor for the Jifty::Handle object that stores the database handle
+    for the application.
+
+  web
+    An accessor for the Jifty::Web object that the web interface uses.
+
+LICENSE
+    Jifty is Copyright 2005 Best Practical Solutions, LLC. Jifty is
+    distributed under the same terms as Perl itself.
+
+SEE ALSO
+    <http://jifty.org>
+
+AUTHORS
+    Jesse Vincent, Alex Vandiver and David Glasser.
+

Added: jifty/branches/schema-plugins/SIGNATURE
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/SIGNATURE	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,610 @@
+This file contains message digests of all files listed in MANIFEST,
+signed via the Module::Signature module, version 0.41.
+
+To verify the content in this distribution, first make sure you have
+Module::Signature installed, then type:
+
+    % cpansign -v
+
+It would check each file's integrity, as well as the signature's
+validity.  If "==> Signature verified OK! <==" is not displayed,
+the distribution may already have been compromised, and you should
+not run its Makefile.PL or Build.PL.
+
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA1
+
+SHA1 ef933bd94b1e449d781c19e74a92412f08e602c5 AUTHORS
+SHA1 fc571a41cf0774f0099bd365a6e21607a2da0151 Changelog
+SHA1 8c31edaf5f07a1bae38d787bbdf2d79bb17d6f7c MANIFEST
+SHA1 d4adbf5948041cd460da5cb7ad21394a790e2022 MANIFEST.SKIP
+SHA1 f7177c83690e1bac7930f31b96c991afb6adbc8b META.yml
+SHA1 767b1c8e6573516120842aabb2033fddab178d39 Makefile.PL
+SHA1 e395a2eabaf8faf8266dedc664c1eb52c6c589cf README
+SHA1 aaf8f7a1025fc97077072672f325e2a5f3c03a41 bin/build_par
+SHA1 f7f44f9a7337def0c97f981073e3ed970851d9ae bin/jifty
+SHA1 bc5d0dc181bffe0694e5282c2d2336eaec5a06ff bin/runcover
+SHA1 9a91a81e3db1a12368153fed9e504aad492cd971 bin/service
+SHA1 543a2677f66d3c8ca671b790509b6c1721ac6270 bin/xgettext
+SHA1 1c042485ba8a21f0f124dd8ed412d43d3805430e debian/README
+SHA1 43b4786695f70ebd8ee74aac9e7eb270b4641b63 debian/changelog
+SHA1 5d9474c0309b7ca09a182d888f73b37a8fe1362c debian/compat
+SHA1 73f845d1185d0d977dd5bea3f046a0ae53451363 debian/control
+SHA1 8fc130ffa6d53c47d94eab1616887c511d54d61f debian/rules
+SHA1 b8bb315ef8fbdd2f069b6339ad0461b5d933d7da doc/ajax-upgraded-links
+SHA1 a0d03921821ca39ea461f1f9fa812fdbb5f501a0 doc/building_a_par
+SHA1 20e10cd0ef04ed61f2c6975caaf70f6cdfd0f657 doc/client_side_continuations
+SHA1 a78a22aa4be910be14347278720e196137819d55 doc/command_naming
+SHA1 9c56b756b1383ad76b48c2149f64c323f35d5050 doc/declarative-test-design
+SHA1 69cafeee7cc90aabb0ae8cf1e9c695794a54c9a6 doc/edit-in-place
+SHA1 fa5b3f272228531a255c7c676d798e3162c68ce0 doc/examples/CounterDemo/Makefile.PL
+SHA1 af23c4b1688a4ad7167173083381cea744d57691 doc/examples/CounterDemo/bin/jifty
+SHA1 d1b66f68f17cc67957c534e1e493c5a0f5b22556 doc/examples/CounterDemo/etc/config.yml
+SHA1 61b3c250cbb86dd5f1e5f42e82020e3e3befaff1 doc/examples/CounterDemo/share/web/templates/index.html
+SHA1 514c712a4e0bd5a16b71c35ab9e607a7db865a67 doc/examples/CounterDemo/t/00-counter-test.t
+SHA1 0eab037e835b3d998df54883377b58802cb78f7a doc/jifty-action-record-search
+SHA1 4f01bbe985a1ad3742b48563992bdbf032a6ce4d doc/jifty-dispatcher.graffle
+SHA1 b07f1666f9da714a9007058ea74d0d4cfa31ebfe doc/jifty-dispatcher.svg
+SHA1 29514abbc940365a861d196d8b1f35ee34f91526 doc/jifty-plugins-2.0
+SHA1 94a787c12228136c7797f81fef39e9bb52786067 doc/jifty-web-form-etc
+SHA1 44839f806443cbb6469f3610f9a6c488d1777f60 doc/packaging
+SHA1 3ec038720e82a1297a3a5db6a0e1b24fe0aa13c2 doc/plugin-requirements
+SHA1 3de2d7f062e78fc8b543f6b93e79852274c6c657 doc/plugin-syntax
+SHA1 c83d2b6f9d5638d1e2abdb56bf7dd4bb549baa65 doc/plugins-restated-assumptions
+SHA1 8616376196cac3c0a3f2edfe2b5a64c2c7799b96 doc/pubsub/backend_message_types
+SHA1 0c128fd9b43899c4445f10d274d0426663bbb4b5 doc/pubsub/subscriptions
+SHA1 7fd8530361a16c75b66a0f2e5cd4f6b4f269a6f0 doc/pubsub/system_architecture.graffle
+SHA1 59e165b4f4b81b5a8ce637ccc7a7d0fb0866f59c doc/pubsub/system_architecture.png
+SHA1 ec93d8fb7af302ae550d122564812e85d7308b22 etc/config.yml
+SHA1 9db8566a4bacf0f3be943b9b3a84c45439c45337 etc/site_config.yml
+SHA1 3d3cce325969be4618e89c976aa9f950618ff39a examples/Chat/Makefile.PL
+SHA1 a7dc1f376cac630ea28d2965e561469deb951cc7 examples/Chat/bin/jifty
+SHA1 418a81711645390f06a417a0e4328901c9c92dc4 examples/Chat/etc/config.yml
+SHA1 08043c294b69d1a2e23940346034283e6f686e14 examples/Chat/lib/Chat/Action/Send.pm
+SHA1 63f552215f7ed2d75278509fab4324dddda90591 examples/Chat/lib/Chat/Event/Message.pm
+SHA1 a8c6b5a40ec985c240db0cfc56922d534b9dd60d examples/Chat/lib/Chat/Server.pm
+SHA1 f6f5a7db0d950b19cb3a0e2f0776ac4fc0186082 examples/Chat/share/web/templates/fragments/message
+SHA1 ceb9ecbd5ba19436ff3cfe423e688bcc0d4b1adc examples/Chat/share/web/templates/fragments/sender
+SHA1 047c066b5f143e1a80056fa158e172448ee912e5 examples/Chat/share/web/templates/index.html
+SHA1 cea50b3afbb46ef7ac3f06c38650f98836799668 examples/Chat/t/00compile.t
+SHA1 3ea590f5bdf90c3b5ec31e0d59345801e34a61b0 examples/Chat/t/01startup.t
+SHA1 3d3cce325969be4618e89c976aa9f950618ff39a examples/Clock/Makefile.PL
+SHA1 a7dc1f376cac630ea28d2965e561469deb951cc7 examples/Clock/bin/jifty
+SHA1 faca2e649d4d9b4b00f5f6bf8932a7610c698415 examples/Clock/clockserv.pl
+SHA1 793ef4d540d2df071daad4ad73514c75d8e9aa51 examples/Clock/etc/config.yml
+SHA1 72feb5e40abfac4e87793b427f9de45c03cfaca4 examples/Clock/lib/Clock/Event/Tick.pm
+SHA1 c2e597a83448804e04989db290404766180033ab examples/Clock/lib/Clock/Server.pm
+SHA1 e70cea658fbef26cdeb5558b89dd9c6890fbd9db examples/Clock/share/web/templates/fragments/time
+SHA1 bddc300d6e81f578c85a8807156822d682c61efe examples/Clock/share/web/templates/index.html
+SHA1 4db882e66864fa40a6532ce5b83699839804b062 examples/MyWeblog/Makefile.PL
+SHA1 a7dc1f376cac630ea28d2965e561469deb951cc7 examples/MyWeblog/bin/jifty
+SHA1 d1394cef410a14394a05e2a6e6a9b32c059cb5d6 examples/MyWeblog/etc/config.yml
+SHA1 df2d54b2520dc8bb7aa1c16d7b5e6e7efcb2de1e examples/MyWeblog/lib/MyWeblog/Model/Post.pm
+SHA1 31c9e2fb12f92ea7a1444ebde93680bcdd441d65 examples/MyWeblog/share/web/templates/_elements/nav
+SHA1 3d2b9750da1277747e5f7a67c587f950245ec12c examples/MyWeblog/share/web/templates/fragments/page_of_posts
+SHA1 e22357ab6d96f44152e83060e34e954f497b413a examples/MyWeblog/share/web/templates/index.html
+SHA1 dc34445c1c7bab11cc129cf10597469f179002ff examples/MyWeblog/share/web/templates/post
+SHA1 242c000d466ce6782ac759b7baea86c3b4c902bd examples/MyWeblog/t/00-model-Post.t
+SHA1 37ff983ec74bee20ce6a6425e2cbd2effa55e586 examples/Ping/Makefile.PL
+SHA1 a7dc1f376cac630ea28d2965e561469deb951cc7 examples/Ping/bin/jifty
+SHA1 2630087e4914b7d8a7fa8bea82e778fab51735dd examples/Ping/etc/config.yml
+SHA1 9b143bab7499c4ebde34e88e635a59bd205a1bf0 examples/Ping/lib/Ping/Action/AddPing.pm
+SHA1 fc630086e84277cf19a9e9eb382c79e41d0dd810 examples/Ping/lib/Ping/Action/CancelPing.pm
+SHA1 918d401c046480483ca935728529e016de6d73f6 examples/Ping/lib/Ping/Event/Pong.pm
+SHA1 ba3f1840a56742c011e96890a327ccbe7ecb3a4d examples/Ping/lib/Ping/PingServer.pm
+SHA1 85e03a9df32331e713be7d637c04fa3ca1a24626 examples/Ping/lib/Ping/Server.pm
+SHA1 dc0966379ee2dc74c10ee8d5688cec5b1db17ac4 examples/Ping/share/web/templates/fragments/pong
+SHA1 05ac283a14e76750d63678d0864a0403aa18fd28 examples/Ping/share/web/templates/index.html
+SHA1 53c8822ddf426e82fe239e4470574913411b1355 examples/Ping/t/00compile.t
+SHA1 6d6c378447f9d74d53c06a2a027736b48b5d0670 examples/Ping/t/01startup.t
+SHA1 603bb9de29fb8cba7f13409c546750972eff645d inc/Module/AutoInstall.pm
+SHA1 9b2f9d83bcf77860f53a0c07c90a4a59ad9f5df1 inc/Module/Install.pm
+SHA1 ad955f51ad2c40d4ba35395c27f5ed899a80bf7a inc/Module/Install/AutoInstall.pm
+SHA1 abe32855d75ab13747cf65765af9947b7a8c3057 inc/Module/Install/Base.pm
+SHA1 95b81d1e91bd634467bf633571eff4420e9c04eb inc/Module/Install/Can.pm
+SHA1 1fe98c63cf9d7271c8cb4183ba230f152df69e26 inc/Module/Install/Fetch.pm
+SHA1 0606a8b02a420600bc3e2b65ab82f70266784926 inc/Module/Install/Include.pm
+SHA1 2249171a2b72cd73ff2c0a06597d29f86e5df456 inc/Module/Install/Makefile.pm
+SHA1 381bb98ea3877bba49ae85e7a7ea130645fd3dbf inc/Module/Install/Metadata.pm
+SHA1 b384bd42bc6263dc56d04a884ef4e5fe340049f8 inc/Module/Install/Scripts.pm
+SHA1 fcae3a3bda09e6ba955c8746c2bdf582a23b8d56 inc/Module/Install/Share.pm
+SHA1 0c2118868ef82ac517eb6d9c3bd93e6eb9bbf83e inc/Module/Install/Win32.pm
+SHA1 e827d6d43771032fa3df35c0ad5e5698d0e54cda inc/Module/Install/WriteAll.pm
+SHA1 c17e8f3cf8ebe1eb4929fd2bd2fd530a9de1abd0 lib/Email/Send/Jifty/Test.pm
+SHA1 9380a1872911cf469d08049e4c76645eecef63f8 lib/Jifty.pm
+SHA1 f2db715c8a15d0c7c5c7cf30511dac6547f72fc5 lib/Jifty/API.pm
+SHA1 25ed41c1b89c6d39a8e6d027a9120b3e35785951 lib/Jifty/Action.pm
+SHA1 a6d9d87b4fa06cfab36f249a67946b45669dcee7 lib/Jifty/Action/Autocomplete.pm
+SHA1 19147544734762a243ba7e8b603168930aad9c62 lib/Jifty/Action/Record.pm
+SHA1 39d9bca03c6502e128b453fe3bda5e1d4173f343 lib/Jifty/Action/Record/Create.pm
+SHA1 224f3ed1a4710fb13e4627acd22067e2fa5c35c6 lib/Jifty/Action/Record/Delete.pm
+SHA1 1e400299405a06d3d09bb7548d97c52b850b7f43 lib/Jifty/Action/Record/Search.pm
+SHA1 821d06cdd92876efd01e08800eae46e52e13c2a6 lib/Jifty/Action/Record/Update.pm
+SHA1 ab7a0e0bae4cd1ecbda260b339a4116e65708a7f lib/Jifty/Action/Redirect.pm
+SHA1 cf349fcfe9ee28216eae2213ab0016ceaffaf2a7 lib/Jifty/Bootstrap.pm
+SHA1 f6e9cb12da25b65df8bc2762423d1fcc9d41ec3e lib/Jifty/ClassLoader.pm
+SHA1 1009fa942a8cf3da853694f321d6f67d70613a79 lib/Jifty/Client.pm
+SHA1 a4e91b327848fe1c5b76e4ffaa926300e4c1ef2f lib/Jifty/Collection.pm
+SHA1 ce2f398600582262a2f5562a1de77bc03f6e2676 lib/Jifty/Config.pm
+SHA1 48514a496198c022aff7e4d1c578824addda7c91 lib/Jifty/Continuation.pm
+SHA1 15970f85b57eab885568a71f164dc627c791287c lib/Jifty/CurrentUser.pm
+SHA1 4521a0e9a145697c836e2247fbdb4e93b8966366 lib/Jifty/DateTime.pm
+SHA1 9595652e061d4066615b1ac811700da46394ea65 lib/Jifty/Dispatcher.pm
+SHA1 c9c904906c35def343f14c0c216b62771c07f842 lib/Jifty/Event.pm
+SHA1 fb9f33e2838fbff0cd5b5a784adee8b0fc347ebc lib/Jifty/Event/Model.pm
+SHA1 5a6b765e4e61e1a098cc997127c9a5e9405ea32d lib/Jifty/Everything.pm
+SHA1 818bd0aa6afeb39bf96e0068fe3222c74133b4d8 lib/Jifty/Filter/DateTime.pm
+SHA1 8f83cbce4937acba8d2f5c944d3d3a95a0156d9c lib/Jifty/Handle.pm
+SHA1 0c5f2e31fc9cdd266ff3c10bc3ff6715a3b77c9d lib/Jifty/Handler.pm
+SHA1 89fb4f3164b4ea84eb8084829bbca9e93d2259b1 lib/Jifty/I18N.pm
+SHA1 7ce311e2005ef0df27d71b1d80a4f05237dda9f4 lib/Jifty/JSON.pm
+SHA1 3af93349fe8a236c714d88367077f88af11f5d32 lib/Jifty/LetMe.pm
+SHA1 f76b940651eb2c53bce0af9be451a24b0358fec9 lib/Jifty/Logger.pm
+SHA1 858723b0874be436d44be113934f182eff7e9388 lib/Jifty/Manual/AccessControl.pod
+SHA1 ef9ff36385a9f780ac0204bffb9425818d78b789 lib/Jifty/Manual/Actions.pod
+SHA1 d320630f6613f4aa1ec3b9537129fd9ca847fb61 lib/Jifty/Manual/Continuations.pod
+SHA1 976198d6f93f9c46625c3fd1bd9d89a13ab89f40 lib/Jifty/Manual/Cookbook.pod
+SHA1 38a90072f4a25eac8d4480b00290b069e6397673 lib/Jifty/Manual/FAQ.pod
+SHA1 0f95658ca3d4ed3deb1951f514774446a583247f lib/Jifty/Manual/Glossary.pod
+SHA1 f272be20cc67acaf01c116f432a8f42d82c57877 lib/Jifty/Manual/Models.pod
+SHA1 1204d70c868084ac3114fae277e98a756f83f819 lib/Jifty/Manual/ObjectModel.pod
+SHA1 537017160a444f84bf1673cc3722db644c2b662e lib/Jifty/Manual/PageRegions.pod
+SHA1 8ba19a0760196ceb1afbeaa06e7314e254bef258 lib/Jifty/Manual/RequestHandling.pod
+SHA1 11c2a307f398203ebd804cee990a89e940c406f6 lib/Jifty/Manual/Style.pod
+SHA1 69c995fe4da233dc1b54655813cb6e51d979b759 lib/Jifty/Manual/Tutorial.pod
+SHA1 4ad61c8957fdfed1aa5cc60294cbfb43a359e2d2 lib/Jifty/Manual/Tutorial_de.pod
+SHA1 45f95f61826365d39a5eab2cc119967273d17077 lib/Jifty/Manual/Tutorial_ja.pod
+SHA1 0398a7d053b6c78f7d769f7251d3a01af80cbe57 lib/Jifty/Manual/Upgrading.pod
+SHA1 d0b7a4277c8a3d1a393d7c51fff48b059461f87c lib/Jifty/Manual/UsingCSSandJS.pod
+SHA1 59ce74460e9c1fe8aa1114c2e874348583b76548 lib/Jifty/Mason/Halo.pm
+SHA1 809ace5bafff5ad2461d5495464863ba808dfd19 lib/Jifty/Model/Metadata.pm
+SHA1 36de25464ae4eb07675f453cc590b634c6ff5a37 lib/Jifty/Model/Session.pm
+SHA1 0efcdf22d66e521cf250c1398caf3aba93ed795d lib/Jifty/Model/SessionCollection.pm
+SHA1 e24506b3e0fbdb450e55f3935980813c9c9e2bee lib/Jifty/Module/Pluggable.pm
+SHA1 f0090580d6a54fd8114b8fc1b4cb644c8c3dd2f5 lib/Jifty/Notification.pm
+SHA1 7223070583b1b15f651db7a71b97e039e084aa4f lib/Jifty/Object.pm
+SHA1 c3fde2a862013cd6284637d79c751c3c2e360720 lib/Jifty/Param.pm
+SHA1 11d4de5da85ee53e6f3f434a5a519ebc574cedab lib/Jifty/Param/Schema.pm
+SHA1 69708ae740a40953c50217c37e23740179da80be lib/Jifty/Plugin.pm
+SHA1 d80065a87ec1dd8b0bbcd1853e9a7192a18a04f5 lib/Jifty/Plugin/ClassLoader.pm
+SHA1 20d3f35b391236d9a0e554e62368b4f6fd1383dc lib/Jifty/Plugin/REST.pm
+SHA1 690d1ca831e42d1b1810875abdbae0ff480620e2 lib/Jifty/Plugin/REST/Dispatcher.pm
+SHA1 3fccef02727c8e75a0b4c93f940e9e18e0a499d4 lib/Jifty/Record.pm
+SHA1 0283384c06b36339143690b4beb7830ab4648e0f lib/Jifty/Request.pm
+SHA1 7e4d83147f5f665bb0e8d290ea70340b82a205c2 lib/Jifty/Request/Mapper.pm
+SHA1 0a92b4cdb402463e303b897195c9ad914767c27f lib/Jifty/Response.pm
+SHA1 56988d89d24e33a5ed2c2c6539b02593cc9e3344 lib/Jifty/Result.pm
+SHA1 85de033731ae63d0b3efec515a3608337cbc5c31 lib/Jifty/RightsFrom.pm
+SHA1 0306af64a6289796db4b5ff2b8c5e7c23506a9e3 lib/Jifty/Script.pm
+SHA1 a576b768487f81889ade60cb10f89eb2073adadc lib/Jifty/Script/Action.pm
+SHA1 17bb02c22e126df9ff783843e5921e46b060c2be lib/Jifty/Script/App.pm
+SHA1 1ad9937d9e28f1568954bcffac500be87f3cb355 lib/Jifty/Script/Deps.pm
+SHA1 cb42aff8740412f9e7a26915a4e852817aa39af8 lib/Jifty/Script/FastCGI.pm
+SHA1 e25a259fc9fd3183a7981236f428f54ac4c4f49e lib/Jifty/Script/Help.pm
+SHA1 be1f648841609388f48214e632e0b169216dd58a lib/Jifty/Script/Model.pm
+SHA1 77c221bb4b7162b2fc95c841ceeea04194afb264 lib/Jifty/Script/Plugin.pm
+SHA1 eba3c96bcfae42ea6a9e5d2cbef6bca19617b47e lib/Jifty/Script/Po.pm
+SHA1 afb4c37e7503e53676773bb3d6f65fde111679fc lib/Jifty/Script/Schema.pm
+SHA1 4bbe2362a6cd195fac119a37cd71119afb5516f6 lib/Jifty/Script/Server.pm
+SHA1 0646a96ca5f3da1585e3fb1dcc9b3540d2b8f50f lib/Jifty/Server.pm
+SHA1 4c51d2df15281788b74d080488b0c5959cd95a1a lib/Jifty/Subs.pm
+SHA1 43277d31da2a30afd160dbe2077314285898438f lib/Jifty/Subs/Render.pm
+SHA1 df6cadbfbd91dcbf91d334a2132882821d9b3741 lib/Jifty/Test.pm
+SHA1 24eb481ac109ce0639496d6e16311ae2af0bb0da lib/Jifty/Test/WWW/Mechanize.pm
+SHA1 bacce10076cd7790405d29b0cabd99cae06b739e lib/Jifty/TestServer.pm
+SHA1 206e12811205063b22fcaaf0cd04dd66321d793c lib/Jifty/Upgrade.pm
+SHA1 f3957ac04ad92737d68b2a63f068679b4bacafed lib/Jifty/Upgrade/Internal.pm
+SHA1 01e8cf19fef0094b917712165d5ae0acf37fa31d lib/Jifty/Util.pm
+SHA1 bf4cbc6a7c94597cadb101b609f80004bfb64eef lib/Jifty/View/Mason/Handler.pm
+SHA1 91b4185b0570453805f15b0eb0ce0f50a6f1eef3 lib/Jifty/View/Static/Handler.pm
+SHA1 1b62b886ec2a10b9b078535a7782bd01507bf6a6 lib/Jifty/Web.pm
+SHA1 93f70b329f4e05105edd6a2bacb1d71ed59c5449 lib/Jifty/Web/Form.pm
+SHA1 07cea2d1e0075a331d4f028fee34c5fd1bdda618 lib/Jifty/Web/Form/Clickable.pm
+SHA1 9065dcef2d3e755bcefbc5ec1481bf92e883c287 lib/Jifty/Web/Form/Element.pm
+SHA1 f7b910dc62f6265d4df1a8ec937d902549f1ee49 lib/Jifty/Web/Form/Field.pm
+SHA1 ba87c27d5432a8393ffeaf39323ac80c0f4facab lib/Jifty/Web/Form/Field/Button.pm
+SHA1 c1071263839a663d44d7f922c7c24b5bcc132a82 lib/Jifty/Web/Form/Field/Checkbox.pm
+SHA1 b21ef4e33a628f5d6c54adf5f47f75e69a861644 lib/Jifty/Web/Form/Field/Combobox.pm
+SHA1 cc86a97a80c6c9e22183a099979a712a3900a39a lib/Jifty/Web/Form/Field/Date.pm
+SHA1 e2db0ce71a56f59e8154a8af8187fab198c01fe7 lib/Jifty/Web/Form/Field/Hidden.pm
+SHA1 41b5344b9e5b0dcdc2c944d2cacdc18421aa8a93 lib/Jifty/Web/Form/Field/InlineButton.pm
+SHA1 db78f61b0fe36589d0f3acbf8df470f89cbc6ee9 lib/Jifty/Web/Form/Field/Password.pm
+SHA1 1006c6e4e07fbbf5ef5ccd2020cbe82ebbd8da1c lib/Jifty/Web/Form/Field/Radio.pm
+SHA1 5e6d07f48f59a7b2106a8f22ead83263368b26ad lib/Jifty/Web/Form/Field/ResetButton.pm
+SHA1 f865b1b411cf40aa3a0bf0cd51e364a0f23a4d3c lib/Jifty/Web/Form/Field/Select.pm
+SHA1 5586d4fdda6b5b8e6014750d5b912be898b18465 lib/Jifty/Web/Form/Field/Text.pm
+SHA1 bb397fc6092ddb8196342e80a4148d07fcd037b0 lib/Jifty/Web/Form/Field/Textarea.pm
+SHA1 2548412d5bfbd08050d53ab5c58e0d962d4b2448 lib/Jifty/Web/Form/Field/Unrendered.pm
+SHA1 e37541952c969f2e74f942782d483de75e9265f9 lib/Jifty/Web/Form/Field/Upload.pm
+SHA1 16cff04150ba0ae8bffe04c1c661d80972876248 lib/Jifty/Web/Form/Link.pm
+SHA1 e4baa13068c7de8e4737efa4dbc18f1f059e6047 lib/Jifty/Web/Menu.pm
+SHA1 e17189fe3d525c4fe2a251db20a5a6eabbc7c786 lib/Jifty/Web/PageRegion.pm
+SHA1 093ab860163f03e9d85c8bb366d694830b7d940a lib/Jifty/Web/Session.pm
+SHA1 f655fb9734715ebf51fb5e9b554c02b9e4e2ac61 lib/Jifty/Web/Session/ClientSide.pm
+SHA1 c4de1ef964243aae5ab90d0a5a6dd9c213eb9f80 lib/Jifty/YAML.pm
+SHA1 feeb00d6502ca54367036767f5e3f20275580291 plugins/AuthCASLogin/MANIFEST
+SHA1 f7ee4953b3c05710fe531cc9b579f65ed04af761 plugins/AuthCASLogin/Makefile.PL
+SHA1 e02920219c74449947a6c3a04c35a25ad944faf4 plugins/AuthCASLogin/debian/changelog
+SHA1 9c6b057a2b9d96a4067a749ee3b3b0158d390cf1 plugins/AuthCASLogin/debian/compat
+SHA1 bf43e4b8cb112727059c7738c762f71184fe7a1d plugins/AuthCASLogin/debian/control
+SHA1 c361200ede7340a93f131034eaaaa71f36bf1617 plugins/AuthCASLogin/debian/copyright
+SHA1 b612a9f276fb1016d242acf81406946dd146bfcf plugins/AuthCASLogin/debian/files
+SHA1 df2b684174a5e8bdf6d14b985020057702b4c884 plugins/AuthCASLogin/debian/rules
+SHA1 2481d6a02ca4959e29bed6eca04499cf808cc8a3 plugins/AuthCASLogin/lib/Jifty/Plugin/AuthCASLogin.pm
+SHA1 6a687e551737ce850bfa02c15871f462845f8f3b plugins/AuthCASLogin/lib/Jifty/Plugin/AuthCASLogin/Action/CASLogin.pm
+SHA1 44ee4a567f2fd636aac8b6a8bc6c46a898bff080 plugins/AuthCASLogin/lib/Jifty/Plugin/AuthCASLogin/Action/CASLogout.pm
+SHA1 b743661f79c8620f19275181e0458589eca4b6a7 plugins/AuthCASLogin/lib/Jifty/Plugin/AuthCASLogin/Dispatcher.pm
+SHA1 36215b6306dd37ebbf6206f526e9464d2d261d2a plugins/AuthCASLogin/share/web/templates/caslogin
+SHA1 f971aec45ba679dc55a9955077324f5c0ea75cca plugins/AuthCASLogin/share/web/templates/caslogout
+SHA1 c93e6f367c6a110cb05f8fefa4cb37881daeebe7 plugins/AuthCASLogin/t/00-load.t
+SHA1 96e2b7d839f54a14c42dd421516a267d7e2c4b94 plugins/AuthCASOnly/MANIFEST
+SHA1 62ee3ecfa8b4923ee68aa5f3179f2b3c5c8551dd plugins/AuthCASOnly/META.yml
+SHA1 9e218ee17e541c947ceb6f78d4714fc4baa43895 plugins/AuthCASOnly/Makefile.PL
+SHA1 0211277b86a7c26952a44da5ea55945a32ca2689 plugins/AuthCASOnly/debian/changelog
+SHA1 9c6b057a2b9d96a4067a749ee3b3b0158d390cf1 plugins/AuthCASOnly/debian/compat
+SHA1 df5baa3318d61d253b6256a2a3abb06aa28669a5 plugins/AuthCASOnly/debian/control
+SHA1 c361200ede7340a93f131034eaaaa71f36bf1617 plugins/AuthCASOnly/debian/copyright
+SHA1 6178f211c12057c7f8bf110ae4d9ae9961ba90c7 plugins/AuthCASOnly/debian/files
+SHA1 df2b684174a5e8bdf6d14b985020057702b4c884 plugins/AuthCASOnly/debian/rules
+SHA1 e324ade45843a878b10cc5bd78061401dcf56685 plugins/AuthCASOnly/lib/Jifty/Plugin/AuthCASOnly.pm
+SHA1 827a868f825e94c7630368d4cd98d591edacc188 plugins/AuthCASOnly/lib/Jifty/Plugin/AuthCASOnly/Action/CASLogin.pm
+SHA1 cc9279734210b6a8de3312716ee36868d437f7db plugins/AuthCASOnly/lib/Jifty/Plugin/AuthCASOnly/Action/CASLogout.pm
+SHA1 b401b903bece0f11a45c7fe129ada9e6e2681f4b plugins/AuthCASOnly/lib/Jifty/Plugin/AuthCASOnly/CurrentUser.pm
+SHA1 d0a159254cb3d99a5d3664ffd8c8d42c3daa40f1 plugins/AuthCASOnly/lib/Jifty/Plugin/AuthCASOnly/Dispatcher.pm
+SHA1 67aea2e30d74af4e01b66ef5bebb4d69d78f2b37 plugins/AuthCASOnly/lib/Jifty/Plugin/AuthCASOnly/Model/CASUser.pm
+SHA1 36215b6306dd37ebbf6206f526e9464d2d261d2a plugins/AuthCASOnly/share/web/templates/caslogin
+SHA1 f971aec45ba679dc55a9955077324f5c0ea75cca plugins/AuthCASOnly/share/web/templates/caslogout
+SHA1 de334fd98ec8b7bf6b72b9f4d98da1405f2fd9a9 plugins/AuthCASOnly/t/00-load.t
+SHA1 49e6479da4844f3e44e073c8ff055177a97b73c6 plugins/AuthLDAPLogin/MANIFEST
+SHA1 a9231ecdff2144eeecefb05318f6b0929b9b888a plugins/AuthLDAPLogin/Makefile.PL
+SHA1 3d40e1b567b7ca16f6c6bbc2110feac5e4d03030 plugins/AuthLDAPLogin/debian/changelog
+SHA1 9c6b057a2b9d96a4067a749ee3b3b0158d390cf1 plugins/AuthLDAPLogin/debian/compat
+SHA1 81d9344b39a55abe73c3bc969676f5970b851fe2 plugins/AuthLDAPLogin/debian/control
+SHA1 c361200ede7340a93f131034eaaaa71f36bf1617 plugins/AuthLDAPLogin/debian/copyright
+SHA1 df2b684174a5e8bdf6d14b985020057702b4c884 plugins/AuthLDAPLogin/debian/rules
+SHA1 9a467c6b819af40acba655cf70795c9867a31544 plugins/AuthLDAPLogin/lib/Jifty/Plugin/AuthLDAPLogin.pm
+SHA1 4f8ca81ec122164153bfeed566934cb96d3e5a5e plugins/AuthLDAPLogin/lib/Jifty/Plugin/AuthLDAPLogin/Action/LDAPLogin.pm
+SHA1 db807f73a0a665abe5f18bcbdb6587d830e1f209 plugins/AuthLDAPLogin/lib/Jifty/Plugin/AuthLDAPLogin/Action/LDAPLogout.pm
+SHA1 e69c4b3784c49e7c389d5635cf08020b7ac833be plugins/AuthLDAPLogin/lib/Jifty/Plugin/AuthLDAPLogin/Dispatcher.pm
+SHA1 bddb067764a83d2beaa4d4672486e9009b0dee4f plugins/AuthLDAPLogin/share/po/en.po
+SHA1 6046f83cca826e2ff0f43ce95b0d842ce65df37b plugins/AuthLDAPLogin/share/po/fr.po
+SHA1 a52f49740c6a0c5dfb5ef2413b26d40c71e8dba0 plugins/AuthLDAPLogin/share/web/templates/ldaplogin
+SHA1 f971aec45ba679dc55a9955077324f5c0ea75cca plugins/AuthLDAPLogin/share/web/templates/ldaplogout
+SHA1 43c8998946172ac32eeefdfbf95a663d6e60e40a plugins/AuthLDAPLogin/t/00-load.t
+SHA1 8c487e541a3d5115e8b04f35ac6555cca05de933 plugins/AuthLDAPOnly/MANIFEST
+SHA1 76658d43f96759a30895e9ab9116d3fe2ec475bf plugins/AuthLDAPOnly/Makefile.PL
+SHA1 6490648ff1114d79ba05a70e8106d5d0226d72ae plugins/AuthLDAPOnly/debian/changelog
+SHA1 9c6b057a2b9d96a4067a749ee3b3b0158d390cf1 plugins/AuthLDAPOnly/debian/compat
+SHA1 e256f776a58f8ca1de7a20082e855e03bace79d8 plugins/AuthLDAPOnly/debian/control
+SHA1 c361200ede7340a93f131034eaaaa71f36bf1617 plugins/AuthLDAPOnly/debian/copyright
+SHA1 68dfa9d87a39ca9c6480d8a93e7efb18565b4db3 plugins/AuthLDAPOnly/debian/rules
+SHA1 69f53febd6a99e6e61e5b87a387139a74f509b8d plugins/AuthLDAPOnly/lib/Jifty/Plugin/AuthLDAPOnly.pm
+SHA1 3a1a5ac6571c49fd766eba8f8fb94dbc35081192 plugins/AuthLDAPOnly/lib/Jifty/Plugin/AuthLDAPOnly/Action/LDAPLogin.pm
+SHA1 8709ec6fd348b2229eecab4778fe52b9e3f5f241 plugins/AuthLDAPOnly/lib/Jifty/Plugin/AuthLDAPOnly/Action/LDAPLogout.pm
+SHA1 5776159e43fe28077ebe66359d888620c1a78c30 plugins/AuthLDAPOnly/lib/Jifty/Plugin/AuthLDAPOnly/CurrentUser.pm
+SHA1 11063de8ce0267140e4eeacc8caa552de893d435 plugins/AuthLDAPOnly/lib/Jifty/Plugin/AuthLDAPOnly/Dispatcher.pm
+SHA1 be8d85f7423aabd9e504feb4e759d412990c4f5a plugins/AuthLDAPOnly/lib/Jifty/Plugin/AuthLDAPOnly/Model/LDAPUser.pm
+SHA1 645c578bde4a89f2a47ffbff048863a52691ad11 plugins/AuthLDAPOnly/share/po/en.po
+SHA1 42fa3dbdbfd9c34dd1260df7d70fd215aeb3d987 plugins/AuthLDAPOnly/share/po/fr.po
+SHA1 a52f49740c6a0c5dfb5ef2413b26d40c71e8dba0 plugins/AuthLDAPOnly/share/web/templates/ldaplogin
+SHA1 f971aec45ba679dc55a9955077324f5c0ea75cca plugins/AuthLDAPOnly/share/web/templates/ldaplogout
+SHA1 e6a78e0b4226f86df4d2865bef599aab18b441a5 plugins/AuthLDAPOnly/t/00-load.t
+SHA1 42c851e5906962ad1574ff20cd4284c88a7c1bac plugins/AuthzLDAP/MANIFEST
+SHA1 0086fe18fd01b6ace92636034ea606008ab5a374 plugins/AuthzLDAP/Makefile.PL
+SHA1 d9d714781bef6a0e3892f258350f9d6797eafa9a plugins/AuthzLDAP/lib/Jifty/Plugin/AuthzLDAP.pm
+SHA1 a63d50ed4f93a1d9909af9889fc30d8b3e9140a4 plugins/AuthzLDAP/lib/Jifty/Plugin/AuthzLDAP/Action/LDAPValidate.pm
+SHA1 5b391191e624c0d2e8c16f19c531a2c2993ba49e plugins/AuthzLDAP/lib/Jifty/Plugin/AuthzLDAP/Model/LDAPFilter.pm
+SHA1 dc50e07d76934884116843767a1784571accac4e plugins/AuthzLDAP/share/po/en.po
+SHA1 18be32490b494470824534cccd87b034cd790987 plugins/AuthzLDAP/share/po/fr.po
+SHA1 cd21439e1906eb58e9c576a477a8a31e05340c05 plugins/AuthzLDAP/share/web/templates/error/AccessDenied
+SHA1 289f295702910035ed2b122279ecf8b3b6a6389b plugins/AuthzLDAP/t/00-load.t
+SHA1 e8ce16205eccb1b99224ca81d3a3496163a98864 plugins/EditInPlace/Makefile.PL
+SHA1 f2d5b6fb2d7628c2dd7207a8a7fb3dab1ea2f4bf plugins/EditInPlace/debian/changelog
+SHA1 9c6b057a2b9d96a4067a749ee3b3b0158d390cf1 plugins/EditInPlace/debian/compat
+SHA1 769bf223b7f14e4c1f2074cb4c7328324d26ca19 plugins/EditInPlace/debian/control
+SHA1 df2b684174a5e8bdf6d14b985020057702b4c884 plugins/EditInPlace/debian/rules
+SHA1 603bb9de29fb8cba7f13409c546750972eff645d plugins/EditInPlace/inc/Module/AutoInstall.pm
+SHA1 9b2f9d83bcf77860f53a0c07c90a4a59ad9f5df1 plugins/EditInPlace/inc/Module/Install.pm
+SHA1 ad955f51ad2c40d4ba35395c27f5ed899a80bf7a plugins/EditInPlace/inc/Module/Install/AutoInstall.pm
+SHA1 abe32855d75ab13747cf65765af9947b7a8c3057 plugins/EditInPlace/inc/Module/Install/Base.pm
+SHA1 95b81d1e91bd634467bf633571eff4420e9c04eb plugins/EditInPlace/inc/Module/Install/Can.pm
+SHA1 1fe98c63cf9d7271c8cb4183ba230f152df69e26 plugins/EditInPlace/inc/Module/Install/Fetch.pm
+SHA1 0606a8b02a420600bc3e2b65ab82f70266784926 plugins/EditInPlace/inc/Module/Install/Include.pm
+SHA1 2249171a2b72cd73ff2c0a06597d29f86e5df456 plugins/EditInPlace/inc/Module/Install/Makefile.pm
+SHA1 c6cbbd5beff5b83ac581fed0e294fc6726c868de plugins/EditInPlace/inc/Module/Install/Makefile/Version.pm
+SHA1 381bb98ea3877bba49ae85e7a7ea130645fd3dbf plugins/EditInPlace/inc/Module/Install/Metadata.pm
+SHA1 fcae3a3bda09e6ba955c8746c2bdf582a23b8d56 plugins/EditInPlace/inc/Module/Install/Share.pm
+SHA1 0c2118868ef82ac517eb6d9c3bd93e6eb9bbf83e plugins/EditInPlace/inc/Module/Install/Win32.pm
+SHA1 e827d6d43771032fa3df35c0ad5e5698d0e54cda plugins/EditInPlace/inc/Module/Install/WriteAll.pm
+SHA1 312dfe93d52daa4c72257cf2299aee35f61b0a16 plugins/EditInPlace/lib/Jifty/Plugin/EditInPlace.pm
+SHA1 187cd200affbf8861a91d008273eb9de664a9d60 plugins/EditInPlace/lib/Jifty/Plugin/EditInPlace/Action/FileEditor.pm
+SHA1 69ff778cc486bfd14a97f7690a7706633dbc2e6b plugins/EditInPlace/lib/Jifty/Plugin/EditInPlace/Dispatcher.pm
+SHA1 6f363a3b2826ee1fb01921fefebaf99b111a1c31 plugins/EditInPlace/share/web/templates/__jifty/create_file_inline
+SHA1 dc4e60f93e56496f8010ae81888e84e3352e979f plugins/EditInPlace/share/web/templates/__jifty/edit_file
+SHA1 fea2934c144a7983dc7fb4d2a2826b0f7f6bbc1f plugins/EditInPlace/share/web/templates/__jifty/edit_file_inline
+SHA1 69352f6e1ee12026575162e559aa0ee154be3366 plugins/EmailErrors/Makefile.PL
+SHA1 ff2699ef47b63f7a185f53ffc96bcdc9d05799ec plugins/EmailErrors/doc/site_config.yml
+SHA1 a41d80d1e2c02de64581f13da37ed601e0baabee plugins/EmailErrors/lib/Jifty/Plugin/EmailErrors.pm
+SHA1 e84437393b86a21e865df9ea0d0711a75ef4928e plugins/EmailErrors/lib/Jifty/Plugin/EmailErrors/Dispatcher.pm
+SHA1 20fcd16f0baba3e0114bc73d2dca0c9da3a6405c plugins/EmailErrors/lib/Jifty/Plugin/EmailErrors/Notification/EmailError.pm
+SHA1 da39a3ee5e6b4b0d3255bfef95601890afd80709 plugins/EmailErrors/share/web/templates/.file
+SHA1 6d36892393c5848ca54991272c03460b330734d2 plugins/LetMe/Makefile.PL
+SHA1 88f5ed45b2c506fcab80f178cf4cedd23a62f365 plugins/LetMe/lib/Jifty/Plugin/LetMe.pm
+SHA1 aae4870db361c1076fd151ddd0cf2fd3f02f4813 plugins/LetMe/lib/Jifty/Plugin/LetMe/Dispatcher.pm
+SHA1 5ece7af558c3a7bc83f535e948b7a6b24372daec plugins/Login/MANIFEST
+SHA1 4439300be4c8a1b99726658ceed25674216a079e plugins/Login/META.yml
+SHA1 5d7bbe469fc6a8e0c5f303539c1d235f7c4f50a2 plugins/Login/Makefile.PL
+SHA1 4c552f502b762c8e1a12848423a36ddff99bd38b plugins/Login/debian/changelog
+SHA1 9c6b057a2b9d96a4067a749ee3b3b0158d390cf1 plugins/Login/debian/compat
+SHA1 90e2e662014841fff93a7e346908f66178dfcc07 plugins/Login/debian/control
+SHA1 df2b684174a5e8bdf6d14b985020057702b4c884 plugins/Login/debian/rules
+SHA1 017bedfcba1e0c72b36301e6ef21b8712b84d175 plugins/Login/inc/Module/Install.pm
+SHA1 b1a70869c098ba602151631386fc510b5bfd3511 plugins/Login/inc/Module/Install/Base.pm
+SHA1 dd7313db23119d49ae78593bfa576554fb5b0fd8 plugins/Login/inc/Module/Install/Can.pm
+SHA1 7c3a5153a5574556d895f23e121a41e680da11c1 plugins/Login/inc/Module/Install/Fetch.pm
+SHA1 176d68fe7c07b6ab7cfe09093078b8127bbde786 plugins/Login/inc/Module/Install/Makefile.pm
+SHA1 8b37b38215d14f922b3d5132ce33d11d21d531ba plugins/Login/inc/Module/Install/Metadata.pm
+SHA1 9915504ffb4f14696ecca28af457e03f795ed2ce plugins/Login/inc/Module/Install/Share.pm
+SHA1 7eb44b00058c44795038d7fa4c0c77470f20a7f8 plugins/Login/inc/Module/Install/Win32.pm
+SHA1 6d05967a5e1680b5f118315aaa6b8a1e143d79d8 plugins/Login/inc/Module/Install/WriteAll.pm
+SHA1 b85d6515d225ef48073ac3f0ee1744019bff113f plugins/Login/lib/Jifty/Plugin/Login.pm
+SHA1 e2c32d8af104a88ce499131173ccc97247bd177e plugins/Login/lib/Jifty/Plugin/Login/Action/ChangePassword.pm
+SHA1 490f6f80d1addbd3b49908068f4f076301a0df85 plugins/Login/lib/Jifty/Plugin/Login/Action/ConfirmEmail.pm
+SHA1 dd8061d61508a1e9df2f0486750a6e1aeedd6311 plugins/Login/lib/Jifty/Plugin/Login/Action/Login.pm
+SHA1 58fd545139f90dd71964ec38ecffb386949cf5a6 plugins/Login/lib/Jifty/Plugin/Login/Action/Logout.pm
+SHA1 b869cffda9c3d66c648faac4952e0cd02333a541 plugins/Login/lib/Jifty/Plugin/Login/Action/RecoverPassword.pm
+SHA1 122c7769c6ba202aa4e791f9f34972e58fc1fcd2 plugins/Login/lib/Jifty/Plugin/Login/Action/ResetLostPassword.pm
+SHA1 c2438302917e04b15f940d12452a3d07328ba95d plugins/Login/lib/Jifty/Plugin/Login/Action/SendAccountConfirmation.pm
+SHA1 e238cc4ec479aefb8864ca99984cdea4c76141cf plugins/Login/lib/Jifty/Plugin/Login/Action/SendPasswordReminder.pm
+SHA1 6a41bfe4d8c024874b16f4c8f01d5d16fb77fcd3 plugins/Login/lib/Jifty/Plugin/Login/Action/Signup.pm
+SHA1 81875f6ab2d807bfe4671af084b4f238d383464c plugins/Login/lib/Jifty/Plugin/Login/CurrentUser.pm
+SHA1 525f9e063afd64961982b513c5eab0a85909c917 plugins/Login/lib/Jifty/Plugin/Login/Dispatcher.pm
+SHA1 26715ae2299b714b2eeaad1b0d9d709623c2b05d plugins/Login/lib/Jifty/Plugin/Login/Model/User.pm
+SHA1 3a8233f78d474c2671121f87c7e20bc01ba3090a plugins/Login/lib/Jifty/Plugin/Login/Notification/ConfirmAddress.pm
+SHA1 dd2d89075e1d29eb0e20cfc43fba7db98ef02529 plugins/Login/lib/Jifty/Plugin/Login/Notification/ConfirmLostPassword.pm
+SHA1 c67dcd72acc2f4e5b43863c7480697cc711b5e57 plugins/Login/share/po/en.po
+SHA1 d2e2fc32dbf2ebba82469cb0e8918c71660dc00b plugins/Login/share/po/fr.po
+SHA1 b157d8e7752bab6182ff13e64dec194cc67caf50 plugins/Login/share/web/templates/chgpasswd
+SHA1 cf9c995f10d81a5b124f278756808ad3418818e5 plugins/Login/share/web/templates/let/confirm_email
+SHA1 803c22a5ae8761da7bc9f62e392ec7ebef9bd98d plugins/Login/share/web/templates/let/reset_lost_password
+SHA1 8506d39fc45be929fb5a9e289a3682791a605d6d plugins/Login/share/web/templates/login
+SHA1 929a56746e77440a8b0bfda2b020a9583afe6cbf plugins/Login/share/web/templates/passwordreminder
+SHA1 fd4e0da55b29bae4c01d0868c9af9566923fb971 plugins/Login/share/web/templates/signup
+SHA1 6a8008c78f6f0985542b8c42ef3327508b798897 plugins/Nothing/Makefile.PL
+SHA1 514f77cd3c01a1fea159187fde9fa8ab78b0b75c plugins/Nothing/lib/Jifty/Plugin/Nothing.pm
+SHA1 ceeff321fbe0e12b7a4a2421320db57b8bee8732 plugins/Nothing/lib/Jifty/Plugin/Nothing/Dispatcher.pm
+SHA1 d6cbace955b9546e82780a4c070a96265fb788a1 plugins/ProfileBehaviour/Makefile.PL
+SHA1 f315c0902c3f5102ab8ca3159d201a1475074a5e plugins/ProfileBehaviour/lib/Jifty/Plugin/ProfileBehaviour.pm
+SHA1 145948e25dc1be742b76d870a20d13383f902f49 plugins/ProfileBehaviour/lib/Jifty/Plugin/ProfileBehaviour/Dispatcher.pm
+SHA1 1059b85f661b36f61b63cc72d0fc15cf732cc00a plugins/ProfileBehaviour/share/web/static/css/behaviour-profile.css
+SHA1 39746321849746fdfbeb51d7d2f1cb2adc1c3cdf plugins/ProfileBehaviour/share/web/static/js/behaviour.js
+SHA1 2c5d29ea2981cc759cc5f5b27cf7c536d4ea9384 share/dtd/xhtml-lat1.ent
+SHA1 62229286e03ec35c922c8a7f9f3bb68412a78a55 share/dtd/xhtml-special.ent
+SHA1 e7749f99989ec8a9608f6cfbd41a5e5dddc18aec share/dtd/xhtml-symbol.ent
+SHA1 b4a6d708f6b55a48526e9483c718f2ed820df75c share/dtd/xhtml1-strict.dtd
+SHA1 ddc3a6849e3db2ff2c5e1113a36d6e9869718dc4 share/po/en.po
+SHA1 a2f3d82acc9d343569eb0bc53a1517650c446189 share/po/fr.po
+SHA1 44b99ffe0fd8b2410b3ea5c988ebc61f07d30a38 share/po/ja.po
+SHA1 434c54b1132635916b8f6c70227685661593615d share/po/zh_cn.po
+SHA1 55bf7ecd5ef87fe38e5e3c6d6a0df3b7d8041994 share/po/zh_tw.po
+SHA1 da39a3ee5e6b4b0d3255bfef95601890afd80709 share/web/static/css/app-base.css
+SHA1 da39a3ee5e6b4b0d3255bfef95601890afd80709 share/web/static/css/app.css
+SHA1 c566d7ab5005e82dcf2f90809ae757e07d338138 share/web/static/css/autocomplete.css
+SHA1 5f569fc23eb815ee6f6d086aa6df87f6c38952d3 share/web/static/css/autohandler
+SHA1 c8f7207fab0b74a53a2a798bcedd2f9d94ecc32e share/web/static/css/base.css
+SHA1 669f07ab36d148383013733d8230d49e7e7b25ec share/web/static/css/calendar.css
+SHA1 bf48b6bda86f359b95b29a7c3451a3c1b5721385 share/web/static/css/combobox.css
+SHA1 6b4d4cbc063731dabe50356a3560eae6a9f25aef share/web/static/css/context-menus.css
+SHA1 9da37a3dea2259252e33e0cd72198baa04d17530 share/web/static/css/forms.css
+SHA1 52ddd83ee7c511d78537454a097f0264a7db3c1c share/web/static/css/halos.css
+SHA1 c7eede0c22f68e4417748bb0903b48195648f4c0 share/web/static/css/keybindings.css
+SHA1 91cd2873a5521bbc172389d8af2c29f33578fcc9 share/web/static/css/main.css
+SHA1 903069dae3de35d6a3226b8272ff317b8eebd58c share/web/static/css/nav.css
+SHA1 45861338bc40888737738521a44d4adf286b1204 share/web/static/css/notices.css
+SHA1 4389605ead1cf3e51a96f69d700ca70b3be6ca32 share/web/static/css/yui/calendar/calendar.css
+SHA1 b8074785c5372a76038c88feed271f1dd839b561 share/web/static/css/yui/tabview/border_tabs.css
+SHA1 2008bf0af63ee36885dd8bbc5fb134bc09b58135 share/web/static/css/yui/tabview/tabs.css
+SHA1 2f28dd2d59fe5486abce194e6fe68da0e49b1084 share/web/static/favicon.ico
+SHA1 6197eb13b4254f77f16833e1a9a8640e939d574c share/web/static/images/css/bullet_arrow_down.png
+SHA1 fdab0053425ba609387dffb0e2529a8feacc3906 share/web/static/images/css/bullet_arrow_up.png
+SHA1 3410d5156716e13443770cce0f3384eacfdb5adb share/web/static/images/css/fieldbg-autocomplete.gif
+SHA1 97c94d7dfb0683f9d2ac7f9406ca36495fa638e0 share/web/static/images/css/fieldbg.gif
+SHA1 67f41db40d62b81d71cb60c542695e0d7e6d393d share/web/static/images/pony.jpg
+SHA1 6197eb13b4254f77f16833e1a9a8640e939d574c share/web/static/images/silk/bullet_arrow_down.png
+SHA1 fdab0053425ba609387dffb0e2529a8feacc3906 share/web/static/images/silk/bullet_arrow_up.png
+SHA1 757e85619231e57be461e54982877457e0fbf6fc share/web/static/images/silk/calendar.png
+SHA1 899445ef75b18925f3c47969e122562dc6da5158 share/web/static/images/silk/cancel.png
+SHA1 bd221f74c7d3442a1db5f5594a1be86c662497ad share/web/static/images/silk/cancel_grey.png
+SHA1 3855754e8fc9331c67dd2d0789d9f2d6e7daae8b share/web/static/images/silk/error.png
+SHA1 04b482344d75d0732275727bd73cceb9b049d276 share/web/static/images/silk/information.png
+SHA1 62a1b41ac5eefbf3d0ad578a19572d4096d88146 share/web/static/images/silk/pencil.png
+SHA1 2f84cd8d3f46ffe9eb1406a4f6337fa5b5e2bcd1 share/web/static/images/silk/pencil_add.png
+SHA1 d2c01c9ded34297020c6f9abb5240e0b2fd8606a share/web/static/images/yui/us/my/bn/x_d.gif
+SHA1 88a51e34bc8c5d616b76743a52ad1fe0cae8232e share/web/static/images/yui/us/tr/callt.gif
+SHA1 f818910630fc046dbdd6dde2960d3e26c5d4beaf share/web/static/images/yui/us/tr/calrt.gif
+SHA1 eb756c931be8ef70fc27ba0046375f32022e3b6d share/web/static/js/app.js
+SHA1 716b6f8326bd5cd05c31e521351197b5038e052e share/web/static/js/app_behaviour.js
+SHA1 2b28f63068ff486e7fd7937f725f47e886d01831 share/web/static/js/behaviour.js
+SHA1 aa6192ac38a0c708ea0efb60bb98c666d9687423 share/web/static/js/bps_util.js
+SHA1 6b09c590f5e94b238fd99031e6b57ec57e70bd3f share/web/static/js/calendar.js
+SHA1 e9f9931abe8ddf86cf5cfddd1f0e963bb5bf0ccb share/web/static/js/combobox.js
+SHA1 cd5a80e098d28f7cf0b74cee38f08bd336ccdc95 share/web/static/js/context_menu.js
+SHA1 b03b1f06f9c972cfb083c3d87b3dc74e4d85bf77 share/web/static/js/css_browser_selector.js
+SHA1 a90d3ffbc606979282dba3296599f582715021ab share/web/static/js/cssquery/cssQuery-level2.js
+SHA1 ba5ff2e1484476981ccf311ff8fe0062a9b2ac01 share/web/static/js/cssquery/cssQuery-level3.js
+SHA1 a80e117cfca6644bde2bb8bd8d48a093e784a731 share/web/static/js/cssquery/cssQuery-standard.js
+SHA1 95408c5aa294bdbc7b857d35772c1bd888b87b33 share/web/static/js/cssquery/cssQuery.js
+SHA1 d542a217ef9c527340d25bfffbccce27927d1259 share/web/static/js/dom-drag.js
+SHA1 4553f3cb184b09228ed4362898e9d30200a2a585 share/web/static/js/formatDate.js
+SHA1 a1d2c6292d656c275383b97aad6ca913b8a1b031 share/web/static/js/halo.js
+SHA1 44bac00f46d4e6809892fc03c42487bd275be71e share/web/static/js/jifty.js
+SHA1 29fe34f11192976f1a388562188b1eb9af7f4497 share/web/static/js/jifty_smoothscroll.js
+SHA1 8723bf251531e79ab109ea0d3fb2187a8dac8cb6 share/web/static/js/jifty_subs.js
+SHA1 2fd261c3c30a0b17e02e6af79d7c7c587dfe2f2a share/web/static/js/jifty_utils.js
+SHA1 d6c17ef3717315c08659082144a2caf2e970b23d share/web/static/js/jsTrace.js
+SHA1 2d8acba4acfd5508381461ccabe954c2e9df086c share/web/static/js/jsan/DOM/Events.js
+SHA1 36b2a3f1966b97fcc338b557830106bf2d490485 share/web/static/js/jsan/JSAN.js
+SHA1 679d9c011aa8403cfc0ba945a9143c2e9cacccfa share/web/static/js/jsan/Push.js
+SHA1 4d3474847360cb00edc1ae4745ea6b56a7ec1a2b share/web/static/js/jsan/Upgrade.js
+SHA1 7a13c9041326f2e70494f6ed5bcd87396bdd4280 share/web/static/js/jsan/Upgrade/Array/push.js
+SHA1 becdf6868ec4aec2dc93c8c33b0713d1c4f4eb34 share/web/static/js/jsan/Upgrade/Function/apply.js
+SHA1 f15b0364f99d2e4c1af795c82883f89b9eaca9b2 share/web/static/js/json.js
+SHA1 df1c8b06e2d10c743cbd65d7feda8db5a23522d6 share/web/static/js/key_bindings.js
+SHA1 986a63bc533f6fa99c9b0f0226a14f9871b94ce5 share/web/static/js/prototype.js
+SHA1 a1048deeafbc76659e54eb77c0e51b6b79cade19 share/web/static/js/rico.js
+SHA1 164bc59cf75fe943edc80da65b19246fc9b9643e share/web/static/js/scriptaculous/builder.js
+SHA1 afa5b63db51fc4c3c4ff2535d3af5fe5b00add19 share/web/static/js/scriptaculous/controls.js
+SHA1 28f001d3c48395daf0de22876a70d918db3a461e share/web/static/js/scriptaculous/dragdrop.js
+SHA1 21ce51daa693e3716678ac4190369b499b35e8de share/web/static/js/scriptaculous/effects.js
+SHA1 914db330c7fe585dfeddce713558f04328fb51db share/web/static/js/scriptaculous/scriptaculous.js
+SHA1 cc2e31820eed69ae87b1b2befa50e8c4a8519342 share/web/static/js/scriptaculous/slider.js
+SHA1 6b42a40cac7d45f9fd6665e18c4e494704eff9e3 share/web/static/js/scriptaculous/unittest.js
+SHA1 f254696f59ab11c2373c79ba0e6f303d8ac4f71e share/web/static/js/setup_jsan.js
+SHA1 9872547d7c43a3ddb3280fe4e022912b06d59239 share/web/static/js/yui/calendar.js
+SHA1 b378c19c0bbde98f1c7873ca61bb749a63159235 share/web/static/js/yui/container.js
+SHA1 7ddb98d87fa5e228e19c0ce9da2c8d14c8fb8be1 share/web/static/js/yui/dom.js
+SHA1 cb5c1c0e7e8803c95c2f0e313bbeb5a3431f1fba share/web/static/js/yui/event.js
+SHA1 2d81827326f37e25ba95c248498096759f3433c5 share/web/static/js/yui/tabview.js
+SHA1 71c27fdb9e3d24f628a820638ff529098cdd7821 share/web/static/js/yui/yahoo.js
+SHA1 05359f0e32b4bebd965707788228704167689d3c share/web/templates/=/subs
+SHA1 5cfcf2d84b5548b2990efa52e51d3f15a47816b4 share/web/templates/__jifty/admin/_elements/nav
+SHA1 6a096f8d324464e6ad6407442faf7b2af0ecdc7e share/web/templates/__jifty/admin/action/dhandler
+SHA1 1b55e3945405d09df59f0a40182f06f8f59a2e97 share/web/templates/__jifty/admin/autohandler
+SHA1 51eb5ec4ebe6ad25eac8461c132193ded8ca2640 share/web/templates/__jifty/admin/fragments/list/header
+SHA1 80a5f7d081589dba73766f86a79145e997500655 share/web/templates/__jifty/admin/fragments/list/list
+SHA1 a73b777585f562210eccba19f007e2ac89b428c4 share/web/templates/__jifty/admin/fragments/list/new_item
+SHA1 ed176f653007f8a08e88355bfd68e1b0fc645723 share/web/templates/__jifty/admin/fragments/list/search
+SHA1 a118ac016c645629ed720d9626b36266089eee58 share/web/templates/__jifty/admin/fragments/list/update
+SHA1 9d339e7acebf3e929b15c1d8b4642ecbddf7f4b6 share/web/templates/__jifty/admin/fragments/list/view
+SHA1 559940ee5c2e5b56f3a438f377ea110018e20b18 share/web/templates/__jifty/admin/index.html
+SHA1 b0ceaf320862d14064c55a17f72e91024690371d share/web/templates/__jifty/admin/model/dhandler
+SHA1 bdaeeff0c2522a8509d4da45be703d1e8cf5c96c share/web/templates/__jifty/autocomplete.xml
+SHA1 bfeb6c28df0bc0c78119c9ff90f8051e5f6adcc2 share/web/templates/__jifty/css/dhandler
+SHA1 da39a3ee5e6b4b0d3255bfef95601890afd80709 share/web/templates/__jifty/empty
+SHA1 f85d8cae8a2df3ee51b868a744a9e9af21925e0c share/web/templates/__jifty/error/_elements/error_text
+SHA1 e2d761b0b92f818eb2bbb91a15cef0a2471d2245 share/web/templates/__jifty/error/_elements/wrapper
+SHA1 2e8a971eb5623b245d6ed8f551315842a6bfb251 share/web/templates/__jifty/error/autohandler
+SHA1 0cf3e3f2f8447ea5f66fd22d325c578448129718 share/web/templates/__jifty/error/dhandler
+SHA1 78e1e0af483fa3bdac2de4e5a9d82486d67b8537 share/web/templates/__jifty/error/error.css
+SHA1 6834dbbfe494846bb3a70f1f8d6735c4f8c1b326 share/web/templates/__jifty/error/mason_internal_error
+SHA1 b8793db1dd47943dc83173f02f2227f9ca310235 share/web/templates/__jifty/halo
+SHA1 7fbdc70a9c0481d1e67b6154178173d434206fd9 share/web/templates/__jifty/js/dhandler
+SHA1 6a50927b6d7c7f5f2048a691299585cb2dd05677 share/web/templates/__jifty/online_docs/autohandler
+SHA1 d0116574b44622720db7a9efc7fafdf4002ed012 share/web/templates/__jifty/online_docs/content.html
+SHA1 bd81ff4e458c5e1a76c131dce40ffd71fd6a76f7 share/web/templates/__jifty/online_docs/index.html
+SHA1 630b516dede6767e8185de8211e6affd7b564cfe share/web/templates/__jifty/online_docs/toc.html
+SHA1 c31026ba4fa88814a705651878cb89f086f43f9d share/web/templates/__jifty/validator.xml
+SHA1 8ec2abf3fb43be8217f7a0af4054cf40f7e1e47e share/web/templates/__jifty/webservices/json
+SHA1 bbe1d4af8ab26a9e7d528d819bb271b591172f3c share/web/templates/__jifty/webservices/xml
+SHA1 c7384fcdf1ee7a3e347388c6619f6aa2f297656e share/web/templates/__jifty/webservices/yaml
+SHA1 8145ab6043a7bbd70e1bfa33e068fb741f4ee3a5 share/web/templates/_elements/header
+SHA1 f36e8e0557117d8fdcebad74694fac718101ce27 share/web/templates/_elements/keybindings
+SHA1 0d84d7af43685d7161fc800a2c8b464b8586c57d share/web/templates/_elements/menu
+SHA1 4da6700ff1fdc390436ff05906f4df979c44d572 share/web/templates/_elements/nav
+SHA1 b74c8647eb89e1e189d3cefbbfae51da608edcff share/web/templates/_elements/page_nav
+SHA1 c255d8da7c3eb68fc66f9f7c041c92b247489fcc share/web/templates/_elements/sidebar
+SHA1 bc8991085ddd0c664bb57a1ad4a79e9bca1807ea share/web/templates/_elements/wrapper
+SHA1 35adfc3fd3cbf1c2c3310dd2c2a7eb7317495ced share/web/templates/autohandler
+SHA1 106fc0286e2aff5a01d9a623c37fc807451f2037 share/web/templates/dhandler
+SHA1 347e3ad8491b742c4cd8e63e77fb4cae2152034c share/web/templates/helpers/calendar.html
+SHA1 1bd17a07884f71740a048c41b67ac9b06915bf76 share/web/templates/index.html
+SHA1 7f9dae91a9bfc2743eec1d7aaf78e16fc9f1baba t/00-load.t
+SHA1 1e1a7b63b3ea8d2712214eb28b5fe69aae76da1d t/01-dependencies.t
+SHA1 ade22974f54a6e0991e14be587be0c9797e72ed5 t/01-test-web.t
+SHA1 95fe956e7bae756a7bc25e65a6761ee0c64981e7 t/01-version_checks.t
+SHA1 4fa0e0143339298278c5e22a58236c6b71555508 t/02-connect.t
+SHA1 46221e3b0272c3ef2f5ce0032239dc3aba1d2e04 t/03-form-protocol.t
+SHA1 411bf10441c2c9312b0a245c6a7bef73e001875e t/03-is_passing-no_plan.t
+SHA1 70d3ae1817b53aca414debcdede37425538b3c0a t/03-is_passing.t
+SHA1 69172edb1ef6e20ec71a08ca49b7bed474c3fb07 t/03-test-mailbox.t
+SHA1 53a150bc6dff7517e22624008baff4ed3bec993d t/04-test_file.t
+SHA1 b3e2d70996dbbe82510a8e75646b83b96d0b566a t/05-dispatcher.t
+SHA1 e53c8d489eb565025b1a5ab2eda6e9757ccaf0a8 t/06-forms.t
+SHA1 0712de44cbe60eea251a1955b0bcb218f22adf4a t/07-limit-actions.t
+SHA1 38ab90a10fc0cbbbfc3205e543312950302f37a2 t/08-client.t
+SHA1 ea9587b57587f6b9b5e02e3d30b96807f7b62200 t/09-url.t
+SHA1 a7e9e7792684ccbd1500b4ad88c97a5bc1f7dd54 t/10-i18n.t
+SHA1 d571f6fae9d1a33060fda8c89951492a02b1af01 t/11-config-files.t
+SHA1 3b834f5b8a6371c502eb0dc1f1cb9d79a5257c7a t/12-param-schema.t
+SHA1 59c44900b1cb957d262f96363ceff21b46e0d598 t/99-pod-coverage.t
+SHA1 bb0da54f2b3f2d7955baa41ee458cb3d1887f475 t/99-pod.t
+SHA1 a7dc1f376cac630ea28d2965e561469deb951cc7 t/Continuations/bin/jifty
+SHA1 adbc53cbd328b4d49d3336586fe8f7b7124da970 t/Continuations/lib/Continuations/Action/CrossBridge.pm
+SHA1 01ebcf50d361afff117bfbb14125470da3010f90 t/Continuations/lib/Continuations/Action/GetGrail.pm
+SHA1 55c7de13306c111504b1c9a10fb3147a3d8be79f t/Continuations/lib/Continuations/Dispatcher.pm
+SHA1 904ccf244ddafa2cf756e2de1f251b0962399750 t/Continuations/share/web/templates/autohandler
+SHA1 3b7dfa8465eba725b1322a16c89e86db5aeda1e0 t/Continuations/share/web/templates/black-knight-color.html
+SHA1 33dec4df44d11fb3c62a83a2bbb9e21b82942a35 t/Continuations/share/web/templates/black-knight-name.html
+SHA1 3b57df6661dd414469de257a9229b0c64a86f028 t/Continuations/share/web/templates/black-knight-quest.html
+SHA1 b69550b92c17e7749ca64b299e7e15bcd8596eae t/Continuations/share/web/templates/black-knight.html
+SHA1 a57494ba5021699868a520313332b08eee3bb1e9 t/Continuations/share/web/templates/help-help.html
+SHA1 6831427bd1a041b49a320d60ef7baf2e2e62be1a t/Continuations/share/web/templates/index-help.html
+SHA1 4acb52a5373144d2cb5ccb7a39961e5306c8b5ee t/Continuations/share/web/templates/index.html
+SHA1 3b7b51b4428dcbf0b9b1d55c39fd139a3ee4868a t/Continuations/t/00-prototype.t
+SHA1 c36179582e4dfabdc7b39cbd8196f187140e24eb t/Continuations/t/01-raw-api.t
+SHA1 313bd1afbbd9e066db03080e99766961f9fba2a8 t/Continuations/t/02-api.t
+SHA1 f68f215bb0d9eae9054185b1335ae1fdd0f82708 t/Continuations/t/03-gc.t
+SHA1 acb9008e28ca12dfdc63d007f4f9cad74144b09f t/Continuations/t/04-before-blocks.t
+SHA1 5919e0929f27b3abcd3b6ee4148d2306594ee7dd t/DateTime.t
+SHA1 29a9d0b63d274805fe33c3123faf1a5f871a0e24 t/Jifty.pm
+SHA1 a7dc1f376cac630ea28d2965e561469deb951cc7 t/Mapper/bin/jifty
+SHA1 b381fb60f53d16f8846f7011b8f8a862eaa834c0 t/Mapper/lib/Mapper/Action/CrossBridge.pm
+SHA1 6bee8bba7db76902f300f69bbc7ffbd045380074 t/Mapper/lib/Mapper/Action/GetGrail.pm
+SHA1 904ccf244ddafa2cf756e2de1f251b0962399750 t/Mapper/share/web/templates/autohandler
+SHA1 14a865ad9c903f69dffa595c463f343dd29f62e2 t/Mapper/share/web/templates/index.html
+SHA1 3b7b51b4428dcbf0b9b1d55c39fd139a3ee4868a t/Mapper/t/00-prototype.t
+SHA1 548bf96ee16a84e3793d89cc0dd68ab67bd03f66 t/Mapper/t/01-raw-api.t
+SHA1 c9c9c8fa7e4284baecf87967eef108715068d249 t/Mapper/t/02-api.t
+SHA1 a7dc1f376cac630ea28d2965e561469deb951cc7 t/TestApp-Plugin-REST/bin/jifty
+SHA1 4762d5e154fcbeb0b188a1ecb90c4997403c9d24 t/TestApp-Plugin-REST/etc/config.yml
+SHA1 61845f11966aadecf3bb885fcc5b33ef66e9637f t/TestApp-Plugin-REST/lib/TestApp/Plugin/REST/Action/DoSomething.pm
+SHA1 3670c345f54846479d42b9636e0484130e1e72c1 t/TestApp-Plugin-REST/lib/TestApp/Plugin/REST/Dispatcher.pm
+SHA1 2bc709eb73c4f72267835a06f07de3bc9fe0adba t/TestApp-Plugin-REST/lib/TestApp/Plugin/REST/Model/Group.pm
+SHA1 4ed01e5665fe265e1d45bfcce37a57e8c3087605 t/TestApp-Plugin-REST/lib/TestApp/Plugin/REST/Model/User.pm
+SHA1 5184bbae7c9a4653841156f54b34bac4abf7d54f t/TestApp-Plugin-REST/t/00-model-User.t
+SHA1 3b7b51b4428dcbf0b9b1d55c39fd139a3ee4868a t/TestApp-Plugin-REST/t/00-prototype.t
+SHA1 b6c65e7f47ff136c7d370b85cf01f27537dd81ff t/TestApp-Plugin-REST/t/01-config.t
+SHA1 3e915c7d6a7c7a0fee3246da9b1f8e782b5a317a t/TestApp-Plugin-REST/t/02-basic-use.t
+SHA1 a7dc1f376cac630ea28d2965e561469deb951cc7 t/TestApp/bin/jifty
+SHA1 f9a9321b803e4f248ecdd86fd71613164c01bd86 t/TestApp/lib/TestApp/Action/DoSomething.pm
+SHA1 f93118ca17be86a7c171ee47864d7149baf7344c t/TestApp/lib/TestApp/Action/DoSomethingElse.pm
+SHA1 6e27f855429d18181d6c5a0de6b83492fa5c6219 t/TestApp/lib/TestApp/CurrentUser.pm
+SHA1 de8a0bb9c5dfe2ceb00f38c6baf66627f3b361b2 t/TestApp/lib/TestApp/Dispatcher.pm
+SHA1 3424b48c4def8e714a6508afa7a43569075131c8 t/TestApp/lib/TestApp/Model/User.pm
+SHA1 67f41db40d62b81d71cb60c542695e0d7e6d393d t/TestApp/share/web/static/images/pony.jpg
+SHA1 1e4b29a138e61f49c1ceffe50c15ed0a087613fc t/TestApp/share/web/templates/currentuser
+SHA1 a2e7bf8d2d52bbaf360af24b4ffc00c68d7e31f2 t/TestApp/share/web/templates/dispatch/basic
+SHA1 d1e244371109ce216bfd6b9ac03374737461577c t/TestApp/share/web/templates/dispatch/basic-show
+SHA1 44795ddb863c9c32c05678bf8288c9816e3366a9 t/TestApp/share/web/templates/dosomethingelse
+SHA1 f5870c2fb3222b86d97f14bdf8155821c887987b t/TestApp/share/web/templates/editform
+SHA1 ef0db81c421ba89231ea6d72f355b7dd8fd5e8e2 t/TestApp/share/web/templates/index.html
+SHA1 2f721db97a3b571d0006f6ed9a0d0c8bffef8642 t/TestApp/share/web/templates/manual_redirect
+SHA1 9a50bb56338896f9cd50b7098a9e28397ad28a34 t/TestApp/share/web/templates/somedir/dhandler
+SHA1 350ab77f7c18d826ed91eaf251f3f80ebd9605ba t/TestApp/t/00-model-User.t
+SHA1 3b7b51b4428dcbf0b9b1d55c39fd139a3ee4868a t/TestApp/t/00-prototype.t
+SHA1 94a1fe86cc34fbdce9087f30a69a859063ca8714 t/TestApp/t/01-config.t
+SHA1 6c6726cd87697675c80828825ff34109daec7f86 t/TestApp/t/02-dispatch.t
+SHA1 d438a2c8aa2fa15c80da4f2a44ecfe65856b58f8 t/TestApp/t/03-static.t
+SHA1 dc8e0ea29839c6dd50843d7c95a907874f6d5472 t/TestApp/t/04-sessions.t
+SHA1 071288e6e1c64cef819f8e3adb7b20a8c7044804 t/TestApp/t/05-actions-before-redirect.pm
+SHA1 79dc79df5690e3d7b418bf6475c13ea0d4c1f8a4 t/TestApp/t/05-editactions-Cachable.t
+SHA1 47ac4941336e8bc00821e89b0ccfdf9232be53ca t/TestApp/t/05-editactions-Record.t
+SHA1 1a425b17fe88ed3c6783e7894bc514641ad0fffa t/TestApp/t/06-validation.t
+SHA1 0a73294c477197748994580ed1615cbe42bfa335 t/TestApp/t/07-sandboxing.t
+SHA1 842377402228a26ff444c565831bc560a66c0302 t/TestApp/t/08-notifications.t
+SHA1 1fce112ee319adb146b787ad23ef3b050759063b t/TestApp/t/09-redirect.t
+SHA1 55ba141d6c73a6dfa7ccccb6cb9f32253ed8decb t/TestApp/t/10-compress.t
+SHA1 18667b3bdcbe7b9dd2715e16c8bbffe51c28548f t/TestApp/t/11-current_user.t
+SHA1 019605c6e627bf65ee3d2ed1b8d2b34fc8e10853 t/TestApp/t/12-search.t
+SHA1 69401ad0579fa743f087731536229d2806dd1d6a t/TestApp/t/config-Cachable
+SHA1 710c4b0faaea46f90a7b071e5396541a59ed0ff1 t/TestApp/t/config-Record
+SHA1 30274351a6eb9342daef843ffb8a2aafee38afb4 t/TestApp/t/instance_id.t
+SHA1 ee548850452b377e08f36a9269c1b8f7911bdb2d t/TestApp/t/regex_meta_in_path_info.t
+SHA1 c8fb21f31b593627b38129ee9dd41eaf9c556ced t/lib/Jifty/SubTest.pm
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.3 (GNU/Linux)
+
+iD4DBQFFromfEi9d9xCOQEYRAjV1AJ9gxbyucs3vXDll+msp25TGsP4ZlwCUDDbz
+1ktLZJXD6wX2Hkn/232QNg==
+=ItK4
+-----END PGP SIGNATURE-----

Added: jifty/branches/schema-plugins/bin/build_par
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/bin/build_par	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,23 @@
+#!/bin/sh
+pp  -z 9 -s \
+	-o ../par/jifty-`perl -e'print $^O'`-`grep '^version' META.yml|cut -d" " -f 2` \
+	-M `grep :: Makefile.PL  |cut -d\' -f 2|grep -v '^use' |xargs -n 1  echo -n " -M "` \
+	-I lib -I inc\
+	-M `(cd inc ;find -type f -name \*.pm|cut -c 3-| grep -v '^Module/Install.pm$'|xargs -n 1  echo -n " -M ")` \
+	-M `(cd lib ;find -type f -name \*.pm|cut -c 3-| xargs -n 1  echo -n " -M ")` \
+	-a 'share/;lib/auto/Jifty/' \
+	bin/jifty
+
+
+# Make a parball
+# Execute code to get dependencies
+# Compress it a lot
+# -s 
+# name it as "jifty-platform-version" in jifty/par (next to jifty-trunk)
+# include all our dependencies from Makefile.PL
+# use the "lib" directory
+# include all of Jifty's libs to pick up dependencies
+# include Jifty's share directory in the par
+# do all this by sourcing bin/jifty
+
+

Added: jifty/branches/schema-plugins/bin/jifty
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/bin/jifty	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,15 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+use File::Basename qw(dirname); 
+use UNIVERSAL::require;
+
+BEGIN {
+    Jifty::Util->require or die $UNIVERSAL::require::ERROR;
+    my $root = Jifty::Util->app_root;
+    unshift @INC, "$root/lib" if ($root);
+}
+
+use Jifty::Script;
+local $SIG{INT} = sub { warn "Stopped\n"; exit; };
+Jifty::Script->dispatch();

Added: jifty/branches/schema-plugins/bin/runcover
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/bin/runcover	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,4 @@
+#!/bin/sh
+# XXX: this should be in makefile target, but i have no idea about how to do that in M::I
+
+env JIFTY_TESTSERVER_COVERAGE=-db,`pwd`/cover_db HARNESS_PERL_SWITCHES=-MDevel::Cover make test

Added: jifty/branches/schema-plugins/bin/service
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/bin/service	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,32 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+use LWP::UserAgent;
+use Getopt::Long;
+
+my($class, $host, $path, $format, $moniker);
+GetOptions("class=s" => \$class,
+           "host=s" => \$host,
+           "path=s" => \$path,
+           "format=s" => \$format,
+           "moniker=s" => \$moniker);
+$format  ||= "yaml";
+$host    ||= "http://localhost:8888";
+$path    ||= "/__jifty/webservices/$format";
+$moniker ||= "moniker";
+
+my %args = @ARGV;
+
+my $ua = LWP::UserAgent->new;
+my $res = $ua->post("$host$path", {
+                           "J:A-$moniker" => $class,
+                           map {("J:A:F-$_-$moniker" => $args{$_})} keys %args
+                          });
+
+if ($res->is_success) {
+    print $res->content;
+} else {
+    die $res->status_line;
+}
+

Added: jifty/branches/schema-plugins/bin/xgettext
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/bin/xgettext	Mon Jan 29 21:05:02 2007
@@ -0,0 +1 @@
+find {bin,lib,share/web/templates/} -type f |xargs xgettext.pl -p share/po -o en.po

Added: jifty/branches/schema-plugins/debian/README
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/debian/README	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,15 @@
+This directory contains Debian packaging files for Jifty.
+
+There is a debian repository at:
+
+ deb http://debian.jifty.org/debian sarge jifty
+
+It contains jifty packages as well as some jifty plugins, updates for
+sarge and ubuntu 6.06 LTS
+
+Please let us know if there are any issues by sending mail to the
+jifty mailing list and we'll try and fix them.
+
+Bart Bunting
+Yves Agostini
+

Added: jifty/branches/schema-plugins/debian/changelog
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/debian/changelog	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,50 @@
+jifty (0.70117-1) unstable; urgency=low
+
+  * New cpan release
+
+ -- AGOSTINI Yves <agostini at univ-metz.fr>  Thu, 18 Jan 2007 08:52:53 +0100
+
+jifty (0.70116-1) unstable; urgency=low
+
+  * New cpan release
+
+ -- AGOSTINI Yves <agostini at univ-metz.fr>  Wed, 17 Jan 2007 08:46:31 +0100
+
+jifty (0.61123-1) unstable; urgency=low
+
+  * New cpan release 
+
+ -- AGOSTINI Yves <agostini at univ-metz.fr>  Wed, 24 Nov 2006 21:08:26 +0200
+
+jifty (0.60912-2) unstable; urgency=low
+
+  * Add missing dependencies, 
+  * Add fr.po 
+
+ -- AGOSTINI Yves <agostini at univ-metz.fr>  Wed, 25 Oct 2006 09:46:26 +0200
+
+jifty (0.60912-1) unstable; urgency=low
+
+  * Change version and clean dependencies on module-refresh-perl and
+    locale-maketext-lexicon for sarge
+
+ -- AGOSTINI Yves <agostini at univ-metz.fr>  Mon, 23 Oct 2006 10:55:17 +0200
+
+jifty (0.60728-3) unstable; urgency=low
+
+  * Missed some date dependencies
+
+ -- Bart Bunting <bart at ursys.com.au>  Fri, 25 Aug 2006 09:30:28 +1000
+
+jifty (0.60728-2) unstable; urgency=low
+
+  * More dependencies, we're nearly there seems I missed some.
+
+ -- Bart Bunting <bart at bunting.net.au>  Fri, 25 Aug 2006 06:55:04 +1000
+
+jifty (0.60728-1) unstable; urgency=low
+
+  * Initial debianisation.
+
+ -- Bart Bunting <bart at ursys.com.au>  Tue, 22 Aug 2006 10:39:31 +1000
+

Added: jifty/branches/schema-plugins/debian/compat
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/debian/compat	Mon Jan 29 21:05:02 2007
@@ -0,0 +1 @@
+5

Added: jifty/branches/schema-plugins/debian/control
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/debian/control	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,118 @@
+Source: jifty
+Section: web
+Priority: optional
+Maintainer: Bart Bunting <bart at debian.org>
+Build-Depends-Indep: debhelper (>> 5), libmodule-corelist-perl,
+ libdevel-cover-perl, libmodule-scandeps-perl, libpod-simple-perl,
+ libtest-base-perl, libtest-www-mechanize-perl (>> 1.04),
+ libtest-pod-coverage-perl
+
+Package: jifty
+Section: perl
+Architecture: all
+Depends: ${perl:Depends}, libjifty-perl
+Description: Jifty perl libraries
+ Yet another web framework.
+ .
+ What's cool about Jifty? (Buzzwords)
+ .
+ DRY (Don't Repeat Yourself)
+        Jifty tries not to make you say things more than once.
+ .
+ Full-stack
+        Out of the proverbial box, Jifty comes with one way to do everything
+        you should need to do: One database mapper, one templating system,
+        one web services layer, one AJAX toolkit, one set of handlers for
+        standalone or FastCGI servers. We work hard to make all the bits
+        play well together, so you don't have to.
+ .
+ Continuations
+        With Jifty, it's easy to let the user go off and do something else,
+        like fill out a wizard, look something up in the help system or go
+        twiddle their preferences and come right back to where they were.
+ .
+ Form-based dispatch
+        This is one of the things that Jifty does that we've not seen
+        anywhere else. Jifty owns your form rendering and processing. This
+        means you never need to write form handling logic. All you say is "I
+        want an input for this argument here" and Jifty takes care of the
+        rest. (Even autocomplete and validation)
+ .
+ A Pony
+        Jifty is the only web application framework that comes with a pony.
+ .
+ This package provides Jifty scripts.
+
+Package: libjifty-perl
+Section: perl
+Architecture: all
+Depends: ${perl:Depends}, perl (>> 5.8.3),
+ libapp-cli-perl (>> 0.03), libcache-cache-perl,
+ libcalendar-simple-perl, libclass-accessor-perl,
+ libclass-container-perl, libclass-data-inheritable-perl,
+ perl-modules, libcgi-cookie-splitter-perl, libcgi-simple-perl,
+ libcrypt-cbc-perl, libcrypt-rijndael-perl,
+ libcompress-zlib-perl, libcss-squish-perl (>> 0.05), 
+ libdbd-sqlite3-perl, libdata-page-perl, libossp-uuid-perl,
+ libdatetime-perl, libdate-manip-perl, libemail-folder-perl,
+ libemail-localdelivery-perl, libemail-mime-perl,
+ libemail-mime-creator-perl, libemail-mime-contenttype-perl,
+ libemail-send-perl (>> 1.99_01), libemail-simple-perl,
+ libemail-simple-creator-perl, libexporter-lite-perl,
+ libfile-find-rule-perl, libfile-mmagic-perl,
+ libfile-sharedir-perl (>> 0.04), libhtml-parser-perl,
+ libhtml-lint-perl, libhtml-mason-perl (>> 1.31), 
+ libwww-perl, libhttp-server-simple-perl (>> 0.26), 
+ libhttp-server-simple-recorder-perl, libhash-merge-perl, libhook-lexwrap-perl,
+ libipc-pubsub-perl (>> 0.22), libjifty-dbi-perl (>> 0.31),
+ liblocale-maketext-lexicon-perl, liblog-log4perl-perl,
+ libmime-types-perl, libmodule-pluggable-perl (>> 3.1),
+ libmodule-corelist-perl, libmodule-refresh-perl,
+ libmodule-scandeps-perl, libobject-declare-perl (>> 0.13),
+ libparams-validate-perl, libscalar-defer-perl (>> 0.06),
+ libstring-koremutake-perl, libsql-reservedwords-perl,
+ libtest-base-perl, libuniversal-require-perl, liburi-perl,
+ libxml-writer-perl (>> 0.601), libxml-simple-perl,
+ libxml-xpath-perl, libversion-perl, libyaml-syck-perl (>> 0.72), 
+ libyaml-perl (>> 0.35), libjson-perl (>> 0.01),
+ libpod-simple-perl, 
+ libsub-exporter-perl, libcache-memcached-perl, liblog-dispatch-perl
+Recommends: libmodule-install-perl,
+ libtest-pod-coverage-perl, libtest-www-mechanize-perl (>> 1.04), libwww-mechanize-perl (>> 1.12),
+ libclass-accessor-named-perl, libdevel-cover-perl,
+ libmodule-install-perl, libpar-dist-fromcpan-perl, libtest-mockobject-perl,
+ libtest-mockmodule-perl,
+ libcgi-fast-perl, libapache-mod-fastcgi,
+ libjifty-plugin-editinplace-perl, libjifty-plugin-login-perl
+Description: Jifty perl libraries
+ Yet another web framework.
+ .
+ What's cool about Jifty? (Buzzwords)
+ .
+ DRY (Don't Repeat Yourself)
+        Jifty tries not to make you say things more than once.
+ .
+ Full-stack
+        Out of the proverbial box, Jifty comes with one way to do everything
+        you should need to do: One database mapper, one templating system,
+        one web services layer, one AJAX toolkit, one set of handlers for
+        standalone or FastCGI servers. We work hard to make all the bits
+        play well together, so you don't have to.
+ .
+ Continuations
+        With Jifty, it's easy to let the user go off and do something else,
+        like fill out a wizard, look something up in the help system or go
+        twiddle their preferences and come right back to where they were.
+ .
+ Form-based dispatch
+        This is one of the things that Jifty does that we've not seen
+        anywhere else. Jifty owns your form rendering and processing. This
+        means you never need to write form handling logic. All you say is "I
+        want an input for this argument here" and Jifty takes care of the
+        rest. (Even autocomplete and validation)
+ .
+ A Pony
+        Jifty is the only web application framework that comes with a pony.
+ .
+ This package provides the Jifty perl libraries.
+

Added: jifty/branches/schema-plugins/debian/rules
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/debian/rules	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,80 @@
+#!/usr/bin/make -f
+# This debian/rules file is provided as a template for normal perl
+# packages. It was created by Marc Brockschmidt <marc at dch-faq.de> for
+# the Debian Perl Group (http://pkg-perl.alioth.debian.org/) but may
+# be used freely wherever it is useful.
+#
+# It was later modified by Jason Kohles <email at jasonkohles.com>
+# http://www.jasonkohles.com/ to support Module::Build installed modules
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+# If set to a true value then MakeMaker's prompt function will
+# always return the default without waiting for user input.
+export PERL_MM_USE_DEFAULT=1
+
+PACKAGE=$(shell dh_listpackages)
+
+ifndef PERL
+PERL = /usr/bin/perl
+endif
+
+#TMP     =$(CURDIR)/debian/$(PACKAGE)
+TMP     =$(CURDIR)/debian/libjifty-dbi-perl
+
+build: build-stamp
+build-stamp:
+	dh_testdir
+
+	# Add commands to compile the package here
+	$(PERL) Makefile.PL installdirs=vendor
+	$(MAKE)
+	$(MAKE) test
+	touch build-stamp
+
+clean:
+	dh_testdir
+	dh_testroot
+
+	# Add commands to clean up after the build process here
+	-$(MAKE) clean
+	dh_clean build-stamp install-stamp
+
+install: build install-stamp
+install-stamp:
+	dh_testdir
+	dh_testroot
+	dh_clean -k
+
+	# Add commands to install the package into debian/$PACKAGE_NAME here
+	$(MAKE) install DESTDIR=$(CURDIR)/debian/libjifty-perl
+	find debian/libjifty-perl --name "*.svn*" | xargs rm -rf
+	touch install-stamp
+
+binary-arch:
+# We have nothing to do by default.
+
+binary-indep: build install
+	dh_testdir
+	dh_testroot
+#	dh_installcron
+#	dh_installmenu
+#	dh_installexamples
+	dh_installdocs README
+	dh_installchangelogs Changelog
+	dh_perl
+	dh_link
+	dh_strip
+	dh_compress
+	dh_fixperms
+	dh_installdeb
+	dh_gencontrol
+	dh_md5sums
+	dh_builddeb
+
+source diff:                                                                  
+	@echo >&2 'source and diff are obsolete - use dpkg-source -b'; false
+
+binary: binary-indep binary-arch
+.PHONY: build clean binary-indep binary-arch binary

Added: jifty/branches/schema-plugins/doc/ajax-upgraded-links
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/doc/ajax-upgraded-links	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,39 @@
+When I click "next page", 
+
+
+    If I don't have ajax, reload the whole page, with the changed helper parameters.
+
+
+        To reload the whole page, I'm going to need:
+
+            * All args to the current page.
+            * Which args should go away
+            * Which new args should be added.
+
+
+
+        Basically, this is 
+        
+            BASE PATH: framework->current_url
+            
+            PARAMS:  framework->current_request_args
+                with this_element->new_version_params replacing
+                      this_element->current_version_params
+                
+
+
+    if I have ajax, refetch the enclosing div, with the changed parameters.
+        To reload just that section of the page, I'm going to need:
+
+            * The path to the bit I want to replace.
+            * The name of the surrounding div to replace it with?
+            * The moniker? of the section to replace.
+            * The parameters I should pass to that section.
+             Are those the same as the things for the next-page link?
+
+
+        Basically, this would be: 
+        
+            BASE PATH this_element->direct_url (no params?)
+                  and replace  this_element->current_version_params
+                  with this_element->new_version_params

Added: jifty/branches/schema-plugins/doc/building_a_par
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/doc/building_a_par	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,93 @@
+Incomplete instructions
+
+ $ pp `find lib/ -name \*pm |perl -ne '$_ =~ s/.pm$//;print "-M ". join("::",grep { ! /(?:lib|\-)/ } split("/",$_)) '`  -I lib bin/jifty
+use warnings;
+use strict;
+use Cwd qw(cwd);
+use Module::CoreList;
+use File::Copy;
+use File::Path qw(mkpath);
+use File::Spec::Functions qw(splitdir catfile catdir);
+use CPAN;
+use CPAN::Config;
+use YAML;
+use Time::Local;
+
+ at INC = grep {! /local/} @INC; # don't want cpan.pm to make decisions based locally installed modules;
+use vars qw/$INSTALLED $FAILED $SKIP_DEPS_FOR/;
+$INSTALLED     = {};
+$FAILED        = {};
+$SKIP_DEPS_FOR = { };
+process_cpan();
+
+sub process_cpan {
+
+    #    my $self = shift;
+    my $path = cwd();
+    my @modules = @ARGV;
+
+    # We install Scalar::Util first to break a scary dependency loop.
+    mkdir "$path/.cpan";
+    mkdir "$path/.cpan/build";
+    print join "\n", @modules, "\n";
+
+    unshift @INC, '$path/lib';
+    $ENV{'PERL5LIB'} = "$path/lib";
+
+    $CPAN::Config->{build_dir}            = "$path/.cpan/build";
+    $CPAN::Config->{cpan_home}            = "$path/.cpan/build";
+    $CPAN::Config->{histfile}             = "$path/.cpan/histfile;";
+    $CPAN::Config->{keep_source_where}    = "$path/.cpan/sources";
+    $CPAN::Config->{prerequisites_policy} = "follow";
+    $CPAN::Config->{makepl_arg}
+        = "PREFIX=$path PERL5LIB=$path/lib LIB=$path/lib INSTALLMAN1DIR=$path/man/man1 INSTALLMAN3DIR=$path/man/man3 INSTALLBIN=$path/bin INSTALLSCRIPT=$path/bin";
+    $CPAN::Config->{make_install_arg} =~ s/UNINST=1//;
+
+    my @objs = map { CPAN::Shell->expand( 'Module', $_ ) } @modules;
+    for my $i ( 0 .. $#objs ) {
+        delete $objs[$i]
+            if grep { $_->{RO}->{CPAN_FILE} eq $objs[$i]->{RO}->{CPAN_FILE} }
+            @objs[ $i + 1 .. $#objs ];
+    }
+
+    foreach my $mod (@modules) {
+
+        #foreach my $mod ( grep { defined $_ } @objs ) {
+        install_mod($mod);
+    }
+    print YAML::Dump($FAILED);
+}
+
+sub install_mod {
+    my $mod_name = shift;
+    my $version = shift;
+    my $mod = CPAN::Shell->expand( 'Module', $mod_name );
+    my $first_in = Module::CoreList->first_release($mod_name => $version);
+    if ( defined $first_in and $first_in <= 5.00803 ) { print "Skipping $mod_name. It's been core since $first_in\n"; return }
+    if ( $mod->distribution->isa_perl ) { print "Skipping $mod_name. It's only in the core. OOPS\n";return}
+    if ( $INSTALLED->{ $mod->cpan_file } ) { print "Skipping $mod_name. We've already installed it\n";return}
+
+    if ( $FAILED->{ $mod->cpan_file } >= 3 ) {
+        print YAML::Dump($INSTALLED);
+        print YAML::Dump($FAILED);
+
+        die "We've tried to install "
+            . $mod->distribution->as_string
+            . " twice";
+
+    }
+
+    # Install to local
+    unless ( $SKIP_DEPS_FOR->{$mod_name} ) {
+        $mod->make;
+        my $deps = $mod->distribution->prereq_pm;
+        foreach my $dep ( keys %$deps ) {
+            install_mod($dep => $deps->{$dep});
+        }
+    }
+    #$mod->force();
+    $mod->install;
+    $INSTALLED->{ $mod->cpan_file } = 1;
+}
+
+1;

Added: jifty/branches/schema-plugins/doc/client_side_continuations
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/doc/client_side_continuations	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,129 @@
+01:01 <audreyt> I think DNS is your programming language of choice ;)
+01:01 <obra> *snicker*
+01:01 <obra> . o O {You must have seen the cname on quit.fsck.com }
+01:02 <audreyt> rofl
+01:02 <audreyt> mm, instead of passing cookies
+01:02 <audreyt> and maintain url transparency across post and get
+01:03 <audreyt> just pass the J:C and session token in DNS!]
+01:03 <obra> *groan*
+01:03 <audreyt> deadbeef18405930.hiveminder.com/
+01:03 <obra> alex and I were talking about client-side continuation serialization today
+01:03 <obra> We're about to do continuation garbage collection
+01:03 <audreyt> instead of passing it as part of request_uri
+01:03 <audreyt> in pathinfo
+01:04 <audreyt> this guarantees auth zone separation etc
+01:04 <audreyt> (very bad idea.)
+01:04 <obra> laugh
+01:04 <obra> definitely "Worst Impractical"
+01:04 <audreyt> this is not unlike .NET Passport
+01:05 <audreyt> where you visit something.microsoft.com that redispatch to your domain
+01:05 <audreyt> but there is a reason why it failed.
+01:05 <obra> *nod* OpenId seems to be doing a bit better at it
+01:05 <audreyt> right
+01:05 <audreyt> so, client side CC
+01:05 <audreyt> in hidden fields
+01:05 <obra> I worry about the amount of content you might need to pass around. and the fact that you lose GET support
+01:05 <audreyt> HMAC_SHA1 with server digest
+01:06 <audreyt> for small continuations (practically everything)
+01:06 <audreyt> you can embed it as part of pathuri
+01:06 <obra> except our continuations aren't that small. and when deeply nested you totally lose
+01:06 <audreyt> /=/deadbeefdeadbeefbeefbeef1982398102957190824091840984124/moose.html
+01:06 <audreyt> yeah. in which case you fallback to cookie storage.
+01:07 <obra> oh. you mean having a continuation id in the url and a cookie with the content?
+01:07 <audreyt> yeah
+01:07 <audreyt> the id is the cookie key
+01:07 <audreyt> you can have multipel cookies
+01:07 <audreyt> they can expire using normal cookie expiry semantics
+01:07 <obra> and then every GET or POST pushes all the cookies to the server
+01:07 <audreyt> not neccessarily
+01:08 <obra> using the path restriction in the cookie?
+01:08 <audreyt> the path component protects you
+01:08 <audreyt> the path component protects yothat's what cookies are for
+01:08 <audreyt> ys
+01:08 <obra> hm.
+01:08 <audreyt> self validating
+01:08 <audreyt> in a sense.
+01:08 <obra> you have a compelling argument, madam.
+01:08 <obra> hm
+01:08 <audreyt> I believe it's somewhat original
+01:08 <audreyt> or at least independent invention :)
+01:08 <obra> :)
+01:09 <obra> So. the first step is that alex is getting continuations into their own database table
+01:09 <obra> alex really wanted to sign them, rather than do digest validation
+01:09 <obra> because he wants _no_ server state for a continuation
+01:09 <audreyt> a private key is a state.
+01:09 <audreyt> same as a server secret.
+01:09 <obra> er. sorry. no unique state
+01:09 <audreyt> same.
+01:10 <audreyt> if you do HMAC_SHA1, only the server secret is required
+01:10 <audreyt> not nonce
+01:10 <audreyt> global shared secret
+01:10 <obra> ahhh.
+01:10 <audreyt> cheaper than signing.
+01:10 <obra> I missed that. sorry
+01:10 <audreyt> equally strong.
+01:10 <audreyt> np :)
+01:11 <obra> It's certainly an interesting argument for "how jifty can scale up"
+01:12 <obra> if we have the cookie and url scheme, is there a reason to complicate it with sometimes having hidden form fields?
+01:12 <audreyt> only if you want per-form, as in region, continuation
+01:13 <obra> regions have their own paths ;)
+01:13 <audreyt> good then
+01:14 <audreyt> so, cookie is specced to be min 4k
+01:14 <obra> I will admit that I get twitchy about how easily this is remotely 0wnable if you capture the server secret.
+01:14 <audreyt> and at least 20 per thing
+01:14 <audreyt> that gives 80k min storage
+01:14 <audreyt> in practice the 20 limit is not enforced
+01:14 <audreyt> so you get effectively unlimited storage with splitting
+01:15 <audreyt> I'll note that if you get server secret then you can set up fake forms.
+01:15 <audreyt> both requires owner permission on the share/
+01:16 <audreyt> and really there's little point in worrying at that stage.
+01:16 <obra> that also requires dHa willing dispatcher
+01:16 <obra> Alex's proposed attack was:
+01:16 <audreyt> nod.
+01:16 <obra> push a results message at the user containing "You must change your password. click here"
+01:16 <obra> phishing attack with a valid url
+01:17 <obra> I'd probably be mollified with a randomly generated session key 
+01:17 <obra> and actually have the session key stored server-side and ~nothing else
+01:18 <obra> (Does that make sense?)
+01:19 <audreyt> thinking
+01:19 <audreyt> how is it any different than the old cookie sessionid scheme?
+01:19 <audreyt> I mean the attack
+01:20 <audreyt> persumably the action will always need old passwd as input
+01:20 <audreyt> so it can't be automated
+01:21 <audreyt> I fail to see why client side state has anything to do with this.
+01:21 <obra> jifty action results contain messages.
+01:21 <obra> jifty apps display those messages
+01:21 <obra> the messages are defined to be able to contain html
+01:21 <audreyt> you mean rogue action classes?
+01:21 <obra> imagine an attack that pushes a mini form submitting to a third party into that html
+01:22 <obra> no, someone who redirects you back to hiveminder with a continuation constructed to make it appear that you needed to change your pw
+01:22 <audreyt> it is clearly result-message scrubbing thing...
+01:22 <audreyt> anyway. back
+01:22 <audreyt> if you want to have login/logout
+01:22 <audreyt> and continuations never work across logouts
+01:23 <audreyt> then the server secret is just the session id.
+01:23 <audreyt> i.e. nonce.
+01:23 <audreyt> which you don't give the user in entirety
+01:23 <audreyt> just as you observed
+01:23 <obra> nod
+01:23 <obra> I think that works
+01:23 <audreyt> and for public non-currentuser-related requests
+01:23 <audreyt> it may make sense to have a sessionless form
+01:24 <audreyt> where bookmarks may be shared.
+01:24 <obra> The case that didn't work in my head was with a global nonce.
+01:24 <audreyt> but I think the use case is rarer
+01:24 <audreyt> I think per-session makes most sense.
+01:24 <obra> indeed. It's been my conjecture that even anonymous users can be given sessions.
+01:24 <audreyt> if Inever logout, my bookmarks always work
+01:25 <audreyt> if I logout, they only work if I-or the server- explicitly requested affinity
+01:25 <obra> and the server has the option to deny that request 
+01:25 <audreyt> otherwise it's thrown away, and the logout link should reset the continuation cookies.
+01:25 <audreyt> yes.
+01:25 <audreyt> ok, very good design, I think.
+01:25 <obra> I'm reasonably happy with that, I think
+01:26 <audreyt> woot :)
+01:26 <audreyt> now I must run to $job
+01:26 <obra> shall we throw this log in jifty/doc?
+01:26 <audreyt> already horribly late ;)
+01:26 <audreyt> sure, please do
+01:26 <obra> oops. so sorry.

Added: jifty/branches/schema-plugins/doc/command_naming
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/doc/command_naming	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,10 @@
+Current commands are all nouns. we should be moving to simple active verbs
+    
+    jifty server -> jifty run
+    jifty fastcgi -> jifty run --fastcgi
+    jifty action -> jifty create --action=
+    jifty app   -> jifty create --app=
+    jifty model -> Jifty create --model=
+
+    jifty schema -> jifty setup --schema
+    jifty po    -> jifty setup --lexicon / jifty create --lexicon

Added: jifty/branches/schema-plugins/doc/declarative-test-design
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/doc/declarative-test-design	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,128 @@
+# -*- mode: cperl -*-
+
+# A ``setup'' block indicates tests that must pass for any of the
+# remaining tests to continue.
+
+setup {
+    BTDT::Test->log_in;
+    expect_url(qr{/todo});
+    follow_link 'Groups';
+    expect_url(qr{/groups});
+    expect_content('alpha');
+
+    # These are custom local methods; not Test::WWW::Whatever ones.
+    make_and_verify_group($mech, name => 'my folks', desc => 'stuff');
+    make_and_verify_group($mech, name => 'blue pants', desc => 'some description');
+    make_and_verify_group_trimming($mech, name => '   leading and trailing spaces   ', desc => '  more spaces here  ');
+
+    make_and_verify_group($mech, name => 'other folks');
+}
+
+# Each ``test'' block is its own test. If anything fails, we abort the
+# current test block, and continue with the next one.
+
+test {
+    follow_link 'Groups';
+    follow_link 'New Group';
+    submit_action 'newgroup', { name => 'my folks' };
+
+    expect_url { not => qr{groups/\d+/members} };
+    expect_url qr{/groups/create};
+    find_input { value => "my folks" };
+    expect_content qr{Sorry, but someone else beat you to that name};
+};
+
+test {
+    follow_link 'my folks';
+    follow_link 'Members';
+
+    expect_url qr{/groups/\d+/members};
+    expect_content 'Manage group members';
+    expect_content 'my folks';
+    expect_content qr{Good Test *organizer}
+};
+
+test {
+    follow_link 'my folks';
+    follow_link 'My tasks';
+    follow_link 'Braindump';
+    expect_content 'See more syntax for braindump';
+
+    fill_action 'quickcreate', { text => 'Buy new computer [personal money]' };
+    click { button => 'Create' };
+
+    expect { content => 'Buy new computer' };
+    expect_content_not 'See more syntax for braindump';
+
+    follow_link 'my folks' => 'My tasks' => 'Braindump';
+    submit_action 'quickcreate',
+      { text => 'Buy new computer [personal money]' },
+      { button => 'Create' };
+
+    click 'Edit';
+
+    submit_action find_action('BTDT::Action::UpdateTask'),
+        { owner_id => 'otheruser at example.com' };
+}
+
+test {
+    new_session {
+        log_in 'otheruser at example.com', 'something';
+        click 'unaccepted';
+        page { content => 'Pay off Mafia' };
+        click 'Pay off Mafia';
+
+        # Manual submit_action
+        find_element { name => qr{J:A-(?:\d+)?accept} };
+        submit_form { 'J:A:F-accepted-accept' => 1 };
+
+        page { content       => 'Task accepted',
+               content_lacks => 'denied' };
+
+    };
+};
+
+# Various ways of specifying tests should only run under certain
+# platforms
+
+with ('javascript') => test {
+    
+};
+
+test {
+    browser 'iexplore';
+};
+
+test {
+    browser 'WWW::Mechanize';
+};
+
+test {
+    need_feature 'XMLHTTPRequest';
+};
+
+
+run_all;
+
+
+# A syntax idea from jesse:
+
+flow "Check to see if the user can login" => test {
+    get '/';
+    element '//body' matches /You're not logged in/;
+
+    follow link 'Login';
+
+    my $login_form = element id 'login-form'
+
+    fill $login_form  =>  { 
+        email => 'jesse at fsck.com',
+        password => 'I hate you'
+    }
+
+    click button 'Login!';
+
+}
+
+
+

Added: jifty/branches/schema-plugins/doc/edit-in-place
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/doc/edit-in-place	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,22 @@
+# These are dispatcher rules that halos and error handling need to be
+# able to edit files in place.  These should become a plugin of some
+# sort, when we have that infrastructure.
+
+before '*', run {
+    Jifty->api->allow(qr/^Jifty::Action::Devel/)
+      if Jifty->config->framework('DevelMode');
+};
+
+on qr'^/__jifty/edit/(.*?)/(.*)$', run {
+    my $editor = Jifty->web->new_action(
+        class     => 'Jifty::Action::Devel::FileEditor',
+        moniker   => 'editpage',
+        arguments => {
+            source_path => $2,
+            file_type   => $1,
+        }
+    );
+
+    set editor => $editor;
+    show '/__jifty/edit_file';
+};

Added: jifty/branches/schema-plugins/doc/examples/CounterDemo/Makefile.PL
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/doc/examples/CounterDemo/Makefile.PL	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,6 @@
+use inc::Module::Install;
+name('CounterDemo');
+version('0.01');
+requires('Jifty' => '0.60615');
+
+WriteAll;

Added: jifty/branches/schema-plugins/doc/examples/CounterDemo/bin/jifty
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/doc/examples/CounterDemo/bin/jifty	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,18 @@
+#!/usr/bin/env perl
+
+eval 'exec /usr/bin/env perl  -S $0 ${1+"$@"}'
+    if 0; # not running under some shell
+use warnings;
+use strict;
+use File::Basename qw(dirname); 
+use UNIVERSAL::require;
+
+BEGIN {
+    Jifty::Util->require or die $UNIVERSAL::require::ERROR;
+    my $root = Jifty::Util->app_root;
+    unshift @INC, "$root/lib" if ($root);
+}
+
+use Jifty::Script;
+$SIG{INT} = $SIG{TERM} = sub { warn "Stopped\n"; exit; };
+Jifty::Script->dispatch();

Added: jifty/branches/schema-plugins/doc/examples/CounterDemo/etc/config.yml
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/doc/examples/CounterDemo/etc/config.yml	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,36 @@
+--- 
+framework: 
+  AdminMode: 0
+  ApplicationClass: CounterDemo
+  ApplicationName: CounterDemo
+  Database: 
+    Database: counterdemo
+    Driver: SQLite
+    Host: localhost
+    Password: ''
+    RecordBaseClass: Jifty::DBI::Record::Cachable
+    User: ''
+    Version: 0.0.1
+  DevelMode: 1
+  L10N: 
+    PoDir: share/po
+  LogLevel: INFO
+  Mailer: Sendmail
+  MailerArgs: []
+
+  Plugins: []
+
+  Web: 
+    BaseURL: http://localhost
+    DataDir: var/mason
+    Globals: []
+
+    MasonConfig: 
+      autoflush: 0
+      default_escape_flags: h
+      error_format: text
+      error_mode: fatal
+    Port: 8888
+    ServeStaticFiles: 1
+    StaticRoot: share/web/static
+    TemplateRoot: share/web/templates

Added: jifty/branches/schema-plugins/doc/examples/CounterDemo/share/web/templates/index.html
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/doc/examples/CounterDemo/share/web/templates/index.html	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,23 @@
+<%args>
+$counter => 0
+</%args>
+<%init>
+my $request;
+
+$request = Jifty::Request->new( path => Jifty->web->request->path );
+$request->argument( counter => $counter + 1 );
+my $up = Jifty::Continuation->new( request => $request );
+
+$request = Jifty::Request->new( path => Jifty->web->request->path );
+$request->argument( counter => $counter - 1 );
+my $down = Jifty::Continuation->new( request => $request );
+</%init>
+
+<&| /_elements/wrapper, title => "Continuations counter demo" &>
+
+<h1> The counter is: <% $counter %></h1>
+
+<% Jifty->web->link(call => $down, label => '--') %>
+<% Jifty->web->link(call => $up, label => '++') %>
+
+</&>

Added: jifty/branches/schema-plugins/doc/examples/CounterDemo/t/00-counter-test.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/doc/examples/CounterDemo/t/00-counter-test.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,53 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+
+=head1 DESCRIPTION
+
+Basic continuation counter test.
+
+=cut
+
+use Jifty::Test tests => 20;
+use Jifty::Test::WWW::Mechanize;
+
+my $server = Jifty::Test->make_server;
+
+my $URL = $server->started_ok();
+
+ok($URL, "Started the test server");
+
+my $mech = Jifty::Test::WWW::Mechanize->new();
+
+$mech->get_ok($URL, "Got the home page");
+
+$mech->content_contains('The counter is: 0', "Counter starts at 0");
+ok($mech->find_link(text => "++"), "Found the increment link");
+
+$mech->follow_link_ok(text => "++");
+$mech->content_contains('The counter is: 1', "Incremented the counter");
+
+$mech->follow_link_ok(text => "++");
+$mech->content_contains('The counter is: 2', "Incremented the counter");
+
+$mech->follow_link_ok(text => "--");
+$mech->content_contains('The counter is: 1', "Decremented the counter");
+
+$mech->follow_link_ok(text => "--");
+$mech->follow_link_ok(text => "--");
+$mech->follow_link_ok(text => "--");
+$mech->follow_link_ok(text => "--");
+
+$mech->content_contains('The counter is: -3', "Decremented the counter 4 times");
+
+$mech->back;
+$mech->back;
+
+$mech->content_contains('The counter is: -1', "Back at -1");
+
+$mech->follow_link_ok(text => "--");
+
+$mech->content_contains('The counter is: -2', "Going back then following links DTRT");
+
+$mech->get($URL);
+$mech->content_contains('The counter is: 0', "Loading the initial page again resets the counter");

Added: jifty/branches/schema-plugins/doc/jifty-action-record-search
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/doc/jifty-action-record-search	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,59 @@
+-*- mode: outline; outline-regexp: "\\s *\\*+" -*-
+
+Jifty::Action::Record::Search should take arguments and return a
+FooCollection as the q{search} field of its q{content}.
+
+It will generate arguments for each field, according to the SQL type
+of the field:
+
+* text
+ * I<field>, I<field>_not
+  * Do substring searching on the given field
+  * This is case sensitive.
+
+ * I<field_is>
+  * ? Does exact matching ?
+
+* time, date, or datetime
+ * T<field>_after, I<field>_before
+ * T<field> for exact matching?
+
+* int
+ * I<field>
+  * Exact matching
+
+ * I<field_gt>
+ * I<field_lt>
+  * Less than/greater than
+
+ * I<field_gte>
+ * I<field_lte>
+  * These are perhaps superfluous, but could be convenient.
+  * (I want auto-loaded arguments ...)
+
+* boolean
+ * I<field>
+  * Can be true, false, or undefined
+
+
+* refers_to an object
+ * I<field>
+ * I<field_not>
+ * Accept ids
+  * ? Generate valid_values for this ?
+
+* refers_to a collection
+ * Do nothing?
+ * I<field>_contains ?
+ * I<field>_lacks ?
+
+******* 
+
+* Validators and canonicalizers
+ * Never use validators
+ * Use canonicalizers on any "exact match" fields
+ * What's the right behaviour on substring/comparison match?
+
+* valid_values
+ * valid_values should remain valid_values for any exact match fields,
+ * become available_values otherwise

Added: jifty/branches/schema-plugins/doc/jifty-dispatcher.graffle
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/doc/jifty-dispatcher.graffle	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,2641 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>ActiveLayerIndex</key>
+	<integer>0</integer>
+	<key>AutoAdjust</key>
+	<true/>
+	<key>CanvasColor</key>
+	<dict>
+		<key>w</key>
+		<string>1</string>
+	</dict>
+	<key>CanvasOrigin</key>
+	<string>{0, 0}</string>
+	<key>CanvasScale</key>
+	<real>1</real>
+	<key>ColumnAlign</key>
+	<integer>1</integer>
+	<key>ColumnSpacing</key>
+	<real>36</real>
+	<key>CreationDate</key>
+	<string>2006-07-09 18:16:50 -0400</string>
+	<key>Creator</key>
+	<string>Audrey Tang</string>
+	<key>DisplayScale</key>
+	<string>1 cm = 1 cm</string>
+	<key>GraphDocumentVersion</key>
+	<integer>5</integer>
+	<key>GraphicsList</key>
+	<array>
+		<dict>
+			<key>Bounds</key>
+			<string>{{1835, 198}, {72, 45}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>NSKern</key>
+				<real>0.0</real>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>56</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>GradientColor</key>
+					<dict>
+						<key>w</key>
+						<string>0.666667</string>
+					</dict>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10002\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs24 \cf0 \expnd0\expndtw0\kerning0
+Partial orders between mounted dispatchers}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{1835, 117}, {72, 45}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>NSKern</key>
+				<real>0.0</real>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>54</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>GradientColor</key>
+					<dict>
+						<key>w</key>
+						<string>0.666667</string>
+					</dict>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10002\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs24 \cf0 \expnd0\expndtw0\kerning0
+plugin}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{809, 198}, {72, 45}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>NSKern</key>
+				<real>0.0</real>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>123</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>GradientColor</key>
+					<dict>
+						<key>w</key>
+						<string>0.666667</string>
+					</dict>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10002\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs24 \cf0 \expnd0\expndtw0\kerning0
+Topic}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{1673, 198}, {72, 45}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>NSKern</key>
+				<real>0.0</real>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>121</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>GradientColor</key>
+					<dict>
+						<key>w</key>
+						<string>0.666667</string>
+					</dict>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10002\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs24 \cf0 \expnd0\expndtw0\kerning0
+Flow control}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{1565, 198}, {72, 45}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>NSKern</key>
+				<real>0.0</real>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>117</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>GradientColor</key>
+					<dict>
+						<key>w</key>
+						<string>0.666667</string>
+					</dict>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10002\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs24 \cf0 \expnd0\expndtw0\kerning0
+Goto (for "before/on" stage)}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{1457, 198}, {72, 45}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>NSKern</key>
+				<real>0.0</real>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>111</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>GradientColor</key>
+					<dict>
+						<key>w</key>
+						<string>0.666667</string>
+					</dict>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10002\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs24 \cf0 \expnd0\expndtw0\kerning0
+=request params initially}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{1349, 198}, {72, 45}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>NSKern</key>
+				<real>0.0</real>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>109</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>GradientColor</key>
+					<dict>
+						<key>w</key>
+						<string>0.666667</string>
+					</dict>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10002\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs24 \cf0 \expnd0\expndtw0\kerning0
+Stash manipulation}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{1727, 117}, {72, 45}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>NSKern</key>
+				<real>0.0</real>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>131</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>GradientColor</key>
+					<dict>
+						<key>w</key>
+						<string>0.666667</string>
+					</dict>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10002\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs24 \cf0 \expnd0\expndtw0\kerning0
+already_run}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{1619, 117}, {72, 45}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>NSKern</key>
+				<real>0.0</real>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>119</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>GradientColor</key>
+					<dict>
+						<key>w</key>
+						<string>0.666667</string>
+					</dict>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10002\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs24 \cf0 \expnd0\expndtw0\kerning0
+next_rule/last_rule/abort}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{1511, 117}, {72, 45}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>NSKern</key>
+				<real>0.0</real>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>113</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>GradientColor</key>
+					<dict>
+						<key>w</key>
+						<string>0.666667</string>
+					</dict>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10002\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs24 \cf0 \expnd0\expndtw0\kerning0
+show/redirect/dispatch}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{1403, 117}, {72, 45}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>NSKern</key>
+				<real>0.0</real>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>106</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>GradientColor</key>
+					<dict>
+						<key>w</key>
+						<string>0.666667</string>
+					</dict>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10002\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs24 \cf0 \expnd0\expndtw0\kerning0
+get/set/default/del}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{1295, 117}, {72, 45}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>NSKern</key>
+				<real>0.0</real>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>2</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>GradientColor</key>
+					<dict>
+						<key>w</key>
+						<string>0.666667</string>
+					</dict>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10002\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs24 \cf0 \expnd0\expndtw0\kerning0
+when/under}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{1295, 279}, {72, 45}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>NSKern</key>
+				<real>0.0</real>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>103</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>GradientColor</key>
+					<dict>
+						<key>w</key>
+						<string>0.666667</string>
+					</dict>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10002\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs24 \cf0 \expnd0\expndtw0\kerning0
+\{child; child\}}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{1187, 279}, {72, 45}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>NSKern</key>
+				<real>0.0</real>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>101</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>GradientColor</key>
+					<dict>
+						<key>w</key>
+						<string>0.666667</string>
+					</dict>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10002\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs24 \cf0 \expnd0\expndtw0\kerning0
+[child, child]}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{1241, 198}, {72, 45}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>NSKern</key>
+				<real>0.0</real>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>99</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>GradientColor</key>
+					<dict>
+						<key>w</key>
+						<string>0.666667</string>
+					</dict>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10002\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs24 \cf0 \expnd0\expndtw0\kerning0
+Numerous children}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{1133, 198}, {72, 45}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>NSKern</key>
+				<real>0.0</real>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>89</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>GradientColor</key>
+					<dict>
+						<key>w</key>
+						<string>0.666667</string>
+					</dict>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10002\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs24 \cf0 \expnd0\expndtw0\kerning0
+run \{...\} \\&amp;sub 'sub'}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{1025, 360}, {72, 45}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>NSKern</key>
+				<real>0.0</real>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>70</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>GradientColor</key>
+					<dict>
+						<key>w</key>
+						<string>0.666667</string>
+					</dict>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10002\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs24 \cf0 \expnd0\expndtw0\kerning0
+/foo/*/\{bar,baz\}}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{1025, 279}, {72, 45}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>NSKern</key>
+				<real>0.0</real>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>85</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>GradientColor</key>
+					<dict>
+						<key>w</key>
+						<string>0.666667</string>
+					</dict>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10002\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs24 \cf0 \expnd0\expndtw0\kerning0
+(may be compiled from shellglob)}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{1025, 198}, {72, 45}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>NSKern</key>
+				<real>0.0</real>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>68</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>GradientColor</key>
+					<dict>
+						<key>w</key>
+						<string>0.666667</string>
+					</dict>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10002\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs24 \cf0 \expnd0\expndtw0\kerning0
+Regex on PATH}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{917, 198}, {72, 45}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>NSKern</key>
+				<real>0.0</real>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>82</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>GradientColor</key>
+					<dict>
+						<key>w</key>
+						<string>0.666667</string>
+					</dict>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10002\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs24 \cf0 \expnd0\expndtw0\kerning0
+GET/POST/etc}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{1565, 36}, {72, 45}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>NSKern</key>
+				<real>0.0</real>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>105</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>GradientColor</key>
+					<dict>
+						<key>w</key>
+						<string>0.666667</string>
+					</dict>
+					<key>MiddleColor</key>
+					<dict>
+						<key>w</key>
+						<string>0.333333</string>
+					</dict>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10002\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs24 \cf0 \expnd0\expndtw0\kerning0
+Body actions}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{1187, 117}, {72, 45}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>NSKern</key>
+				<real>0.0</real>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>72</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>GradientColor</key>
+					<dict>
+						<key>w</key>
+						<string>0.666667</string>
+					</dict>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10002\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs24 \cf0 \expnd0\expndtw0\kerning0
+Body}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{971, 117}, {72, 45}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>NSKern</key>
+				<real>0.0</real>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>79</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>GradientColor</key>
+					<dict>
+						<key>w</key>
+						<string>0.666667</string>
+					</dict>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10002\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs24 \cf0 \expnd0\expndtw0\kerning0
+Condition}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{1079, 36}, {72, 45}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>NSKern</key>
+				<real>0.0</real>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>78</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>GradientColor</key>
+					<dict>
+						<key>w</key>
+						<string>0.666667</string>
+					</dict>
+					<key>MiddleColor</key>
+					<dict>
+						<key>w</key>
+						<string>0.333333</string>
+					</dict>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10002\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs24 \cf0 \expnd0\expndtw0\kerning0
+Rule}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{539, 36}, {72, 45}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>NSKern</key>
+				<real>0.0</real>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>45</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>GradientColor</key>
+					<dict>
+						<key>w</key>
+						<string>0.666667</string>
+					</dict>
+					<key>MiddleColor</key>
+					<dict>
+						<key>w</key>
+						<string>0.333333</string>
+					</dict>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10002\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs24 \cf0 \expnd0\expndtw0\kerning0
+Stages}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{809, 117}, {72, 45}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>NSKern</key>
+				<real>0.0</real>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>50</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>GradientColor</key>
+					<dict>
+						<key>w</key>
+						<string>0.666667</string>
+					</dict>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10002\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs24 \cf0 \expnd0\expndtw0\kerning0
+after}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{701, 117}, {72, 45}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>NSKern</key>
+				<real>0.0</real>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>129</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>GradientColor</key>
+					<dict>
+						<key>w</key>
+						<string>0.666667</string>
+					</dict>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10002\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs24 \cf0 \expnd0\expndtw0\kerning0
+Display new Actions}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{593, 117}, {72, 45}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>NSKern</key>
+				<real>0.0</real>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>48</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>GradientColor</key>
+					<dict>
+						<key>w</key>
+						<string>0.666667</string>
+					</dict>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10002\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs24 \cf0 \expnd0\expndtw0\kerning0
+on}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{485, 117}, {72, 45}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>NSKern</key>
+				<real>0.0</real>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>125</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>GradientColor</key>
+					<dict>
+						<key>w</key>
+						<string>0.666667</string>
+					</dict>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10002\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs24 \cf0 \expnd0\expndtw0\kerning0
+Run prev Actions}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{377, 117}, {72, 45}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>NSKern</key>
+				<real>0.0</real>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>46</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>GradientColor</key>
+					<dict>
+						<key>w</key>
+						<string>0.666667</string>
+					</dict>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10002\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs24 \cf0 \expnd0\expndtw0\kerning0
+before}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Font</key>
+				<string>LiHeiPro</string>
+				<key>Size</key>
+				<real>24</real>
+			</dict>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>46</integer>
+			</dict>
+			<key>ID</key>
+			<integer>8</integer>
+			<key>Points</key>
+			<array>
+				<string>{538.553, 76.7236}</string>
+				<string>{449.447, 121.276}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>0</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>45</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Font</key>
+				<string>LiHeiPro</string>
+				<key>Size</key>
+				<real>24</real>
+			</dict>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>48</integer>
+			</dict>
+			<key>ID</key>
+			<integer>47</integer>
+			<key>Points</key>
+			<array>
+				<string>{590.277, 81.416}</string>
+				<string>{613.723, 116.584}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>0</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>45</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Font</key>
+				<string>LiHeiPro</string>
+				<key>Size</key>
+				<real>24</real>
+			</dict>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>50</integer>
+			</dict>
+			<key>ID</key>
+			<integer>49</integer>
+			<key>Points</key>
+			<array>
+				<string>{611.479, 69.4437}</string>
+				<string>{808.521, 128.556}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>0</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>45</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Font</key>
+				<string>LiHeiPro</string>
+				<key>Size</key>
+				<real>24</real>
+			</dict>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>79</integer>
+			</dict>
+			<key>ID</key>
+			<integer>80</integer>
+			<key>Points</key>
+			<array>
+				<string>{1084.6, 81.3}</string>
+				<string>{1037.4, 116.7}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>0</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>78</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Font</key>
+				<string>LiHeiPro</string>
+				<key>Size</key>
+				<real>24</real>
+			</dict>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>82</integer>
+			</dict>
+			<key>ID</key>
+			<integer>81</integer>
+			<key>Points</key>
+			<array>
+				<string>{991.723, 162.416}</string>
+				<string>{968.277, 197.584}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>0</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>79</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Font</key>
+				<string>LiHeiPro</string>
+				<key>Size</key>
+				<real>24</real>
+			</dict>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>68</integer>
+			</dict>
+			<key>ID</key>
+			<integer>83</integer>
+			<key>Points</key>
+			<array>
+				<string>{1022.28, 162.416}</string>
+				<string>{1045.72, 197.584}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>0</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>79</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Font</key>
+				<string>LiHeiPro</string>
+				<key>Size</key>
+				<real>24</real>
+			</dict>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>85</integer>
+			</dict>
+			<key>ID</key>
+			<integer>84</integer>
+			<key>Points</key>
+			<array>
+				<string>{1061, 243.5}</string>
+				<string>{1061, 278.5}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>0</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>68</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Font</key>
+				<string>LiHeiPro</string>
+				<key>Size</key>
+				<real>24</real>
+			</dict>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>70</integer>
+			</dict>
+			<key>ID</key>
+			<integer>86</integer>
+			<key>Points</key>
+			<array>
+				<string>{1061, 324.5}</string>
+				<string>{1061, 359.5}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>0</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>85</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Font</key>
+				<string>LiHeiPro</string>
+				<key>Size</key>
+				<real>24</real>
+			</dict>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>72</integer>
+			</dict>
+			<key>ID</key>
+			<integer>87</integer>
+			<key>Points</key>
+			<array>
+				<string>{1145.4, 81.3}</string>
+				<string>{1192.6, 116.7}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>0</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>78</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Font</key>
+				<string>LiHeiPro</string>
+				<key>Size</key>
+				<real>24</real>
+			</dict>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>89</integer>
+			</dict>
+			<key>ID</key>
+			<integer>88</integer>
+			<key>Points</key>
+			<array>
+				<string>{1207.72, 162.416}</string>
+				<string>{1184.28, 197.584}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>0</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>72</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Font</key>
+				<string>LiHeiPro</string>
+				<key>Size</key>
+				<real>24</real>
+			</dict>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>99</integer>
+			</dict>
+			<key>ID</key>
+			<integer>98</integer>
+			<key>Points</key>
+			<array>
+				<string>{1238.28, 162.416}</string>
+				<string>{1261.72, 197.584}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>0</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>72</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Font</key>
+				<string>LiHeiPro</string>
+				<key>Size</key>
+				<real>24</real>
+			</dict>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>101</integer>
+			</dict>
+			<key>ID</key>
+			<integer>100</integer>
+			<key>Points</key>
+			<array>
+				<string>{1261.72, 243.416}</string>
+				<string>{1238.28, 278.584}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>0</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>99</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Font</key>
+				<string>LiHeiPro</string>
+				<key>Size</key>
+				<real>24</real>
+			</dict>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>103</integer>
+			</dict>
+			<key>ID</key>
+			<integer>102</integer>
+			<key>Points</key>
+			<array>
+				<string>{1292.28, 243.416}</string>
+				<string>{1315.72, 278.584}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>0</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>99</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Font</key>
+				<string>LiHeiPro</string>
+				<key>Size</key>
+				<real>24</real>
+			</dict>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>106</integer>
+			</dict>
+			<key>ID</key>
+			<integer>107</integer>
+			<key>Points</key>
+			<array>
+				<string>{1564.55, 76.7236}</string>
+				<string>{1475.45, 121.276}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>0</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>105</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Font</key>
+				<string>LiHeiPro</string>
+				<key>Size</key>
+				<real>24</real>
+			</dict>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>109</integer>
+			</dict>
+			<key>ID</key>
+			<integer>108</integer>
+			<key>Points</key>
+			<array>
+				<string>{1423.72, 162.416}</string>
+				<string>{1400.28, 197.584}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>0</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>106</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Font</key>
+				<string>LiHeiPro</string>
+				<key>Size</key>
+				<real>24</real>
+			</dict>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>111</integer>
+			</dict>
+			<key>ID</key>
+			<integer>110</integer>
+			<key>Points</key>
+			<array>
+				<string>{1454.28, 162.416}</string>
+				<string>{1477.72, 197.584}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>0</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>106</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Font</key>
+				<string>LiHeiPro</string>
+				<key>Size</key>
+				<real>24</real>
+			</dict>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>113</integer>
+			</dict>
+			<key>ID</key>
+			<integer>112</integer>
+			<key>Points</key>
+			<array>
+				<string>{1585.72, 81.416}</string>
+				<string>{1562.28, 116.584}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>0</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>105</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Font</key>
+				<string>LiHeiPro</string>
+				<key>Size</key>
+				<real>24</real>
+			</dict>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>117</integer>
+			</dict>
+			<key>ID</key>
+			<integer>116</integer>
+			<key>Points</key>
+			<array>
+				<string>{1562.28, 162.416}</string>
+				<string>{1585.72, 197.584}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>0</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>113</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Font</key>
+				<string>LiHeiPro</string>
+				<key>Size</key>
+				<real>24</real>
+			</dict>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>119</integer>
+			</dict>
+			<key>ID</key>
+			<integer>118</integer>
+			<key>Points</key>
+			<array>
+				<string>{1616.28, 81.416}</string>
+				<string>{1639.72, 116.584}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>0</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>105</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Font</key>
+				<string>LiHeiPro</string>
+				<key>Size</key>
+				<real>24</real>
+			</dict>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>121</integer>
+			</dict>
+			<key>ID</key>
+			<integer>120</integer>
+			<key>Points</key>
+			<array>
+				<string>{1670.28, 162.416}</string>
+				<string>{1693.72, 197.584}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>0</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>119</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Font</key>
+				<string>LiHeiPro</string>
+				<key>Size</key>
+				<real>24</real>
+			</dict>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>123</integer>
+			</dict>
+			<key>ID</key>
+			<integer>122</integer>
+			<key>Points</key>
+			<array>
+				<string>{845, 162.5}</string>
+				<string>{845, 197.5}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>0</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>50</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Font</key>
+				<string>LiHeiPro</string>
+				<key>Size</key>
+				<real>24</real>
+			</dict>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>125</integer>
+			</dict>
+			<key>ID</key>
+			<integer>124</integer>
+			<key>Points</key>
+			<array>
+				<string>{559.723, 81.416}</string>
+				<string>{536.277, 116.584}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>0</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>45</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Font</key>
+				<string>LiHeiPro</string>
+				<key>Size</key>
+				<real>24</real>
+			</dict>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>129</integer>
+			</dict>
+			<key>ID</key>
+			<integer>128</integer>
+			<key>Points</key>
+			<array>
+				<string>{611.447, 76.7236}</string>
+				<string>{700.553, 121.276}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>0</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>45</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Font</key>
+				<string>LiHeiPro</string>
+				<key>Size</key>
+				<real>24</real>
+			</dict>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>131</integer>
+			</dict>
+			<key>ID</key>
+			<integer>130</integer>
+			<key>Points</key>
+			<array>
+				<string>{1637.45, 76.7236}</string>
+				<string>{1726.55, 121.276}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>0</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>105</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Font</key>
+				<string>LiHeiPro</string>
+				<key>Size</key>
+				<real>24</real>
+			</dict>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>54</integer>
+			</dict>
+			<key>ID</key>
+			<integer>132</integer>
+			<key>Points</key>
+			<array>
+				<string>{1637.48, 69.4437}</string>
+				<string>{1834.52, 128.556}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>0</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>105</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Font</key>
+				<string>LiHeiPro</string>
+				<key>Size</key>
+				<real>24</real>
+			</dict>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>56</integer>
+			</dict>
+			<key>ID</key>
+			<integer>135</integer>
+			<key>Points</key>
+			<array>
+				<string>{1871, 162.5}</string>
+				<string>{1871, 197.5}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>0</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>54</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Font</key>
+				<string>LiHeiPro</string>
+				<key>Size</key>
+				<real>24</real>
+			</dict>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>2</integer>
+			</dict>
+			<key>ID</key>
+			<integer>137</integer>
+			<key>Points</key>
+			<array>
+				<string>{1564.52, 69.4437}</string>
+				<string>{1367.48, 128.556}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>0</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>105</integer>
+			</dict>
+		</dict>
+	</array>
+	<key>GridInfo</key>
+	<dict/>
+	<key>GuidesLocked</key>
+	<string>NO</string>
+	<key>GuidesVisible</key>
+	<string>YES</string>
+	<key>HPages</key>
+	<integer>4</integer>
+	<key>ImageCounter</key>
+	<integer>1</integer>
+	<key>IsPalette</key>
+	<string>NO</string>
+	<key>KeepToScale</key>
+	<false/>
+	<key>Layers</key>
+	<array>
+		<dict>
+			<key>Lock</key>
+			<string>NO</string>
+			<key>Name</key>
+			<string>Layer 1</string>
+			<key>Print</key>
+			<string>YES</string>
+			<key>View</key>
+			<string>YES</string>
+		</dict>
+	</array>
+	<key>LayoutInfo</key>
+	<dict>
+		<key>LayoutTarget</key>
+		<integer>3</integer>
+		<key>LineLength</key>
+		<real>0.10000000149011612</real>
+		<key>Spring</key>
+		<real>1.8235714435577393</real>
+	</dict>
+	<key>LinksVisible</key>
+	<string>NO</string>
+	<key>MagnetsVisible</key>
+	<string>NO</string>
+	<key>MasterSheet</key>
+	<string>Master 1</string>
+	<key>MasterSheets</key>
+	<array>
+		<dict>
+			<key>ActiveLayerIndex</key>
+			<integer>0</integer>
+			<key>AutoAdjust</key>
+			<true/>
+			<key>CanvasColor</key>
+			<dict>
+				<key>w</key>
+				<string>1</string>
+			</dict>
+			<key>CanvasOrigin</key>
+			<string>{0, 0}</string>
+			<key>CanvasScale</key>
+			<real>1</real>
+			<key>ColumnAlign</key>
+			<integer>1</integer>
+			<key>ColumnSpacing</key>
+			<real>36</real>
+			<key>DisplayScale</key>
+			<string>1 cm = 1 cm</string>
+			<key>GraphicsList</key>
+			<array/>
+			<key>GridInfo</key>
+			<dict/>
+			<key>HPages</key>
+			<integer>1</integer>
+			<key>IsPalette</key>
+			<string>NO</string>
+			<key>KeepToScale</key>
+			<false/>
+			<key>Layers</key>
+			<array>
+				<dict>
+					<key>Lock</key>
+					<string>NO</string>
+					<key>Name</key>
+					<string>Layer 1</string>
+					<key>Print</key>
+					<string>YES</string>
+					<key>View</key>
+					<string>YES</string>
+				</dict>
+			</array>
+			<key>LayoutInfo</key>
+			<dict>
+				<key>LayoutTarget</key>
+				<integer>3</integer>
+			</dict>
+			<key>Orientation</key>
+			<integer>2</integer>
+			<key>OutlineStyle</key>
+			<string>Basic</string>
+			<key>RowAlign</key>
+			<integer>1</integer>
+			<key>RowSpacing</key>
+			<real>36</real>
+			<key>SheetTitle</key>
+			<string>Master 1</string>
+			<key>UniqueID</key>
+			<integer>1</integer>
+			<key>VPages</key>
+			<integer>1</integer>
+		</dict>
+	</array>
+	<key>ModificationDate</key>
+	<string>2006-07-09 19:23:03 -0400</string>
+	<key>Modifier</key>
+	<string>Audrey Tang</string>
+	<key>NotesVisible</key>
+	<string>NO</string>
+	<key>Orientation</key>
+	<integer>2</integer>
+	<key>OriginVisible</key>
+	<string>NO</string>
+	<key>OutlineStyle</key>
+	<string>Basic</string>
+	<key>PageBreaks</key>
+	<string>YES</string>
+	<key>PrintInfo</key>
+	<dict>
+		<key>NSBottomMargin</key>
+		<array>
+			<string>float</string>
+			<string>0</string>
+		</array>
+		<key>NSLeftMargin</key>
+		<array>
+			<string>float</string>
+			<string>0</string>
+		</array>
+		<key>NSRightMargin</key>
+		<array>
+			<string>float</string>
+			<string>0</string>
+		</array>
+		<key>NSTopMargin</key>
+		<array>
+			<string>float</string>
+			<string>0</string>
+		</array>
+	</dict>
+	<key>ReadOnly</key>
+	<string>NO</string>
+	<key>RowAlign</key>
+	<integer>1</integer>
+	<key>RowSpacing</key>
+	<real>36</real>
+	<key>SheetTitle</key>
+	<string>Canvas 1</string>
+	<key>SmartAlignmentGuidesActive</key>
+	<string>YES</string>
+	<key>SmartDistanceGuidesActive</key>
+	<string>YES</string>
+	<key>UniqueID</key>
+	<integer>1</integer>
+	<key>UseEntirePage</key>
+	<true/>
+	<key>VPages</key>
+	<integer>1</integer>
+	<key>WindowInfo</key>
+	<dict>
+		<key>CurrentSheet</key>
+		<string>0</string>
+		<key>DrawerOpen</key>
+		<true/>
+		<key>DrawerTab</key>
+		<string>Outline</string>
+		<key>DrawerWidth</key>
+		<real>209</real>
+		<key>FitInWindow</key>
+		<false/>
+		<key>Frame</key>
+		<string>{{500, 4}, {586, 742}}</string>
+		<key>ShowRuler</key>
+		<false/>
+		<key>ShowStatusBar</key>
+		<true/>
+		<key>VisibleRegion</key>
+		<string>{{1496, 4}, {571, 628}}</string>
+		<key>Zoom</key>
+		<string>1</string>
+	</dict>
+</dict>
+</plist>

Added: jifty/branches/schema-plugins/doc/jifty-dispatcher.svg
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/doc/jifty-dispatcher.svg	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="357 20 1570 409" width="1570pt" height="409pt"><metadata xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:date>2006-07-11 00:19Z</dc:date><!-- Produced by OmniGraffle Professional 4.1.2 beta 2 --></metadata><defs><filter id="Shadow" filterUnits="userSpaceOnUse"><feGaussianBlur in="SourceAlpha" result="blur" stdDeviation="3.488"/><feOffset in="blur" result="offset" dx="0" dy="4"/><feFlood flood-color="Black" flood-opacity=".75" result="flood"/><feComposite in="flood" in2="offset" operator="in"/></filter><font-face font-size="12pt" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="583.33337" cap-height="750" ascent="770.01953" descent="-229.98047" font-weight="500"><!--{
+    NSCTFontTraitsAttribute = {
+        NSCTFontProportionTrait = 0; 
+        NSCTFontSlantTrait = 0; 
+        NSCTFontSymbolicTrait = 0; 
+        NSCTFontWeightTrait = 0; 
+    }; 
+    NSFontNameAttribute = Helvetica; 
+}--><font-face-src><font-face-name name="Helvetica"/></font-face-src></font-face></defs><g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1"><title>Canvas 1</title><g><title>Layer 1</title><g><use xl:href="#id46_Graphic" filter="url(#Shadow)"/><use xl:href="#id125_Graphic" filter="url(#Shadow)"/><use xl:href="#id48_Graphic" filter="url(#Shadow)"/><use xl:href="#id129_Graphic" filter="url(#Shadow)"/><use xl:href="#id50_Graphic" filter="url(#Shadow)"/><use xl:href="#id45_Graphic" filter="url(#Shadow)"/><use xl:href="#id78_Graphic" filter="url(#Shadow)"/><use xl:href="#id79_Graphic" filter="url(#Shadow)"/><use xl:href="#id72_Graphic" filter="url(#Shadow)"/><use xl:href="#id105_Graphic" filter="url(#Shadow)"/><use xl:href="#id82_Graphic" filter="url(#Shadow)"/><use xl:href="#id68_Graphic" filter="url(#Shadow)"/><use xl:href="#id85_Graphic" filter="url(#Shadow)"/><use xl:href="#id70_Graphic" filter="url(#Shadow)"/><use xl:href="#id89_Graphic" filter="url(#Shadow)"/><use xl:href="#id99_Graphic" filter="url(#Shadow)"/><use xl:href="#id101_Graphic" filter="url(#Shadow)"/><use xl:href="#id103_Graphic" filter="url(#Shadow)"/><use xl:href="#id2_Graphic" filter="url(#Shadow)"/><use xl:href="#id106_Graphic" filter="url(#Shadow)"/><use xl:href="#id113_Graphic" filter="url(#Shadow)"/><use xl:href="#id119_Graphic" filter="url(#Shadow)"/><use xl:href="#id131_Graphic" filter="url(#Shadow)"/><use xl:href="#id109_Graphic" filter="url(#Shadow)"/><use xl:href="#id111_Graphic" filter="url(#Shadow)"/><use xl:href="#id117_Graphic" filter="url(#Shadow)"/><use xl:href="#id121_Graphic" filter="url(#Shadow)"/><use xl:href="#id54_Graphic" filter="url(#Shadow)"/><use xl:href="#id56_Graphic" filter="url(#Shadow)"/></g><line x1="1564.5211" y1="69.44368" x2="1367.4789" y2="128.556335" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="1871" y1="162.5" x2="1871" y2="197.5" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="1637.4789" y1="69.44368" x2="1834.5211" y2="128.556335" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="1637.4473" y1="76.72361" x2="1726.5527" y2="121.276405" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="611.4472" y1="76.72361" x2="700.5528" y2="121.27638" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="559.72266" y1="81.416023" x2="536.27734" y2="116.58398" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="1670.2773" y1="162.41603" x2="1693.7227" y2="197.58397" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="1616.2773" y1="81.416023" x2="1639.7227" y2="116.58398" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="1562.2773" y1="162.41603" x2="1585.7227" y2="197.58397" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="1585.7227" y1="81.416023" x2="1562.2773" y2="116.58398" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="1454.27734" y1="162.41603" x2="1477.7227" y2="197.58397" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="1423.72266" y1="162.41603" x2="1400.27734" y2="197.58397" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="1564.5527" y1="76.72361" x2="1475.4473" y2="121.276405" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="1292.27734" y1="243.41603" x2="1315.72266" y2="278.58398" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="1261.72266" y1="243.41603" x2="1238.27734" y2="278.58398" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="1238.27734" y1="162.41603" x2="1261.72266" y2="197.58397" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="1207.72266" y1="162.41603" x2="1184.27734" y2="197.58397" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="1145.4" y1="81.300003" x2="1192.6" y2="116.7" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="1061" y1="347.5" x2="1061" y2="359.5" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="1061" y1="243.5" x2="1061" y2="278.5" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="1022.27734" y1="162.41603" x2="1045.72266" y2="197.58397" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="991.72266" y1="162.41603" x2="968.27734" y2="197.58397" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="1084.6" y1="81.300003" x2="1037.4" y2="116.7" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="611.47894" y1="69.44368" x2="808.52106" y2="128.55632" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="590.27734" y1="81.416023" x2="613.72266" y2="116.58398" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="538.5528" y1="76.72361" x2="449.44724" y2="121.27638" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><g id="id46_Graphic"><rect x="377" y="117" width="72" height="45" fill="White"/><rect x="377" y="117" width="72" height="45" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(382 133)" fill="Black"><tspan font-size="12pt" fill="Black" x="13.987305" y="11" textLength="34.025002">before</tspan></text></g><g id="id125_Graphic"><rect x="485" y="117" width="72" height="45" fill="White"/><rect x="485" y="117" width="72" height="45" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(490 126)" fill="Black"><tspan font-size="12pt" fill="Black" x="6.654297" y="11" textLength="52.025">Run prev </tspan><tspan font-size="12pt" fill="Black" x="11.324219" y="25" textLength="39.351002">Actions</tspan></text></g><g id="id48_Graphic"><rect x="593" y="117" width="72" height="45" fill="White"/><rect x="593" y="117" width="72" height="45" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(598 133)" fill="Black"><tspan font-size="12pt" fill="Black" x="24.326172" y="11" textLength="13.347">on</tspan></text></g><g id="id129_Graphic"><rect x="701" y="117" width="72" height="45" fill="White"/><rect x="701" y="117" width="72" height="45" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(706 119)" fill="Black"><tspan font-size="12pt" fill="Black" x="11.3271484" y="11" textLength="42.679">Display </tspan><tspan font-size="12pt" fill="Black" x="19.993164" y="25" textLength="25.347">new </tspan><tspan font-size="12pt" fill="Black" x="11.324219" y="39" textLength="39.351002">Actions</tspan></text></g><g id="id50_Graphic"><rect x="809" y="117" width="72" height="45" fill="White"/><rect x="809" y="117" width="72" height="45" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(814 133)" fill="Black"><tspan font-size="12pt" fill="Black" x="18.994141" y="11" textLength="24.011">after</tspan></text></g><g id="id45_Graphic"><rect x="539" y="36" width="72" height="45" fill="White"/><rect x="539" y="36" width="72" height="45" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(544 52)" fill="Black"><tspan font-size="12pt" fill="Black" x="12.3203125" y="11" textLength="37.359001">Stages</tspan></text></g><g id="id78_Graphic"><rect x="1079" y="36" width="72" height="45" fill="White"/><rect x="1079" y="36" width="72" height="45" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(1084 52)" fill="Black"><tspan font-size="12pt" fill="Black" x="18.660156" y="11" textLength="24.679001">Rule</tspan></text></g><g id="id79_Graphic"><rect x="971" y="117" width="72" height="45" fill="White"/><rect x="971" y="117" width="72" height="45" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(976 133)" fill="Black"><tspan font-size="12pt" fill="Black" x="5.649414" y="11" textLength="50.701">Condition</tspan></text></g><g id="id72_Graphic"><rect x="1187" y="117" width="72" height="45" fill="White"/><rect x="1187" y="117" width="72" height="45" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(1192 133)" fill="Black"><tspan font-size="12pt" fill="Black" x="17.324219" y="11" textLength="27.351">Body</tspan></text></g><g id="id105_Graphic"><rect x="1565" y="36" width="72" height="45" fill="White"/><rect x="1565" y="36" width="72" height="45" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(1570 45)" fill="Black"><tspan font-size="12pt" fill="Black" x="17.324219" y="11" textLength="30.685">Body </tspan><tspan font-size="12pt" fill="Black" x="11.989258" y="25" textLength="38.021">actions</tspan></text></g><g id="id82_Graphic"><rect x="917" y="198" width="72" height="45" fill="White"/><rect x="917" y="198" width="72" height="45" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(922 207)" fill="Black"><tspan font-size="12pt" fill="Black" x="16.999023" y="11" textLength="28.001">GET/</tspan><tspan font-size="12pt" fill="Black" x="4.993164" y="25" textLength="52.013">POST/etc</tspan></text></g><g id="id68_Graphic"><rect x="1025" y="198" width="72" height="45" fill="White"/><rect x="1025" y="198" width="72" height="45" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(1030 207)" fill="Black"><tspan font-size="12pt" fill="Black" x="5.3154297" y="11" textLength="54.703">Regex on </tspan><tspan font-size="12pt" fill="Black" x="14.998047" y="25" textLength="30.234">PATH</tspan></text></g><g id="id85_Graphic"><rect x="1025" y="279" width="72" height="68" fill="White"/><rect x="1025" y="279" width="72" height="68" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(1030 285.5)" fill="Black"><tspan font-size="12pt" fill="Black" x="9.326172" y="11" textLength="46.681">(may be </tspan><tspan font-size="12pt" fill="Black" x="6.9882812" y="25" textLength="51.356998">compiled </tspan><tspan font-size="12pt" fill="Black" x="19" y="39" textLength="27.333">from </tspan><tspan font-size="12pt" fill="Black" x="5.3183594" y="53" textLength="51.363">shellglob)</tspan></text></g><g id="id70_Graphic"><rect x="1025" y="360" width="72" height="45" fill="White"/><rect x="1025" y="360" width="72" height="45" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(1030 369)" fill="Black"><tspan font-size="12pt" fill="Black" x="15.323242" y="11" textLength="31.353">/foo/*/</tspan><tspan font-size="12pt" fill="Black" x="6.979492" y="25" textLength="47.384">{bar,baz}</tspan></text></g><g id="id89_Graphic"><rect x="1133" y="198" width="72" height="45" fill="White"/><rect x="1133" y="198" width="72" height="45" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(1138 207)" fill="Black"><tspan font-size="12pt" fill="Black" x="11.652344" y="11" textLength="42.029">run {...} </tspan><tspan font-size="12pt" fill="Black" x="2.0253906" y="25" textLength="57.949">\&amp;sub 'sub'</tspan></text></g><g id="id99_Graphic"><rect x="1241" y="198" width="72" height="45" fill="White"/><rect x="1241" y="198" width="72" height="45" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(1246 207)" fill="Black"><tspan font-size="12pt" fill="Black" x="3.3232422" y="11" textLength="58.687">Numerous </tspan><tspan font-size="12pt" fill="Black" x="9.988281" y="25" textLength="42.023">children</tspan></text></g><g id="id101_Graphic"><rect x="1187" y="279" width="72" height="45" fill="White"/><rect x="1187" y="279" width="72" height="45" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(1192 288)" fill="Black"><tspan font-size="12pt" fill="Black" x="15.326172" y="11" textLength="34.681">[child, </tspan><tspan font-size="12pt" fill="Black" x="16.993164" y="25" textLength="28.013">child]</tspan></text></g><g id="id103_Graphic"><rect x="1295" y="279" width="72" height="45" fill="White"/><rect x="1295" y="279" width="72" height="45" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(1300 288)" fill="Black"><tspan font-size="12pt" fill="Black" x="14.989258" y="11" textLength="35.355">{child; </tspan><tspan font-size="12pt" fill="Black" x="16.65625" y="25" textLength="28.687">child}</tspan></text></g><g id="id2_Graphic"><rect x="1295" y="117" width="72" height="45" fill="White"/><rect x="1295" y="117" width="72" height="45" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(1300 126)" fill="Black"><tspan font-size="12pt" fill="Black" x="14.989258" y="11" textLength="32.021">when/</tspan><tspan font-size="12pt" fill="Black" x="15.654297" y="25" textLength="30.691">under</tspan></text></g><g id="id106_Graphic"><rect x="1403" y="117" width="72" height="45" fill="White"/><rect x="1403" y="117" width="72" height="45" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(1408 126)" fill="Black"><tspan font-size="12pt" fill="Black" x="11.321289" y="11" textLength="39.356998">get/set/</tspan><tspan font-size="12pt" fill="Black" x="3.3115234" y="25" textLength="55.376">default/del</tspan></text></g><g id="id113_Graphic"><rect x="1511" y="117" width="72" height="45" fill="White"/><rect x="1511" y="117" width="72" height="45" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(1516 119)" fill="Black"><tspan font-size="12pt" fill="Black" x="15.326172" y="11" textLength="31.347">show/</tspan><tspan font-size="12pt" fill="Black" x="9.326172" y="25" textLength="43.347">redirect/</tspan><tspan font-size="12pt" fill="Black" x="8.652344" y="39" textLength="44.695">dispatch</tspan></text></g><g id="id119_Graphic"><rect x="1619" y="117" width="72" height="45" fill="White"/><rect x="1619" y="117" width="72" height="45" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(1624 119)" fill="Black"><tspan font-size="12pt" fill="Black" x="4.6503906" y="11" textLength="52.699">next_rule/</tspan><tspan font-size="12pt" fill="Black" x="6.654297" y="25" textLength="48.691002">last_rule/</tspan><tspan font-size="12pt" fill="Black" x="17.324219" y="39" textLength="27.351">abort</tspan></text></g><g id="id131_Graphic"><rect x="1727" y="117" width="72" height="45" fill="White"/><rect x="1727" y="117" width="72" height="45" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(1732 126)" fill="Black"><tspan font-size="12pt" fill="Black" x="2.649414" y="11" textLength="56.701">already_ru</tspan><tspan font-size="12pt" fill="Black" x="27.663086" y="25" textLength="6.673">n</tspan></text></g><g id="id109_Graphic"><rect x="1349" y="198" width="72" height="45" fill="White"/><rect x="1349" y="198" width="72" height="45" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(1354 200)" fill="Black"><tspan font-size="12pt" fill="Black" x="15.657227" y="11" textLength="34.019">Stash </tspan><tspan font-size="12pt" fill="Black" x=".31445312" y="25" textLength="61.371">manipulatio</tspan><tspan font-size="12pt" fill="Black" x="27.663086" y="39" textLength="6.673">n</tspan></text></g><g id="id111_Graphic"><rect x="1457" y="198" width="72" height="45" fill="White"/><rect x="1457" y="198" width="72" height="45" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(1462 200)" fill="Black"><tspan font-size="12pt" fill="Black" x="5.4853516" y="11" textLength="54.363">(=request </tspan><tspan font-size="12pt" fill="Black" x="10.993164" y="25" textLength="43.347">params </tspan><tspan font-size="12pt" fill="Black" x="10.996094" y="39" textLength="40.007">initially)</tspan></text></g><g id="id117_Graphic"><rect x="1565" y="198" width="72" height="45" fill="White"/><rect x="1565" y="198" width="72" height="45" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(1570 200)" fill="Black"><tspan font-size="12pt" fill="Black" x="7.3251953" y="11" textLength="50.683">Goto (for </tspan><tspan font-size="12pt" fill="Black" x="1.38671875" y="25" textLength="62.56">"before/on" </tspan><tspan font-size="12pt" fill="Black" x="14.324219" y="39" textLength="33.351002">stage)</tspan></text></g><g id="id121_Graphic"><rect x="1673" y="198" width="72" height="45" fill="White"/><rect x="1673" y="198" width="72" height="45" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(1678 207)" fill="Black"><tspan font-size="12pt" fill="Black" x="18.332031" y="11" textLength="28.669">Flow </tspan><tspan font-size="12pt" fill="Black" x="12.991211" y="25" textLength="36.016998">control</tspan></text></g><g id="id54_Graphic"><rect x="1835" y="117" width="72" height="45" fill="White"/><rect x="1835" y="117" width="72" height="45" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(1840 133)" fill="Black"><tspan font-size="12pt" fill="Black" x="14.986328" y="11" textLength="32.027">plugin</tspan></text></g><g id="id56_Graphic"><rect x="1835" y="198" width="72" height="97" fill="White"/><rect x="1835" y="198" width="72" height="97" stroke="Black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(1840 212)" fill="Black"><tspan font-size="12pt" fill="Black" x="13.993164" y="11" textLength="37.347">Partial </tspan><tspan font-size="12pt" fill="Black" x="13.993164" y="25" textLength="37.347">orders </tspan><tspan font-size="12pt" fill="Black" x="8.3154297" y="39" textLength="48.703">between </tspan><tspan font-size="12pt" fill="Black" x="7.6503906" y="53" textLength="50.033">mounted </tspan><tspan font-size="12pt" fill="Black" x=".31738281" y="67" textLength="61.365">dispatchers</tspan></text></g></g></g></svg>

Added: jifty/branches/schema-plugins/doc/jifty-model-svk
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/doc/jifty-model-svk	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,154 @@
+=head1 name
+
+Jifty SVK Model docs
+
+=head1 SUMMARY
+
+
+
+=head1 IMPLEMENTATION
+
+=head2 Reading and writing from the data store
+
+- Upon each mount, read everything, expiry-keyed by revnum
+    - Build in-memory index for common accesses
+    - Encourage ->begin and ->commit to take advantage of svk txns
+    - Otherwise it's autocommit
+        - svn:author is $ApplicationClass-$ApplicationUser
+
+=head2 Implementation plan
+
+=head3 Functionality
+
+=over
+
+=item  create objects
+
+=item  read objects
+
+=item  find objects
+
+=item  update objects
+
+=item  delete objects
+
+=back
+
+
+=head3 API
+
+- First step is a memory-only, svn-compatible layout backend store
+    - Composed of alternate implementation of APIs of:
+        - Jifty::Record
+        - Jifty::Collection
+
+
+=head4 Jifty::Record
+
+Methods:
+
+=over
+
+=item create
+
+=item set
+
+=item value (get)
+
+=item delete
+
+=item 
+
+
+
+=back
+
+
+=head4 Jifty::Collection
+
+=over
+
+=item next
+
+=item items_array_ref
+
+=item unlimit
+
+=item search 
+
+This is a new API. It replaces Jifty::DBI::Record's "limit"
+
+
+My $tasks = MyApp::Model::TaskCollection->new();
+
+grep { $_->summary =~ 'foo'} grep { $_->owner 
+
+
+# Tasks with a summary matching 'patch' AND  with patches (computed)
+
+$tasks->with( summary => qr/patch/)->with( sub { $_->has_patches()  } ) 
+
+# Tasks with a summary matching 'patch' or with patches (computed)
+
+$tasks->in( $tasks->with(summary => qr/patch), $tasks->with(sub {$_->has_patches}))
+
+# Tasks with a summary that doesn't match 'patch' with patches
+$tasks->without( summary => qr/patch/)->with( sub { $_->has_patches()  } ) 
+
+
+=back
+
+=head2 Data storage format (In SVN)
+
+- Subversion based object store, using headless YAML::Syck for now
+    - Each object is a /UUID/ directory
+        - Optionally encoded as /U/UUID/ or /U/UU/UUID/ etc
+    - Each field is a /UUID/field_name file
+        - If it has a jifty:class then it's going to be blessed into that
+    - Type the object is encoded as the dirprop jifty:table
+        - It's blessed into the "class" name defined in the jifty-table repository
+    - Time of creation object is encoded as the dirprop jifty:created
+        - A simple floating number of Time::HiRes::time()
+    - The jifty:table themselves are but UUIDs of table-objects
+        - Also stored in the data store
+        - They are of the builtin table "Jifty::Model::Table"
+            - "class"   : [ "Perl::Land::Class" ]   # Record Class
+        - They are of the builtin table "Jifty::Model::Column"
+            - "table"   : the Table it's associated of
+            - "name"    : field name
+            - "type"    : jifty type-tag
+            - ...other column info...
+        - J::M::T and J::M::C (recursively defined) are always present in any data store
+        - Consequently, jifty model --create must do a uuidgen when backending SVN.
+
+
+=head3 Runtime storage format (in memory)
+
+    - In-memory structure looks like this:
+        {$type-uuid}
+            [objects-sorted-by-timestamp]
+                - inside-out objects comprised of a single UUID as payload
+        {$object-uuid}
+            - real stuff, blessed hashes of
+                field       => value-or-object
+                '.'         => UUID
+                '.created'  => timestamp
+                '.table'    => table name
+    - Column/schema info is encoded in the store itself as {$type-uuid-of-Jifty::Model::Type}{*}
+        - Introspect/modifiable as any regular model
+
+=head2 Limitations
+
+
+=head3 Runtime Typecasting
+
+my $uuid = $typed_record->id;   # concat of time and uuid
+# ...somebody retypes it and stores it...
+$typed_record->load_by_id($uuid); # oops
+
+=head3 Performance
+
+=head3 Search
+
+=head3 Scalability
+

Added: jifty/branches/schema-plugins/doc/jifty-plugins-2.0
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/doc/jifty-plugins-2.0	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,141 @@
+* Plugins and actions need to work better
+* Plugins should be able to create model classes
+* So should applications
+* Plugins can add columns to model classes
+* Plugins can add logic to model classes (before_*, after_* hooks)
+* Plugins should be able to add properties to columns
+* The app always overrides plugins
+* Plugins are loaded and evaluated in order
+* Models should become mixins when appropriate
+  - But if the app doesn't define one, they can create classes/tables #'
+
+
+The plugins we want in our ponyverse:
+* ::Users
+* ::Users::Identity::EmailedToken
+*                  ::UsernamePassword
+*                  ::OpenID
+*                  ::Certs
+*                  ::{Assertion,Kerberos,...}
+*        ::ResetIdentity (change password for any identity once logged in / add identity)
+* ::Users::Signup
+* ::Users::Login
+* ::Users::EmailAddress
+
+::Users
+* unique ID
+* created_date, update_date, created_by, updated_by
+* display_name field (which you can force to something -- e.g. email, openid, name from cert)
+ * (Jifty::Record _brief_description returns this?)
+* last_login (?)
+* Do superuser and nobody live in the database? (That's authz)
+
+Where do identity plugins hook?
+* Add columns
+* Get credentials, return:
+    * These are valid
+    * These are invalid
+    * Other auth error ``I don't know''
+  * bounce trips go through continuations
+* load_by_unique_id
+
+* ::Login
+* /login =>
+  * list of identity types
+   * for each, fields for login columns declared by the ::Identity::Foo plugin
+  * submit button
+* Login action
+  * load_by_unique_id
+  * if the user doesn't exist, callback (to signup) or error
+  * Passes credentials to the identity plugin's auth method
+   * Identities that need confirmation error here if needed
+  * frob the session as appropriate
+  * frob CurrentUser
+  * Adds Dispatcher variables
+* Logout action
+  * frob the session and current user
+  * dispatcher rule on /logout redirects
+
+* ::Signup
+* Signup action
+  * verifies credentials - validates fields (inc. uniqueness)
+  * Creates a record
+  * Add a message, and push the user to the login page with a filled-out login action
+
+* What do ``mixins'' *mean*?
+ * We want injection, not actual mixins
+
+
+----- Where is this going to be hard? -----
+* Mixing columns into users
+* Getting the autogenerated actions to DTRT
+* hooks look kinda like Plagger
+* If there are multiple plugin models with no app model,
+  they all get mixed into the null model
+
+
+----- Username/password plugin ------
+
+# Model (Jifty::Plugin::User::Identity::UsernamePassword::Model::User)
+
+column 'username' =>
+  type is 'text',
+  is unique;
+
+column 'password' =>
+  type is 'text',
+  render_as 'Password',
+  filters are qw(Jifty::DBI::Filter::SaltHash);
+
+sub password_is {
+    my $self = shift;
+    my $pass = shift;
+
+    return undef unless $self->_value('password');
+
+    my ($hash, $salt) = @{$self->_value('password')};
+
+    return 1 if ( $hash eq Digest::MD5::md5_hex($pass . $salt) );
+    return undef;
+
+}
+
+# ``exports''
+sub load_by_unique_id {
+    my $self = shift;
+    my %args = (username => undef, @_);
+    return $self->load_by_columns(username => $args{username});
+}
+
+sub validate_credentials {
+    my $self = shift;
+    my %args = (username => undef, @_);
+    return unless $self->id;
+    return $self->password_is($args{password});
+}
+
+# Jifty::Plugin::User::Identity::UsernamePassword::Action::Login
+
+param username =>
+  render_as 'Text';
+
+param password =>
+  render_as 'Password';
+
+
+
+# Login plugin Action::Login
+sub check_credentials {
+    my $self = shift;
+    my $userobj = MyApp::Model::User->new;
+    my $user;
+    foreach my $identity_type ($userobj->identity_plugins) {
+        if($userobj->($identity_type . "::load_by_unique_id")($self->argument_values)) {
+            if($userobj->($identity_type . "::validate_credentials")($self->argument_values)) {
+                ...
+            } else {
+                ...
+            }
+        }
+    }
+}

Added: jifty/branches/schema-plugins/doc/jifty-web-form-etc
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/doc/jifty-web-form-etc	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,69 @@
+The Jifty::Web::Form::* classes are currently a bit of a mess. This is
+an effort to document their internals so they make some sense to
+whoever finally gets motivates to fix them (which may well be me).
+
+C<Jifty::Web::Form::Element> is the base of *every* class in the
+hierarchy. Virtually any widget that Jifty renders, be it a link,
+button, or form field, comes from some subclass of
+C<Jifty::Web::Form::Element>
+
+C<Element> itself deals essentially only with writing out javascript
+event handlers for widgets. Its <javascript> method walks the
+C<onclick> argument to all those buttons and links, building up a data
+structure that is eventually serialized using JSON and passed to
+jifty.js's C<update> function in an C<onclick=> wrapper.
+
+Form fields (usually created by C<form_field> calls on a
+C<Jifty::Action> are all subclasses of C<Jifty::Web::Form::Field>,
+(C<Jifty::Web::Form::Field::I<something>).
+
+C<Field> takes care of most of the work of rendering form fields. It
+renders the label, autocompletion, placeholders, hints, validation
+errors and warnings, and so on. Subclasses override methods as
+appropriate to add or alter details of the rendering. Note that
+subclasses need *not* concern themselves with making sure that the
+field is rendered as the correct kind of widget (field, select, etc.),
+assuming they represent a standard, single, HTML C<input> tag (as
+opposed to, say, C<Combobox>). That is dealt with by the C<type>
+magic:
+
+Whenever you create a C<Field>, it has a C<type>. Normally, this
+represents the C<type> property on the C<input> tag. In order to make
+the system extensible, however, it *also* indicates what perl class
+this widget will be. C<Jifty::Web::Form::Field::new> blesses the
+returned object into C<'Jifty::Web::Form::Field::' . ucfirst
+$type>. The stock rendering renders an input field with
+C<type="$type">, but subclasses can override C<render_widget> to
+render something else -- see C<Jifty::Web::Form::Field::Combobox> for
+an example of doing this.
+
+Simple links are instances of C<Jifty::Web::Form::Field::Link>. This
+class is fairly simple -- it merely renders as a HTML C<a> tag with
+the appropriate attributes.
+
+The only detail that remains is the non-javascript state-variable
+emulation of AJAX support on links and buttons. This is
+C<Jifty::Web::Form::Field::Clickable>'s job. C<Clickable> is also a
+C<Element>, which means it inherits the Javascript hooks. Its
+constructor builds up a list of state variables that need to be
+B<saved> from the previous request, while its C<generate> method walks
+the javascript hooks inherited from C<Element> and generates new state
+variables that will need to be sent by this button to simulate the
+AJAX effects given in the C<onclick>. If the clickable needs to submit
+actions, C<generate> creates it as a button, and otherwise it creates
+a link. It does this by creating a *new*
+C<Jifty::Web::Form::Field::Link> or C<Jifty::Web::Form::Field::Field>
+object, and serializing all the state variables it's built up onto
+that object appropriately (in the name, if it's a button, as GET
+parameters if it's a link).
+
+This final step, where a C<Clickable> (which is not renderable) clones
+itself into a different class that can be rendered, makes use of the
+C<accessors> method defined in C<Element>. Every subclass of
+C<Element> keeps a list of the accessors defined on that class
+(typically these are defined using C<Class::Accessor::Fast>, but not
+always). When C<Clickable> needs to clone itself, it walks the list
+C<($self->accessors)>, building up a hash of all the accessor
+key-value pairs on itself, and passes these to C<new> on C<Field> or
+C<Link>, which calls accessors on itself for every C<key => value>
+pair in its argument list.

Added: jifty/branches/schema-plugins/doc/packaging
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/doc/packaging	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,78 @@
+For Jifty:
+
+* It should be possible for Jifty's developers to package bin/jifty as a 
+  single platform-specifc "runnable" application, including lib/ share/ and all
+  non-core perl dependencies. (+ libraries?)
+
+
+
+For any Jifty application:
+
+* It should be possible for an end-user to package the source code of a Jifty 
+  application as a single platform-specific "runnable" application.
+
+* Ideally, it should be possible to turn a packaged application into 
+  an unpacked copy of the application's source code and dependent libraries.
+
+* It should be possible to install a Jifty application on a local disk 
+  such that multiple instances of the application can run with only
+  a site_config.yml file. (or without it)
+
+* It should be possible for an end user of one of these installed
+  applications to snapshot the application and all its assets, internal
+  libraries and dependencies to a private directory)
+
+    In all cases:
+        * --with-deps includes the installed versions of our dependencies.
+        * plugins are treated as CPAN dists for the purposes of dependency management
+
+    jifty package --file /tmp/myapp.exe --with-deps
+        * Takes multiple forms of targets
+        * Defaults to the zipped form
+        * --file is the path of the output file 
+
+    jifty dist --with-deps
+        * make a source distribution of this application, suitable for handing to a developer
+    jifty isolate  --with-deps 
+        * needs a better name 
+        * Pull in all dependencies, as well as jifty.
+
+    jifty install/uninstall
+        * some sort of management. will also work on plugins
+
+
+
+# A dist blib builder
+
+# A dist blib packager (->zip with a makefile.pl - it works as a par and as a dist)
+
+# Package multiple dists as a single dist.
+
+# Pony: an automatic deps-writer that will design a list of deps for 
+the systemwide currently installed modules.
+
+
+# Must include shared libs (expat, svn, etc)
+
+# Some way to deal with systemwide shared libs, like Pg
+
+# an alien extractor that analyzes already-built XS libs (in either blib/ or sitelib)
+with platform-specific tools (ldd, xtools, etc) and pull in shared libraries, similar
+to how Alien::* currentl does it in a case-by-case basis.
+
+# A dist blib upgrader
+
+# A cpan workalike for jifty dists
+
+# An installation scheme for
+
+    {$INSTALL_DIR,$APP_DIR}/share/jifty/deps/{`archname`,noarch}
+
+
+# one blib/ directory for the tuple of
+    (dist, version, `arch` or "noarch" , perl version?)
+
+# an @INC populator     
+    pushes a glob for arch-independent  (rename this: blib-noarch/*
+    pushes a glob for per-arch libs  blib-`arch`/*
+

Added: jifty/branches/schema-plugins/doc/plugin-requirements
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/doc/plugin-requirements	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,52 @@
+21:36 <obra> so. what would people like to see out of a jifty plugin framework?
+21:39 <audreyt> I'd like to see that a dispatcher plugin cannot affect an action plugin
+21:43 <obra> What do you mean? I'd expect that a plugin would be able to install dispatcher rules and actions
+21:43 <obra> so we could make the "EditFile" thing a plugin
+21:44 <audreyt> I'd like to see JiftyX::EditFile::Dispatch
+21:44 <audreyt> er Dispatcher
+21:44 <audreyt> JiftyX::EditFile::Action
+21:44 <audreyt> as separate namespaces (maybe inside the same EditFile.pm)
+21:44 <audreyt> of course, JiftyX is better spelled as Jifty::Plugin
+21:44 <obra> Jiftyhashi?
+21:45 <audreyt> Java has coffee beans
+21:45 <audreyt> maybe Jifty has tea bags
+21:45 <audreyt> Jif::Tea::Bag
+21:45 <obra> *groan*
+21:46 <obra> so. about plugins
+21:46 <obra> I guess there are two different things here
+21:46 <obra> 1) plugins for parts of jifty should be in the right namespaces
+21:46 <obra> 2) should a plugin be able to install dispatcher rules directly?
+21:46 <obra> ie, "use this plugin and it adds rules at these trigger points"
+21:48 <obra> I want "Login" and "Signup" to become plugins
+21:48 <obra> so I can stop cargo culting them between apps
+21:48 <audreyt> so sorta like rt callbacks
+21:48 <obra> yeah. was thinking more like plagger's system. it looks pretty
+21:48 <obra> named points. so even if they move between files, you don't lose
+21:48 <audreyt> yup
+21:49 <obra> so. this means I need to be able to:
+21:49 <obra> install actions
+21:49 <obra> install dispatcher rules at all stages
+21:49 <obra> install static content
+21:49 <obra> install templates
+21:49 <obra> possibly install widgets (aka form fields)
+21:49 <obra> install javascript libs and css files
+21:50 <obra> (special cases of static content)
+21:50 <obra> (but also hooks to add those things to the generated header)
+21:50 <audreyt> right
+21:50 <obra> what else am I missing? ;)
+21:50 <obra> oh. new jifty commands?
+21:51 <audreyt> po files are considered templates?
+21:51 <obra> # ./bin/jifty fooplugin
+21:51 <obra> no. pofiles are anotehr item for the list
+21:51 <audreyt> declare dependency? (normal cpan semantics?)
+21:51 <obra> another
+21:51 <obra> yeah. I'd just use perl require lines
+21:51 <audreyt> partial ordering? (run this before that)
+21:52 <obra> in dispatchers?
+21:52 <obra> or actions?
+21:52 <obra> or what?
+21:57 <audreyt> sorry
+21:57 <audreyt> from callbacks
+21:57 <obra> nod. I worry a little bit about the complexity. when would we want it for callbacks other than the dispatcher rules?
+21:58 <audreyt> if all your login etc are in well known places
+

Added: jifty/branches/schema-plugins/doc/plugin-syntax
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/doc/plugin-syntax	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,61 @@
+# Login plugin
+package Login::Dispatcher;
+
+on 'login'    => 'login';
+on 'logout'   => 'logout';
+on 'signup'   => 'a_moment';
+
+after 'logout' => run {...clear_cookie...};
+before 'login' => run {...check_for_already_logged_in...};
+
+# RequiredAuth plugin
+package RequiredAuth::Dispatcher;
+
+before '*' => [
+    plugin Login,
+    run { ...check for auth, otherwise 401...  }
+],
+
+on 'auth_required' => run {...}
+
+# Dummy application
+package Dummy::Dispatcher;
+
+## implicit from config.yml:
+##    plugins:
+##       - RequireAuth:
+##           prefix: /blah
+##           args: vals
+##       - Login:
+##           prefix: /blah
+##           args: vals
+# trigger RequireAuth;
+
+after plugins 'Login::*' => [
+    on "logout" => show '/my/logout',
+]
+
+before plugins '*' => [
+    on "logout" => show '/my/logout',
+]
+
+on 'logout' => [
+    show '/my/logout',
+    plugin Login,
+]
+
+after 'login' => [
+]
+after PUT 'login' => [
+]
+
+after plugin Login => (
+before plugin Login => (
+    on "logout" => show '/my/logout',
+);
+
+
+on 'logout' => [
+    before_plugin Login,
+        show '/my/logout',
+]

Added: jifty/branches/schema-plugins/doc/plugins-restated-assumptions
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/doc/plugins-restated-assumptions	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,80 @@
+* Plugins should be able to provide
+
+    * New actions
+    * New templates
+    * New models?
+
+* Plugins should be configurable
+    * should be able to be told about your  
+        * actions
+        * templates
+        * models
+
+* Plugins should be able to add dispatcher rules
+
+* The application should be able to spec what order 
+  dispatcher rules are run in. This gets sticky. 
+
+* If you don't spec explicit dispatcher rule ordering, what do you get?
+
+    for each of (before, on, after) {
+        run the plugin rules in the order plugins are defined
+        run my app's rules
+
+    }
+
+
+
+        before plugin Jifty::Login => {
+            before 'login' => {
+                .....
+            }
+        }
+
+
+
+Plugins provide templates.
+
+
+    auto/Jifty/Plugin/Login/share  (module::sharedir. wherever it puts stuff)
+
+
+    plugins have a default path they expose templates at.
+
+    so, Plugin::Login would expose templates at
+
+    /=/Login/
+
+    That path is 100% overridable by the app when it loads the plugin
+
+    An app's content always overrides a plugin's content.
+
+    If two plugins provide PO files or (templates to the same location,
+        either a built in location or an app-driven location), the most
+        recently loaded plugin's content override the older plugin's
+        content.
+
+
+Anatomy of a plugin -  In the end, plugins are apps. and apps are plugins.
+
+
+Jifty::Plugin::<name>
+
+    t/
+    lib/
+        Jifty/
+            Plugin/
+                <name>.pm
+                <name>/
+                        Dispatcher.pm
+                        Action/
+                        Model/ 
+    share/
+        po/
+        web/
+            static/
+            templates/
+~
+
+We're defining mount points for templates
+

Added: jifty/branches/schema-plugins/doc/pubsub/backend_message_types
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/doc/pubsub/backend_message_types	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,15 @@
+Payload Types:
+
+ClientAction --> Republisher
+    #subscription
+    - Add Subscription (IP)
+    - Remove Subscription (IP)
+
+Republisher --> ClientRender
+    #pong-$IP
+    - Pong (IP, IS_ALIVE)
+
+Server --> Republisher
+    #pong
+    - Pong (IP, IS_ALIVE)
+

Added: jifty/branches/schema-plugins/doc/pubsub/subscriptions
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/doc/pubsub/subscriptions	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,83 @@
+Subscriptions we want to support
+
+
+
+    Create,Read,Update,Delete
+        
+        Class: MyApp::Record::Foo
+     
+
+
+For hiveminder: NEWS
+
+    * on every page, we have a little box with the 3 most recent news entries:
+
+    <h2>News</h2>
+    <%perl>
+        my $sub = Jifty::ClientSubscription->new( 
+            query_class => 'BTDT::Model::News',
+            query => ['id_not' => undef],
+            # Ordering?
+            on_add => [
+            { render_with => '/fragments/news/item',
+              render => after => 'news.entries.last_child' 
+            },
+            { # how do we remove something if there are more than 3?
+            }
+
+            ]);
+
+
+For hiveminder:
+
+    My page is:
+
+        list of: tasks matching 'owner me, complete is not true'
+        each item is rendered as '/fragments/tasklist/view,  with args %ARGS and record => $found
+
+
+    Subscribe to:
+
+        * creation of things that match 'owner me, complete is not true'
+        * update, delete to anything that matches 'owner me, complete is not true' after the changes
+        * update, delete to anything that matches 'owner me, complete is not true' before the changes
+       
+
+
+    "Render this component as":
+
+        wrapper
+            list of items matching the subscription 'owner me, complete is not true'
+                render each item with '/fragments/tasklist/view', args => %ARGS, item $found
+
+
+            my $subscription = Jifty::ClientSubscription->new(
+                on_add => {
+                    args        => \%ARGS,
+                    render_with => '/fragments/tasklist/view',
+                    render => after => '.parent.last_child'
+                                # before, after, replacing
+                                
+                },
+                on_remove => {
+                    args        => \%ARGS,
+                    render => replacing => 'self'
+                    render_with => '/fragments/tasklist/fade_away'
+                },
+                query_class => 'BTDT::Model::Task',
+                query       => [ owner => 'me', complete_not => 'true' ]
+            );
+
+
+# Session::ClientSide-ish encoding of SIDs to subscribe
+# Each request carries IDs of its existing subs (stashed into ->subs)
+my $sid = Jifty->subs->add(
+    query_class => 'Ping',
+    query       => [ host => '127.0.0.1' ],
+    render_with => '/fragments/pong',
+);
+Jifty->subs->cancel($sid);
+
+# This actually just calls the fragments with Publisher-msg structures as %ARGS
+# Print-on-void-context, return-on-other-contexts
+Jifty->subs->render;

Added: jifty/branches/schema-plugins/doc/pubsub/system_architecture.graffle
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/doc/pubsub/system_architecture.graffle	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,1744 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>ActiveLayerIndex</key>
+	<integer>0</integer>
+	<key>AutoAdjust</key>
+	<true/>
+	<key>CanvasColor</key>
+	<dict>
+		<key>w</key>
+		<string>1</string>
+	</dict>
+	<key>CanvasOrigin</key>
+	<string>{0, 0}</string>
+	<key>CanvasScale</key>
+	<real>1</real>
+	<key>ColumnAlign</key>
+	<integer>1</integer>
+	<key>ColumnSpacing</key>
+	<real>36</real>
+	<key>CreationDate</key>
+	<string>2006-10-24 12:01:34 -0700</string>
+	<key>Creator</key>
+	<string>Jesse Vincent</string>
+	<key>DisplayScale</key>
+	<string>1 in = 1 in</string>
+	<key>GraphDocumentVersion</key>
+	<integer>5</integer>
+	<key>GraphicsList</key>
+	<array>
+		<dict>
+			<key>Bounds</key>
+			<string>{{756, 39.835}, {211, 137}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>ID</key>
+			<integer>82</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
+
+\f0\fs24 \cf0 Jifty Push System Architecture.\
+A. Tang  and J. Vincent - \
+October 2006}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{330.451, 150.979}, {256, 14}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FitText</key>
+			<string>YES</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>80</integer>
+			<key>Line</key>
+			<dict>
+				<key>ID</key>
+				<integer>24</integer>
+				<key>Position</key>
+				<real>0.62390816211700439</real>
+				<key>RotationType</key>
+				<integer>0</integer>
+			</dict>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>shadow</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+				<key>stroke</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
+
+\f0\fs24 \cf0 (With a kill-me-when-republish-to-nobody flag)}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>ID</key>
+			<integer>78</integer>
+			<key>Points</key>
+			<array>
+				<string>{822.47, 460.7}</string>
+				<string>{822.329, 431.61}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>FilledArrow</string>
+					<key>Pattern</key>
+					<integer>2</integer>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>77</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{748.211, 461.2}, {149, 97.8}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>ID</key>
+			<integer>77</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>Pattern</key>
+					<integer>2</integer>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
+
+\f0\fs24 \cf0 Publish unsubscription from 'ping' events \
+($SUB into $DEL-SUBS)\
+\
+(May also happen \
+upon session GC)}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{45.4648, 366.851}, {907.535, 54}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>NSKern</key>
+				<real>0.0</real>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>1</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>GradientColor</key>
+					<dict>
+						<key>w</key>
+						<string>0.666667</string>
+					</dict>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
+
+\f0\fs24 \cf0 Message Bus}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{241.567, 505}, {149, 54}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>ID</key>
+			<integer>75</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
+
+\f0\fs24 \cf0 Publish subscription to 'ping' events \
+($SUB into $ADD-SUBS)}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{105, 505}, {133, 54}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>ID</key>
+			<integer>74</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
+
+\f0\fs24 \cf0 Start ping server}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{398.5, 285.356}, {49, 28}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FitText</key>
+			<string>YES</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>72</integer>
+			<key>Line</key>
+			<dict>
+				<key>ID</key>
+				<integer>71</integer>
+				<key>Position</key>
+				<real>0.51288449764251709</real>
+				<key>RotationType</key>
+				<integer>0</integer>
+			</dict>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>shadow</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+				<key>stroke</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
+
+\f0\fs24 \cf0 Send\
+[$SUB]}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>ID</key>
+			<integer>71</integer>
+			<key>Points</key>
+			<array>
+				<string>{423, 349.516}</string>
+				<string>{423, 251.716}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>FilledArrow</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{784.411, 610.503}, {77, 42}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FitText</key>
+			<string>YES</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>69</integer>
+			<key>Line</key>
+			<dict>
+				<key>ID</key>
+				<integer>68</integer>
+				<key>Position</key>
+				<real>0.42627021670341492</real>
+				<key>RotationType</key>
+				<integer>0</integer>
+			</dict>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>shadow</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+				<key>stroke</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
+
+\f0\fs24 \cf0 Jifty::Action:\
+Unsubscribe\
+Action::Ping}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>77</integer>
+			</dict>
+			<key>ID</key>
+			<integer>68</integer>
+			<key>Points</key>
+			<array>
+				<string>{823, 685}</string>
+				<string>{822.792, 559.5}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>FilledArrow</string>
+					<key>Pattern</key>
+					<integer>2</integer>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{323.771, 263.402}, {83, 28}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FitText</key>
+			<string>YES</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>62</integer>
+			<key>Line</key>
+			<dict>
+				<key>ID</key>
+				<integer>61</integer>
+				<key>Position</key>
+				<real>0.27589595317840576</real>
+				<key>RotationType</key>
+				<integer>0</integer>
+			</dict>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>shadow</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+				<key>stroke</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
+
+\f0\fs24 \cf0 Poll\
+$ADD-SUBS }</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>ID</key>
+			<integer>61</integer>
+			<key>Points</key>
+			<array>
+				<string>{364.995, 247.455}</string>
+				<string>{365.995, 356}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>FilledArrow</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{982.555, 751.48}, {61, 14}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FitText</key>
+			<string>YES</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>60</integer>
+			<key>Line</key>
+			<dict>
+				<key>ID</key>
+				<integer>59</integer>
+				<key>Position</key>
+				<real>0.89849430322647095</real>
+				<key>RotationType</key>
+				<integer>0</integer>
+			</dict>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>shadow</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+				<key>stroke</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
+
+\f0\fs24 \cf0 Time &gt;&gt;&gt;}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>ID</key>
+			<integer>59</integer>
+			<key>Points</key>
+			<array>
+				<string>{30.8634, 757}</string>
+				<string>{1124.02, 758.647}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>FilledArrow</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{652.329, 607.255}, {84, 28}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FitText</key>
+			<string>YES</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>55</integer>
+			<key>Line</key>
+			<dict>
+				<key>ID</key>
+				<integer>54</integer>
+				<key>Position</key>
+				<real>0.51953768730163574</real>
+				<key>RotationType</key>
+				<integer>0</integer>
+			</dict>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>shadow</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+				<key>stroke</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
+
+\f0\fs24 \cf0 Push+Render\
+$EVENT}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>ID</key>
+			<integer>54</integer>
+			<key>Points</key>
+			<array>
+				<string>{694.329, 546.643}</string>
+				<string>{694.329, 690.255}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>FilledArrow</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{655.57, 439.363}, {49, 24}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FitText</key>
+			<string>YES</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica-Bold</string>
+				<key>Size</key>
+				<real>10</real>
+			</dict>
+			<key>ID</key>
+			<integer>56</integer>
+			<key>Line</key>
+			<dict>
+				<key>ID</key>
+				<integer>52</integer>
+				<key>Position</key>
+				<real>0.45289328694343567</real>
+				<key>RotationType</key>
+				<integer>0</integer>
+			</dict>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>shadow</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+				<key>stroke</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica-Bold;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
+
+\f0\b\fs20 \cf0 Send\
+$EVENT}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>ID</key>
+			<integer>52</integer>
+			<key>Points</key>
+			<array>
+				<string>{680.07, 424.383}</string>
+				<string>{680.07, 483.957}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>FilledArrow</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{603.273, 452.914}, {52, 28}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FitText</key>
+			<string>YES</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>51</integer>
+			<key>Line</key>
+			<dict>
+				<key>ID</key>
+				<integer>50</integer>
+				<key>Position</key>
+				<real>0.41240772604942322</real>
+				<key>RotationType</key>
+				<integer>0</integer>
+			</dict>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>shadow</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+				<key>stroke</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
+
+\f0\fs24 \cf0 Poll\
+[$SUB] }</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>ID</key>
+			<integer>50</integer>
+			<key>Points</key>
+			<array>
+				<string>{629, 489.057}</string>
+				<string>{629.661, 435.363}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>FilledArrow</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{532.773, 447.813}, {52, 28}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FitText</key>
+			<string>YES</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>49</integer>
+			<key>Line</key>
+			<dict>
+				<key>ID</key>
+				<integer>48</integer>
+				<key>Position</key>
+				<real>0.41240772604942322</real>
+				<key>RotationType</key>
+				<integer>0</integer>
+			</dict>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>shadow</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+				<key>stroke</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
+
+\f0\fs24 \cf0 Poll\
+[$SUB] }</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>ID</key>
+			<integer>48</integer>
+			<key>Points</key>
+			<array>
+				<string>{558.5, 483.957}</string>
+				<string>{559.161, 430.263}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>FilledArrow</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{432.043, 446.396}, {52, 28}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FitText</key>
+			<string>YES</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>47</integer>
+			<key>Line</key>
+			<dict>
+				<key>ID</key>
+				<integer>46</integer>
+				<key>Position</key>
+				<real>0.36850014328956604</real>
+				<key>RotationType</key>
+				<integer>0</integer>
+			</dict>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>shadow</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+				<key>stroke</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
+
+\f0\fs24 \cf0 Poll\
+[$SUB] }</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>ID</key>
+			<integer>46</integer>
+			<key>Points</key>
+			<array>
+				<string>{458, 481}</string>
+				<string>{458.117, 425.087}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>FilledArrow</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{558.5, 256.36}, {57, 42}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FitText</key>
+			<string>YES</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>45</integer>
+			<key>Line</key>
+			<dict>
+				<key>ID</key>
+				<integer>44</integer>
+				<key>Position</key>
+				<real>0.24552829563617706</real>
+				<key>RotationType</key>
+				<integer>0</integer>
+			</dict>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>shadow</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+				<key>stroke</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
+
+\f0\fs24 \cf0 Publish \
+$EVENT\
+to $SUB}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>ID</key>
+			<integer>44</integer>
+			<key>Points</key>
+			<array>
+				<string>{587, 249.615}</string>
+				<string>{587, 362.615}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>FilledArrow</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>ID</key>
+			<integer>42</integer>
+			<key>Points</key>
+			<array>
+				<string>{315.076, 504.5}</string>
+				<string>{312.334, 428.426}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>FilledArrow</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>75</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{55.0165, 435.416}, {163, 42}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FitText</key>
+			<string>YES</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>41</integer>
+			<key>Line</key>
+			<dict>
+				<key>ID</key>
+				<integer>40</integer>
+				<key>Position</key>
+				<real>0.48351499438285828</real>
+				<key>RotationType</key>
+				<integer>0</integer>
+			</dict>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>shadow</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+				<key>stroke</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
+
+\f0\fs24 \cf0 Add \
+Subscription Channel $SUB:\
+Action::Ping}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>ID</key>
+			<integer>40</integer>
+			<key>Points</key>
+			<array>
+				<string>{137, 482.62}</string>
+				<string>{136, 428.426}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>FilledArrow</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{383.843, 121.654}, {149, 28}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FitText</key>
+			<string>YES</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>38</integer>
+			<key>Line</key>
+			<dict>
+				<key>ID</key>
+				<integer>24</integer>
+				<key>Position</key>
+				<real>0.42278197407722473</real>
+				<key>RotationType</key>
+				<integer>0</integer>
+			</dict>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>shadow</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+				<key>stroke</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
+
+\f0\fs24 \cf0 Publishes Event $EVENT:\
+PING 127.0.0.1 1ms}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{30.8634, 623.165}, {71, 28}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FitText</key>
+			<string>YES</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>36</integer>
+			<key>Line</key>
+			<dict>
+				<key>ID</key>
+				<integer>34</integer>
+				<key>Position</key>
+				<real>0.38565847277641296</real>
+				<key>RotationType</key>
+				<integer>0</integer>
+			</dict>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>shadow</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+				<key>stroke</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
+
+\f0\fs24 \cf0 Jifty Action:\
+Ping}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>ID</key>
+			<integer>34</integer>
+			<key>Points</key>
+			<array>
+				<string>{65, 690}</string>
+				<string>{68.5352, 553}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>FilledArrow</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{39, 693.868}, {1051.46, 54}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>ID</key>
+			<integer>31</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
+
+\f0\fs24 \cf0 Client Browser}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{470.033, 293.501}, {88.9045, 42}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FitText</key>
+			<string>Vertical</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>29</integer>
+			<key>Line</key>
+			<dict>
+				<key>ID</key>
+				<integer>28</integer>
+				<key>Position</key>
+				<real>0.59332269430160522</real>
+				<key>RotationType</key>
+				<integer>0</integer>
+			</dict>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>shadow</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+				<key>stroke</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
+
+\f0\fs24 \cf0 Publishes to \
+Channel \
+"$SERVER"}</string>
+			</dict>
+			<key>Wrap</key>
+			<string>YES</string>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>ID</key>
+			<integer>28</integer>
+			<key>Points</key>
+			<array>
+				<string>{514.486, 247.455}</string>
+				<string>{514.486, 360.455}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>FilledArrow</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{44.4648, 205.059}, {907.535, 36}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>NSKern</key>
+				<real>0.0</real>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>27</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>GradientColor</key>
+					<dict>
+						<key>w</key>
+						<string>0.666667</string>
+					</dict>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
+
+\f0\fs24 \cf0 Republisher}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>ID</key>
+			<integer>24</integer>
+			<key>Points</key>
+			<array>
+				<string>{458.117, 88.7247}</string>
+				<string>{458.652, 199.725}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>FilledArrow</string>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{174, 29.3902}, {410.773, 59.5743}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>w</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>NSKern</key>
+				<real>0.0</real>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>4</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>GradientColor</key>
+					<dict>
+						<key>w</key>
+						<string>0.666667</string>
+					</dict>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
+
+\f0\fs24 \cf0 Server $SERVER}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>4</integer>
+			</dict>
+			<key>ID</key>
+			<integer>22</integer>
+			<key>Points</key>
+			<array>
+				<string>{129.731, 504.727}</string>
+				<string>{-23, 405}</string>
+				<string>{344.348, 89.2904}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>FilledArrow</string>
+					<key>LineType</key>
+					<integer>1</integer>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>74</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{65, 502}, {340.944, 59.5743}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>ID</key>
+			<integer>79</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{45.4648, 490.194}, {907.535, 54}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>ID</key>
+			<integer>32</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
+
+\f0\fs24 \cf0 Jifty Server}</string>
+			</dict>
+		</dict>
+	</array>
+	<key>GridInfo</key>
+	<dict/>
+	<key>GuidesLocked</key>
+	<string>NO</string>
+	<key>GuidesVisible</key>
+	<string>YES</string>
+	<key>HPages</key>
+	<integer>2</integer>
+	<key>ImageCounter</key>
+	<integer>1</integer>
+	<key>IsPalette</key>
+	<string>NO</string>
+	<key>KeepToScale</key>
+	<false/>
+	<key>Layers</key>
+	<array>
+		<dict>
+			<key>Lock</key>
+			<string>NO</string>
+			<key>Name</key>
+			<string>Layer 1</string>
+			<key>Print</key>
+			<string>YES</string>
+			<key>View</key>
+			<string>YES</string>
+		</dict>
+	</array>
+	<key>LayoutInfo</key>
+	<dict>
+		<key>LayoutTarget</key>
+		<integer>3</integer>
+	</dict>
+	<key>LinksVisible</key>
+	<string>NO</string>
+	<key>MagnetsVisible</key>
+	<string>NO</string>
+	<key>MasterSheet</key>
+	<string>Master 1</string>
+	<key>MasterSheets</key>
+	<array>
+		<dict>
+			<key>ActiveLayerIndex</key>
+			<integer>0</integer>
+			<key>AutoAdjust</key>
+			<true/>
+			<key>CanvasColor</key>
+			<dict>
+				<key>w</key>
+				<string>1</string>
+			</dict>
+			<key>CanvasOrigin</key>
+			<string>{0, 0}</string>
+			<key>CanvasScale</key>
+			<real>1</real>
+			<key>ColumnAlign</key>
+			<integer>1</integer>
+			<key>ColumnSpacing</key>
+			<real>36</real>
+			<key>DisplayScale</key>
+			<string>1 in = 1 in</string>
+			<key>GraphicsList</key>
+			<array/>
+			<key>GridInfo</key>
+			<dict/>
+			<key>HPages</key>
+			<integer>1</integer>
+			<key>IsPalette</key>
+			<string>NO</string>
+			<key>KeepToScale</key>
+			<false/>
+			<key>Layers</key>
+			<array>
+				<dict>
+					<key>Lock</key>
+					<string>NO</string>
+					<key>Name</key>
+					<string>Layer 1</string>
+					<key>Print</key>
+					<string>YES</string>
+					<key>View</key>
+					<string>YES</string>
+				</dict>
+			</array>
+			<key>LayoutInfo</key>
+			<dict>
+				<key>LayoutTarget</key>
+				<integer>3</integer>
+			</dict>
+			<key>Orientation</key>
+			<integer>2</integer>
+			<key>OutlineStyle</key>
+			<string>Basic</string>
+			<key>RowAlign</key>
+			<integer>1</integer>
+			<key>RowSpacing</key>
+			<real>36</real>
+			<key>SheetTitle</key>
+			<string>Master 1</string>
+			<key>UniqueID</key>
+			<integer>1</integer>
+			<key>VPages</key>
+			<integer>1</integer>
+		</dict>
+	</array>
+	<key>ModificationDate</key>
+	<string>2006-10-24 15:25:32 -0700</string>
+	<key>Modifier</key>
+	<string>Jesse Vincent</string>
+	<key>NotesVisible</key>
+	<string>NO</string>
+	<key>Orientation</key>
+	<integer>2</integer>
+	<key>OriginVisible</key>
+	<string>NO</string>
+	<key>OutlineStyle</key>
+	<string>Basic</string>
+	<key>PageBreaks</key>
+	<string>YES</string>
+	<key>PrintInfo</key>
+	<dict>
+		<key>NSBottomMargin</key>
+		<array>
+			<string>float</string>
+			<string>0</string>
+		</array>
+		<key>NSLeftMargin</key>
+		<array>
+			<string>float</string>
+			<string>0</string>
+		</array>
+		<key>NSRightMargin</key>
+		<array>
+			<string>float</string>
+			<string>0</string>
+		</array>
+		<key>NSTopMargin</key>
+		<array>
+			<string>float</string>
+			<string>0</string>
+		</array>
+	</dict>
+	<key>ReadOnly</key>
+	<string>NO</string>
+	<key>RowAlign</key>
+	<integer>1</integer>
+	<key>RowSpacing</key>
+	<real>36</real>
+	<key>SheetTitle</key>
+	<string>Canvas 1</string>
+	<key>SmartAlignmentGuidesActive</key>
+	<string>YES</string>
+	<key>SmartDistanceGuidesActive</key>
+	<string>YES</string>
+	<key>UniqueID</key>
+	<integer>1</integer>
+	<key>UseEntirePage</key>
+	<true/>
+	<key>VPages</key>
+	<integer>1</integer>
+	<key>WindowInfo</key>
+	<dict>
+		<key>CurrentSheet</key>
+		<string>0</string>
+		<key>DrawerOpen</key>
+		<true/>
+		<key>DrawerTab</key>
+		<string>Outline</string>
+		<key>DrawerWidth</key>
+		<real>209</real>
+		<key>FitInWindow</key>
+		<false/>
+		<key>Frame</key>
+		<string>{{0, 0}, {1680, 1072}}</string>
+		<key>ShowRuler</key>
+		<false/>
+		<key>ShowStatusBar</key>
+		<true/>
+		<key>VisibleRegion</key>
+		<string>{{-250, -92}, {1665, 958}}</string>
+		<key>Zoom</key>
+		<string>1</string>
+	</dict>
+</dict>
+</plist>

Added: jifty/branches/schema-plugins/doc/pubsub/system_architecture.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/amazon-jifty.pdf
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/blogdemo.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/component-tree.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/edit.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/error.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/euroscon.css
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/doc/talks/euroscon.css	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,219 @@
+ at charset "UTF-8";
+
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Takahashi-Method-based Presentation Tool in XUL.
+ *
+ * The Initial Developer of the Original Code is SHIMODA Hiroshi.
+ * Portions created by the Initial Developer are Copyright (C) 2005
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s): SHIMODA Hiroshi <piro at p.club.ne.jp>
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#canvas {
+	/*
+        color: black !important;
+	background: white !important;
+        */
+	color: #000 !important;
+	background: white !important;
+        /* font-weight: bold; */
+	font-family:
+                "Candara"
+                "Georgia"
+                "DejaVu Serif Condensed"
+                "Arial"
+		"Bitstream Vera Sans"
+		"Verdana"
+		"Apple LiGothic"
+		"Arial Black"
+                "Bitstream Vera Sans"
+		sans-serif !important;
+}
+#canvas * {
+	cursor: pointer !important;
+}
+#canvas image {
+	width: auto;
+	height: auto;
+}
+.link-text {
+	color: #000066 !important;
+	text-decoration: none !important;
+}
+.link-text:hover {
+	color: #3333FF !important;
+/*        border-bottom: dotted 1px; */
+}
+.link-text:active {
+	color: #9999FF !important;
+}
+.s {
+        text-decoration: line-through;
+}
+.iu {
+        text-decoration: underline;
+        font-style: italic;
+}
+.ui {
+/*      text-decoration: underline; */
+        font-style: italic;
+}
+.u {
+        text-decoration: underline;
+}
+.date {
+        font-style: italic;
+        text-decoration: underline;
+        font-size: 66%;
+}
+.i {
+        font-style: italic;
+        font-family: "Cambria"
+                     "Times New Roman"
+                     "Bitstream Vera Serif"
+                     serif;
+}
+.t {
+        font-style: italic;
+}
+.tag {
+        color: #339933;
+}
+.att {
+        color: #333399;
+}
+.key {
+        color: #009999;
+}
+.h {
+        color: #000;
+        margin: 0px;
+}
+.c {
+        color: #C39;
+        margin: 0px;
+}
+.m {
+        color: #963;
+        margin: 0px;
+	font-family: "Comic Sans MS";
+}
+.cz {
+        color: #C39;
+        margin: 0px;
+	font-family: "Candara";
+        margin-right: -10px;
+}
+.z {
+        margin: 0px;
+        margin-left: -10px;
+	font-family: "Candara"
+}
+.x {
+        color: #C00;
+        margin: 0px;
+}
+.xs {
+        color: #633;
+        margin: 0px;
+        text-decoration: line-through;
+}
+.ci {
+        color: #C39;
+        margin: 0px;
+        font-style: italic;
+        font-family: "Constantia"
+                     "Times New Roman"
+                     "Bitstream Vera Serif"
+                     serif;
+}
+.cu {
+        color: #C39;
+        margin: 0px;
+        text-decoration: underline;
+}
+.ct {
+        color: #C39;
+        margin: 0px;
+        font-style: italic;
+}
+.hs {
+        color: #f33;
+        margin: 0px;
+        text-decoration: line-through;
+}
+.ht {
+        color: #aaa;
+        font-style: italic;
+}
+.pre {
+        font-family: "Consolas"
+                     "Anonymous"
+		     "Andale Mono"
+                     "Bitstream Vera Sans Mono"
+                     monospace;
+        padding-bottom: 8px;
+}
+#canvas[rendering="true"] image {
+	display: none;
+}
+#canvas[rendering="true"] *,
+#canvas[rendering="true"] .text-link {
+	color: white !important;
+}
+
+
+tabbox, tabpanels, tabpanel {
+	margin: 0;
+	padding: 0;
+}
+
+
+
+
+#canvas[eva="true"] {
+	background: white !important;
+	color: black !important;
+	font-family:
+                "Georgia"
+                "DejaVu Serif Condensed"
+		"Apple LiGothic"
+		"Arial Black"
+		serif !important;
+}
+#canvas[eva="true"] .link-text {
+	color: red !important;
+	text-decoration: none !important;
+}
+#canvas[eva="true"] .link-text:hover {
+	color: pink !important;
+}
+#canvas[eva="true"] .link-text:active {
+	color: orange !important;
+}
+#canvas[rendering="true"] *,
+#canvas[rendering="true"] .text-link {
+	color: black !important;
+}
+
+
+
+
+#canvasToolbar {
+	position: relative;
+}
+

Added: jifty/branches/schema-plugins/doc/talks/halo-overview.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/halo.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/lpw_intro_jifty_2006.key.tgz
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/lpw_intro_jifty_2006.pdf
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/lpw_scary_jifty.key.tgz
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/lpw_scary_jifty.pdf
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/new-entry.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/npw.2006.xul
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/Makefile.PL
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/Makefile.PL	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,6 @@
+use inc::Module::Install;
+name('Blog');
+version('0.01');
+requires('Jifty' => '0.60507');
+requires('Regexp::Common::profanity_us' => '0'); 
+WriteAll;

Added: jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/bin/jifty
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/bin/jifty	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,18 @@
+#!/opt/local/bin/perl
+
+eval 'exec /opt/local/bin/perl  -S $0 ${1+"$@"}'
+    if 0; # not running under some shell
+use warnings;
+use strict;
+use File::Basename qw(dirname); 
+use UNIVERSAL::require;
+
+BEGIN {
+    Jifty::Util->require or die $UNIVERSAL::require::ERROR;
+    my $root = Jifty::Util->app_root;
+    unshift @INC, "$root/lib" if ($root);
+}
+
+use Jifty::Script;
+$SIG{INT} = $SIG{TERM} = sub { warn "Stopped\n"; exit; };
+Jifty::Script->dispatch();

Added: jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/etc/config.yml
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/etc/config.yml	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,36 @@
+--- 
+framework: 
+  AdminMode: 1
+  ApplicationClass: Blog
+  ApplicationName: Blog
+  Database: 
+    Database: blog
+    Driver: SQLite
+    Host: localhost
+    Password: ''
+    RecordBaseClass: Jifty::DBI::Record::Cachable
+    User: ''
+    Version: 0.0.2
+  DevelMode: 1
+  L10N: 
+    PoDir: share/po
+  LogLevel: INFO
+  Mailer: Sendmail
+  MailerArgs: []
+
+  Plugins: []
+
+  Web: 
+    BaseURL: http://localhost
+    DataDir: var/mason
+    Globals: []
+
+    MasonConfig: 
+      autoflush: 0
+      default_escape_flags: h
+      error_format: text
+      error_mode: fatal
+    Port: 8888
+    ServeStaticFiles: 1
+    StaticRoot: share/web/static
+    TemplateRoot: share/web/templates

Added: jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/lib/Blog/Dispatcher.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/lib/Blog/Dispatcher.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,23 @@
+ package Blog::Dispatcher;
+ use Jifty::Dispatcher -base;
+
+ before '*' => run {
+    my $top = Jifty->web->navigation;
+    $top->child('List Entries' => url => '/');
+    $top->child('New Entry'    => url => '/new_entry');
+ }
+ on '/' => run {
+     my $entries =
+        Blog::Model::EntryCollection->new();
+     $entries->unlimit();
+
+     set entries => $entries;
+ };
+ on '/new_entry' => run {
+     set create => Jifty->web->new_action(
+         class => 'CreateEntry',
+         moniker => 'new_entry',
+     );
+ };
+
+1;

Added: jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/lib/Blog/Model/Entry.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/lib/Blog/Model/Entry.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,42 @@
+use strict;
+use warnings;
+
+package Blog::Model::Entry::Schema;
+use Jifty::DBI::Schema;
+
+# Your column definitions go here.  See L<Jifty::DBI::Schema> for
+# documentation about how to write column definitions.
+
+column
+    title => type is 'text',
+    default is 'Untitled';
+
+column
+    body => type is 'text',
+    render_as 'Textarea';
+
+package Blog::Model::Entry;
+use base qw/Blog::Record/;
+
+# Your model-specific methods go here.
+
+use Regexp::Common 'profanity_us';
+
+sub canonicalize_body {
+    my $self = shift;
+    my $body = shift;
+    $body =~ s/$RE{profanity}/**expletives**/gi;
+    return $body;
+}
+
+sub validate_body {
+    my $self = shift;
+    my $body = shift;
+    if ( $body =~ /$RE{profanity}/i ) {
+        return ( 0,
+            'Would you speak like that in front of your mother? *cough*' );
+    }
+    return ( 1, "OK" );
+}
+
+1;

Added: jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/share/web/templates/index.html
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/share/web/templates/index.html	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,12 @@
+<%ARGS>
+$entries # From the dispatcher
+</%ARGS>
+<&|/_elements/wrapper&>
+% while (my $entry = $entries->next) {
+  <h2><% $entry->title %></h2>
+  <div class="body">
+    <% $entry->body %>
+  </div>
+% }
+</&>
+

Added: jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/share/web/templates/new_entry
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/share/web/templates/new_entry	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,13 @@
+<%ARGS>
+$create # From the dispatcher
+</%ARGS>
+<&|/_elements/wrapper,
+   title => 'Create an article' &>
+<% Jifty->web->form->start %> 
+% foreach my $arg ($create->argument_names) {
+<% $create->form_field($arg) %>
+% }
+<% Jifty->web->form->submit( label => 'Save' ) %>
+<% Jifty->web->form->end %>
+</&>
+

Added: jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/t/00-model-Entry.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/doc/talks/npw.2006/Blog/t/00-model-Entry.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,49 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+
+=head1 DESCRIPTION
+
+A basic test harness for the Entry model.
+
+=cut
+
+use Jifty::Test tests => 11;
+
+# Make sure we can load the model
+use_ok('Blog::Model::Entry');
+
+# Grab a system use
+my $system_user = Blog::CurrentUser->superuser;
+ok($system_user, "Found a system user");
+
+# Try testing a create
+my $o = Blog::Model::Entry->new(current_user => $system_user);
+my ($id) = $o->create();
+ok($id, "Entry create returned success");
+ok($o->id, "New Entry has valid id set");
+is($o->id, $id, "Create returned the right id");
+
+# And another
+$o->create();
+ok($o->id, "Entry create returned another value");
+isnt($o->id, $id, "And it is different from the previous one");
+
+# Searches in general
+my $collection =  Blog::Model::EntryCollection->new(current_user => $system_user);
+$collection->unlimit;
+is($collection->count, 2, "Finds two records");
+
+# Searches in specific
+$collection->limit(column => 'id', value => $o->id);
+is($collection->count, 1, "Finds one record with specific id");
+
+# Delete one of them
+$o->delete;
+$collection->redo_search;
+is($collection->count, 0, "Deleted row is gone");
+
+# And the other one is still there
+$collection->unlimit;
+is($collection->count, 1, "Still one left");
+

Added: jifty/branches/schema-plugins/doc/talks/npw.2006/blog.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/npw.2006/canonicalize_entry.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/npw.2006/halo.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/npw.2006/halo2.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/npw.2006/hello.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/npw.2006/hello_jesse.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/npw.2006/jifty_3_files.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/npw.2006/jifty_app.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/npw.2006/jifty_edit_model.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/npw.2006/jifty_model.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/npw.2006/jifty_schema_setup.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/npw.2006/jifty_server.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/npw.2006/jifty_web_1.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/npw.2006/jifty_web_crud.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/npw.2006/jifty_web_crud_create_2.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/npw.2006/jifty_web_crud_edit.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/npw.2006/jifty_web_crud_edited.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/npw.2006/jifty_web_crud_new.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/npw.2006/jifty_web_dbadmin.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/npw.2006/makefile_pl.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/npw.2006/new_entry.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/npw.2006/online_docs.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/npw.2006/tests.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/npw.2006/validate_entry.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/npw2005.xul
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/oscon-europe.2006.xul
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/oscon.2006.xul
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/pony.jpg
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/pony.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/takahashi.css
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/doc/talks/takahashi.css	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,184 @@
+ at charset "UTF-8";
+
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Takahashi-Method-based Presentation Tool in XUL.
+ *
+ * The Initial Developer of the Original Code is SHIMODA Hiroshi.
+ * Portions created by the Initial Developer are Copyright (C) 2005
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s): SHIMODA Hiroshi <piro at p.club.ne.jp>
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#canvas {
+    /*
+	background: black !important;
+	color: white !important;
+        */
+        font-weight: bold;
+	font-family:
+                "Candara"
+                "Georgia"
+                "DejaVu Serif Condensed"
+                "Arial"
+		"Bitstream Vera Sans"
+		"Verdana"
+		"Apple LiGothic"
+		"Arial Black"
+                "Bitstream Vera Sans"
+		sans-serif !important;
+}
+#canvas * {
+	cursor: pointer !important;
+}
+#canvas image {
+	width: auto;
+	height: auto;
+}
+.link-text {
+	color: #99CCFF !important;
+	text-decoration: none !important;
+}
+.link-text:hover {
+	color: #CCEEFF !important;
+/*        border-bottom: dotted 1px; */
+}
+.link-text:active {
+	color: white !important;
+}
+.s {
+        text-decoration: line-through;
+}
+.iu {
+        text-decoration: underline;
+        font-style: italic;
+}
+.ui {
+/*      text-decoration: underline; */
+        font-style: italic;
+}
+.u {
+        text-decoration: underline;
+}
+.i {
+        font-style: italic;
+        font-family: "Times New Roman"
+                     "Bitstream Vera Serif"
+                     serif;
+}
+.c {
+        font-family: "Anonymous"
+		     "Lucida Console"
+		     "Andale Mono"
+                     "Bitstream Vera Sans Mono"
+                     monospace;
+}
+.t {
+        font-style: italic;
+}
+
+.h {
+        color: #ff0000;
+}
+
+.tag {
+        color: #33f;
+}
+.warn {
+        color: #f00;
+}
+.att {
+        color: #9999cc;
+}
+.key {
+        color: #00ffff;
+}
+.hid {
+        color: #999999;
+}
+.hidt {
+        color: #999999;
+        font-style: italic;
+}
+.pre {
+        font-family: "Anonymous"
+		     "Lucida Console"
+		     "Andale Mono"
+                     "Bitstream Vera Sans Mono"
+                     monospace;
+        padding-bottom: 8px;
+}
+#canvas[rendering="true"] image {
+	display: none;
+}
+#canvas[rendering="true"] *,
+#canvas[rendering="true"] .text-link {
+	color: white !important;
+}
+
+
+tabbox, tabpanels, tabpanel {
+	margin: 0;
+	padding: 0;
+}
+
+
+
+
+#canvas[eva="true"] {
+	background: white !important;
+	color: black !important;
+	font-family:
+                "Georgia"
+                "DejaVu Serif Condensed"
+		"Apple LiGothic"
+		"Arial Black"
+		serif !important;
+}
+#canvas[eva="true"] .link-text {
+	color: red !important;
+	text-decoration: none !important;
+}
+#canvas[eva="true"] .link-text:hover {
+	color: pink !important;
+}
+#canvas[eva="true"] .link-text:active {
+	color: orange !important;
+}
+#canvas[rendering="true"] *,
+#canvas[rendering="true"] .text-link {
+	color: black !important;
+}
+
+
+
+
+#canvasToolbar {
+	position: relative;
+}
+
+.subtitle {
+	display: none;
+    font-family: "Kochi Gothic";
+    color: yellow;
+    text-align: center;
+    font-size: 40px;
+    background: black;
+    bottom: 10;
+    padding-bottom: 10px;
+    padding-top: 10px;
+    spacing: 10px;
+}

Added: jifty/branches/schema-plugins/doc/talks/takahashi.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/doc/talks/takahashi.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,525 @@
+var Presentation = {
+    init : function(option){
+        this.size = 9;
+
+        this._offset  = 0;
+        this.canvas   = document.getElementById('canvas');
+        this.content  = document.getElementById('content');
+        this.textbox  = document.getElementById('textField');
+        this.deck     = document.getElementById('deck');
+        this.scroller = document.getElementById('scroller');
+
+               this.canvas.appendChild(document.createElement('description'));
+               this.canvas.lastChild.setAttribute('id', "caption");
+               this.canvas.lastChild.setAttribute('class', 'subtitle');
+        this.toolbar         = document.getElementById('canvasToolbar');
+        this.toolbarHeight   = this.toolbar.boxObject.height;
+        this.isToolbarHidden = true;
+        this.toolbar.setAttribute('style', 'margin-top:'+(0-this.toolbarHeight)+'px;margin-bottom:0px;');
+
+        if(option){
+            for(var i in option){this[i] = option[i]}
+        }
+
+        if (this.readParameter()) {
+            this.takahashi();
+        }
+
+        document.documentElement.focus();
+    },
+
+    takahashi : function(){
+        if (!document.title)
+            document.title = this.data[0].replace(/[\r\n]/g, ' ');
+
+        if(!this.data[this.offset]){
+            this.offset = this.data.length-1;
+        }
+        document.getElementById("current_page").value = this.offset+1;
+        document.getElementById("max_page").value     = this.data.length;
+
+        this.scroller.setAttribute('maxpos', this.data.length-1);
+        this.scroller.setAttribute('curpos', this.offset);
+
+        var broadcaster = document.getElementById('canBack');
+        if (!this.offset)
+            broadcaster.setAttribute('disabled', true);
+        else
+            broadcaster.removeAttribute('disabled');
+
+        var broadcaster = document.getElementById('canForward');
+        if (this.offset == this.data.length-1)
+            broadcaster.setAttribute('disabled', true);
+        else
+            broadcaster.removeAttribute('disabled');
+
+        this.canvas.setAttribute('rendering', true);
+
+        var text = this.data[this.offset].
+                replace(/^[\r\n]+/g,"").replace(/[\r\n]+$/g,"").replace(/(\r\n|[\r\n])/g,"\n")
+                .split('\n');
+        var range = document.createRange();
+        range.selectNodeContents(this.content);
+        range.deleteContents();
+        range.detach();
+
+        var line;
+        var newLine;
+        var uri;
+        var image_width;
+        var image_total_width  = 0;
+        var image_height;
+        var image_total_height = 0;
+        var image_src;
+        var code_listing = 0;
+
+
+        var labelId = 0;
+
+        for (var i = 0; i < text.length; i++)
+        {
+            this.content.appendChild(document.createElement('hbox'));
+            this.content.lastChild.setAttribute('align', 'center');
+            this.content.lastChild.setAttribute('pack', 'center');
+
+            line = text[i];
+            image_width  = 0;
+            image_height = 0;
+            var subtitle = '';
+
+            if (line.match(/^~/)) {
+              subtitle = line.substring(1);
+              line = '';
+            }
+
+            if (line.match(/^#/)) {
+                continue;
+            }
+
+            if (line.match(/^\\#/)) {
+                line = line.substring(1);
+            }
+
+            if (line.match(/^ /)) {
+              code_listing = 1; 
+              this.content.lastChild.setAttribute('align', 'left');
+              this.content.lastChild.setAttribute('class', 'pre');
+              line = line.substring(1)
+            } 
+            while (line.match(/^([^\{]+)?(\{\{ima?ge? +src="([^"]+)" +width="([0-9]+)" +height="([0-9]+)"[^\}]*\}\}|\{\{(([^\|]+)?\||)([^\}]+)\}\})(.+)?/))
+            {
+                if (RegExp.$1) {
+                    this.content.lastChild.appendChild(document.createElement('description'));
+                    this.content.lastChild.lastChild.setAttribute('value', RegExp.$1);
+                }
+                newLine = line.substring((RegExp.$1+RegExp.$2).length);
+
+                // Images
+                if (/^([^\{]+)?\{\{ima?ge? +src="([^"]+)" +width="([0-9]+)" +height="([0-9]+)"[^\}]*\}\}/.test(line)) {
+                    this.content.lastChild.appendChild(document.createElement('image'));
+                    image_src = RegExp.$2;
+                    if (image_src.indexOf('http://') < 0 &&
+                        image_src.indexOf('https://') < 0)
+                        image_src = this.dataFolder+image_src;
+                    this.content.lastChild.lastChild.setAttribute('src', image_src);
+                    this.content.lastChild.lastChild.setAttribute('width', parseInt(RegExp.$3 || '0'));
+                    this.content.lastChild.lastChild.setAttribute('height', parseInt(RegExp.$4 || '0'));
+                    image_width  += parseInt(RegExp.$3 || '0');
+                    image_height = Math.max(image_height, parseInt(RegExp.$4 || '0'));
+                }
+
+                // Styles
+                // else if (/^([^\{]+)?\{\{#([^\|]+)\|([^\}]+)\}\}/.test(line)) {
+                else if (/^([^\{]+)?\{\{(#([^\|]+)?\|)([^\}]+)\}\}/.test(line)) {
+                    uri = RegExp.$4;
+                    this.content.lastChild.appendChild(document.createElement('description'));
+                    this.content.lastChild.lastChild.setAttribute('value', uri);
+                    this.content.lastChild.lastChild.setAttribute('class', RegExp.$3);
+                }
+
+                // Links
+                else if (/^([^\{]+)?\{\{(([^\|]+)?\||)([^\}]+)\}\}/.test(line)) {
+                    uri = RegExp.$4;
+                    if (uri.indexOf('://') < 0)
+                        uri = this.dataFolder+uri;
+                    this.content.lastChild.appendChild(document.createElement('description'));
+                    this.content.lastChild.lastChild.setAttribute('value', RegExp.$3 || RegExp.$4);
+                    this.content.lastChild.lastChild.setAttribute('href', uri);
+                    this.content.lastChild.lastChild.setAttribute('tooltiptext', uri);
+                    this.content.lastChild.lastChild.setAttribute('statustext', uri);
+                    this.content.lastChild.lastChild.setAttribute('class', 'link-text');
+                }
+
+                line = newLine;
+            }
+
+            if (line) {
+                this.content.lastChild.appendChild(document.createElement('description'));
+                this.content.lastChild.lastChild.setAttribute('value', line);
+            }
+
+            image_total_width = Math.max(image_total_width, image_width);
+            image_total_height += image_height;
+        }
+
+        this.content.setAttribute('style', 'font-size:10px;');
+          caption =  document.getElementById('caption');
+        if (subtitle) {
+               caption.setAttribute('value', subtitle);
+        } else {
+               caption.setAttribute('value', '');
+
+        }
+
+        if (this.content.boxObject.width) {
+            var canvas_w  = this.canvas.boxObject.width;
+            var canvas_h  = this.canvas.boxObject.height-image_total_height;
+
+            var content_w = this.content.boxObject.width;
+            var new_fs = Math.round((canvas_w/content_w) * this.size);
+
+            if (new_fs > 32) {
+                new_fs = new_fs - (new_fs % 32) 
+            }
+            //if (code_listing) { new_fs = 48;}
+
+            this.content.setAttribute('style', 'top: 0');
+            this.content.setAttribute('style', 'font-size:'+ new_fs + "px");
+
+            if (this.content.boxObject.width < image_total_width) {
+                content_w = image_total_width;
+                new_fs = Math.round((canvas_w/content_w) * this.size);
+                this.content.setAttribute('style', 'font-size:'+ new_fs + "px");
+            }
+
+            var content_h = this.content.boxObject.height;
+            if(content_h >= (canvas_h - 70)){ // That 50 is space for subtitles
+                new_fs = Math.round(((canvas_h-70)/content_h) * new_fs);
+                this.content.setAttribute('style', 'font-size:'+ new_fs + "px");
+                content_h = this.content.boxObject.height;
+            }
+        }
+        this.canvas.removeAttribute('rendering');
+    },
+
+    reload : function() {
+        if (this.dataPath != location.href) {
+            var path = this.dataPath;
+            if (location.href.match(/^https?:/)) {
+                var request = new XMLHttpRequest();
+                request.open('GET', path);
+                request.onload = function() {
+                    Presentation.textbox.value = request.responseText;
+                    Presentation.data = Presentation.textbox.value.split('----');
+
+                    Presentation.takahashi();
+
+                    path = null;
+                    request = null;
+                };
+                request.send(null);
+            }
+            else {
+                document.getElementById('dataLoader').setAttribute('src', 'about:blank');
+                window.setTimeout(function() {
+                    document.getElementById('dataLoader').setAttribute('src', path);
+                    path = null;
+                }, 10);
+            }
+        }
+        else
+            window.location.reload();
+    },
+
+    forward : function(){
+        this.offset++;
+        this.takahashi();
+    },
+    back : function(){
+        this.offset--;
+        if(this.offset < 0){this.offset = 0}
+        this.takahashi();
+    },
+    home : function(){
+        this.offset = 0;
+        this.takahashi();
+    },
+    end : function(){
+        this.offset = this.data.length-1;
+        this.takahashi();
+    },
+    showPage : function(aPageOffset){
+        this.offset = aPageOffset ? aPageOffset : 0 ;
+        this.takahashi();
+    },
+
+    addPage : function() {
+        if (this.textbox.value &&
+            !this.textbox.value.match(/(\r\n|[\r\n])$/))
+            this.textbox.value += '\n';
+        this.textbox.value += '----\n';
+        this.onEdit();
+    },
+
+    toggleEditMode : function(){
+        this.deck.selectedIndex = (this.deck.selectedIndex == 0) ? 1 : 0 ;
+    },
+    toggleEvaMode : function(){
+        var check = document.getElementById('toggleEva');
+        if (this.canvas.getAttribute('eva') == 'true') {
+            this.canvas.removeAttribute('eva');
+            check.checked = false;
+        }
+        else {
+            this.canvas.setAttribute('eva', true);
+            check.checked = true;
+        }
+    },
+
+    onPresentationClick : function(aEvent){
+        if (!this.isToolbarHidden)
+            this.showHideToolbar();
+
+        switch(aEvent.button)
+        {
+            case 0:
+                var uri = aEvent.target.getAttribute('href');
+                if (uri)
+                    window.open(uri);
+                else {
+                    this.forward();
+                    document.documentElement.focus();
+                }
+                break;
+            case 2:
+                this.back();
+                document.documentElement.focus();
+                break;
+            default:
+                break;
+        }
+    },
+    onScrollerDragStart : function(){
+        this.scroller.dragging = true;
+    },
+    onScrollerDragMove : function(){
+        if (this.scroller.dragging)
+            this.showPage(parseInt(this.scroller.getAttribute('curpos')));
+    },
+    onScrollerDragDrop : function(){
+        if (this.scroller.dragging) {
+            this.showPage(parseInt(this.scroller.getAttribute('curpos')));
+        }
+         this.scroller.dragging = false;
+    },
+    onEdit : function() {
+        this.data = this.textbox.value.split('----');
+        this.takahashi();
+    },
+
+    onKeyPress : function(aEvent) {
+        switch(aEvent.keyCode)
+        {
+            case aEvent.DOM_VK_BACK_SPACE:
+                if (this.isPresentationMode) {
+                    aEvent.preventBubble();
+                    aEvent.preventDefault();
+                    Presentation.back();
+                }
+                break;
+            default:
+                break;
+        }
+    },
+
+
+    onToolbarArea   : false,
+    toolbarHeight   : 0,
+    toolbarDelay    : 300,
+    toolbarTimer    : null,
+    isToolbarHidden : false,
+    onMouseMoveOnCanvas : function(aEvent) {
+        if (this.scroller.dragging) return;
+
+        this.onToolbarArea = (aEvent.clientY < this.toolbarHeight);
+
+        if (this.isToolbarHidden == this.onToolbarArea) {
+            if (this.toolbarTimer) window.clearTimeout(this.toolbarTimer);
+            this.toolbarTimer = window.setTimeout('Presentation.onMouseMoveOnCanvasCallback()', this.toolbarDelay);
+        }
+    },
+    onMouseMoveOnCanvasCallback : function() {
+        if (this.isToolbarHidden == this.onToolbarArea)
+            this.showHideToolbar();
+    },
+
+    toolbarAnimationDelay : 100,
+    toolbarAnimationSteps : 5,
+    toolbarAnimationInfo  : null,
+    toolbarAnimationTimer : null,
+    showHideToolbar : function()
+    {
+        if (this.toolbarAnimationTimer) window.clearTimeout(this.toolbarAnimationTimer);
+
+        this.toolbarAnimationInfo = { count : 0 };
+        if (this.isToolbarHidden) {
+            this.toolbarAnimationInfo.start = 0;
+            this.toolbarAnimationInfo.end   = this.toolbarHeight;
+        }
+        else {
+            this.toolbarAnimationInfo.start = this.toolbarHeight;
+            this.toolbarAnimationInfo.end   = 0;
+        }
+        this.toolbarAnimationInfo.current = 0;
+
+        this.toolbar.setAttribute('style', 'margin-top:'+(0-(this.toolbarHeight-this.toolbarAnimationInfo.start))+'px; margin-bottom:'+(0-this.toolbarAnimationInfo.start)+'px;');
+
+        this.toolbarAnimationTimer = window.setTimeout('Presentation.animateToolbar()', this.toolbarAnimationDelay/this.toolbarAnimationSteps);
+    },
+    animateToolbar : function()
+    {
+        this.toolbarAnimationInfo.current += parseInt(this.toolbarHeight/this.toolbarAnimationSteps);
+
+        var top, bottom;
+        if (this.toolbarAnimationInfo.start < this.toolbarAnimationInfo.end) {
+            top    = this.toolbarHeight-this.toolbarAnimationInfo.current;
+            bottom = this.toolbarAnimationInfo.current;
+        }
+        else {
+            top    = this.toolbarAnimationInfo.current;
+            bottom = this.toolbarHeight-this.toolbarAnimationInfo.current;
+        }
+
+        top    = Math.min(Math.max(top, 0), this.toolbarHeight);
+        bottom = Math.min(Math.max(bottom, 0), this.toolbarHeight);
+
+        this.toolbar.setAttribute('style', 'margin-top:'+(0-top)+'px; margin-bottom:'+(0-bottom)+'px');
+
+        if (this.toolbarAnimationInfo.count < this.toolbarAnimationSteps) {
+            this.toolbarAnimationInfo.count++;
+            this.toolbarAnimationTimer = window.setTimeout('Presentation.animateToolbar()', this.toolbarAnimationDelay/this.toolbarAnimationSteps);
+        }
+        else
+            this.isToolbarHidden = !this.isToolbarHidden;
+    },
+
+
+
+    get offset(){
+        return this._offset;
+    },
+    set offset(aValue){
+        this._offset = parseInt(aValue || 0);
+        document.documentElement.setAttribute('lastoffset', this.offset);
+        return this.offset;
+    },
+
+    get data(){
+        if (!this._data) {
+             // Make sure you break the text into parts smaller than 4096
+             // characters, and name them as indicated. Tweak as required.
+             // (What a hack. A JS programmer should find a better way.)
+             // Luc St-Louis, and email is lucs at pobox.com.
+
+                 nodes = document.getElementById('builtinCode').childNodes;
+                 content = '';
+                for (i in nodes) {
+                    if (nodes[i].nodeValue) {
+                    content = content + nodes[i].nodeValue;
+                    }
+                }
+    
+               this._data = content.split(/^----/m);
+        }
+
+        return this._data;
+    },
+    set data(aValue){
+        this._data = aValue;
+        return aValue;
+    },
+
+
+    get isPresentationMode(){
+        return (this.deck.selectedIndex == 0);
+    },
+
+
+    get dataPath(){
+        if (!this._dataPath)
+            this.dataPath = location.href;
+        return this._dataPath;
+    },
+    set dataPath(aValue){
+        var oldDataPath = this._dataPath;
+        this._dataPath = aValue;
+        if (oldDataPath != aValue) {
+            this._dataFolder = this._dataPath.split('?')[0].replace(/[^\/]+$/, '');
+        }
+        return this._dataPath;
+    },
+
+    get dataFolder(){
+        if (!this._dataFolder)
+            this.dataPath = this.dataPath;
+        return this._dataFolder;
+    },
+    set dataFolder(aValue){
+        this._dataFolder = aValue;
+        return this._dataFolder;
+    },
+
+    readParameter : function() {
+        if (location.search) {
+            var param = location.search.replace(/^\?/, '');
+
+            if (param.match(/page=([0-9]+)/i))
+                this.offset = parseInt(RegExp.$1)-1;
+
+            if (param.match(/edit=(1|true|yes)/i))
+                this.toggleEditMode();
+
+            if (param.match(/eva=(1|true|yes)/i))
+                this.toggleEvaMode();
+
+            if (param.match(/data=([^&;]+)/i)) {
+                var path = unescape(RegExp.$1);
+                this.dataPath = path;
+                if (location.href.match(/^https?:/)) {
+                    var request = new XMLHttpRequest();
+                    request.open('GET', path);
+                    request.onload = function() {
+                        Presentation.textbox.value = request.responseText;
+                        Presentation.data = Presentation.textbox.value.split('----');
+
+                        Presentation.takahashi();
+                    };
+                    request.send(null);
+                }
+                else {
+                    document.getElementById('dataLoader').setAttribute('src', path);
+                }
+                return false;
+            }
+        }
+        return true;
+    },
+    onDataLoad : function() {
+        if (!window.frames[0].document.body.hasChildNodes()) return;
+        var data = window.frames[0].document.body.firstChild.innerHTML;
+        if (!data) return;
+
+        this.textbox.value = data;
+        this.data = this.textbox.value.split('----');
+
+        this.takahashi();
+    }
+};
+
+function init()
+{
+    window.removeEventListener('load', init, false);
+
+    Presentation.init();
+}
+window.addEventListener('load', init, false);

Added: jifty/branches/schema-plugins/doc/talks/yapc.asia.2006.xul
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/doc/talks/yapc.na.2006.xul
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/etc/config.yml
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/etc/config.yml	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,6 @@
+# This exists for the test files; they get all kinds of unhappy if the
+# application's name is 'Jifty', because of namespace issues.
+
+framework:
+  ApplicationName: JiftyApp
+  WhichConfigFile: main

Added: jifty/branches/schema-plugins/etc/site_config.yml
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/etc/site_config.yml	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,2 @@
+framework:
+  WhichConfigFile: site

Added: jifty/branches/schema-plugins/examples/Chat/Makefile.PL
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/Chat/Makefile.PL	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,6 @@
+use inc::Module::Install;
+name('Clock');
+version('0.01');
+requires('Jifty' => '0.60912');
+
+WriteAll;

Added: jifty/branches/schema-plugins/examples/Chat/bin/jifty
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/Chat/bin/jifty	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,15 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+use File::Basename qw(dirname); 
+use UNIVERSAL::require;
+
+BEGIN {
+    Jifty::Util->require or die $UNIVERSAL::require::ERROR;
+    my $root = Jifty::Util->app_root;
+    unshift @INC, "$root/lib" if ($root);
+}
+
+use Jifty::Script;
+$SIG{INT} = $SIG{TERM} = sub { warn "Stopped\n"; exit; };
+Jifty::Script->dispatch();

Added: jifty/branches/schema-plugins/examples/Chat/etc/config.yml
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/Chat/etc/config.yml	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,37 @@
+---
+framework:
+  AdminMode: 0
+  ApplicationClass: Chat
+  ApplicationName: Chat
+  PubSub:
+    Enable: 1
+  Database:
+    CheckSchema: 1
+    Database: chat
+    Driver: SQLite
+    Host: localhost
+    Password: ''
+    RecordBaseClass: Jifty::DBI::Record::Cachable
+    User: ''
+    Version: 0.0.1
+  DevelMode: 0
+  L10N:
+    PoDir: share/po
+  LogLevel: INFO
+  Mailer: Sendmail
+  MailerArgs: []
+  Plugins: []
+  Web:
+    BaseURL: http://localhost
+    DataDir: var/mason
+    Globals: []
+    MasonConfig:
+      autoflush: 0
+      default_escape_flags: h
+      error_format: text
+      error_mode: fatal
+    Port: 8888
+    ServeStaticFiles: 1
+    StaticRoot: share/web/static
+    TemplateRoot: share/web/templates
+    ServerClass: Chat::Server

Added: jifty/branches/schema-plugins/examples/Chat/lib/Chat/Action/Send.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/Chat/lib/Chat/Action/Send.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,18 @@
+package Chat::Action::Send;
+use warnings;
+use strict;
+
+use Jifty::Param::Schema;
+use Jifty::Action schema {
+    param message =>
+        label is 'Say something witty:';
+};
+
+sub take_action {
+    my $self = shift;
+    my $msg  = $self->argument_value('message');
+    $msg = "<$1\@${ENV{'REMOTE_ADDR'}}> $msg" if $ENV{HTTP_USER_AGENT} =~ /([^\W\d]+)[\W\d]*$/;
+    Chat::Event::Message->new( { message => $msg } )->publish;
+}
+
+1;

Added: jifty/branches/schema-plugins/examples/Chat/lib/Chat/Event/Message.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/Chat/lib/Chat/Event/Message.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,6 @@
+package Chat::Event::Message;
+use strict;
+use warnings;
+use base 'Chat::Event';
+
+1;

Added: jifty/branches/schema-plugins/examples/Chat/lib/Chat/Server.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/Chat/lib/Chat/Server.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,6 @@
+package Chat::Server;
+use base 'Jifty::Server';
+
+sub net_server { 'Net::Server::Fork' }
+
+1;

Added: jifty/branches/schema-plugins/examples/Chat/share/web/templates/fragments/message
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/Chat/share/web/templates/fragments/message	Mon Jan 29 21:05:02 2007
@@ -0,0 +1 @@
+<div><% ${$ARGS{event}}->{message} %></div>

Added: jifty/branches/schema-plugins/examples/Chat/share/web/templates/fragments/sender
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/Chat/share/web/templates/fragments/sender	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,7 @@
+<%init>
+my $action = Jifty->web->new_action( class => 'Send' );
+</%init>
+<% Jifty->web->form->start %>
+<% $action->form_field('message', focus => 1) %>
+<% Jifty->web->form->submit(onclick => [ { submit => $action }, { refresh_self => 1 } ]) %>
+<% Jifty->web->form->end %>

Added: jifty/branches/schema-plugins/examples/Chat/share/web/templates/index.html
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/Chat/share/web/templates/index.html	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,12 @@
+<&| /_elements/wrapper, title => "Jifty chat server", subtitle => "" &>
+<% Jifty::Web::PageRegion->new( name => "message", path => '/__jifty/empty' )->render %>
+<% Jifty::Web::PageRegion->new( name => "sender", path => '/fragments/sender' )->render %>
+</&>
+<%init>
+Jifty->subs->add(
+    class       => 'Message',
+    mode        => 'Bottom',
+    region      => "message",
+    render_with => '/fragments/message',
+);
+</%init>

Added: jifty/branches/schema-plugins/examples/Chat/t/00compile.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/Chat/t/00compile.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,19 @@
+#!/usr/bin/perl -w
+
+use Test::More;
+
+use File::Find;
+
+my @modules;
+find sub {
+    return unless /\.pm$/;
+    push @modules, $File::Find::name;
+}, "lib";
+
+ at modules = map { s[^lib/][];  $_ =~ s[.pm$][];  $_ =~ s[/][::]g; $_ } @modules;
+
+plan tests => scalar @modules;
+
+for my $module (@modules) {
+    require_ok $module;
+}

Added: jifty/branches/schema-plugins/examples/Chat/t/01startup.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/Chat/t/01startup.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,13 @@
+#!/usr/bin/perl -w
+
+use Jifty::Test 'no_plan';
+use Jifty::Test::WWW::Mechanize;
+
+# Startup the server.
+my $server = Jifty::Test->make_server;
+isa_ok($server, 'Jifty::Server');
+
+my $URL = $server->started_ok;
+my $mech = Jifty::Test::WWW::Mechanize->new;
+
+$mech->get_ok("$URL", "got the front page");

Added: jifty/branches/schema-plugins/examples/Clock/Makefile.PL
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/Clock/Makefile.PL	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,6 @@
+use inc::Module::Install;
+name('Clock');
+version('0.01');
+requires('Jifty' => '0.60912');
+
+WriteAll;

Added: jifty/branches/schema-plugins/examples/Clock/bin/jifty
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/Clock/bin/jifty	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,15 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+use File::Basename qw(dirname); 
+use UNIVERSAL::require;
+
+BEGIN {
+    Jifty::Util->require or die $UNIVERSAL::require::ERROR;
+    my $root = Jifty::Util->app_root;
+    unshift @INC, "$root/lib" if ($root);
+}
+
+use Jifty::Script;
+$SIG{INT} = $SIG{TERM} = sub { warn "Stopped\n"; exit; };
+Jifty::Script->dispatch();

Added: jifty/branches/schema-plugins/examples/Clock/clockserv.pl
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/Clock/clockserv.pl	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,21 @@
+use strict;
+use lib 'lib';
+use Time::HiRes qw( time sleep );
+use Jifty;
+
+BEGIN { Jifty->new };
+
+sub ping {
+    my ($class, $sub) = @_;
+    while (1) {
+        my $new_time = time;
+        warn "Time: $new_time\n";
+        Clock::Event::Tick->new($new_time)->publish;
+        sleep 0.5;
+    }
+    exit;
+}
+
+ping();
+
+1;

Added: jifty/branches/schema-plugins/examples/Clock/etc/config.yml
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/Clock/etc/config.yml	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,37 @@
+---
+framework:
+  AdminMode: 1
+  ApplicationClass: Clock
+  ApplicationName: Clock
+  PubSub:
+    Enable: TRUE
+  Database:
+    CheckSchema: 1
+    Database: clock
+    Driver: SQLite
+    Host: localhost
+    Password: ''
+    RecordBaseClass: Jifty::DBI::Record::Cachable
+    User: ''
+    Version: 0.0.1
+  DevelMode: 1
+  L10N:
+    PoDir: share/po
+  LogLevel: INFO
+  Mailer: Sendmail
+  MailerArgs: []
+  Plugins: []
+  Web:
+    BaseURL: http://localhost
+    DataDir: var/mason
+    Globals: []
+    MasonConfig:
+      autoflush: 0
+      default_escape_flags: h
+      error_format: text
+      error_mode: fatal
+    Port: 8888
+    ServeStaticFiles: 1
+    StaticRoot: share/web/static
+    TemplateRoot: share/web/templates
+    ServerClass: Clock::Server

Added: jifty/branches/schema-plugins/examples/Clock/lib/Clock/Event/Tick.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/Clock/lib/Clock/Event/Tick.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,15 @@
+package Clock::Event::Tick;
+use strict;
+use warnings;
+use base 'Clock::Event';
+
+sub match {
+    my $self    = shift;
+    my $query   = shift;
+    if (my $like = $query->{like}) {
+        return(index($$self, $like) >= 0);
+    }
+    return 1;
+}
+
+1;

Added: jifty/branches/schema-plugins/examples/Clock/lib/Clock/Server.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/Clock/lib/Clock/Server.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,6 @@
+package Clock::Server;
+use base 'Jifty::Server';
+
+sub net_server { 'Net::Server::Fork' }
+
+1;

Added: jifty/branches/schema-plugins/examples/Clock/share/web/templates/fragments/time
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/Clock/share/web/templates/fragments/time	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,4 @@
+<span><% $$event %></span>
+<%ARGS>
+$event
+</%ARGS>

Added: jifty/branches/schema-plugins/examples/Clock/share/web/templates/index.html
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/Clock/share/web/templates/index.html	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,36 @@
+<&| /_elements/wrapper, title => "Hi", subtitle => "Welcome" &>
+<table>
+<tr><td>
+Full: <% $region->render %>
+</td><td>
+Filtered: <% $region_filtered->render %>
+</td></tr>
+</table>
+</&>
+<%init>
+my $region = Jifty::Web::PageRegion->new(
+    name => "clock-time",
+    path => '/__jifty/empty',
+);
+my $region_filtered = Jifty::Web::PageRegion->new(
+    name => "clock-filtered",
+    path => '/__jifty/empty',
+);
+
+Jifty->subs->add(
+  # window_id   => 'random scope',
+    class       => 'Tick',
+    mode        => 'Replace',
+    region      => "clock-time",
+    render_with => '/fragments/time',
+);
+
+Jifty->subs->add(
+  # window_id   => 'random scope',
+    class       => 'Tick',
+    queries     => [{ like => '9' }],
+    mode        => 'Replace',
+    region      => "clock-filtered",
+    render_with => '/fragments/time',
+);
+</%init>

Added: jifty/branches/schema-plugins/examples/MyWeblog/Makefile.PL
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/MyWeblog/Makefile.PL	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,6 @@
+use inc::Module::Install;
+name('MyWeblog');
+version('0.01');
+requires('Jifty' => '0.61123_01');
+
+WriteAll;

Added: jifty/branches/schema-plugins/examples/MyWeblog/bin/jifty
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/MyWeblog/bin/jifty	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,15 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+use File::Basename qw(dirname); 
+use UNIVERSAL::require;
+
+BEGIN {
+    Jifty::Util->require or die $UNIVERSAL::require::ERROR;
+    my $root = Jifty::Util->app_root;
+    unshift @INC, "$root/lib" if ($root);
+}
+
+use Jifty::Script;
+$SIG{INT} = $SIG{TERM} = sub { warn "Stopped\n"; exit; };
+Jifty::Script->dispatch();

Added: jifty/branches/schema-plugins/examples/MyWeblog/etc/config.yml
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/MyWeblog/etc/config.yml	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,41 @@
+--- 
+framework: 
+  AdminMode: 1
+  ApplicationClass: MyWeblog
+  ApplicationName: MyWeblog
+  Database: 
+    CheckSchema: 1
+    Database: myweblog
+    Driver: SQLite
+    Host: localhost
+    Password: ''
+    RecordBaseClass: Jifty::DBI::Record::Cachable
+    User: ''
+    Version: 0.0.3
+  DevelMode: 1
+  L10N: 
+    PoDir: share/po
+  LogLevel: INFO
+  Mailer: Sendmail
+  MailerArgs: []
+
+  Plugins:
+   - Login: {}
+
+  PubSub: 
+    Backend: Memcached
+    Enable: ~
+  Web: 
+    BaseURL: http://localhost
+    DataDir: var/mason
+    Globals: []
+
+    MasonConfig: 
+      autoflush: 0
+      default_escape_flags: h
+      error_format: text
+      error_mode: fatal
+    Port: 8888
+    ServeStaticFiles: 1
+    StaticRoot: share/web/static
+    TemplateRoot: share/web/templates

Added: jifty/branches/schema-plugins/examples/MyWeblog/lib/MyWeblog/Model/Post.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/MyWeblog/lib/MyWeblog/Model/Post.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,24 @@
+use strict;
+use warnings;
+
+package MyWeblog::Model::Post;
+use Jifty::DBI::Schema;
+
+use MyWeblog::Record schema {
+
+         column title =>
+               type is 'text',
+               label is 'Title',
+               default is 'Untitled post';
+
+         column body =>
+               type is 'text',
+               label is 'Content',
+               render_as 'Textarea';
+
+};
+
+# Your model-specific methods go here.
+
+1;
+

Added: jifty/branches/schema-plugins/examples/MyWeblog/share/web/templates/_elements/nav
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/MyWeblog/share/web/templates/_elements/nav	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,11 @@
+<%init>
+my $top = Jifty->web->navigation;
+$top->child( Home => url => "/", sort_order => 1);
+$top->child( Post => url => "/post", label => "Post Article");
+
+if (Jifty->config->framework('AdminMode') ) {
+    $top->child(Administration       => url => "/__jifty/admin/", sort_order => 998);
+    $top->child(OnlineDocs       => url => "/__jifty/online_docs/", label => 'Online docs',  sort_order => 999);
+ }
+return();
+</%init>

Added: jifty/branches/schema-plugins/examples/MyWeblog/share/web/templates/fragments/page_of_posts
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/MyWeblog/share/web/templates/fragments/page_of_posts	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,30 @@
+<%args>
+$page => 1
+</%args>
+<%init>
+my $posts = MyWeblog::Model::PostCollection->new();
+$posts->unlimit();
+$posts->set_page_info( current_page => $page,
+                       per_page     => 15
+                     );
+$m->out("No items found.") if ($posts->pager->total_entries == 0);
+
+</%init>
+% if ($posts->pager->last_page > 1) {
+   Page <% $page %> of <% $posts->pager->last_page %>
+% }
+<dl class="list">
+% while (my $post = $posts->next) {
+ <dt><% $post->title %></dt>
+ <dd><% $post->body %></dd>
+% }
+</dl>
+
+% if ($posts->pager->previous_page) {
+  <% Jifty->web->link( label => "Previous Page", onclick => { args => { page => $posts->pager->previous_page } 
+} ) %>
+% }
+% if ($posts->pager->next_page) {
+  <% Jifty->web->link( label => "Next Page", onclick => { args => { page => $posts->pager->next_page } } ) %>
+% }
+

Added: jifty/branches/schema-plugins/examples/MyWeblog/share/web/templates/index.html
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/MyWeblog/share/web/templates/index.html	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,10 @@
+<&| /_elements/wrapper, title => Jifty->config->framework('ApplicationName') &>
+  
+<% Jifty->web->region(name => "myweblog-posts",
+                      path => "/fragments/page_of_posts") %>
+</&>
+<%init>
+my $posts = MyWeblog::Model::PostCollection->new();
+$posts->unlimit();
+</%init>
+

Added: jifty/branches/schema-plugins/examples/MyWeblog/share/web/templates/post
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/MyWeblog/share/web/templates/post	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,13 @@
+<%init>
+my $action = Jifty->web->new_action(class => 'CreatePost');
+</%init>
+
+<&| /_elements/wrapper, title => "Post to your weblog" &>
+<% Jifty->web->form->start() %>
+<% Jifty->web->form->next_page( url => '/') %>
+<% $action->form_field('title') %>
+<% $action->form_field('body') %>
+<% Jifty->web->form->submit( label => 'Post' ) %>
+<% Jifty->web->form->end() %>
+</&>
+

Added: jifty/branches/schema-plugins/examples/MyWeblog/t/00-model-Post.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/MyWeblog/t/00-model-Post.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,49 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+
+=head1 DESCRIPTION
+
+A basic test harness for the Post model.
+
+=cut
+
+use Jifty::Test tests => 11;
+
+# Make sure we can load the model
+use_ok('MyWeblog::Model::Post');
+
+# Grab a system user
+my $system_user = MyWeblog::CurrentUser->superuser;
+ok($system_user, "Found a system user");
+
+# Try testing a create
+my $o = MyWeblog::Model::Post->new(current_user => $system_user);
+my ($id) = $o->create();
+ok($id, "Post create returned success");
+ok($o->id, "New Post has valid id set");
+is($o->id, $id, "Create returned the right id");
+
+# And another
+$o->create();
+ok($o->id, "Post create returned another value");
+isnt($o->id, $id, "And it is different from the previous one");
+
+# Searches in general
+my $collection =  MyWeblog::Model::PostCollection->new(current_user => $system_user);
+$collection->unlimit;
+is($collection->count, 2, "Finds two records");
+
+# Searches in specific
+$collection->limit(column => 'id', value => $o->id);
+is($collection->count, 1, "Finds one record with specific id");
+
+# Delete one of them
+$o->delete;
+$collection->redo_search;
+is($collection->count, 0, "Deleted row is gone");
+
+# And the other one is still there
+$collection->unlimit;
+is($collection->count, 1, "Still one left");
+

Added: jifty/branches/schema-plugins/examples/Ping/Makefile.PL
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/Ping/Makefile.PL	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,7 @@
+use inc::Module::Install;
+name('Ping');
+version('0.01');
+requires('Jifty' => '0.61025');
+requires('Net::Server' => 0);
+
+WriteAll;

Added: jifty/branches/schema-plugins/examples/Ping/bin/jifty
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/Ping/bin/jifty	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,15 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+use File::Basename qw(dirname); 
+use UNIVERSAL::require;
+
+BEGIN {
+    Jifty::Util->require or die $UNIVERSAL::require::ERROR;
+    my $root = Jifty::Util->app_root;
+    unshift @INC, "$root/lib" if ($root);
+}
+
+use Jifty::Script;
+$SIG{INT} = $SIG{TERM} = sub { warn "Stopped\n"; exit; };
+Jifty::Script->dispatch();

Added: jifty/branches/schema-plugins/examples/Ping/etc/config.yml
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/Ping/etc/config.yml	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,40 @@
+--- 
+framework: 
+  AdminMode: 1
+  ApplicationClass: Ping
+  ApplicationName: Ping
+  PubSub:
+    Enable: 1
+  Database: 
+    CheckSchema: 1
+    Database: ping
+    Driver: SQLite
+    Host: localhost
+    Password: ''
+    RecordBaseClass: Jifty::DBI::Record::Cachable
+    User: ''
+    Version: 0.0.1
+  DevelMode: 0
+  L10N: 
+    PoDir: share/po
+  LogLevel: WARN
+  Mailer: Sendmail
+  MailerArgs: []
+
+  Plugins: []
+
+  Web: 
+    BaseURL: http://localhost
+    DataDir: var/mason
+    Globals: []
+
+    MasonConfig: 
+      autoflush: 0
+      default_escape_flags: h
+      error_format: text
+      error_mode: fatal
+    Port: 8888
+    ServeStaticFiles: 1
+    StaticRoot: share/web/static
+    TemplateRoot: share/web/templates
+    ServerClass: Ping::Server

Added: jifty/branches/schema-plugins/examples/Ping/lib/Ping/Action/AddPing.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/Ping/lib/Ping/Action/AddPing.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,51 @@
+package Ping::Action::AddPing;
+use strict;
+use Jifty::Param::Schema;
+use Jifty::Action schema {
+
+param host =>
+    label is 'Hostname',
+    is mandatory;
+
+param only_failure =>
+    type is 'checkbox',
+    label is 'Failure only?',
+    hints is 'Show only failed pings to me.',
+    default is 0;
+
+};
+
+sub take_action {
+    my $self = shift;
+    my $host = $self->argument_value('host');
+    my $only_failure = $self->argument_value('only_failure');
+
+    Jifty->bus->modify(hosts => sub {
+        $_->{$host} ||= do {
+            if (my $pid = fork) {
+                $pid;
+            }
+            else {
+                exec($^X => "-Ilib", "-MPing::PingServer", "-e", "Ping::PingServer->ping('$host')");
+            }
+        }
+    });
+
+    my $id = Jifty->web->session->id;
+
+    Jifty->bus->modify("$id-ping" => sub {
+        my $sid = $_->{$host}; 
+        Jifty->subs->cancel($sid) if $sid;
+        $_->{$host} = Jifty->subs->add(
+            class       => 'Pong',
+            queries     => [{ host => $host }, $only_failure ? { fail => 1 } : ()],
+            mode        => 'Bottom',
+            region      => 'pong',
+            render_with => '/fragments/pong',
+        );
+    });
+
+    $self->result->message( "Added host: $host" );
+}
+
+1;

Added: jifty/branches/schema-plugins/examples/Ping/lib/Ping/Action/CancelPing.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/Ping/lib/Ping/Action/CancelPing.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,25 @@
+package Ping::Action::CancelPing;
+use strict;
+use Jifty::Param::Schema;
+use Jifty::Action schema {
+
+param host =>
+    label is 'Hostname',
+    is mandatory;
+
+};
+
+sub take_action {
+    my $self = shift;
+    my $host = $self->argument_value('host');
+
+    my $id  = Jifty->web->session->id;
+    my $sid = Jifty->bus->modify("$id-ping" => sub {
+        delete($_->{$host})
+    });
+    Jifty->subs->cancel($sid);
+
+    $self->result->message( "Cancelled host: $host" );
+}
+
+1;

Added: jifty/branches/schema-plugins/examples/Ping/lib/Ping/Event/Pong.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/Ping/lib/Ping/Event/Pong.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,25 @@
+package Ping::Event::Pong;
+use strict;
+use warnings;
+use base 'Ping::Event';
+
+sub match {
+    my $self    = shift;
+    my $query   = shift;
+
+    if ($query->{fail}) {
+        not $$self->{alive};
+    }
+    elsif (my $host = $query->{host}) {
+        $$self->{host} eq $host;
+    }
+    else {
+        1;
+    }
+}
+
+sub render_arguments {
+    %{$_[0]->data};
+}
+
+1;

Added: jifty/branches/schema-plugins/examples/Ping/lib/Ping/PingServer.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/Ping/lib/Ping/PingServer.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,25 @@
+package Ping::PingServer;
+
+use Net::Ping;
+use Time::HiRes qw( time sleep );
+use Jifty;
+
+BEGIN { Jifty->new };
+
+sub ping {
+    my ($class, $host) = @_;
+    my $ping = Net::Ping->new(tcp => 1);
+    while (1) {
+        my $old_time = time;
+        my $alive = $ping->ping($host);
+        my $new_time = time;
+
+        Ping::Event::Pong->new(
+            { host => $host, alive => ($alive ? ($new_time - $old_time + 0.001) : 0), time => $new_time }
+        )->publish;
+        sleep 1;
+    }
+    exit;
+}
+
+1;

Added: jifty/branches/schema-plugins/examples/Ping/lib/Ping/Server.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/Ping/lib/Ping/Server.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,7 @@
+package Ping::Server;
+use base 'Jifty::Server';
+use IO::Socket::INET;
+
+sub net_server { 'Net::Server::Fork' }
+
+1;

Added: jifty/branches/schema-plugins/examples/Ping/share/web/templates/fragments/pong
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/Ping/share/web/templates/fragments/pong	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,23 @@
+<%init>
+my $res = Jifty::Result->new;
+my $tm  = scalar localtime($time);
+if ($alive) {
+    $res->message( "Pong: $host ($tm)" ); # - $alive ($tm)" );
+}
+else {
+    $res->error( "Fail: $host ($tm)" ); # ($tm)" );
+}
+Jifty->web->response->{results} = {};
+Jifty->web->response->result("~$tm~$host" => $res);
+print  Jifty->web->render_messages;
+Jifty->web->response->{results} = {};
+</%init>
+<script>
+var objDiv = document.getElementById('pushed');
+objDiv.scrollTop = objDiv.scrollHeight;
+</script>
+<%args>
+$time
+$host
+$alive
+</%args>

Added: jifty/branches/schema-plugins/examples/Ping/share/web/templates/index.html
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/Ping/share/web/templates/index.html	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,50 @@
+<&|/_elements/wrapper, title => 'Ping for ' . Jifty->web->session->id &>
+
+<div id="pushed" style="overflow: scroll; width: 100%; height: 250px" >
+        <% $region->render %>
+</div>
+
+<table width="80%"><tr valign="top"><td>
+<fieldset>
+<legend>New Pings</legend>
+% my $form = Jifty->web->form->new(name => 'new');
+<% $form->start %>
+<% $action->form_field('host') %>
+<% $action->form_field('only_failure')%>
+<% $form->submit( label => 'Start' )%>
+<% $form->end %>
+</fieldset>
+% my $id = Jifty->web->session->id;
+% my $pings = Jifty->bus->modify("$id-ping") || {};
+% if (my @subs = keys %$pings) {
+</td><td>
+<fieldset>
+<legend>Current Pings</legend>
+<table width="100%">
+% foreach my $sub (@subs) {
+% $form = Jifty->web->form->new(name => $sub);
+<% $form->start %>
+%    my $del = Jifty->web->new_action(class => 'CancelPing', arguments => { host => $sub });
+<tr><td>
+<% $del->form_field('host', render_as => 'Hidden', default_value => $sub) %>
+<% $sub %>
+</td><td>
+<% $form->submit( label => 'Stop' )%>
+<% $form->end %>
+</td></tr>
+% }
+</table>
+</fieldset>
+% }
+</td></tr></table>
+<script>new Jifty.Subs({}).start();</script>
+</&>
+
+<%init>
+my $action = Jifty->web->new_action(class => 'AddPing');
+my $region = Jifty::Web::PageRegion->new(
+    name => 'pong',
+    path => '/__jifty/empty',
+);
+
+</%init>

Added: jifty/branches/schema-plugins/examples/Ping/t/00compile.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/Ping/t/00compile.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,22 @@
+#!/usr/bin/perl -w
+
+use Test::More;
+
+use File::Find;
+
+my @modules;
+find sub {
+    return unless /\.pm$/;
+    push @modules, $File::Find::name;
+}, "lib";
+
+ at modules = map { s[^lib/][];  $_ =~ s[.pm$][];  $_ =~ s[/][::]g; $_ } @modules;
+
+plan tests => scalar @modules;
+
+# Ping::PingServer will not compile without a schema.
+system "bin/jifty schema --setup";
+
+for my $module (@modules) {
+    require_ok $module;
+}

Added: jifty/branches/schema-plugins/examples/Ping/t/01startup.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/examples/Ping/t/01startup.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,13 @@
+#!/usr/bin/perl -w
+
+use Jifty::Test 'no_plan';
+use Jifty::Test::WWW::Mechanize;
+
+# Startup the ping server.
+my $server = Jifty::Test->make_server;
+isa_ok($server, 'Jifty::Server');
+
+my $URL = $server->started_ok;
+my $mech = Jifty::Test::WWW::Mechanize->new;
+
+$mech->get_ok("$URL", "got the front page");

Added: jifty/branches/schema-plugins/inc/Module/AutoInstall.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/inc/Module/AutoInstall.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,768 @@
+#line 1
+package Module::AutoInstall;
+
+use strict;
+use Cwd                 ();
+use ExtUtils::MakeMaker ();
+
+use vars qw{$VERSION};
+BEGIN {
+	$VERSION = '1.03';
+}
+
+# special map on pre-defined feature sets
+my %FeatureMap = (
+    ''      => 'Core Features',    # XXX: deprecated
+    '-core' => 'Core Features',
+);
+
+# various lexical flags
+my ( @Missing, @Existing,  %DisabledTests, $UnderCPAN,     $HasCPANPLUS );
+my ( $Config,  $CheckOnly, $SkipInstall,   $AcceptDefault, $TestOnly );
+my ( $PostambleActions, $PostambleUsed );
+
+# See if it's a testing or non-interactive session
+_accept_default( $ENV{AUTOMATED_TESTING} or ! -t STDIN ); 
+_init();
+
+sub _accept_default {
+    $AcceptDefault = shift;
+}
+
+sub missing_modules {
+    return @Missing;
+}
+
+sub do_install {
+    __PACKAGE__->install(
+        [
+            $Config
+            ? ( UNIVERSAL::isa( $Config, 'HASH' ) ? %{$Config} : @{$Config} )
+            : ()
+        ],
+        @Missing,
+    );
+}
+
+# initialize various flags, and/or perform install
+sub _init {
+    foreach my $arg (
+        @ARGV,
+        split(
+            /[\s\t]+/,
+            $ENV{PERL_AUTOINSTALL} || $ENV{PERL_EXTUTILS_AUTOINSTALL} || ''
+        )
+      )
+    {
+        if ( $arg =~ /^--config=(.*)$/ ) {
+            $Config = [ split( ',', $1 ) ];
+        }
+        elsif ( $arg =~ /^--installdeps=(.*)$/ ) {
+            __PACKAGE__->install( $Config, @Missing = split( /,/, $1 ) );
+            exit 0;
+        }
+        elsif ( $arg =~ /^--default(?:deps)?$/ ) {
+            $AcceptDefault = 1;
+        }
+        elsif ( $arg =~ /^--check(?:deps)?$/ ) {
+            $CheckOnly = 1;
+        }
+        elsif ( $arg =~ /^--skip(?:deps)?$/ ) {
+            $SkipInstall = 1;
+        }
+        elsif ( $arg =~ /^--test(?:only)?$/ ) {
+            $TestOnly = 1;
+        }
+    }
+}
+
+# overrides MakeMaker's prompt() to automatically accept the default choice
+sub _prompt {
+    goto &ExtUtils::MakeMaker::prompt unless $AcceptDefault;
+
+    my ( $prompt, $default ) = @_;
+    my $y = ( $default =~ /^[Yy]/ );
+
+    print $prompt, ' [', ( $y ? 'Y' : 'y' ), '/', ( $y ? 'n' : 'N' ), '] ';
+    print "$default\n";
+    return $default;
+}
+
+# the workhorse
+sub import {
+    my $class = shift;
+    my @args  = @_ or return;
+    my $core_all;
+
+    print "*** $class version " . $class->VERSION . "\n";
+    print "*** Checking for Perl dependencies...\n";
+
+    my $cwd = Cwd::cwd();
+
+    $Config = [];
+
+    my $maxlen = length(
+        (
+            sort   { length($b) <=> length($a) }
+              grep { /^[^\-]/ }
+              map  {
+                ref($_)
+                  ? ( ( ref($_) eq 'HASH' ) ? keys(%$_) : @{$_} )
+                  : ''
+              }
+              map { +{@args}->{$_} }
+              grep { /^[^\-]/ or /^-core$/i } keys %{ +{@args} }
+        )[0]
+    );
+
+    while ( my ( $feature, $modules ) = splice( @args, 0, 2 ) ) {
+        my ( @required, @tests, @skiptests );
+        my $default  = 1;
+        my $conflict = 0;
+
+        if ( $feature =~ m/^-(\w+)$/ ) {
+            my $option = lc($1);
+
+            # check for a newer version of myself
+            _update_to( $modules, @_ ) and return if $option eq 'version';
+
+            # sets CPAN configuration options
+            $Config = $modules if $option eq 'config';
+
+            # promote every features to core status
+            $core_all = ( $modules =~ /^all$/i ) and next
+              if $option eq 'core';
+
+            next unless $option eq 'core';
+        }
+
+        print "[" . ( $FeatureMap{ lc($feature) } || $feature ) . "]\n";
+
+        $modules = [ %{$modules} ] if UNIVERSAL::isa( $modules, 'HASH' );
+
+        unshift @$modules, -default => &{ shift(@$modules) }
+          if ( ref( $modules->[0] ) eq 'CODE' );    # XXX: bugward combatability
+
+        while ( my ( $mod, $arg ) = splice( @$modules, 0, 2 ) ) {
+            if ( $mod =~ m/^-(\w+)$/ ) {
+                my $option = lc($1);
+
+                $default   = $arg    if ( $option eq 'default' );
+                $conflict  = $arg    if ( $option eq 'conflict' );
+                @tests     = @{$arg} if ( $option eq 'tests' );
+                @skiptests = @{$arg} if ( $option eq 'skiptests' );
+
+                next;
+            }
+
+            printf( "- %-${maxlen}s ...", $mod );
+
+            if ( $arg and $arg =~ /^\D/ ) {
+                unshift @$modules, $arg;
+                $arg = 0;
+            }
+
+            # XXX: check for conflicts and uninstalls(!) them.
+            if (
+                defined( my $cur = _version_check( _load($mod), $arg ||= 0 ) ) )
+            {
+                print "loaded. ($cur" . ( $arg ? " >= $arg" : '' ) . ")\n";
+                push @Existing, $mod => $arg;
+                $DisabledTests{$_} = 1 for map { glob($_) } @skiptests;
+            }
+            else {
+                print "missing." . ( $arg ? " (would need $arg)" : '' ) . "\n";
+                push @required, $mod => $arg;
+            }
+        }
+
+        next unless @required;
+
+        my $mandatory = ( $feature eq '-core' or $core_all );
+
+        if (
+            !$SkipInstall
+            and (
+                $CheckOnly
+                or _prompt(
+                    qq{==> Auto-install the }
+                      . ( @required / 2 )
+                      . ( $mandatory ? ' mandatory' : ' optional' )
+                      . qq{ module(s) from CPAN?},
+                    $default ? 'y' : 'n',
+                ) =~ /^[Yy]/
+            )
+          )
+        {
+            push( @Missing, @required );
+            $DisabledTests{$_} = 1 for map { glob($_) } @skiptests;
+        }
+
+        elsif ( !$SkipInstall
+            and $default
+            and $mandatory
+            and
+            _prompt( qq{==> The module(s) are mandatory! Really skip?}, 'n', )
+            =~ /^[Nn]/ )
+        {
+            push( @Missing, @required );
+            $DisabledTests{$_} = 1 for map { glob($_) } @skiptests;
+        }
+
+        else {
+            $DisabledTests{$_} = 1 for map { glob($_) } @tests;
+        }
+    }
+
+    $UnderCPAN = _check_lock();    # check for $UnderCPAN
+
+    if ( @Missing and not( $CheckOnly or $UnderCPAN ) ) {
+        require Config;
+        print
+"*** Dependencies will be installed the next time you type '$Config::Config{make}'.\n";
+
+        # make an educated guess of whether we'll need root permission.
+        print "    (You may need to do that as the 'root' user.)\n"
+          if eval '$>';
+    }
+    print "*** $class configuration finished.\n";
+
+    chdir $cwd;
+
+    # import to main::
+    no strict 'refs';
+    *{'main::WriteMakefile'} = \&Write if caller(0) eq 'main';
+}
+
+# Check to see if we are currently running under CPAN.pm and/or CPANPLUS;
+# if we are, then we simply let it taking care of our dependencies
+sub _check_lock {
+    return unless @Missing;
+
+    if ($ENV{PERL5_CPANPLUS_IS_RUNNING}) {
+        print <<'END_MESSAGE';
+
+*** Since we're running under CPANPLUS, I'll just let it take care
+    of the dependency's installation later.
+END_MESSAGE
+        return 1;
+    }
+
+    _load_cpan();
+
+    # Find the CPAN lock-file
+    my $lock = MM->catfile( $CPAN::Config->{cpan_home}, ".lock" );
+    return unless -f $lock;
+
+    # Check the lock
+    local *LOCK;
+    return unless open(LOCK, $lock);
+
+    if (
+            ( $^O eq 'MSWin32' ? _under_cpan() : <LOCK> == getppid() )
+        and ( $CPAN::Config->{prerequisites_policy} || '' ) ne 'ignore'
+    ) {
+        print <<'END_MESSAGE';
+
+*** Since we're running under CPAN, I'll just let it take care
+    of the dependency's installation later.
+END_MESSAGE
+        return 1;
+    }
+
+    close LOCK;
+    return;
+}
+
+sub install {
+    my $class = shift;
+
+    my $i;    # used below to strip leading '-' from config keys
+    my @config = ( map { s/^-// if ++$i; $_ } @{ +shift } );
+
+    my ( @modules, @installed );
+    while ( my ( $pkg, $ver ) = splice( @_, 0, 2 ) ) {
+
+        # grep out those already installed
+        if ( defined( _version_check( _load($pkg), $ver ) ) ) {
+            push @installed, $pkg;
+        }
+        else {
+            push @modules, $pkg, $ver;
+        }
+    }
+
+    return @installed unless @modules;  # nothing to do
+    return @installed if _check_lock(); # defer to the CPAN shell
+
+    print "*** Installing dependencies...\n";
+
+    return unless _connected_to('cpan.org');
+
+    my %args = @config;
+    my %failed;
+    local *FAILED;
+    if ( $args{do_once} and open( FAILED, '.#autoinstall.failed' ) ) {
+        while (<FAILED>) { chomp; $failed{$_}++ }
+        close FAILED;
+
+        my @newmod;
+        while ( my ( $k, $v ) = splice( @modules, 0, 2 ) ) {
+            push @newmod, ( $k => $v ) unless $failed{$k};
+        }
+        @modules = @newmod;
+    }
+
+    if ( _has_cpanplus() ) {
+        _install_cpanplus( \@modules, \@config );
+    } else {
+        _install_cpan( \@modules, \@config );
+    }
+
+    print "*** $class installation finished.\n";
+
+    # see if we have successfully installed them
+    while ( my ( $pkg, $ver ) = splice( @modules, 0, 2 ) ) {
+        if ( defined( _version_check( _load($pkg), $ver ) ) ) {
+            push @installed, $pkg;
+        }
+        elsif ( $args{do_once} and open( FAILED, '>> .#autoinstall.failed' ) ) {
+            print FAILED "$pkg\n";
+        }
+    }
+
+    close FAILED if $args{do_once};
+
+    return @installed;
+}
+
+sub _install_cpanplus {
+    my @modules   = @{ +shift };
+    my @config    = _cpanplus_config( @{ +shift } );
+    my $installed = 0;
+
+    require CPANPLUS::Backend;
+    my $cp   = CPANPLUS::Backend->new;
+    my $conf = $cp->configure_object;
+
+    return unless $conf->can('conf') # 0.05x+ with "sudo" support
+               or _can_write($conf->_get_build('base'));  # 0.04x
+
+    # if we're root, set UNINST=1 to avoid trouble unless user asked for it.
+    my $makeflags = $conf->get_conf('makeflags') || '';
+    if ( UNIVERSAL::isa( $makeflags, 'HASH' ) ) {
+        # 0.03+ uses a hashref here
+        $makeflags->{UNINST} = 1 unless exists $makeflags->{UNINST};
+
+    } else {
+        # 0.02 and below uses a scalar
+        $makeflags = join( ' ', split( ' ', $makeflags ), 'UNINST=1' )
+          if ( $makeflags !~ /\bUNINST\b/ and eval qq{ $> eq '0' } );
+
+    }
+    $conf->set_conf( makeflags => $makeflags );
+    $conf->set_conf( prereqs   => 1 );
+
+    
+
+    while ( my ( $key, $val ) = splice( @config, 0, 2 ) ) {
+        $conf->set_conf( $key, $val );
+    }
+
+    my $modtree = $cp->module_tree;
+    while ( my ( $pkg, $ver ) = splice( @modules, 0, 2 ) ) {
+        print "*** Installing $pkg...\n";
+
+        MY::preinstall( $pkg, $ver ) or next if defined &MY::preinstall;
+
+        my $success;
+        my $obj = $modtree->{$pkg};
+
+        if ( $obj and defined( _version_check( $obj->{version}, $ver ) ) ) {
+            my $pathname = $pkg;
+            $pathname =~ s/::/\\W/;
+
+            foreach my $inc ( grep { m/$pathname.pm/i } keys(%INC) ) {
+                delete $INC{$inc};
+            }
+
+            my $rv = $cp->install( modules => [ $obj->{module} ] );
+
+            if ( $rv and ( $rv->{ $obj->{module} } or $rv->{ok} ) ) {
+                print "*** $pkg successfully installed.\n";
+                $success = 1;
+            } else {
+                print "*** $pkg installation cancelled.\n";
+                $success = 0;
+            }
+
+            $installed += $success;
+        } else {
+            print << ".";
+*** Could not find a version $ver or above for $pkg; skipping.
+.
+        }
+
+        MY::postinstall( $pkg, $ver, $success ) if defined &MY::postinstall;
+    }
+
+    return $installed;
+}
+
+sub _cpanplus_config {
+	my @config = ();
+	while ( @_ ) {
+		my ($key, $value) = (shift(), shift());
+		if ( $key eq 'prerequisites_policy' ) {
+			if ( $value eq 'follow' ) {
+				$value = CPANPLUS::Internals::Constants::PREREQ_INSTALL();
+			} elsif ( $value eq 'ask' ) {
+				$value = CPANPLUS::Internals::Constants::PREREQ_ASK();
+			} elsif ( $value eq 'ignore' ) {
+				$value = CPANPLUS::Internals::Constants::PREREQ_IGNORE();
+			} else {
+				die "*** Cannot convert option $key = '$value' to CPANPLUS version.\n";
+			}
+		} else {
+			die "*** Cannot convert option $key to CPANPLUS version.\n";
+		}
+	}
+	return @config;
+}
+
+sub _install_cpan {
+    my @modules   = @{ +shift };
+    my @config    = @{ +shift };
+    my $installed = 0;
+    my %args;
+
+    _load_cpan();
+    require Config;
+
+    if (CPAN->VERSION < 1.80) {
+        # no "sudo" support, probe for writableness
+        return unless _can_write( MM->catfile( $CPAN::Config->{cpan_home}, 'sources' ) )
+                  and _can_write( $Config::Config{sitelib} );
+    }
+
+    # if we're root, set UNINST=1 to avoid trouble unless user asked for it.
+    my $makeflags = $CPAN::Config->{make_install_arg} || '';
+    $CPAN::Config->{make_install_arg} =
+      join( ' ', split( ' ', $makeflags ), 'UNINST=1' )
+      if ( $makeflags !~ /\bUNINST\b/ and eval qq{ $> eq '0' } );
+
+    # don't show start-up info
+    $CPAN::Config->{inhibit_startup_message} = 1;
+
+    # set additional options
+    while ( my ( $opt, $arg ) = splice( @config, 0, 2 ) ) {
+        ( $args{$opt} = $arg, next )
+          if $opt =~ /^force$/;    # pseudo-option
+        $CPAN::Config->{$opt} = $arg;
+    }
+
+    local $CPAN::Config->{prerequisites_policy} = 'follow';
+
+    while ( my ( $pkg, $ver ) = splice( @modules, 0, 2 ) ) {
+        MY::preinstall( $pkg, $ver ) or next if defined &MY::preinstall;
+
+        print "*** Installing $pkg...\n";
+
+        my $obj     = CPAN::Shell->expand( Module => $pkg );
+        my $success = 0;
+
+        if ( $obj and defined( _version_check( $obj->cpan_version, $ver ) ) ) {
+            my $pathname = $pkg;
+            $pathname =~ s/::/\\W/;
+
+            foreach my $inc ( grep { m/$pathname.pm/i } keys(%INC) ) {
+                delete $INC{$inc};
+            }
+
+            my $rv = $args{force} ? CPAN::Shell->force( install => $pkg )
+                                  : CPAN::Shell->install($pkg);
+            $rv ||= eval {
+                $CPAN::META->instance( 'CPAN::Distribution', $obj->cpan_file, )
+                  ->{install}
+                  if $CPAN::META;
+            };
+
+            if ( $rv eq 'YES' ) {
+                print "*** $pkg successfully installed.\n";
+                $success = 1;
+            }
+            else {
+                print "*** $pkg installation failed.\n";
+                $success = 0;
+            }
+
+            $installed += $success;
+        }
+        else {
+            print << ".";
+*** Could not find a version $ver or above for $pkg; skipping.
+.
+        }
+
+        MY::postinstall( $pkg, $ver, $success ) if defined &MY::postinstall;
+    }
+
+    return $installed;
+}
+
+sub _has_cpanplus {
+    return (
+        $HasCPANPLUS = (
+            $INC{'CPANPLUS/Config.pm'}
+              or _load('CPANPLUS::Shell::Default')
+        )
+    );
+}
+
+# make guesses on whether we're under the CPAN installation directory
+sub _under_cpan {
+    require Cwd;
+    require File::Spec;
+
+    my $cwd  = File::Spec->canonpath( Cwd::cwd() );
+    my $cpan = File::Spec->canonpath( $CPAN::Config->{cpan_home} );
+
+    return ( index( $cwd, $cpan ) > -1 );
+}
+
+sub _update_to {
+    my $class = __PACKAGE__;
+    my $ver   = shift;
+
+    return
+      if defined( _version_check( _load($class), $ver ) );  # no need to upgrade
+
+    if (
+        _prompt( "==> A newer version of $class ($ver) is required. Install?",
+            'y' ) =~ /^[Nn]/
+      )
+    {
+        die "*** Please install $class $ver manually.\n";
+    }
+
+    print << ".";
+*** Trying to fetch it from CPAN...
+.
+
+    # install ourselves
+    _load($class) and return $class->import(@_)
+      if $class->install( [], $class, $ver );
+
+    print << '.'; exit 1;
+
+*** Cannot bootstrap myself. :-( Installation terminated.
+.
+}
+
+# check if we're connected to some host, using inet_aton
+sub _connected_to {
+    my $site = shift;
+
+    return (
+        ( _load('Socket') and Socket::inet_aton($site) ) or _prompt(
+            qq(
+*** Your host cannot resolve the domain name '$site', which
+    probably means the Internet connections are unavailable.
+==> Should we try to install the required module(s) anyway?), 'n'
+          ) =~ /^[Yy]/
+    );
+}
+
+# check if a directory is writable; may create it on demand
+sub _can_write {
+    my $path = shift;
+    mkdir( $path, 0755 ) unless -e $path;
+
+    return 1 if -w $path;
+
+    print << ".";
+*** You are not allowed to write to the directory '$path';
+    the installation may fail due to insufficient permissions.
+.
+
+    if (
+        eval '$>' and lc(`sudo -V`) =~ /version/ and _prompt(
+            qq(
+==> Should we try to re-execute the autoinstall process with 'sudo'?),
+            ((-t STDIN) ? 'y' : 'n')
+        ) =~ /^[Yy]/
+      )
+    {
+
+        # try to bootstrap ourselves from sudo
+        print << ".";
+*** Trying to re-execute the autoinstall process with 'sudo'...
+.
+        my $missing = join( ',', @Missing );
+        my $config = join( ',',
+            UNIVERSAL::isa( $Config, 'HASH' ) ? %{$Config} : @{$Config} )
+          if $Config;
+
+        return
+          unless system( 'sudo', $^X, $0, "--config=$config",
+            "--installdeps=$missing" );
+
+        print << ".";
+*** The 'sudo' command exited with error!  Resuming...
+.
+    }
+
+    return _prompt(
+        qq(
+==> Should we try to install the required module(s) anyway?), 'n'
+    ) =~ /^[Yy]/;
+}
+
+# load a module and return the version it reports
+sub _load {
+    my $mod  = pop;    # class/instance doesn't matter
+    my $file = $mod;
+
+    $file =~ s|::|/|g;
+    $file .= '.pm';
+
+    local $@;
+    return eval { require $file; $mod->VERSION } || ( $@ ? undef: 0 );
+}
+
+# Load CPAN.pm and it's configuration
+sub _load_cpan {
+    return if $CPAN::VERSION;
+    require CPAN;
+    if ( $CPAN::HandleConfig::VERSION ) {
+        # Newer versions of CPAN have a HandleConfig module
+        CPAN::HandleConfig->load;
+    } else {
+    	# Older versions had the load method in Config directly
+        CPAN::Config->load;
+    }
+}
+
+# compare two versions, either use Sort::Versions or plain comparison
+sub _version_check {
+    my ( $cur, $min ) = @_;
+    return unless defined $cur;
+
+    $cur =~ s/\s+$//;
+
+    # check for version numbers that are not in decimal format
+    if ( ref($cur) or ref($min) or $cur =~ /v|\..*\./ or $min =~ /v|\..*\./ ) {
+        if ( ( $version::VERSION or defined( _load('version') )) and
+             version->can('new') 
+            ) {
+
+            # use version.pm if it is installed.
+            return (
+                ( version->new($cur) >= version->new($min) ) ? $cur : undef );
+        }
+        elsif ( $Sort::Versions::VERSION or defined( _load('Sort::Versions') ) )
+        {
+
+            # use Sort::Versions as the sorting algorithm for a.b.c versions
+            return ( ( Sort::Versions::versioncmp( $cur, $min ) != -1 )
+                ? $cur
+                : undef );
+        }
+
+        warn "Cannot reliably compare non-decimal formatted versions.\n"
+          . "Please install version.pm or Sort::Versions.\n";
+    }
+
+    # plain comparison
+    local $^W = 0;    # shuts off 'not numeric' bugs
+    return ( $cur >= $min ? $cur : undef );
+}
+
+# nothing; this usage is deprecated.
+sub main::PREREQ_PM { return {}; }
+
+sub _make_args {
+    my %args = @_;
+
+    $args{PREREQ_PM} = { %{ $args{PREREQ_PM} || {} }, @Existing, @Missing }
+      if $UnderCPAN or $TestOnly;
+
+    if ( $args{EXE_FILES} and -e 'MANIFEST' ) {
+        require ExtUtils::Manifest;
+        my $manifest = ExtUtils::Manifest::maniread('MANIFEST');
+
+        $args{EXE_FILES} =
+          [ grep { exists $manifest->{$_} } @{ $args{EXE_FILES} } ];
+    }
+
+    $args{test}{TESTS} ||= 't/*.t';
+    $args{test}{TESTS} = join( ' ',
+        grep { !exists( $DisabledTests{$_} ) }
+          map { glob($_) } split( /\s+/, $args{test}{TESTS} ) );
+
+    my $missing = join( ',', @Missing );
+    my $config =
+      join( ',', UNIVERSAL::isa( $Config, 'HASH' ) ? %{$Config} : @{$Config} )
+      if $Config;
+
+    $PostambleActions = (
+        $missing
+        ? "\$(PERL) $0 --config=$config --installdeps=$missing"
+        : "\$(NOECHO) \$(NOOP)"
+    );
+
+    return %args;
+}
+
+# a wrapper to ExtUtils::MakeMaker::WriteMakefile
+sub Write {
+    require Carp;
+    Carp::croak "WriteMakefile: Need even number of args" if @_ % 2;
+
+    if ($CheckOnly) {
+        print << ".";
+*** Makefile not written in check-only mode.
+.
+        return;
+    }
+
+    my %args = _make_args(@_);
+
+    no strict 'refs';
+
+    $PostambleUsed = 0;
+    local *MY::postamble = \&postamble unless defined &MY::postamble;
+    ExtUtils::MakeMaker::WriteMakefile(%args);
+
+    print << "." unless $PostambleUsed;
+*** WARNING: Makefile written with customized MY::postamble() without
+    including contents from Module::AutoInstall::postamble() --
+    auto installation features disabled.  Please contact the author.
+.
+
+    return 1;
+}
+
+sub postamble {
+    $PostambleUsed = 1;
+
+    return << ".";
+
+config :: installdeps
+\t\$(NOECHO) \$(NOOP)
+
+checkdeps ::
+\t\$(PERL) $0 --checkdeps
+
+installdeps ::
+\t$PostambleActions
+
+.
+
+}
+
+1;
+
+__END__
+
+#line 1003

Added: jifty/branches/schema-plugins/inc/Module/Install.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/inc/Module/Install.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,281 @@
+#line 1
+package Module::Install;
+
+# For any maintainers:
+# The load order for Module::Install is a bit magic.
+# It goes something like this...
+#
+# IF ( host has Module::Install installed, creating author mode ) {
+#     1. Makefile.PL calls "use inc::Module::Install"
+#     2. $INC{inc/Module/Install.pm} set to installed version of inc::Module::Install
+#     3. The installed version of inc::Module::Install loads
+#     4. inc::Module::Install calls "require Module::Install"
+#     5. The ./inc/ version of Module::Install loads
+# } ELSE {
+#     1. Makefile.PL calls "use inc::Module::Install"
+#     2. $INC{inc/Module/Install.pm} set to ./inc/ version of Module::Install
+#     3. The ./inc/ version of Module::Install loads
+# }
+
+use 5.004;
+use strict 'vars';
+
+use vars qw{$VERSION};
+BEGIN {
+    # All Module::Install core packages now require synchronised versions.
+    # This will be used to ensure we don't accidentally load old or
+    # different versions of modules.
+    # This is not enforced yet, but will be some time in the next few
+    # releases once we can make sure it won't clash with custom
+    # Module::Install extensions.
+    $VERSION = '0.64';
+}
+
+# Whether or not inc::Module::Install is actually loaded, the
+# $INC{inc/Module/Install.pm} is what will still get set as long as
+# the caller loaded module this in the documented manner.
+# If not set, the caller may NOT have loaded the bundled version, and thus
+# they may not have a MI version that works with the Makefile.PL. This would
+# result in false errors or unexpected behaviour. And we don't want that.
+my $file = join( '/', 'inc', split /::/, __PACKAGE__ ) . '.pm';
+unless ( $INC{$file} ) {
+    die <<"END_DIE";
+Please invoke ${\__PACKAGE__} with:
+
+    use inc::${\__PACKAGE__};
+
+not:
+
+    use ${\__PACKAGE__};
+
+END_DIE
+}
+
+# If the script that is loading Module::Install is from the future,
+# then make will detect this and cause it to re-run over and over
+# again. This is bad. Rather than taking action to touch it (which
+# is unreliable on some platforms and requires write permissions)
+# for now we should catch this and refuse to run.
+if ( -f $0 and (stat($0))[9] > time ) {
+	die << "END_DIE";
+Your installer $0 has a modification time in the future.
+
+This is known to create infinite loops in make.
+
+Please correct this, then run $0 again.
+
+END_DIE
+}
+
+use Cwd        ();
+use File::Find ();
+use File::Path ();
+use FindBin;
+
+*inc::Module::Install::VERSION = *VERSION;
+ at inc::Module::Install::ISA     = __PACKAGE__;
+
+sub autoload {
+    my $self = shift;
+    my $who  = $self->_caller;
+    my $cwd  = Cwd::cwd();
+    my $sym  = "${who}::AUTOLOAD";
+    $sym->{$cwd} = sub {
+        my $pwd = Cwd::cwd();
+        if ( my $code = $sym->{$pwd} ) {
+            # delegate back to parent dirs
+            goto &$code unless $cwd eq $pwd;
+        }
+        $$sym =~ /([^:]+)$/ or die "Cannot autoload $who - $sym";
+        unshift @_, ($self, $1);
+        goto &{$self->can('call')} unless uc($1) eq $1;
+    };
+}
+
+sub import {
+    my $class = shift;
+    my $self  = $class->new(@_);
+    my $who   = $self->_caller;
+
+    unless ( -f $self->{file} ) {
+        require "$self->{path}/$self->{dispatch}.pm";
+        File::Path::mkpath("$self->{prefix}/$self->{author}");
+        $self->{admin} = "$self->{name}::$self->{dispatch}"->new( _top => $self );
+        $self->{admin}->init;
+        @_ = ($class, _self => $self);
+        goto &{"$self->{name}::import"};
+    }
+
+    *{"${who}::AUTOLOAD"} = $self->autoload;
+    $self->preload;
+
+    # Unregister loader and worker packages so subdirs can use them again
+    delete $INC{"$self->{file}"};
+    delete $INC{"$self->{path}.pm"};
+}
+
+sub preload {
+    my ($self) = @_;
+
+    unless ( $self->{extensions} ) {
+        $self->load_extensions(
+            "$self->{prefix}/$self->{path}", $self
+        );
+    }
+
+    my @exts = @{$self->{extensions}};
+    unless ( @exts ) {
+        my $admin = $self->{admin};
+        @exts = $admin->load_all_extensions;
+    }
+
+    my %seen;
+    foreach my $obj ( @exts ) {
+        while (my ($method, $glob) = each %{ref($obj) . '::'}) {
+            next unless $obj->can($method);
+            next if $method =~ /^_/;
+            next if $method eq uc($method);
+            $seen{$method}++;
+        }
+    }
+
+    my $who = $self->_caller;
+    foreach my $name ( sort keys %seen ) {
+        *{"${who}::$name"} = sub {
+            ${"${who}::AUTOLOAD"} = "${who}::$name";
+            goto &{"${who}::AUTOLOAD"};
+        };
+    }
+}
+
+sub new {
+    my ($class, %args) = @_;
+
+    # ignore the prefix on extension modules built from top level.
+    my $base_path = Cwd::abs_path($FindBin::Bin);
+    unless ( Cwd::abs_path(Cwd::cwd()) eq $base_path ) {
+        delete $args{prefix};
+    }
+
+    return $args{_self} if $args{_self};
+
+    $args{dispatch} ||= 'Admin';
+    $args{prefix}   ||= 'inc';
+    $args{author}   ||= ($^O eq 'VMS' ? '_author' : '.author');
+    $args{bundle}   ||= 'inc/BUNDLES';
+    $args{base}     ||= $base_path;
+    $class =~ s/^\Q$args{prefix}\E:://;
+    $args{name}     ||= $class;
+    $args{version}  ||= $class->VERSION;
+    unless ( $args{path} ) {
+        $args{path}  = $args{name};
+        $args{path}  =~ s!::!/!g;
+    }
+    $args{file}     ||= "$args{base}/$args{prefix}/$args{path}.pm";
+
+    bless( \%args, $class );
+}
+
+sub call {
+	my ($self, $method) = @_;
+	my $obj = $self->load($method) or return;
+        splice(@_, 0, 2, $obj);
+	goto &{$obj->can($method)};
+}
+
+sub load {
+    my ($self, $method) = @_;
+
+    $self->load_extensions(
+        "$self->{prefix}/$self->{path}", $self
+    ) unless $self->{extensions};
+
+    foreach my $obj (@{$self->{extensions}}) {
+        return $obj if $obj->can($method);
+    }
+
+    my $admin = $self->{admin} or die <<"END_DIE";
+The '$method' method does not exist in the '$self->{prefix}' path!
+Please remove the '$self->{prefix}' directory and run $0 again to load it.
+END_DIE
+
+    my $obj = $admin->load($method, 1);
+    push @{$self->{extensions}}, $obj;
+
+    $obj;
+}
+
+sub load_extensions {
+    my ($self, $path, $top) = @_;
+
+    unless ( grep { lc $_ eq lc $self->{prefix} } @INC ) {
+        unshift @INC, $self->{prefix};
+    }
+
+    foreach my $rv ( $self->find_extensions($path) ) {
+        my ($file, $pkg) = @{$rv};
+        next if $self->{pathnames}{$pkg};
+
+        local $@;
+        my $new = eval { require $file; $pkg->can('new') };
+        unless ( $new ) {
+            warn $@ if $@;
+            next;
+        }
+        $self->{pathnames}{$pkg} = delete $INC{$file};
+        push @{$self->{extensions}}, &{$new}($pkg, _top => $top );
+    }
+
+    $self->{extensions} ||= [];
+}
+
+sub find_extensions {
+    my ($self, $path) = @_;
+
+    my @found;
+    File::Find::find( sub {
+        my $file = $File::Find::name;
+        return unless $file =~ m!^\Q$path\E/(.+)\.pm\Z!is;
+        my $subpath = $1;
+        return if lc($subpath) eq lc($self->{dispatch});
+
+        $file = "$self->{path}/$subpath.pm";
+        my $pkg = "$self->{name}::$subpath";
+        $pkg =~ s!/!::!g;
+
+        # If we have a mixed-case package name, assume case has been preserved
+        # correctly.  Otherwise, root through the file to locate the case-preserved
+        # version of the package name.
+        if ( $subpath eq lc($subpath) || $subpath eq uc($subpath) ) {
+            open PKGFILE, "<$subpath.pm" or die "find_extensions: Can't open $subpath.pm: $!";
+            my $in_pod = 0;
+            while ( <PKGFILE> ) {
+                $in_pod = 1 if /^=\w/;
+                $in_pod = 0 if /^=cut/;
+                next if ($in_pod || /^=cut/);  # skip pod text
+                next if /^\s*#/;               # and comments
+                if ( m/^\s*package\s+($pkg)\s*;/i ) {
+                    $pkg = $1;
+                    last;
+                }
+            }
+            close PKGFILE;
+        }
+
+        push @found, [ $file, $pkg ];
+    }, $path ) if -d $path;
+
+    @found;
+}
+
+sub _caller {
+    my $depth = 0;
+    my $call  = caller($depth);
+    while ( $call eq __PACKAGE__ ) {
+        $depth++;
+        $call = caller($depth);
+    }
+    return $call;
+}
+
+1;

Added: jifty/branches/schema-plugins/inc/Module/Install/AutoInstall.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/inc/Module/Install/AutoInstall.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,61 @@
+#line 1
+package Module::Install::AutoInstall;
+
+use strict;
+use Module::Install::Base;
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+	$VERSION = '0.64';
+	$ISCORE  = 1;
+	@ISA     = qw{Module::Install::Base};
+}
+
+sub AutoInstall { $_[0] }
+
+sub run {
+    my $self = shift;
+    $self->auto_install_now(@_);
+}
+
+sub write {
+    my $self = shift;
+    $self->auto_install(@_);
+}
+
+sub auto_install {
+    my $self = shift;
+    return if $self->{done}++;
+
+    # Flatten array of arrays into a single array
+    my @core = map @$_, map @$_, grep ref,
+               $self->build_requires, $self->requires;
+
+    my @config = @_;
+
+    # We'll need Module::AutoInstall
+    $self->include('Module::AutoInstall');
+    require Module::AutoInstall;
+
+    Module::AutoInstall->import(
+        (@config ? (-config => \@config) : ()),
+        (@core   ? (-core   => \@core)   : ()),
+        $self->features,
+    );
+
+    $self->makemaker_args( Module::AutoInstall::_make_args() );
+
+    my $class = ref($self);
+    $self->postamble(
+        "# --- $class section:\n" .
+        Module::AutoInstall::postamble()
+    );
+}
+
+sub auto_install_now {
+    my $self = shift;
+    $self->auto_install(@_);
+    Module::AutoInstall::do_install();
+}
+
+1;

Added: jifty/branches/schema-plugins/inc/Module/Install/Base.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/inc/Module/Install/Base.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,70 @@
+#line 1
+package Module::Install::Base;
+
+$VERSION = '0.64';
+
+# Suspend handler for "redefined" warnings
+BEGIN {
+	my $w = $SIG{__WARN__};
+	$SIG{__WARN__} = sub { $w };
+}
+
+### This is the ONLY module that shouldn't have strict on
+# use strict;
+
+#line 41
+
+sub new {
+    my ($class, %args) = @_;
+
+    foreach my $method ( qw(call load) ) {
+        *{"$class\::$method"} = sub {
+            shift()->_top->$method(@_);
+        } unless defined &{"$class\::$method"};
+    }
+
+    bless( \%args, $class );
+}
+
+#line 61
+
+sub AUTOLOAD {
+    my $self = shift;
+    local $@;
+    my $autoload = eval { $self->_top->autoload } or return;
+    goto &$autoload;
+}
+
+#line 76
+
+sub _top { $_[0]->{_top} }
+
+#line 89
+
+sub admin {
+    $_[0]->_top->{admin} or Module::Install::Base::FakeAdmin->new;
+}
+
+sub is_admin {
+    $_[0]->admin->VERSION;
+}
+
+sub DESTROY {}
+
+package Module::Install::Base::FakeAdmin;
+
+my $Fake;
+sub new { $Fake ||= bless(\@_, $_[0]) }
+
+sub AUTOLOAD {}
+
+sub DESTROY {}
+
+# Restore warning handler
+BEGIN {
+	$SIG{__WARN__} = $SIG{__WARN__}->();
+}
+
+1;
+
+#line 138

Added: jifty/branches/schema-plugins/inc/Module/Install/Can.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/inc/Module/Install/Can.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,82 @@
+#line 1
+package Module::Install::Can;
+
+use strict;
+use Module::Install::Base;
+use Config ();
+### This adds a 5.005 Perl version dependency.
+### This is a bug and will be fixed.
+use File::Spec ();
+use ExtUtils::MakeMaker ();
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+	$VERSION = '0.64';
+	$ISCORE  = 1;
+	@ISA     = qw{Module::Install::Base};
+}
+
+# check if we can load some module
+### Upgrade this to not have to load the module if possible
+sub can_use {
+	my ($self, $mod, $ver) = @_;
+	$mod =~ s{::|\\}{/}g;
+	$mod .= '.pm' unless $mod =~ /\.pm$/i;
+
+	my $pkg = $mod;
+	$pkg =~ s{/}{::}g;
+	$pkg =~ s{\.pm$}{}i;
+
+	local $@;
+	eval { require $mod; $pkg->VERSION($ver || 0); 1 };
+}
+
+# check if we can run some command
+sub can_run {
+	my ($self, $cmd) = @_;
+
+	my $_cmd = $cmd;
+	return $_cmd if (-x $_cmd or $_cmd = MM->maybe_command($_cmd));
+
+	for my $dir ((split /$Config::Config{path_sep}/, $ENV{PATH}), '.') {
+		my $abs = File::Spec->catfile($dir, $_[1]);
+		return $abs if (-x $abs or $abs = MM->maybe_command($abs));
+	}
+
+	return;
+}
+
+# can we locate a (the) C compiler
+sub can_cc {
+	my $self   = shift;
+	my @chunks = split(/ /, $Config::Config{cc}) or return;
+
+	# $Config{cc} may contain args; try to find out the program part
+	while (@chunks) {
+		return $self->can_run("@chunks") || (pop(@chunks), next);
+	}
+
+	return;
+}
+
+# Fix Cygwin bug on maybe_command();
+if ( $^O eq 'cygwin' ) {
+	require ExtUtils::MM_Cygwin;
+	require ExtUtils::MM_Win32;
+	if ( ! defined(&ExtUtils::MM_Cygwin::maybe_command) ) {
+		*ExtUtils::MM_Cygwin::maybe_command = sub {
+			my ($self, $file) = @_;
+			if ($file =~ m{^/cygdrive/}i and ExtUtils::MM_Win32->can('maybe_command')) {
+				ExtUtils::MM_Win32->maybe_command($file);
+			} else {
+				ExtUtils::MM_Unix->maybe_command($file);
+			}
+		}
+	}
+}
+
+1;
+
+__END__
+
+#line 157

Added: jifty/branches/schema-plugins/inc/Module/Install/Fetch.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/inc/Module/Install/Fetch.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,93 @@
+#line 1
+package Module::Install::Fetch;
+
+use strict;
+use Module::Install::Base;
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+	$VERSION = '0.64';
+	$ISCORE  = 1;
+	@ISA     = qw{Module::Install::Base};
+}
+
+sub get_file {
+    my ($self, %args) = @_;
+    my ($scheme, $host, $path, $file) = 
+        $args{url} =~ m|^(\w+)://([^/]+)(.+)/(.+)| or return;
+
+    if ( $scheme eq 'http' and ! eval { require LWP::Simple; 1 } ) {
+        $args{url} = $args{ftp_url}
+            or (warn("LWP support unavailable!\n"), return);
+        ($scheme, $host, $path, $file) = 
+            $args{url} =~ m|^(\w+)://([^/]+)(.+)/(.+)| or return;
+    }
+
+    $|++;
+    print "Fetching '$file' from $host... ";
+
+    unless (eval { require Socket; Socket::inet_aton($host) }) {
+        warn "'$host' resolve failed!\n";
+        return;
+    }
+
+    return unless $scheme eq 'ftp' or $scheme eq 'http';
+
+    require Cwd;
+    my $dir = Cwd::getcwd();
+    chdir $args{local_dir} or return if exists $args{local_dir};
+
+    if (eval { require LWP::Simple; 1 }) {
+        LWP::Simple::mirror($args{url}, $file);
+    }
+    elsif (eval { require Net::FTP; 1 }) { eval {
+        # use Net::FTP to get past firewall
+        my $ftp = Net::FTP->new($host, Passive => 1, Timeout => 600);
+        $ftp->login("anonymous", 'anonymous at example.com');
+        $ftp->cwd($path);
+        $ftp->binary;
+        $ftp->get($file) or (warn("$!\n"), return);
+        $ftp->quit;
+    } }
+    elsif (my $ftp = $self->can_run('ftp')) { eval {
+        # no Net::FTP, fallback to ftp.exe
+        require FileHandle;
+        my $fh = FileHandle->new;
+
+        local $SIG{CHLD} = 'IGNORE';
+        unless ($fh->open("|$ftp -n")) {
+            warn "Couldn't open ftp: $!\n";
+            chdir $dir; return;
+        }
+
+        my @dialog = split(/\n/, <<"END_FTP");
+open $host
+user anonymous anonymous\@example.com
+cd $path
+binary
+get $file $file
+quit
+END_FTP
+        foreach (@dialog) { $fh->print("$_\n") }
+        $fh->close;
+    } }
+    else {
+        warn "No working 'ftp' program available!\n";
+        chdir $dir; return;
+    }
+
+    unless (-f $file) {
+        warn "Fetching failed: $@\n";
+        chdir $dir; return;
+    }
+
+    return if exists $args{size} and -s $file != $args{size};
+    system($args{run}) if exists $args{run};
+    unlink($file) if $args{remove};
+
+    print(((!exists $args{check_for} or -e $args{check_for})
+        ? "done!" : "failed! ($!)"), "\n");
+    chdir $dir; return !$?;
+}
+
+1;

Added: jifty/branches/schema-plugins/inc/Module/Install/Include.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/inc/Module/Install/Include.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,34 @@
+#line 1
+package Module::Install::Include;
+
+use strict;
+use Module::Install::Base;
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+	$VERSION = '0.64';
+	$ISCORE  = 1;
+	@ISA     = qw{Module::Install::Base};
+}
+
+sub include {
+	shift()->admin->include(@_);
+}
+
+sub include_deps {
+	shift()->admin->include_deps(@_);
+}
+
+sub auto_include {
+	shift()->admin->auto_include(@_);
+}
+
+sub auto_include_deps {
+	shift()->admin->auto_include_deps(@_);
+}
+
+sub auto_include_dependent_dists {
+	shift()->admin->auto_include_dependent_dists(@_);
+}
+
+1;

Added: jifty/branches/schema-plugins/inc/Module/Install/Makefile.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/inc/Module/Install/Makefile.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,208 @@
+#line 1
+package Module::Install::Makefile;
+
+use strict 'vars';
+use Module::Install::Base;
+use ExtUtils::MakeMaker ();
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+	$VERSION = '0.64';
+	$ISCORE  = 1;
+	@ISA     = qw{Module::Install::Base};
+}
+
+sub Makefile { $_[0] }
+
+my %seen = ();
+
+sub prompt {
+    shift;
+
+    # Infinite loop protection
+    my @c = caller();
+    if ( ++$seen{"$c[1]|$c[2]|$_[0]"} > 3 ) {
+        die "Caught an potential prompt infinite loop ($c[1]|$c[2]|$_[0])";
+    }
+
+    # In automated testing, always use defaults
+    if ( $ENV{AUTOMATED_TESTING} and ! $ENV{PERL_MM_USE_DEFAULT} ) {
+        local $ENV{PERL_MM_USE_DEFAULT} = 1;
+        goto &ExtUtils::MakeMaker::prompt;
+    } else {
+        goto &ExtUtils::MakeMaker::prompt;
+    }
+}
+
+sub makemaker_args {
+    my $self = shift;
+    my $args = ($self->{makemaker_args} ||= {});
+    %$args = ( %$args, @_ ) if @_;
+    $args;
+}
+
+# For mm args that take multiple space-seperated args,
+# append an argument to the current list.
+sub makemaker_append {
+    my $self = shift;
+    my $name = shift;
+    my $args = $self->makemaker_args;
+    $args->{name} = defined $args->{$name}
+    	? join( ' ', $args->{name}, @_ )
+    	: join( ' ', @_ );
+}
+
+sub build_subdirs {
+    my $self    = shift;
+    my $subdirs = $self->makemaker_args->{DIR} ||= [];
+    for my $subdir (@_) {
+        push @$subdirs, $subdir;
+    }
+}
+
+sub clean_files {
+    my $self  = shift;
+    my $clean = $self->makemaker_args->{clean} ||= {};
+    %$clean = (
+        %$clean, 
+        FILES => join(' ', grep length, $clean->{FILES}, @_),
+    );
+}
+
+sub realclean_files {
+    my $self  = shift;
+    my $realclean = $self->makemaker_args->{realclean} ||= {};
+    %$realclean = (
+        %$realclean, 
+        FILES => join(' ', grep length, $realclean->{FILES}, @_),
+    );
+}
+
+sub libs {
+    my $self = shift;
+    my $libs = ref $_[0] ? shift : [ shift ];
+    $self->makemaker_args( LIBS => $libs );
+}
+
+sub inc {
+    my $self = shift;
+    $self->makemaker_args( INC => shift );
+}
+
+sub write {
+    my $self = shift;
+    die "&Makefile->write() takes no arguments\n" if @_;
+
+    my $args = $self->makemaker_args;
+    $args->{DISTNAME} = $self->name;
+    $args->{NAME}     = $self->module_name || $self->name || $self->determine_NAME($args);
+    $args->{VERSION}  = $self->version || $self->determine_VERSION($args);
+    $args->{NAME}     =~ s/-/::/g;
+    if ( $self->tests ) {
+        $args->{test} = { TESTS => $self->tests };
+    }
+    if ($] >= 5.005) {
+        $args->{ABSTRACT} = $self->abstract;
+        $args->{AUTHOR}   = $self->author;
+    }
+    if ( eval($ExtUtils::MakeMaker::VERSION) >= 6.10 ) {
+        $args->{NO_META} = 1;
+    }
+    if ( eval($ExtUtils::MakeMaker::VERSION) > 6.17 and $self->sign ) {
+        $args->{SIGN} = 1;
+    }
+    unless ( $self->is_admin ) {
+        delete $args->{SIGN};
+    }
+
+    # merge both kinds of requires into prereq_pm
+    my $prereq = ($args->{PREREQ_PM} ||= {});
+    %$prereq = ( %$prereq, map { @$_ } map { @$_ } grep $_,
+                 ($self->build_requires, $self->requires) );
+
+    # merge both kinds of requires into prereq_pm
+    my $subdirs = ($args->{DIR} ||= []);
+    if ($self->bundles) {
+        foreach my $bundle (@{ $self->bundles }) {
+            my ($file, $dir) = @$bundle;
+            push @$subdirs, $dir if -d $dir;
+            delete $prereq->{$file};
+        }
+    }
+
+    if ( my $perl_version = $self->perl_version ) {
+        eval "use $perl_version; 1"
+            or die "ERROR: perl: Version $] is installed, "
+                . "but we need version >= $perl_version";
+    }
+
+    my %args = map { ( $_ => $args->{$_} ) } grep {defined($args->{$_})} keys %$args;
+    if ($self->admin->preop) {
+        $args{dist} = $self->admin->preop;
+    }
+
+    my $mm = ExtUtils::MakeMaker::WriteMakefile(%args);
+    $self->fix_up_makefile($mm->{FIRST_MAKEFILE} || 'Makefile');
+}
+
+sub fix_up_makefile {
+    my $self          = shift;
+    my $makefile_name = shift;
+    my $top_class     = ref($self->_top) || '';
+    my $top_version   = $self->_top->VERSION || '';
+
+    my $preamble = $self->preamble 
+        ? "# Preamble by $top_class $top_version\n"
+            . $self->preamble
+        : '';
+    my $postamble = "# Postamble by $top_class $top_version\n"
+        . ($self->postamble || '');
+
+    local *MAKEFILE;
+    open MAKEFILE, "< $makefile_name" or die "fix_up_makefile: Couldn't open $makefile_name: $!";
+    my $makefile = do { local $/; <MAKEFILE> };
+    close MAKEFILE or die $!;
+
+    $makefile =~ s/\b(test_harness\(\$\(TEST_VERBOSE\), )/$1'inc', /;
+    $makefile =~ s/( -I\$\(INST_ARCHLIB\))/ -Iinc$1/g;
+    $makefile =~ s/( "-I\$\(INST_LIB\)")/ "-Iinc"$1/g;
+    $makefile =~ s/^(FULLPERL = .*)/$1 "-Iinc"/m;
+    $makefile =~ s/^(PERL = .*)/$1 "-Iinc"/m;
+
+    # Module::Install will never be used to build the Core Perl
+    # Sometimes PERL_LIB and PERL_ARCHLIB get written anyway, which breaks
+    # PREFIX/PERL5LIB, and thus, install_share. Blank them if they exist
+    $makefile =~ s/^PERL_LIB = .+/PERL_LIB =/m;
+    #$makefile =~ s/^PERL_ARCHLIB = .+/PERL_ARCHLIB =/m;
+
+    # Perl 5.005 mentions PERL_LIB explicitly, so we have to remove that as well.
+    $makefile =~ s/("?)-I\$\(PERL_LIB\)\1//g;
+
+    # XXX - This is currently unused; not sure if it breaks other MM-users
+    # $makefile =~ s/^pm_to_blib\s+:\s+/pm_to_blib :: /mg;
+
+    open  MAKEFILE, "> $makefile_name" or die "fix_up_makefile: Couldn't open $makefile_name: $!";
+    print MAKEFILE  "$preamble$makefile$postamble" or die $!;
+    close MAKEFILE  or die $!;
+
+    1;
+}
+
+sub preamble {
+    my ($self, $text) = @_;
+    $self->{preamble} = $text . $self->{preamble} if defined $text;
+    $self->{preamble};
+}
+
+sub postamble {
+    my ($self, $text) = @_;
+    $self->{postamble} ||= $self->admin->postamble;
+    $self->{postamble} .= $text if defined $text;
+    $self->{postamble}
+}
+
+1;
+
+__END__
+
+#line 334

Added: jifty/branches/schema-plugins/inc/Module/Install/Metadata.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/inc/Module/Install/Metadata.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,315 @@
+#line 1
+package Module::Install::Metadata;
+
+use strict 'vars';
+use Module::Install::Base;
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+	$VERSION = '0.64';
+	$ISCORE  = 1;
+	@ISA     = qw{Module::Install::Base};
+}
+
+my @scalar_keys = qw{
+    name module_name abstract author version license
+    distribution_type perl_version tests
+};
+
+my @tuple_keys = qw{
+    build_requires requires recommends bundles
+};
+
+sub Meta            { shift        }
+sub Meta_ScalarKeys { @scalar_keys }
+sub Meta_TupleKeys  { @tuple_keys  }
+
+foreach my $key (@scalar_keys) {
+    *$key = sub {
+        my $self = shift;
+        return $self->{values}{$key} if defined wantarray and !@_;
+        $self->{values}{$key} = shift;
+        return $self;
+    };
+}
+
+foreach my $key (@tuple_keys) {
+    *$key = sub {
+        my $self = shift;
+        return $self->{values}{$key} unless @_;
+
+        my @rv;
+        while (@_) {
+            my $module = shift or last;
+            my $version = shift || 0;
+            if ( $module eq 'perl' ) {
+                $version =~ s{^(\d+)\.(\d+)\.(\d+)}
+                             {$1 + $2/1_000 + $3/1_000_000}e;
+                $self->perl_version($version);
+                next;
+            }
+            my $rv = [ $module, $version ];
+            push @rv, $rv;
+        }
+        push @{ $self->{values}{$key} }, @rv;
+        @rv;
+    };
+}
+
+sub sign {
+    my $self = shift;
+    return $self->{'values'}{'sign'} if defined wantarray and !@_;
+    $self->{'values'}{'sign'} = ( @_ ? $_[0] : 1 );
+    return $self;
+}
+
+sub dynamic_config {
+	my $self = shift;
+	unless ( @_ ) {
+		warn "You MUST provide an explicit true/false value to dynamic_config, skipping\n";
+		return $self;
+	}
+	$self->{'values'}{'dynamic_config'} = $_[0] ? 1 : 0;
+	return $self;
+}
+
+sub all_from {
+    my ( $self, $file ) = @_;
+
+    unless ( defined($file) ) {
+        my $name = $self->name
+            or die "all_from called with no args without setting name() first";
+        $file = join('/', 'lib', split(/-/, $name)) . '.pm';
+        $file =~ s{.*/}{} unless -e $file;
+        die "all_from: cannot find $file from $name" unless -e $file;
+    }
+
+    $self->version_from($file)      unless $self->version;
+    $self->perl_version_from($file) unless $self->perl_version;
+
+    # The remaining probes read from POD sections; if the file
+    # has an accompanying .pod, use that instead
+    my $pod = $file;
+    if ( $pod =~ s/\.pm$/.pod/i and -e $pod ) {
+        $file = $pod;
+    }
+
+    $self->author_from($file)   unless $self->author;
+    $self->license_from($file)  unless $self->license;
+    $self->abstract_from($file) unless $self->abstract;
+}
+
+sub provides {
+    my $self     = shift;
+    my $provides = ( $self->{values}{provides} ||= {} );
+    %$provides = (%$provides, @_) if @_;
+    return $provides;
+}
+
+sub auto_provides {
+    my $self = shift;
+    return $self unless $self->is_admin;
+
+    unless (-e 'MANIFEST') {
+        warn "Cannot deduce auto_provides without a MANIFEST, skipping\n";
+        return $self;
+    }
+
+    # Avoid spurious warnings as we are not checking manifest here.
+
+    local $SIG{__WARN__} = sub {1};
+    require ExtUtils::Manifest;
+    local *ExtUtils::Manifest::manicheck = sub { return };
+
+    require Module::Build;
+    my $build = Module::Build->new(
+        dist_name    => $self->name,
+        dist_version => $self->version,
+        license      => $self->license,
+    );
+    $self->provides(%{ $build->find_dist_packages || {} });
+}
+
+sub feature {
+    my $self     = shift;
+    my $name     = shift;
+    my $features = ( $self->{values}{features} ||= [] );
+
+    my $mods;
+
+    if ( @_ == 1 and ref( $_[0] ) ) {
+        # The user used ->feature like ->features by passing in the second
+        # argument as a reference.  Accomodate for that.
+        $mods = $_[0];
+    } else {
+        $mods = \@_;
+    }
+
+    my $count = 0;
+    push @$features, (
+        $name => [
+            map {
+                ref($_) ? ( ref($_) eq 'HASH' ) ? %$_
+                                                : @$_
+                        : $_
+            } @$mods
+        ]
+    );
+
+    return @$features;
+}
+
+sub features {
+    my $self = shift;
+    while ( my ( $name, $mods ) = splice( @_, 0, 2 ) ) {
+        $self->feature( $name, @$mods );
+    }
+    return $self->{values}->{features}
+    	? @{ $self->{values}->{features} }
+    	: ();
+}
+
+sub no_index {
+    my $self = shift;
+    my $type = shift;
+    push @{ $self->{values}{no_index}{$type} }, @_ if $type;
+    return $self->{values}{no_index};
+}
+
+sub read {
+    my $self = shift;
+    $self->include_deps( 'YAML', 0 );
+
+    require YAML;
+    my $data = YAML::LoadFile('META.yml');
+
+    # Call methods explicitly in case user has already set some values.
+    while ( my ( $key, $value ) = each %$data ) {
+        next unless $self->can($key);
+        if ( ref $value eq 'HASH' ) {
+            while ( my ( $module, $version ) = each %$value ) {
+                $self->can($key)->($self, $module => $version );
+            }
+        }
+        else {
+            $self->can($key)->($self, $value);
+        }
+    }
+    return $self;
+}
+
+sub write {
+    my $self = shift;
+    return $self unless $self->is_admin;
+    $self->admin->write_meta;
+    return $self;
+}
+
+sub version_from {
+    my ( $self, $file ) = @_;
+    require ExtUtils::MM_Unix;
+    $self->version( ExtUtils::MM_Unix->parse_version($file) );
+}
+
+sub abstract_from {
+    my ( $self, $file ) = @_;
+    require ExtUtils::MM_Unix;
+    $self->abstract(
+        bless(
+            { DISTNAME => $self->name },
+            'ExtUtils::MM_Unix'
+        )->parse_abstract($file)
+     );
+}
+
+sub _slurp {
+    my ( $self, $file ) = @_;
+
+    local *FH;
+    open FH, "< $file" or die "Cannot open $file.pod: $!";
+    do { local $/; <FH> };
+}
+
+sub perl_version_from {
+    my ( $self, $file ) = @_;
+
+    if (
+        $self->_slurp($file) =~ m/
+        ^
+        use \s*
+        v?
+        ([\d_\.]+)
+        \s* ;
+    /ixms
+      )
+    {
+        my $v = $1;
+        $v =~ s{_}{}g;
+        $self->perl_version($1);
+    }
+    else {
+        warn "Cannot determine perl version info from $file\n";
+        return;
+    }
+}
+
+sub author_from {
+    my ( $self, $file ) = @_;
+    my $content = $self->_slurp($file);
+    if ($content =~ m/
+        =head \d \s+ (?:authors?)\b \s*
+        ([^\n]*)
+        |
+        =head \d \s+ (?:licen[cs]e|licensing|copyright|legal)\b \s*
+        .*? copyright .*? \d\d\d[\d.]+ \s* (?:\bby\b)? \s*
+        ([^\n]*)
+    /ixms) {
+        my $author = $1 || $2;
+        $author =~ s{E<lt>}{<}g;
+        $author =~ s{E<gt>}{>}g;
+        $self->author($author); 
+    }
+    else {
+        warn "Cannot determine author info from $file\n";
+    }
+}
+
+sub license_from {
+    my ( $self, $file ) = @_;
+
+    if (
+        $self->_slurp($file) =~ m/
+        =head \d \s+
+        (?:licen[cs]e|licensing|copyright|legal)\b
+        (.*?)
+        (=head\\d.*|=cut.*|)
+        \z
+    /ixms
+      )
+    {
+        my $license_text = $1;
+        my @phrases      = (
+            'under the same (?:terms|license) as perl itself' => 'perl',
+            'GNU public license'                              => 'gpl',
+            'GNU lesser public license'                       => 'gpl',
+            'BSD license'                                     => 'bsd',
+            'Artistic license'                                => 'artistic',
+            'GPL'                                             => 'gpl',
+            'LGPL'                                            => 'lgpl',
+            'BSD'                                             => 'bsd',
+            'Artistic'                                        => 'artistic',
+        );
+        while ( my ( $pattern, $license ) = splice( @phrases, 0, 2 ) ) {
+            $pattern =~ s{\s+}{\\s+}g;
+            if ( $license_text =~ /\b$pattern\b/i ) {
+                $self->license($license);
+                return 1;
+            }
+        }
+    }
+
+    warn "Cannot determine license info from $file\n";
+    return 'unknown';
+}
+
+1;

Added: jifty/branches/schema-plugins/inc/Module/Install/Scripts.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/inc/Module/Install/Scripts.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,50 @@
+#line 1
+package Module::Install::Scripts;
+
+use strict;
+use Module::Install::Base;
+use File::Basename ();
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+	$VERSION = '0.64';
+	$ISCORE  = 1;
+	@ISA     = qw{Module::Install::Base};
+}
+
+sub prompt_script {
+    my ($self, $script_file) = @_;
+
+    my ($prompt, $abstract, $default);
+    foreach my $line ( $self->_read_script($script_file) ) {
+        last unless $line =~ /^#/;
+        $prompt = $1   if $line =~ /^#\s*prompt:\s+(.*)/;
+        $default = $1  if $line =~ /^#\s*default:\s+(.*)/;
+        $abstract = $1 if $line =~ /^#\s*abstract:\s+(.*)/;
+    }
+    unless (defined $prompt) {
+        my $script_name = File::Basename::basename($script_file);
+        $prompt = "Do you want to install '$script_name'";
+        $prompt .= " ($abstract)" if defined $abstract;
+        $prompt .= '?';
+    }
+    return unless $self->prompt($prompt, ($default || 'n')) =~ /^[Yy]/;
+    $self->install_script($script_file);
+}
+
+sub install_script {
+    my $self = shift;
+    my $args = $self->makemaker_args;
+    my $exe_files = $args->{EXE_FILES} ||= [];
+    push @$exe_files, @_;
+}
+
+sub _read_script {
+    my ($self, $script_file) = @_;
+    local *SCRIPT;
+    open SCRIPT, $script_file
+      or die "Can't open '$script_file' for input: $!\n";
+    return <SCRIPT>;
+}
+
+1;

Added: jifty/branches/schema-plugins/inc/Module/Install/Share.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/inc/Module/Install/Share.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,40 @@
+#line 1
+package Module::Install::Share;
+
+use strict;
+use Module::Install::Base;
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+	$VERSION = '0.64';
+	$ISCORE  = 1;
+	@ISA     = qw{Module::Install::Base};
+}
+
+sub install_share {
+	my ($self, $dir) = @_;
+
+	if ( ! defined $dir ) {
+		die "Cannot find the 'share' directory" unless -d 'share';
+		$dir = 'share';
+	}
+
+	$self->postamble(<<"END_MAKEFILE");
+config ::
+\t\$(NOECHO) \$(MOD_INSTALL) \\
+\t\t\"$dir\" \$(INST_AUTODIR)
+
+END_MAKEFILE
+
+	# The above appears to behave incorrectly when used with old versions
+	# of ExtUtils::Install (known-bad on RHEL 3, with 5.8.0)
+	# So when we need to install a share directory, make sure we add a
+	# dependency on a moderately new version of ExtUtils::MakeMaker.
+	$self->build_requires( 'ExtUtils::MakeMaker' => '6.11' );
+}
+
+1;
+
+__END__
+
+#line 98

Added: jifty/branches/schema-plugins/inc/Module/Install/Win32.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/inc/Module/Install/Win32.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,65 @@
+#line 1
+package Module::Install::Win32;
+
+use strict;
+use Module::Install::Base;
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+	$VERSION = '0.64';
+	$ISCORE  = 1;
+	@ISA     = qw{Module::Install::Base};
+}
+
+# determine if the user needs nmake, and download it if needed
+sub check_nmake {
+	my $self = shift;
+	$self->load('can_run');
+	$self->load('get_file');
+	
+	require Config;
+	return unless (
+		$^O eq 'MSWin32'                     and
+		$Config::Config{make}                and
+		$Config::Config{make} =~ /^nmake\b/i and
+		! $self->can_run('nmake')
+	);
+
+	print "The required 'nmake' executable not found, fetching it...\n";
+
+	require File::Basename;
+	my $rv = $self->get_file(
+		url       => 'http://download.microsoft.com/download/vc15/Patch/1.52/W95/EN-US/Nmake15.exe',
+		ftp_url   => 'ftp://ftp.microsoft.com/Softlib/MSLFILES/Nmake15.exe',
+		local_dir => File::Basename::dirname($^X),
+		size      => 51928,
+		run       => 'Nmake15.exe /o > nul',
+		check_for => 'Nmake.exe',
+		remove    => 1,
+	);
+
+	if (!$rv) {
+        die <<'END_MESSAGE';
+
+-------------------------------------------------------------------------------
+
+Since you are using Microsoft Windows, you will need the 'nmake' utility
+before installation. It's available at:
+
+  http://download.microsoft.com/download/vc15/Patch/1.52/W95/EN-US/Nmake15.exe
+      or
+  ftp://ftp.microsoft.com/Softlib/MSLFILES/Nmake15.exe
+
+Please download the file manually, save it to a directory in %PATH% (e.g.
+C:\WINDOWS\COMMAND\), then launch the MS-DOS command line shell, "cd" to
+that directory, and run "Nmake15.exe" from there; that will create the
+'nmake.exe' file needed by this module.
+
+You may then resume the installation process described in README.
+
+-------------------------------------------------------------------------------
+END_MESSAGE
+	}
+}
+
+1;

Added: jifty/branches/schema-plugins/inc/Module/Install/WriteAll.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/inc/Module/Install/WriteAll.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,43 @@
+#line 1
+package Module::Install::WriteAll;
+
+use strict;
+use Module::Install::Base;
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+	$VERSION = '0.64';
+	$ISCORE  = 1;
+	@ISA     = qw{Module::Install::Base};
+}
+
+sub WriteAll {
+    my $self = shift;
+    my %args = (
+        meta        => 1,
+        sign        => 0,
+        inline      => 0,
+        check_nmake => 1,
+        @_
+    );
+
+    $self->sign(1)                if $args{sign};
+    $self->Meta->write            if $args{meta};
+    $self->admin->WriteAll(%args) if $self->is_admin;
+
+    if ( $0 =~ /Build.PL$/i ) {
+        $self->Build->write;
+    } else {
+        $self->check_nmake if $args{check_nmake};
+        unless ( $self->makemaker_args->{'PL_FILES'} ) {
+        	$self->makemaker_args( PL_FILES => {} );
+        }
+        if ($args{inline}) {
+            $self->Inline->write;
+        } else {
+            $self->Makefile->write;
+        }
+    }
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Email/Send/Jifty/Test.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Email/Send/Jifty/Test.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,16 @@
+package Email::Send::Jifty::Test;
+require Jifty::Test;
+use strict;
+use warnings;
+
+=head1 WHY?
+
+Because L<Email::Send> 1.99_01 requires senders to be in this namespace.
+
+=cut
+
+*is_available = \&Jifty::Test::is_available;
+*send = \&Jifty::Test::send;
+
+1;
+

Added: jifty/branches/schema-plugins/lib/Jifty.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,398 @@
+use warnings;
+use strict;
+
+package Jifty;
+use IPC::PubSub 0.22;
+use Data::UUID;
+use encoding 'utf8';
+# Work around the fact that Time::Local caches thing on first require
+BEGIN { local $ENV{'TZ'} = "GMT";  require Time::Local;}
+$Jifty::VERSION = '0.70117';
+
+=head1 NAME
+
+Jifty - an application framework
+
+=head1 DESCRIPTION
+
+Yet another web framework.
+
+=head2 What's cool about Jifty? (Buzzwords)
+
+=over 4
+
+=item DRY (Don't Repeat Yourself)
+
+Jifty tries not to make you say things more than once.
+
+=item Full-stack
+
+Out of the proverbial box, Jifty comes with one way to do everything
+you should need to do: One database mapper, one templating system, one
+web services layer, one AJAX toolkit, one set of handlers for
+standalone or FastCGI servers. We work hard to make all the bits play
+well together, so you don't have to.
+
+=item Continuations
+
+With Jifty, it's easy to let the user go off and do something else,
+like fill out a wizard, look something up in the help system or go
+twiddle their preferences and come right back to where they were.
+
+=item Form-based dispatch
+
+This is one of the things that Jifty does that we've not seen anywhere
+else. Jifty owns your form rendering and processing. This means you
+never need to write form handling logic. All you say is "I want an
+input for this argument here" and Jifty takes care of the rest. (Even
+autocomplete and validation)
+
+=item A Pony
+
+Jifty is the only web application framework that comes with a pony.
+
+=back
+
+=head2 Introduction
+
+If this is your first time using Jifty, L<Jifty::Manual::Tutorial> is
+probably a better place to start.
+
+=cut
+
+
+use base qw/Jifty::Object/;
+use Jifty::Everything;
+
+use vars qw/$HANDLE $CONFIG $LOGGER $HANDLER $API $CLASS_LOADER $PUB_SUB @PLUGINS/;
+
+=head1 METHODS
+
+=head2 new PARAMHASH
+
+This class method instantiates a new C<Jifty> object. This object
+deals with configuration files, logging and database handles for the
+system.  Before this method returns, it calls the application's C<start>
+method (i.e. C<MyApp-E<gt>start>) to handle any application-specific startup.
+
+Most of the time, the server will call this for you to set up
+your C<Jifty> object.  If you are writing command-line programs that
+want to use your libraries (as opposed to web services) you will need
+to call this yourself.
+
+See L<Jifty::Config> for details on how to configure your Jifty
+application.
+
+=head3 Arguments
+
+=over
+
+=item no_handle
+
+If this is set to true, Jifty will not create a L<Jifty::Handle> and
+connect to a database.  Only use this if you're about to drop the
+database or do something extreme like that; most of Jifty expects the
+handle to exist.  Defaults to false.
+
+=item logger_component
+
+The name that Jifty::Logger will log under.  If you don't specify anything
+Jifty::Logger will log under the empty string.  See L<Jifty::Logger> for
+more infomation.
+
+=back
+
+=cut
+
+sub new {
+    my $ignored_class = shift;
+
+    my %args = (
+        no_handle        => 0,
+        logger_component => undef,
+        @_
+    );
+
+    # Load the configuration. stash it in ->config
+    Jifty->config( Jifty::Config->new() );
+
+
+    # Now that we've loaded the configuration, we can remove the temporary 
+    # Jifty::DBI::Record baseclass for records and insert our "real" baseclass,
+    # which is likely Record::Cachable or Record::Memcached
+    @Jifty::Record::ISA = grep { $_ ne 'Jifty::DBI::Record' } @Jifty::Record::ISA;
+
+    my $record_base_class = Jifty->config->framework('Database')->{'RecordBaseClass'};
+    Jifty::Util->require( $record_base_class );
+    push @Jifty::Record::ISA, $record_base_class unless $record_base_class eq 'Jifty::Record';
+
+    Jifty->logger( Jifty::Logger->new( $args{'logger_component'} ) );
+
+    # Set up plugins
+    my @plugins;
+    for my $plugin (@{Jifty->config->framework('Plugins')}) {
+        my $class = "Jifty::Plugin::".(keys %{$plugin})[0];
+        my %options = %{ $plugin->{(keys %{$plugin})[0]} };
+        Jifty::Util->require($class);
+        Jifty::ClassLoader->new(base => $class)->require;
+        push @plugins, $class->new(%options);
+    }
+
+    Jifty->plugins(@plugins);
+
+    # Now that we have the config set up and loaded plugins,
+    # load the localization files.
+    Jifty::I18N->refresh();
+    
+    # Get a classloader set up
+    my $class_loader = Jifty::ClassLoader->new(
+        base => Jifty->app_class,
+    );
+
+    Jifty->class_loader($class_loader);
+    $class_loader->require;
+
+    Jifty->handler(Jifty::Handler->new());
+    Jifty->api(Jifty::API->new());
+
+    # Let's get the database rocking and rolling
+    Jifty->setup_database_connection(%args);
+
+    # Call the application's start method to let it do anything
+    # application specific for startup
+    my $app = Jifty->app_class;
+    
+    $app->start()
+        if $app->can('start');
+    
+}
+
+=head2 config
+
+An accessor for the L<Jifty::Config> object that stores the
+configuration for the Jifty application.
+
+=cut
+
+sub config {
+    my $class = shift;
+    $CONFIG = shift if (@_);
+    return $CONFIG;
+}
+
+=head2 logger
+
+An accessor for our L<Jifty::Logger> object for the application.
+
+=cut
+
+sub logger {
+    my $class = shift;
+    $LOGGER = shift if (@_);
+    return $LOGGER;
+}
+
+=head2 handler
+
+An accessor for our L<Jifty::Handler> object.
+
+=cut
+
+sub handler {
+    my $class = shift;
+    $HANDLER = shift if (@_);
+    return $HANDLER;
+}
+
+=head2 handle
+
+An accessor for the L<Jifty::Handle> object that stores the database
+handle for the application.
+
+=cut
+
+sub handle {
+    my $class = shift;
+    $HANDLE = shift if (@_);
+    return $HANDLE;
+}
+
+=head2 api
+
+An accessor for the L<Jifty::API> object that publishes and controls
+information about the application's L<Jifty::Action>s.
+
+=cut
+
+sub api {
+    my $class = shift;
+    $API = shift if (@_);
+    return $API;
+}
+
+=head2 app_class(@names)
+
+Return Class in application space.  For example C<app_class('Model', 'Foo')>
+returns YourApp::Model::Foo.
+
+=cut
+
+sub app_class {
+    shift;
+    join('::', Jifty->config->framework('ApplicationClass'), @_);
+}
+
+=head2 web
+
+An accessor for the L<Jifty::Web> object that the web interface uses. 
+
+=cut
+
+sub web {
+    $HTML::Mason::Commands::JiftyWeb ||= Jifty::Web->new();
+    return $HTML::Mason::Commands::JiftyWeb;
+}
+
+=head2 subs
+
+An accessor for the L<Jifty::Subs> object that the subscription uses. 
+
+=cut
+
+sub subs {
+    return Jifty::Subs->new;
+}
+
+=head2 bus
+
+Returns an IPC::PubSub object for the current application.
+
+=cut
+
+sub bus {
+
+    unless ($PUB_SUB) {
+        my @args;
+
+        my $backend = Jifty->config->framework('PubSub')->{'Backend'};
+        if ( $backend eq 'Memcached' ) {
+            require IO::Socket::INET;
+
+            # If there's a running memcached on the default port. this should become configurable
+            if ( IO::Socket::INET->new('127.0.0.1:11211') ) {
+                @args = ( Jifty->app_instance_id );
+            } else {
+                $backend = 'JiftyDBI';
+            }
+        } 
+        
+        if ($backend eq 'JiftyDBI' ) {
+                @args    = (
+                    db_config    => Jifty->handle->{db_config},
+                    table_prefix => '_jifty_pubsub_',
+                );
+            }
+        $PUB_SUB = IPC::PubSub->new( $backend => @args );
+
+    }
+    return $PUB_SUB;
+}
+
+=head2 plugins
+
+Returns a list of L<Jifty::Plugin> objects for this Jifty application.
+
+=cut
+
+sub plugins {
+    my $class = shift;
+    @PLUGINS = @_ if @_;
+    return @PLUGINS;
+}
+
+=head2 class_loader
+
+An accessor for the L<Jifty::ClassLoader> object that stores the loaded
+classes for the application.
+
+=cut
+
+sub class_loader {
+    my $class = shift;
+    $CLASS_LOADER = shift if (@_);
+    return $CLASS_LOADER;
+}
+
+=head2 setup_database_connection
+
+Set up our database connection. Optionally takes a param hash with a
+single argument.  This method is automatically called by L</new>.
+
+=over
+
+=item no_handle
+
+Defaults to false. If true, Jifty won't try to set up a database handle
+
+=back
+
+
+If C<no_handle> is set or our application's config file is missing a C<Database> configuration
+ section or I<has> a C<SkipDatabase: 1> directive in its framework configuration, does nothing.
+
+=cut
+
+sub setup_database_connection {
+    my $self = shift;
+    my %args = (no_handle =>0,
+                @_);
+    unless ( $args{'no_handle'}
+        or Jifty->config->framework('SkipDatabase')
+        or not Jifty->config->framework('Database') )
+    {
+
+        my $handle_class = Jifty->app_class("Handle");
+        Jifty::Util->require( $handle_class );
+        Jifty->handle( $handle_class->new );
+        Jifty->handle->connect();
+        Jifty->handle->check_schema_version();
+    }
+}
+
+
+=head2 app_instance_id
+
+Returns a globally unique id for this instance of this jifty 
+application. This value is generated the first time it's accessed
+
+=cut
+
+sub app_instance_id {
+    my $self = shift;
+    my $app_instance_id = Jifty::Model::Metadata->load("application_instance_uuid");
+    unless ($app_instance_id) {
+        require Data::UUID;
+        $app_instance_id = Data::UUID->new->create_str();
+        Jifty::Model::Metadata->store(application_instance_uuid => $app_instance_id );
+    }
+    return $app_instance_id;
+}
+
+
+=head1 LICENSE
+
+Jifty is Copyright 2005-2006 Best Practical Solutions, LLC.
+Jifty is distributed under the same terms as Perl itself.
+
+=head1 SEE ALSO
+
+L<http://jifty.org>
+
+=head1 AUTHORS
+
+Jesse Vincent, Alex Vandiver and David Glasser.
+
+
+=cut
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/API.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/API.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,225 @@
+use warnings;
+use strict;
+
+package Jifty::API;
+
+=head1 NAME
+
+Jifty::API - Manages and allow reflection on the Jifty::Actions that
+make up a Jifty application's API
+
+=cut
+
+
+use base qw/Class::Accessor::Fast Jifty::Object/;
+
+
+__PACKAGE__->mk_accessors(qw(action_limits));
+
+=head1 METHODS
+
+=head2 new
+
+Creates a new C<Jifty::API> object
+
+=cut
+
+sub new {
+    my $class = shift;
+    my $self  = bless {}, $class;
+
+    $self->reset;
+
+    Jifty::Module::Pluggable->import(
+        search_path => [
+            Jifty->app_class("Action"),
+            "Jifty::Action",
+            map {ref($_)."::Action"} Jifty->plugins,
+        ],
+        except   => qr/\.#/,
+        sub_name => "_actions",
+    );
+
+    return ($self);
+}
+
+=head2 qualify ACTIONNAME
+
+Returns the fully qualified package name for the given provided
+action.  If the C<ACTIONNAME> starts with C<Jifty::> or
+C<ApplicationClass>::Action, simply returns the given name; otherwise,
+it prefixes it with the C<ApplicationClass>::Action.
+
+=cut
+
+sub qualify {
+    my $self   = shift;
+    my $action = shift;
+
+    my $base_path = Jifty->app_class("Action");
+
+    return $action
+        if $action =~ /^Jifty::/
+        or $action =~ /^\Q$base_path\E/;
+
+    return $base_path . "::" . $action;
+}
+
+=head2 reset
+
+Resets which actions are allowed to the defaults; that is, all of the
+application's actions, L<Jifty::Action::Autocomplete>, and
+L<Jifty::Action::Redirect> are allowed; everything else is denied.
+See L</restrict> for the details of how limits are processed.
+
+=cut
+
+sub reset {
+    my $self = shift;
+
+    # Set up defaults
+    my $app_actions = Jifty->app_class("Action");
+
+    $self->action_limits(
+        [   { deny => 1, restriction => qr/.*/ },
+            {   allow       => 1,
+                restriction => qr/^\Q$app_actions\E/,
+            },
+            { allow => 1, restriction => 'Jifty::Action::Autocomplete' },
+            { allow => 1, restriction => 'Jifty::Action::Redirect' },
+        ]
+    );
+}
+
+=head2 allow RESTRICTIONS
+
+Takes a list of strings or regular expressions, and adds them in order
+to the list of limits for the purposes of L</is_allowed>.  See
+L</restrict> for the details of how limits are processed.
+
+=cut
+
+sub allow {
+    my $self = shift;
+    $self->restrict( allow => @_ );
+}
+
+=head2 deny RESTRICTIONS
+
+Takes a list of strings or regular expressions, and adds them in order
+to the list of limits for the purposes of L</is_allowed>.  See
+L</restrict> for the details of how limits are processed.
+
+=cut
+
+sub deny {
+    my $self = shift;
+    $self->restrict( deny => @_ );
+}
+
+=head2 restrict POLARITY RESTRICTIONS
+
+Method that L</allow> and L</deny> call internally; I<POLARITY> is
+either C<allow> or C<deny>.  Allow and deny limits are evaluated in
+the order they're called.  The last limit that applies will be the one
+which takes effect.  Regexes are matched against the class; strings
+are fully L</qualify|qualified> and used as an exact match against the
+class name.  The base set of restrictions (which is reset every
+request) is set in L</reset>, and usually modified by the
+application's L<Jifty::Dispatcher> if need be.
+
+If you call:
+
+    Jifty->api->deny  ( qr'Foo' );
+    Jifty->api->allow ( qr'FooBar' );
+    Jifty->api->deny  ( qr'FooBarDeleteTheWorld' );
+
+..then:
+
+    calls to MyApp::Action::Baz will succeed.
+    calls to MyApp::Action::Foo will fail.
+    calls to MyApp::Action::FooBar will pass.
+    calls to MyApp::Action::TrueFoo will fail.
+    calls to MyApp::Action::TrueFooBar will pass.
+    calls to MyApp::Action::TrueFooBarDeleteTheWorld will fail.
+    calls to MyApp::Action::FooBarDeleteTheWorld will fail.
+
+=cut
+
+sub restrict {
+    my $self         = shift;
+    my $polarity     = shift;
+    my @restrictions = @_;
+
+    die "Polarity must be 'allow' or 'deny'"
+        unless $polarity eq "allow"
+        or $polarity     eq "deny";
+
+    for my $restriction (@restrictions) {
+
+        # Don't let the user "allow .*"
+        die "For security reasons, Jifty won't let you allow all actions"
+            if $polarity eq "allow"
+            and ref $restriction
+            and $restriction =~ /^\(\?[-xism]*:\^?\.\*\$?\)$/;
+
+        # Fully qualify it if it's a string
+        $restriction = $self->qualify($restriction)
+            unless ref $restriction;
+
+        # Add to list of restrictions
+        push @{ $self->action_limits },
+            { $polarity => 1, restriction => $restriction };
+    }
+}
+
+=head2 is_allowed CLASS
+
+Returns false if the I<CLASS> name (which is fully qualified if it is
+not already) is allowed to be executed.  See L</restrict> above for
+the rules that the class name must pass.
+
+=cut
+
+sub is_allowed {
+    my $self  = shift;
+    my $class = shift;
+
+    # Qualify the action
+    $class = $self->qualify($class);
+
+    # Assume that it doesn't pass; however, the real fallbacks are
+    # controlled by L</reset>, above.
+    my $allow = 0;
+
+    # Walk all of the limits
+    for my $limit ( @{ $self->action_limits } ) {
+
+        # Regexes are =~ matches, strigns are eq matches
+        if ( ( ref $limit->{restriction} and $class =~ $limit->{restriction} )
+            or ( $class eq $limit->{restriction} ) )
+        {
+
+            # If the restriction passes, set the current allow/deny
+            # bit according to if this was a positive or negative
+            # limit
+            $allow = $limit->{allow} ? 1 : 0;
+        }
+    }
+    return $allow;
+}
+
+=head2 actions
+
+Lists the class names of all of the allowed actions for this Jifty
+application; this may include actions under the C<Jifty::Action::>
+namespace, in addition to your application's actions.
+
+=cut
+
+sub actions {
+    my $self = shift;
+    return sort grep { $self->is_allowed($_) } $self->_actions;
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Action.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Action.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,1115 @@
+use warnings;
+use strict;
+
+package Jifty::Action;
+
+=head1 NAME
+
+Jifty::Action - The ability to Do Things in the framework
+
+=head1 SYNOPSIS
+
+    package MyApp::Action::Foo;
+    use Jifty::Param::Schema;
+    use Jifty::Action schema {
+
+    param bar =>
+        type is 'checkbox',
+        label is 'Want Bar?',
+        hints is 'Bar is this cool thing that you really want.',
+        default is 0;
+
+    };
+  
+    sub take_action {
+        ...
+    }
+  
+  1;
+
+=head1 DESCRIPTION
+
+C<Jifty::Action> is the superclass for all actions in Jifty.
+Action classes form the meat of the L<Jifty> framework; they
+control how form elements interact with the underlying model.
+
+See also L<Jifty::Action::Record> for data-oriented actions, 
+L<Jifty::Result> for how to return values from actions.
+
+See L<Jifty::Param::Schema> for more details on the declarative 
+syntax.
+
+See L<Jifty::Manual::Actions> for examples of using actions.
+
+=cut
+
+
+use base qw/Jifty::Object Class::Accessor::Fast Class::Data::Inheritable/;
+
+__PACKAGE__->mk_accessors(qw(moniker argument_values values_from_request order result sticky_on_success sticky_on_failure));
+__PACKAGE__->mk_classdata(qw/PARAMS/);
+
+=head1 COMMON METHODS
+
+These common methods are designed to 
+
+=head2 new 
+
+Construct a new action.  Subclasses who need do custom initialization
+should start with:
+
+    my $class = shift; my $self = $class->SUPER::new(@_)
+
+B<Do not call this yourself>; always go through C<<
+Jifty->web->new_action >>!  The arguments that this will be
+called with include:
+
+=over
+
+=item moniker
+
+The L<moniker|Jifty::Manual::Glossary/moniker> of the action.  Defaults to an
+autogenerated moniker.
+
+=item order
+
+An integer that determines the ordering of the action's execution.
+Lower numbers occur before higher numbers.  Defaults to 0.
+
+=item arguments
+
+A hash reference of default values for the
+L<arguments|Jifty::Manual::Glossary/argument> of the action.  Defaults to
+none.
+
+=item sticky_on_failure
+
+A boolean value that determines if the form fields are
+L<sticky|Jifty::Manual::Glossary/sticky> when the action fails.  Defaults to
+true.
+
+=item sticky_on_success
+
+A boolean value that determines if the form fields are
+L<sticky|Jifty::Manual::Glossary/sticky> when the action succeeds.  Defaults
+to false.
+
+=begin private
+
+=item request_arguments
+
+A hashref of arguments passed in as part of the
+L<Jifty::Request>. Internal use only.
+
+=end private
+
+=back
+
+=cut
+
+sub new {
+    my $class = shift;
+    my $self = bless {}, $class;
+    my %args = (
+        order      => undef,
+        arguments  => {},
+        request_arguments => {},
+        sticky_on_success => 0,
+        sticky_on_failure => 1,
+        current_user => undef,
+        @_);
+
+    if ($args{'current_user'}) {
+        $self->current_user($args{current_user});
+    } else {
+        $self->_get_current_user();
+    }
+
+    if ($args{'moniker'}) {
+        $self->moniker($args{'moniker'});
+    } else {
+        $self->moniker($self->_generate_moniker);
+    }
+    $self->order($args{'order'});
+
+    $self->argument_values( { %{ $args{'request_arguments' } }, %{ $args{'arguments'} } } );
+
+    # Keep track of whether arguments came from the request, or were
+    # programmatically set elsewhere
+    $self->values_from_request({});
+    $self->values_from_request->{$_} = 1 for keys %{ $args{'request_arguments' } };
+    $self->values_from_request->{$_} = 0 for keys %{ $args{'arguments' } };
+    
+    $self->result(Jifty->web->response->result($self->moniker) || Jifty::Result->new);
+    $self->result->action_class(ref($self));
+
+    $self->sticky_on_success($args{sticky_on_success});
+    $self->sticky_on_failure($args{sticky_on_failure});
+
+    return $self;
+}
+
+=head2 _generate_moniker 
+
+Construct a moniker for a new (or soon-to-be-constructed) action that did not have
+an explicit moniker specified.  The algorithm is simple: We snapshot the call stack,
+prefix it with the action class, and then append it with an per-request autoincrement
+counter in case the same class/stack is encountered twice, which can happen if the
+programmer placed a C<new_action> call inside a loop.
+
+Monikers generated this way are guaranteed to work across requests.
+
+=cut
+
+sub _generate_moniker {
+    my $self = shift;
+
+    use Digest::MD5 qw(md5_hex);
+    my $frame = 1;
+    my @stack = (ref($self) || $self);
+    while (my ($pkg, $filename, $line) = caller($frame++)) {
+        push @stack, $pkg, $filename, $line;
+    }
+
+    # Increment the per-request moniker digest counter, for the case of looped action generation
+    my $digest = md5_hex("@stack");
+    # We should always have a stash. but if we don't, fake something up
+    # (some hiveminder tests create actions outside of a Jifty::Web)
+    my $serial = Jifty->handler->stash ? ++(Jifty->handler->stash->{monikers}{$digest}) : rand();
+    my $moniker = "auto-$digest-$serial";
+    $self->log->debug("Generating moniker $moniker from stack for $self");
+    return $moniker;
+}
+
+
+=head2 arguments
+
+B<Note>: this API is now deprecated in favour of the declarative syntax
+offered by L<Jifty::Param::Schema>.
+
+This method, along with L</take_action>, is the most commonly
+overridden method.  It should return a hash which describes the
+L<arguments|Jifty::Manual::Glossary/argument> this action takes:
+
+  {
+    argument_name    => {label => "properties go in this hash"},
+    another_argument => {mandatory => 1}
+  }
+
+Each argument listed in the hash will be turned into a
+L<Jifty::Web::Form::Field> object.  For each argument, the hash that
+describes it is used to set up the L<Jifty::Web::Form::Field> object by
+calling the keys as methods with the values as arguments.  That is, in
+the above example, Jifty will run code similar to the following:
+
+  # For 'argument_name'
+  $f = Jifty::Web::Form::Field->new;
+  $f->name( "argument_name" );
+  $f->label( "Properties go in this hash" );
+
+If an action has parameters that B<must> be passed to it to execute,
+these should have the L<constructor|Jifty::Manual::Glossary/constructor>
+property set.  This is separate from the
+L<mandatory|Jifty::Manual::Glossary/mandatory> property, which deal with
+requiring that the user enter a value for that field.
+
+
+=cut
+
+sub arguments {
+    my  $self= shift;
+    return($self->PARAMS || {});
+}
+
+=head2 run
+
+This routine, unsurprisingly, actually runs the action.
+
+If the result of the action is currently a success (validation did not
+fail), C<run> calls L</take_action>, and finally L</cleanup>.
+
+If you're writing your own actions, you probably want to override
+C<take_action> instead.
+
+=cut
+
+sub run {
+    my $self = shift;
+    $self->log->debug("Running action ".ref($self) . " " .$self->moniker);
+    unless ($self->result->success) {
+        $self->log->debug("Not taking action, as it doesn't validate");
+
+        # dump field warnings and errors to debug log
+        foreach my $what (qw/warnings errors/) {
+            my $f = "field_" . $what;
+            my @r =
+                map {
+                    $_ . ": " . $self->result->{$f}->{$_}
+                } grep { $self->result->{$f}->{$_} }
+                    keys %{ $self->result->{$f} };
+            $self->log->debug("Action result $what:\n\t", join("\n\t", @r)) if (@r);
+        }
+
+        return;
+    }
+    $self->log->debug("Taking action ".ref($self) . " " .$self->moniker);
+    my $ret = $self->take_action;
+    $self->log->debug("Result: ".(defined $ret ? $ret : "(undef)"));
+    
+    $self->cleanup;
+}
+
+=head2 validate
+
+
+Checks authorization with L</check_authorization>, calls C</setup>,
+canonicalizes and validates each argument that was submitted, but
+doesn't actually call L</take_action>.
+
+The outcome of all of this is stored on the L</result> of the action.
+
+=cut
+
+sub validate {
+    my $self = shift;
+    $self->check_authorization || return;
+    $self->setup || return;
+    $self->_canonicalize_arguments;
+    $self->_validate_arguments;
+}
+
+=head2 check_authorization
+
+Returns true if whoever invoked this action is authorized to perform
+this action. 
+
+By default, always returns true.
+
+=cut
+
+sub check_authorization { 1; }
+
+
+=head2 setup
+
+C<setup> is expected to return a true value, or
+L</run> will skip all other actions.
+
+By default, does nothing.
+
+=cut
+
+sub setup { 1; }
+
+
+=head2 take_action
+
+Do whatever the action is supposed to do.  This and
+L</arguments> are the most commonly overridden methods.
+
+By default, does nothing.
+
+The return value from this method is NOT returned. (Instead, you
+should be using the L</result> object to store a result).
+
+=cut
+
+sub take_action { 1; }
+
+
+=head2 cleanup
+
+Perform any action-specific cleanup.  By default, does nothing.
+
+Runs after L</take_action> -- whether or not L</take_action> returns success.
+
+=cut
+
+sub cleanup { 1; }
+
+=head2 moniker
+
+Returns the L<moniker|Jifty::Manual::Glossary/moniker> for this action.
+
+=head2 argument_value ARGUMENT [VALUE]
+
+Returns the value from the argument with the given name, for this
+action.  If I<VALUE> is provided, sets the value.
+
+=cut
+
+sub argument_value {
+    my $self = shift;
+    my $arg = shift;
+
+    if(@_) {
+        $self->values_from_request->{$arg} = 0;
+        $self->argument_values->{$arg} = shift;
+    }
+    return $self->argument_values->{$arg};
+}
+
+
+=head2 has_argument ARGUMENT
+
+Returns true if the action has been provided with an value for the
+given argument, including a default_value, and false if none was ever
+passed in.
+
+=cut
+
+sub has_argument {
+    my $self = shift;
+    my $arg = shift;
+
+    return exists $self->argument_values->{$arg};
+}
+
+
+=head2 form_field ARGUMENT
+
+Returns a L<Jifty::Web::Form::Field> object for this argument.  If
+there is no entry in the L</arguments> hash that matches the given
+C<ARGUMENT>, returns C<undef>.
+
+=cut
+
+
+sub form_field {
+    my $self = shift;
+    my $arg_name = shift;
+
+    my $mode = $self->arguments->{$arg_name}{'render_mode'};
+    $mode = 'update' unless $mode && $mode eq 'read';
+
+    $self->_form_widget( argument => $arg_name,
+                         render_mode => $mode,
+                         @_);
+}
+
+
+=head2 form_value ARGUMENT
+
+Returns a L<Jifty::Web::Form::Field> object that renders a display
+value instead of an editable widget for this argument.  If there is no
+entry in the L</arguments> hash that matches the given C<ARGUMENT>,
+returns C<undef>.
+
+=cut
+
+sub form_value {
+    my $self = shift;
+    my $arg_name = shift;
+    $self->_form_widget( argument => $arg_name,
+                         render_mode => 'read',
+                         @_);
+
+}
+
+# Generalized helper for the two above
+sub _form_widget {
+    my $self       = shift;
+    my %args = ( argument => undef,
+                 render_mode => 'update',
+                 @_);
+
+    my $field = $args{'argument'};
+    
+    my $arg_name = $field. '!!' .$args{'render_mode'};
+
+    if ( not exists $self->{_private_form_fields_hash}{$arg_name} ) {
+
+        my $field_info = $self->arguments->{$field};
+
+        my $sticky = 0;
+        # Check stickiness iff the values came from the request
+        if(Jifty->web->response->result($self->moniker)) {
+            $sticky = 1 if $self->sticky_on_failure and $self->result->failure;
+            $sticky = 1 if $self->sticky_on_success and $self->result->success;
+        }
+
+        # $sticky can be overrided per-parameter
+        $sticky = $field_info->{sticky} if defined $field_info->{sticky};
+
+        if ($field_info) {
+            # form_fields overrides stickiness of what the user last entered.
+            my $default_value;
+            $default_value = $field_info->{'default_value'}
+              if exists $field_info->{'default_value'};
+            $default_value = $self->argument_value($field)
+              if $self->has_argument($field) && !$self->values_from_request->{$field};
+
+            $self->{_private_form_fields_hash}{$arg_name}
+                = Jifty::Web::Form::Field->new(
+                %$field_info,
+                action        => $self,
+                name          => $field,
+                sticky        => $sticky,
+                sticky_value  => $self->argument_value($field),
+                default_value => $default_value,
+                render_mode   => $args{'render_mode'},
+                %args
+                );
+
+        }    # else $field remains undef
+        else {
+            Jifty->log->warn("$arg_name isn't a valid field for $self");
+        }
+    } elsif ( $args{render_as} ) {
+        bless $self->{_private_form_fields_hash}{$arg_name},
+          "Jifty::Web::Form::Field::$args{render_as}";
+    }
+    return $self->{_private_form_fields_hash}{$arg_name};
+}
+
+=head2 hidden ARGUMENT VALUE
+
+A shortcut for specifying a form field C<ARGUMENT> which should render
+as a hidden form field, with the default value C<VALUE>.
+
+=cut
+
+sub hidden {
+    my $self = shift;
+    my ($arg, $value, @other) = @_;
+    $self->form_field( $arg, render_as => 'hidden', default_value => $value, @other);
+}
+
+=head2 order [INTEGER]
+
+Gets or sets the order that the action will be run in.  This should be
+an integer, with lower numbers being run first.  Defaults to zero.
+
+=head2 result [RESULT]
+
+Returns the L<Jifty::Result> method associated with this action.  If
+an action with the same moniker existed in the B<last> request, then
+this contains the results of that action.
+
+=head2 register
+
+Registers this action as being present, by outputting a snippet of
+HTML.  This expects that an HTML form has already been opened.  Note
+that this is not a guarantee that the action will be run, even if the
+form is submitted.  See L<Jifty::Request> for the definition of
+"L<active|Jifty::Manual::Glossary/active>" actions.
+
+Normally, L<Jifty::Web/new_action> takes care of calling this when it
+is needed.
+
+=cut
+
+sub register {
+    my $self = shift;
+    Jifty->web->out( qq!<div class="hidden"><input type="hidden"! .
+                       qq! name="@{[$self->register_name]}"! .
+                       qq! id="@{[$self->register_name]}"! .
+                       qq! value="@{[ref($self)]}"! .
+                       qq! /></div>\n! );
+
+
+
+    my %args = %{$self->arguments};
+
+    while ( my ( $name, $info ) = each %args ) {
+        next unless $info->{'constructor'};
+        Jifty::Web::Form::Field->new(
+            %$info,
+            action        => $self,
+            input_name    => $self->fallback_form_field_name($name),
+            sticky        => 0,
+            default_value => ($self->argument_value($name) || $info->{'default_value'}),
+            render_as     => 'Hidden'
+        )->render();
+    }
+    return '';
+}
+
+=head2 render_errors
+
+Render any the L<Jifty::Result/error> of this action, if any, as HTML.
+Returns nothing.
+
+=cut
+
+sub render_errors {
+    my $self = shift;
+    
+    if (defined $self->result->error) {
+        # XXX TODO FIXME escape?
+        Jifty->web->out( '<div class="form_errors">'
+                . '<span class="error">'
+                . $self->result->error
+                . '</span>'
+                . '</div>' );
+    }
+    return '';
+}
+
+=head2 button arguments => { KEY => VALUE }, PARAMHASH
+
+Create and render a button.  It functions nearly identically like
+L<Jifty::Web/link>, except it takes C<arguments> in addition to
+C<parameters>, and defaults to submitting this L<Jifty::Action>.
+Returns nothing. 
+
+Recommended reading: L<Jifty::Web::Form::Element>, where most of 
+the cool options to button and other things of its ilk are documented.
+
+=cut
+
+sub button {
+    my $self = shift;
+    my %args = ( arguments => {},
+                 submit    => $self,
+                 register  => 0,
+                 @_);
+
+    if ($args{register}) {
+        # If they ask us to register the action, do so
+        Jifty->web->form->register_action( $self );
+        Jifty->web->form->print_action_registration($self->moniker);
+    } elsif ( not Jifty->web->form->printed_actions->{ $self->moniker } ) {
+        # Otherwise, if we're not registered yet, do it in the button
+        my $arguments = $self->arguments;
+        $args{parameters}{ $self->register_name } = ref $self;
+        $args{parameters}{ $self->fallback_form_field_name($_) }
+            = $self->argument_value($_) || $arguments->{$_}->{'default_value'}
+            for grep { $arguments->{$_}{constructor} } keys %{ $arguments };
+    }
+    $args{parameters}{$self->form_field_name($_)} = $args{arguments}{$_}
+      for keys %{$args{arguments}};
+
+    Jifty->web->link(%args);
+}
+
+=head3 return PARAMHASH
+
+Creates and renders a button, like L</button>, which additionally
+defaults to calling the current continuation.
+
+Takes an additional argument, C<to>, which can specify a default path
+to return to if there is no current continuation.
+
+=cut
+
+sub return {
+    my $self = shift;
+    my %args = (@_);
+    my $continuation = Jifty->web->request->continuation;
+    if (not $continuation and $args{to}) {
+        $continuation = Jifty::Continuation->new(request => Jifty::Request->new(path => $args{to}));
+    }
+    delete $args{to};
+
+    $self->button( call => $continuation, %args );
+}
+
+
+=head1 NAMING METHODS
+
+These methods return the names of HTML form elements related to this
+action.
+
+=head2 register_name
+
+Returns the name of the "registration" query argument for this action
+in a web form.
+
+=cut
+
+sub register_name {
+    my $self = shift;
+    return 'J:A-' . (defined $self->order ? $self->order . "-" : "") .$self->moniker;
+}
+
+
+sub _prefix_field {
+    my $self = shift;
+    my ($field_name, $prefix) = @_;
+    return join("-", $prefix, $field_name, $self->moniker);
+}
+
+=head2 form_field_name ARGUMENT
+
+Turn one of this action's L<arguments|Jifty::Manual::Glossary/arguments> into
+a fully qualified name; takes the name of the field as an argument.
+
+=cut
+
+sub form_field_name {
+    my $self = shift;
+    return $self->_prefix_field(shift, "J:A:F");
+}
+
+=head2 fallback_form_field_name ARGUMENT
+
+Turn one of this action's L<arguments|Jifty::Manual::Glossary/arguments> into
+a fully qualified "fallback" name; takes the name of the field as an
+argument.
+
+This is specifically to support checkboxes, which only show up in the
+query string if they are checked.  Jifty creates a checkbox with the
+value of L<form_field_name> as its name and a value of 1, and a hidden
+input with the value of L<fallback_form_field_name> as its name and a
+value of 0; using this information, L<Jifty::Request> can both
+determine if the checkbox was present at all in the form, as well as
+its true value.
+
+=cut
+
+sub fallback_form_field_name {
+    my $self = shift;
+    return $self->_prefix_field(shift, "J:A:F:F");
+}
+
+=head2 error_div_id ARGUMENT
+
+Turn one of this action's L<arguments|Jifty::Manual::Glossary/arguments> into
+the id for the div in which its errors live; takes name of the field
+as an argument.
+
+=cut
+
+sub error_div_id {
+  my $self = shift;
+  my $field_name = shift;
+  return 'errors-' . $self->form_field_name($field_name);
+}
+
+=head2 warning_div_id ARGUMENT
+
+Turn one of this action's L<arguments|Jifty::Manual::Glossary/arguments> into
+the id for the div in which its warnings live; takes name of the field
+as an argument.
+
+=cut
+
+sub warning_div_id {
+  my $self = shift;
+  my $field_name = shift;
+  return 'warnings-' . $self->form_field_name($field_name);
+}
+
+=head2 canonicalization_note_div_id ARGUMENT
+
+Turn one of this action's L<arguments|Jifty::Manual::Glossary/arguments> into
+the id for the div in which its canonicalization notes live; takes name of the field
+as an argument.
+
+=cut
+
+sub canonicalization_note_div_id {
+  my $self = shift;
+  my $field_name = shift;
+  return 'canonicalization_note-' . $self->form_field_name($field_name);
+}
+
+
+=head1 VALIDATION METHODS
+
+=head2 argument_names
+
+Returns the list of argument names.  This information is extracted
+from L</arguments>.
+
+=cut
+
+
+sub argument_names {
+    my $self      = shift;
+    my %arguments = %{ $self->arguments };
+    return (
+        sort {
+            (($arguments{$a}->{'sort_order'} ||0 ) <=> ($arguments{$b}->{'sort_order'} || 0))
+                || (($arguments{$a}->{'name'} || '') cmp ($arguments{$b}->{'name'} ||'' ))
+                || $a cmp $b
+            } keys %arguments
+    );
+}
+
+
+=head2 _canonicalize_arguments
+
+Canonicalizes each of the L<arguments|Jifty::Manual::Glossary/arguments> that
+this action knows about.
+
+This is done by calling L</_canonicalize_argument> for each field
+described by L</arguments>.
+
+=cut
+
+# XXX TODO: This is named with an underscore to prevent infinite
+# looping with arguments named "argument" or "arguments".  We need a
+# better solution.
+sub _canonicalize_arguments {
+    my $self   = shift;
+
+    $self->_canonicalize_argument($_)
+      for $self->argument_names;
+}
+
+
+=head2 _canonicalize_argument ARGUMENT
+
+Canonicalizes the value of an L<argument|Jifty::Manual::Glossary/argument>.
+If the argument has an attribute named B<canonicalizer>, call the
+subroutine reference that attribute points points to.
+
+If it doesn't have a B<canonicalizer> attribute, but the action has a
+C<canonicalize_I<ARGUMENT>> function, also invoke that function.
+
+If neither of those are true, by default canonicalize dates using
+_canonicalize_date
+
+Note that it is possible that a canonicalizer will be called multiple
+times on the same field -- canonicalizers should be careful to do
+nothing to already-canonicalized data.
+
+=cut
+
+# XXX TODO: This is named with an underscore to prevent infinite
+# looping with arguments named "argument" or "arguments".  We need a
+# better solution.
+sub _canonicalize_argument {
+    my $self  = shift;
+    my $field = shift;
+
+    my $field_info = $self->arguments->{$field};
+    my $value = $self->argument_value($field);
+    my $default_method = 'canonicalize_' . $field;
+
+    # XXX TODO: Do we really want to skip undef values?
+    return unless defined $value;
+
+    if ( $field_info->{canonicalizer}
+        and defined &{ $field_info->{canonicalizer} } )
+    {
+        $value = $field_info->{canonicalizer}->( $self, $value );
+    } elsif ( $self->can($default_method) ) {
+        $value = $self->$default_method( $value );
+    } elsif (   defined( $field_info->{render_as} )
+             && lc( $field_info->{render_as} ) eq 'date') {
+        $value = $self->_canonicalize_date( $value );
+    }
+
+    $self->argument_value($field => $value);
+}
+
+
+=head2 _canonicalize_date DATE
+
+Parses and returns the date using L<Jifty::DateTime::new_from_string>.
+
+=cut
+
+sub _canonicalize_date {
+    my $self = shift;
+    my $val = shift;
+    return undef unless defined $val and $val =~ /\S/;
+    return undef unless my $obj = Jifty::DateTime->new_from_string($val);
+    return $obj->ymd;
+}
+
+=head2 _validate_arguments
+
+Validates the form fields.  This is done by calling
+L</_validate_argument> for each field described by L</arguments>
+
+=cut
+
+# XXX TODO: This is named with an underscore to prevent infinite
+# looping with arguments named "argument" or "arguments".  We need a
+# better solution.
+sub _validate_arguments {
+    my $self   = shift;
+    
+    $self->_validate_argument($_)
+      for $self->argument_names;
+
+
+    return $self->result->success;
+}
+
+=head2 _validate_argument ARGUMENT
+
+Validate your form fields.  If the field C<ARGUMENT> is mandatory,
+checks for a value.  If the field has an attribute named B<validator>,
+call the subroutine reference validator points to.
+
+If the action doesn't have an explicit B<validator> attribute, but
+does have a C<validate_I<ARGUMENT>> function, invoke that function.
+
+=cut
+
+# XXX TODO: This is named with an underscore to prevent infinite
+# looping with arguments named "argument" or "arguments".  We need a
+# better solution.
+sub _validate_argument {
+    my $self  = shift;
+    my $field = shift;
+
+    return unless $field;
+    
+    $self->log->debug(" validating argument $field");
+
+    my $field_info = $self->arguments->{$field};
+    return unless $field_info;
+
+    my $value = $self->argument_value($field);
+    
+    if ( !defined $value || !length $value ) {
+        if ( $field_info->{mandatory} ) {
+            return $self->validation_error( $field => _("You need to fill in this field") );
+        }
+    }
+
+    # If we have a set of allowed values, let's check that out.
+    # XXX TODO this should be a validate_valid_values sub
+    if ( $value && $field_info->{valid_values} ) {
+
+        unless ( grep $_->{'value'} eq $value,
+            @{ $self->valid_values($field) } )
+        {
+
+            return $self->validation_error(
+                $field => _("That doesn't look like a correct value") );
+        }
+
+   # ... but still check through a validator function even if it's in the list
+    }
+
+    my $default_validator = 'validate_' . $field;
+
+    # Finally, fall back to running a validator sub
+    if ( $field_info->{validator}
+        and defined &{ $field_info->{validator} } )
+    {
+        return $field_info->{validator}->( $self, $value );
+    }
+
+    elsif ( $self->can($default_validator) ) {
+        return $self->$default_validator( $value );
+    }
+
+    # If none of the checks have failed so far, then it's ok
+    else {
+        return $self->validation_ok($field);
+    }
+}
+
+=head2 _autocomplete_argument ARGUMENT
+
+Get back a list of possible completions for C<ARGUMENT>.  The list
+should either be a list of scalar values or a list of hash references.
+Each hash reference must have a key named C<value>.  There can also
+additionally be a key named C<label> which, if present, will be used
+as the user visible label.  If C<label> is not present then the
+contents of C<value> will be used for the label.
+
+If the field has an attribute named B<autocompleter>, call the
+subroutine reference B<autocompleter> points to.
+
+If the field doesn't have an explicit B<autocompleter> attribute, but
+does have a C<autocomplete_I<ARGUMENT>> function, invoke that
+function.
+
+
+=cut
+
+# XXX TODO: This is named with an underscore to prevent infinite
+# looping with arguments named "argument" or "arguments".  We need a
+# better solution.
+sub _autocomplete_argument {
+    my $self  = shift;
+    my $field = shift;
+    my $field_info = $self->arguments->{$field};
+    my $value = $self->argument_value($field);
+
+    my $default_autocomplete = 'autocomplete_' . $field;
+
+    if ( $field_info->{autocompleter}  )
+    {
+        return $field_info->{autocompleter}->( $self, $value );
+    }
+
+    elsif ( $self->can($default_autocomplete) ) {
+        return $self->$default_autocomplete( $value );
+    }
+
+}
+
+=head2 valid_values ARGUMENT
+
+Given an L<parameter|Jifty::Manual::Glossary/parameter> name, returns the
+list of valid values for it, based on its C<valid_values> field.
+
+This method returns a hash referenece with a C<display> field for the string
+to display for the value, and a C<value> field for the value to actually send
+to the server.
+
+(Avoid using this -- this is not the appropriate place for this logic
+to be!)
+
+=cut
+
+sub valid_values {
+    my $self = shift;
+    my $field = shift;
+
+    $self->_values_for_field( $field => 'valid' );
+}
+
+=head2 available_values ARGUMENT
+
+Just like L<valid_values>, but if our action has a set of available
+recommended values, returns that instead. (We use this to
+differentiate between a list of acceptable values and a list of
+suggested values)
+
+=cut
+
+sub available_values {
+    my $self = shift;
+    my $field = shift;
+
+    $self->_values_for_field( $field => 'available' ) || $self->_values_for_field( $field => 'valid' );
+
+}
+
+# TODO XXX FIXME this is probably in the wrong place, logically
+sub _values_for_field {
+    my $self  = shift;
+    my $field = shift;
+    my $type = shift;
+
+    my $vv_orig = $self->arguments->{$field}{$type .'_values'};
+    local $@;
+    my @values = eval { @$vv_orig } or return $vv_orig;
+
+    my $vv = [];
+
+    for my $v (@values) {
+        if ( ref $v eq 'HASH' ) {
+            if ( $v->{'collection'} ) {
+                my $disp = $v->{'display_from'};
+                my $val  = $v->{'value_from'};
+                # XXX TODO: wrap this in an eval?
+                push @$vv, map {
+                    {
+                        display => ( $_->$disp() || '' ),
+                        value   => ( $_->$val()  || '' )
+                    }
+                } grep {$_->check_read_rights} @{ $v->{'collection'}->items_array_ref };
+
+            }
+            else {
+
+                # assume it's already display/value
+                push @$vv, $v;
+            }
+        }
+        else {
+
+            # just a string
+            push @$vv, { display => $v, value => $v };
+        }
+    }
+
+    return $vv;
+}
+
+=head2 validation_error ARGUMENT => ERROR TEXT
+
+Used to report an error during validation.  Inside a validator you
+should write:
+
+  return $self->validation_error( $field => "error");
+
+..where C<$field> is the name of the argument which is at fault.
+
+=cut
+
+sub validation_error {
+    my $self = shift;
+    my $field = shift;
+    my $error = shift;
+  
+    $self->result->field_error($field => $error); 
+  
+    return 0;
+}
+
+=head2 validation_warning ARGUMENT => WARNING TEXT
+
+Used to report a warning during validation.  Inside a validator you
+should write:
+
+  return $self->validation_warning( $field => "warning");
+
+..where C<$field> is the name of the argument which is at fault.
+
+=cut
+
+sub validation_warning {
+    my $self = shift;
+    my $field = shift;
+    my $warning = shift;
+  
+    $self->result->field_warning($field => $warning); 
+  
+    return 0;
+}
+
+=head2 validation_ok ARGUMENT
+
+Used to report that a field B<does> validate.  Inside a validator you
+should write:
+
+  return $self->validation_ok($field);
+
+=cut
+
+sub validation_ok {
+    my $self = shift;
+    my $field = shift;
+
+    $self->result->field_error($field => undef);
+    $self->result->field_warning($field => undef);
+
+    return 1;
+}
+
+=head2 canonicalization_note ARGUMENT => NOTE
+
+Used to send an informational message to the user from the canonicalizer.  
+Inside a canonicalizer you can write:
+
+  $self->canonicalization_note( $field => "I changed $field for you");
+
+..where C<$field> is the name of the argument which the canonicalizer is 
+processing
+
+=cut
+
+sub canonicalization_note {
+    my $self = shift;
+    my $field = shift;
+    my $info = shift;
+  
+    $self->result->field_canonicalization_note($field => $info); 
+
+    return;
+
+}
+
+=head2 autogenerated
+
+Autogenerated Actions will always return true when this method is called. 
+"Regular" actions will return false.
+
+=cut
+
+sub autogenerated {0}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Action/Autocomplete.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Action/Autocomplete.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,75 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Jifty::Action::Autocomplete
+
+=head1 DESCRIPTION
+
+A built-in L<Jifty::Action> which returns suggested autocompletions
+for a given argument of an action. Generally this is called by Jifty's
+internals through C</__jifty/autocomplete.xml>.
+
+This action gets its data to C</__jifty/autocomplete.xml> by filling in the
+C<completions> of the L<Jifty::Result/content>.
+
+=cut
+
+
+
+package Jifty::Action::Autocomplete;
+use base qw/Jifty::Action/;
+
+=head2 arguments
+
+The arguments for C<Autocomplete> are:
+
+=over 4
+
+=item action
+
+The L<moniker|Jifty::Manual::Glossary/moniker> of an action we want to pull a
+field to autocomplete from.
+
+=item argument
+
+The fully qualified name of the L<argument|Jifty::Manual::Glossary/argument>
+to C<action> that we want to complete.
+
+=back
+
+=cut
+
+sub arguments {
+    {
+        moniker => {},
+        argument => {}
+    }
+}
+
+=head2 take_action
+
+Find the submitted action in the L<Jifty::Request> named by the
+C<action> above, and ask it for autocompletion possibilites for the
+L<argument> in question.
+
+=cut
+
+sub take_action {
+    my $self = shift;
+
+    my $moniker = $self->argument_value('moniker');
+    my $argument = $self->argument_value('argument');
+
+    my $request_action = Jifty->web->request->action($moniker);
+    my $action = Jifty->web->new_action_from_request($request_action);
+
+    my @completions = $action->_autocomplete_argument($argument);
+    $self->result->content->{completions} = \@completions;
+
+    return 1;
+}
+
+1;
+

Added: jifty/branches/schema-plugins/lib/Jifty/Action/Record.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Action/Record.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,399 @@
+use warnings;
+use strict;
+
+package Jifty::Action::Record;
+
+=head1 NAME
+
+Jifty::Action::Record -- An action tied to a record in the database.
+
+=head1 DESCRIPTION
+
+Represents a web-based action that is a create, update, or delete of a
+L<Jifty::Record> object.  This automatically populates the arguments
+method of L<Jifty::Action> so that you don't need to bother.  To
+actually use this class, you probably want to inherit from one of
+L<Jifty::Action::Record::Create>, L<Jifty::Action::Record::Update>, or
+L<Jifty::Action::Record::Delete> and override the C<record_class>
+method.
+
+=cut
+
+use base qw/Jifty::Action/;
+
+use Scalar::Util qw/ blessed /;
+
+__PACKAGE__->mk_accessors(qw(record _cached_arguments));
+
+=head1 METHODS
+
+=head2 record
+
+Access to the underlying L<Jifty::Record> object for this action is
+through the C<record> accessor.
+
+=head2 record_class
+
+This method can either be overridden to return a string specifying the
+name of the record class, or the name of the class can be passed to
+the constructor.
+
+=cut
+
+sub record_class {
+    my $self = shift;
+    my $class = ref $self;
+    my $hint = $class eq __PACKAGE__ ? "" :
+        " (did you forget to override record_class in $class?)";
+    $self->log->fatal("Jifty::Action::Record must be subclassed to be used" .
+        $hint);
+}
+
+=head2 new PARAMHASH
+
+Construct a new C<Jifty::Action::Record> (as mentioned in
+L<Jifty::Action>, this should only be called by C<<
+framework->new_action >>.  The C<record> value, if provided in the
+PARAMHASH, will be used to load the L</record>; otherwise, the
+parimary keys will be loaded from the action's argument values, and
+the L</record> loaded from those primary keys.
+
+=cut
+
+sub new {
+    my $class = shift;
+    my %args  = (
+        record => undef,
+        @_,
+    );
+    my $self = $class->SUPER::new(%args);
+
+
+    my $record_class = $self->record_class;
+    Jifty::Util->require($record_class);
+
+    if (ref $args{'record'} && !$args{'record'}->isa($record_class)) {
+        Carp::confess($args{'record'}." isn't a $record_class");
+    }
+
+
+
+    # Set up record
+    if ( ref $record_class ) {
+        $self->record($record_class);
+        $self->argument_value( $_, $self->record->$_ )
+            for @{ $self->record->_primary_keys };
+    } elsif ( ref $args{record} and $args{record}->isa($record_class) ) {
+        $self->record( $args{record} );
+        $self->argument_value( $_, $self->record->$_ )
+            for @{ $self->record->_primary_keys };
+    } else {
+
+        # We could leave out the explicit current user, but it'd have
+        # a slight negative performance implications
+        $self->record(
+            $record_class->new( current_user => $self->current_user ) );
+        my %given_pks = ();
+        for my $pk ( @{ $self->record->_primary_keys } ) {
+            $given_pks{$pk} = $self->argument_value($pk)
+                if defined $self->argument_value($pk);
+        }
+        $self->record->load_by_primary_keys(%given_pks) if %given_pks;
+    }
+    return $self;
+}
+
+=head2 arguments
+
+Overrides the L<Jifty::Action/arguments> method, to automatically
+provide a form field for every writable attribute of the underlying
+L</record>.
+
+This also creates built-in validation and autocompletion methods
+(validate_$fieldname and autocomplete_$fieldname) for action fields
+that are defined "validate" or "autocomplete". These methods can
+be overridden in any Action which inherits from this class.
+
+Additionally, if our model class defines canonicalize_, validate_, or
+autocomplete_ FIELD, generate appropriate an appropriate
+canonicalizer, validator, or autocompleter that will call that method
+with the value to be validated, canonicalized, or autocompleted.
+
+C<validate_FIELD> should return a (success boolean, message) list.
+
+C<autocomplete_FIELD> should return a the same kind of list as
+L<Jifty::Action::_autocomplete_argument|Jifty::Action/_autocomplete_argument>
+
+C<canonicalized_FIELD> should return the canonicalized value.
+
+=cut
+
+sub arguments {
+    my $self = shift;
+
+    return $self->_cached_arguments if $self->_cached_arguments;
+
+        my $field_info = {};
+
+        my @fields = $self->possible_fields;
+
+        # we use a while here because we may be modifying the fields
+        # on the fly.
+        while ( my $field = shift @fields ) {
+            my $info = {};
+            my $column;
+            if ( ref $field ) {
+                $column = $field;
+                $field  = $column->name;
+            } else {
+                $column = $self->record->column($field);
+                my $current_value = $self->record->$field;
+
+                # If the current value is actually a pointer to
+                # another object, dereference it
+                $current_value = $current_value->id
+                    if blessed($current_value)
+                    and $current_value->isa('Jifty::Record');
+                $info->{default_value} = $current_value if $self->record->id;
+            }
+
+            # 
+            #  if($field =~ /^(.*)_id$/ && $self->record->column($1)) {
+            #    $column = $self->record->column($1);
+            #}
+
+            ##################
+            my $render_as = $column->render_as;
+            $render_as = defined $render_as ? lc($render_as) : '';
+
+            if ( defined (my $valid_values = $column->valid_values)) {
+                $info->{valid_values} = [ @$valid_values ];
+                $info->{render_as}    = 'Select';
+            } elsif ( defined $column->type && $column->type =~ /^bool/i ) {
+                $info->{render_as} = 'Checkbox';
+            } elsif ( $render_as eq 'password' )
+            {
+                my $same = sub {
+                    my ( $self, $value ) = @_;
+                    if ( $value ne $self->argument_value($field) ) {
+                        return $self->validation_error(
+                            ($field.'_confirm') => _("The passwords you typed didn't match each other")
+                        );
+                    } else {
+                        return $self->validation_ok( $field . '_confirm' );
+                    }
+                };
+
+                $field_info->{ $field . "_confirm" } = {
+                    render_as => 'Password',
+                    virtual => '1',
+                    validator => $same,
+                    sort_order => ($column->sort_order +.01),
+                    mandatory => 0
+                };
+            }
+
+            elsif ( defined (my $refers_to = $column->refers_to) ) {
+                if ( UNIVERSAL::isa( $refers_to, 'Jifty::Record' ) ) {
+
+                    my $collection = Jifty::Collection->new(
+                        record_class => $refers_to,
+                        current_user => $self->record->current_user
+                    );
+                    $collection->unlimit;
+
+                    my $method = $refers_to->_brief_description();
+
+                    $info->{valid_values} = [
+                        {   display_from => $refers_to->can($method) ? $method : "id",
+                            value_from => 'id',
+                            collection => $collection
+                        }
+                    ];
+
+                    $info->{render_as} = 'Select';
+                } else {
+                    # No need to generate arguments for
+                    # JDBI::Collections, as we can't do anything
+                    # useful with them yet, anyways.
+                    next;
+                }
+            }
+
+	    #########
+
+
+            # build up a validator sub if the column implements validation
+            # and we're not overriding it at the action level
+            my $validate_method = "validate_" . $field;
+
+            if ( $column->validator and not $self->can($validate_method) ) {
+                $info->{ajax_validates} = 1;
+                $info->{validator} = sub {
+                    my $self  = shift;
+                    my $value = shift;
+                    my ( $is_valid, $message )
+                        = &{ $column->validator }( $self->record, $value );
+
+                    if ($is_valid) {
+                        return $self->validation_ok($field);
+                    } else {
+                        unless ($message) {
+                            $self->log->error(
+                                qq{Schema validator for $field didn't explain why the value '$value' is invalid}
+                            );
+                        }
+                        return (
+                            $self->validation_error(
+                                $field => ($message || _("That doesn't look right, but I don't know why"))
+                            )
+                        );
+                    }
+                };
+            }
+            my $autocomplete_method = "autocomplete_" . $field;
+
+            if ( $self->record->can($autocomplete_method) ) {
+                $info->{'autocompleter'} ||= sub {
+                    my ( $self, $value ) = @_;
+                    my %columns;
+                    $columns{$_} = $self->argument_value($_)
+                        for grep { $_ ne $field } $self->possible_fields;
+                    return $self->record->$autocomplete_method( $value,
+                        %columns );
+                };
+            }
+            elsif ($column->autocompleted) {
+                # Auto-generated autocompleter
+                $info->{'autocompleter'} ||= sub {
+                    my ( $self, $value ) = @_;
+
+                    my $collection = Jifty::Collection->new(
+                        record_class => $self->record_class,
+                        current_user => $self->record->current_user
+                    );
+
+                    $collection->unlimit;
+                    $collection->rows_per_page(20);
+                    $collection->limit(column => $field, value => $value, operator => 'STARTSWITH', entry_aggregator => 'AND') if length($value);
+                    $collection->limit(column => $field, value => 'NULL', operator => 'IS NOT', entry_aggregator => 'AND');
+                    $collection->limit(column => $field, value => '', operator => '!=', entry_aggregator => 'AND');
+                    $collection->columns('id', $field);
+                    $collection->order_by(column => $field);
+                    $collection->group_by(column => $field);
+
+                    my @choices;
+                    while (my $record = $collection->next) {
+                        push @choices, $record->$field;
+                    }
+                    return @choices;
+                };
+            }
+
+            my $canonicalize_method = "canonicalize_" . $field;
+            if ( $self->record->can($canonicalize_method) ) {
+                $info->{'ajax_canonicalizes'} = 1;
+                $info->{'canonicalizer'} ||= sub {
+                    my ( $self, $value ) = @_;
+                    return $self->record->$canonicalize_method($value);
+                };
+            } elsif ( $render_as eq 'date')
+            {
+                $info->{'ajax_canonicalizes'} = 1;
+            }
+
+            # If we're hand-coding a render_as, hints or label, let's use it.
+            for (qw(render_as label hints max_length mandatory sort_order)) {
+
+                if ( defined (my $val = $column->$_) ) {
+                    $info->{$_} = $val;
+                }
+            }
+            $field_info->{$field} = $info;
+        }
+
+    if ($self->can('PARAMS')) {
+        # User-defined declarative schema fields can override default ones here
+        my $params = $self->PARAMS;
+
+        # We really, really want our sort_order to prevail over user-defined ones
+        # (as opposed to all other param fields).  So we do exactly that here.
+        while (my ($key, $param) = each %$params) {
+            defined(my $sort_order = $param->sort_order) or next;
+
+            # The .99 below means that it's autogenerated by Jifty::Param::Schema.
+            if ($sort_order =~ /\.99$/) {
+                $param->sort_order($field_info->{$key}{sort_order});
+            }
+        }
+
+        use Jifty::Param::Schema ();
+        $self->_cached_arguments(Jifty::Param::Schema::merge_params($field_info, $params));
+    }
+    else {
+        $self->_cached_arguments($field_info);
+    }
+
+    return $self->_cached_arguments();
+}
+
+=head2 possible_fields
+
+Returns the list of fields on the object that the action can update.
+This defaults to all of the fields of the object.
+
+=cut
+
+sub possible_fields {
+    my $self = shift;
+    return map { $_->name } grep { $_->type ne "serial" } $self->record->columns;
+}
+
+=head2 take_action
+
+Throws an error unless it is overridden; use
+Jifty::Action::Record::Create, ::Update, or ::Delete
+
+=cut
+
+sub take_action {
+    my $self = shift;
+    $self->log->fatal(
+        "Use one of the Jifty::Action::Record subclasses, ::Create, ::Update or ::Delete or ::Search"
+    );
+}
+
+sub _setup_event_before_action {
+    my $self = shift;
+
+    my $event_info = {};
+    $event_info->{as_hash_before} = $self->record->as_hash;
+    $event_info->{record_id} = $self->record->id;
+    $event_info->{record_class} = ref($self->record);
+    $event_info->{action_class} = ref($self);
+    $event_info->{action_arguments} = $self->argument_values; # XXX does this work?
+    $event_info->{current_user_id} = $self->current_user->id || 0;
+    return ($event_info);
+}
+
+sub _setup_event_after_action {
+    my $self = shift;
+    my $event_info = shift;
+    $event_info->{result} = $self->result;    
+    $event_info->{timestamp} = time(); 
+    $event_info->{as_hash_after} = $self->record->as_hash;
+
+    my $event_class = $event_info->{'record_class'};
+    $event_class =~ s/::Model::/::Event::Model::/g;
+    Jifty::Util->require($event_class);
+    $event_class->new($event_info)->publish;
+}
+
+=head1 SEE ALSO
+
+L<Jifty::Action>, L<Jifty::Record>, L<Jifty::DBI::Record>,
+L<Jifty::Action::Record::Create>, L<Jifty::Action::Record::Update>
+
+=cut
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Action/Record/Create.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Action/Record/Create.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,109 @@
+use warnings;
+use strict;
+
+package Jifty::Action::Record::Create;
+
+=head1 NAME
+
+Jifty::Action::Record::Create - Automagic creation action
+
+=head1 DESCRIPTION
+
+This class is used as the base class for L<Jifty::Action>s that are
+merely creating L<Jifty::Record> objects.  To use it, subclass it and
+override the C<record_class> method to return the name of the
+L<Jifty::Record> subclass that this action creates.
+
+=cut
+
+use base qw/Jifty::Action::Record/;
+
+=head1 METHODS
+
+=head2 arguments
+
+Set the default value in each of the fields to whatever the default of
+the column is in the model
+
+=cut
+
+sub arguments {
+    my $self = shift;
+    
+    my $args = $self->SUPER::arguments;
+    for my $arg (keys %{$args}) {
+        my $column = $self->record->column($arg) or next;
+        $args->{$arg}{default_value} = $column->default
+          if not $args->{$arg}->{default_value};
+    }
+    return $args;
+}
+
+=head2 take_action
+
+Overrides the virtual C<take_action> method on L<Jifty::Action> to call
+the appropriate C<Jifty::Record>'s C<create> method when the action is
+run, thus creating a new object in the database.
+
+The C<id> of the new row is returned in the C<id> content of the
+L<Jifty::Result> for the action.  You can use this in conjunction with
+L<request mapping|Jifty::Request::Mapper> in order to give later parts
+of the request access to the C<id>.
+
+=cut
+
+sub take_action {
+    my $self   = shift;
+    my $record = $self->record;
+
+    my $event_info = $self->_setup_event_before_action();
+    
+    
+    my %values;
+    # Virtual arguments aren't really ever backed by data structures. they're added by jifty for things like confirmations
+    for (grep { defined $self->argument_value($_) && !$self->arguments->{$_}->{virtual} } $self->argument_names) {
+        $values{$_} = $self->argument_value($_);
+        if (ref $values{$_} eq "Fh") { # CGI.pm's "lightweight filehandle class"
+            local $/;
+            my $fh = $values{$_};
+            binmode $fh;
+            $values{$_} = scalar <$fh>;
+        }
+    }
+    my $id;
+    my $msg = $record->create(%values);
+    # Handle errors?
+    if (ref($msg)) { # If it's a Class::ReturnValue
+        ($id,$msg) = $msg->as_array;
+    }
+
+    if (! $record->id ) {
+        $self->log->debug(_("Create of %1 failed: %2", ref($record), $msg));
+        $self->result->error($msg || _("An error occurred.  Try again later"));
+    }
+
+    else { 
+        # Return the id that we created
+        $self->result->content(id => $self->record->id);
+        $self->report_success if  not $self->result->failure;
+    }
+    $self->_setup_event_after_action($event_info) ;
+
+    return ($self->record->id);
+}
+
+=head2 report_success
+
+Sets the L<Jifty::Result/message> to default success message,
+"Created". Override this if you want to report some other
+more user-friendly result.
+
+=cut
+
+sub report_success {
+    my $self = shift;
+    $self->result->message(_("Created"))
+}
+
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Action/Record/Delete.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Action/Record/Delete.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,79 @@
+use warnings;
+use strict;
+
+package Jifty::Action::Record::Delete;
+
+=head1 NAME
+
+Jifty::Action::Record::Delete - Automagic delete action
+
+=head1 DESCRIPTION
+
+This class is used as the base class for L<Jifty::Action>s that are
+merely deleting L<Jifty::Record> objects.  To use it, subclass it and
+override the C<record_class> method to return the name of the
+L<Jifty::Record> subclass that this action should delete.
+
+=cut
+
+use base qw/Jifty::Action::Record/;
+
+=head1 METHODS
+
+=head2 arguments
+
+Overrides the L<Jifty::Action::Record/arguments> method to specify
+that all of the primary keys B<must> have values when submitted; that
+is, they are L<constructors|Jifty::Manual::Glossary/constructors>.  No other
+arguments are required.
+
+=cut
+
+sub arguments {
+    my $self = shift;
+    my $arguments = {};
+
+    for my $pk (@{ $self->record->_primary_keys }) {
+        $arguments->{$pk}{'constructor'} = 1;
+        # XXX TODO IS THERE A BETTER WAY TO NOT RENDER AN ITEM IN arguments
+        $arguments->{$pk}{'render_as'} = 'Unrendered'; 
+        # primary key fields should always be hidden fields
+    }
+    return $arguments;
+}
+
+=head2 take_action
+
+Overrides the virtual C<take_action> method on L<Jifty::Action> to
+delete the row from the database.
+
+=cut
+
+sub take_action {
+    my $self = shift;
+
+    my $event_info = $self->_setup_event_before_action();
+
+    my ( $val, $msg ) = $self->record->delete;
+    $self->result->error($msg) if not $val and $msg;
+
+    $self->report_success if not $self->result->failure;
+    $self->_setup_event_after_action($event_info);
+
+    return 1;
+}
+
+=head2 report_success
+
+Sets the L<Jifty::Result/message> to default success message,
+"Deleted". Override this if you want to report some other more
+user-friendly result.
+
+=cut
+
+sub report_success {
+    my $self = shift;
+    $self->result->message(_("Deleted"))
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Action/Record/Search.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Action/Record/Search.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,253 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Jifty::Action::Record::Search
+
+=head1 DESCRIPTION
+
+The class is a base class for L<Jifty::Action>s that serve to provide
+an interface to general searches through L<Jifty::Record> objects. To
+use it, subclass it and override the C<record_class> method to return
+the fully qualified name of the model to do searches over.
+
+=cut
+
+package Jifty::Action::Record::Search;
+use base qw/Jifty::Action::Record/;
+
+=head1 METHODS
+
+=head2 arguments
+
+Remove validators from arguments, as well as ``mandatory''
+restrictions. Remove any arguments that render as password fields, or
+refer to collections.
+
+Generate additional search arguments for each field based on the
+following criteria:
+
+=over 4
+
+=item C<text>, C<char> or C<varchar> fields
+
+Create C<field>_contains and C<field>_lacks arguments
+
+=item C<date>, or C<timestamp> fields
+
+Create C<field>_before, C<field>_after, C<field>_since and
+C<field>_until arguments.
+
+=item C<integer>, C<float>, C<double>, C<decimal> or C<numeric> fields
+
+Generate C<field>_lt, C<field>_gt, C<field>_le and C<field>_ge arguments, as
+well as a C<field>_dwim field that accepts a prefixed comparison operator in
+the search value, such as C<< >100 >> and C<< !100 >>.
+
+=back
+
+=cut
+
+sub arguments {
+    my $self = shift;
+    return $self->_cached_arguments if $self->_cached_arguments;
+    
+    my $args = $self->SUPER::arguments;
+    for my $field (keys %$args) {
+        
+        my $info = $args->{$field};
+
+        my $column = $self->record->column($field);
+        # First, modify the ``exact match'' search field (same name as
+        # the original argument)
+
+        delete $info->{validator};
+        delete $info->{mandatory};
+
+        if($info->{valid_values}) {
+            my $valid_values = $info->{valid_values};
+
+            local $@;
+            $info->{valid_values} = $valid_values = (eval { [ @$valid_values ] } || [$valid_values]);
+
+            # For radio display, display an "any" label as empty choices looks weird
+            if (lc $info->{render_as} eq 'radio') {
+                if (@$valid_values > 1) {
+                    unshift @$valid_values, { display => _("(any)"), value => '' };
+                    $info->{default_value} ||= '';
+                }
+                else {
+                    # We've got only one choice anyway...
+                    $info->{default_value} ||= $valid_values->[0];
+                }
+            }
+            else {
+                unshift @$valid_values, "";
+            }
+        }
+
+        if(lc $info->{'render_as'} eq 'password') {
+            delete $args->{$field};
+            next;
+        }
+
+        warn "No column for: $field" unless($column);
+        
+        if(defined(my $refers_to = $column->refers_to)) {
+            delete $args->{$field}
+             if UNIVERSAL::isa($refers_to, 'Jifty::Collection');
+        }
+        # XXX TODO: What about booleans? Checkbox doesn't quite work,
+        # since there are three choices: yes, no, either.
+
+        # Magic _id refers_to columns
+        next if($field =~ /^(.*)_id$/ && $self->record->column($1));
+
+        my $label = $info->{label} || $field;
+        $args->{"${field}_not"} = { %$info, label => _("%1 is not", $label) };
+        my $type = lc($column->type);
+        if($type =~ /(?:text|char)/) {
+            $info->{render_as} = 'text';
+            $args->{"${field}_contains"} = { %$info, label => _("%1 contains", $label) };
+            $args->{"${field}_lacks"} = { %$info, label => _("%1 lacks", $label) };
+        } elsif($type =~ /(?:date|time)/) {
+            $args->{"${field}_after"} = { %$info, label => _("%1 after", $label) };
+            $args->{"${field}_before"} = { %$info, label => _("%1 before", $label) };
+            $args->{"${field}_since"} = { %$info, label => _("%1 since", $label) };
+            $args->{"${field}_until"} = { %$info, label => _("%1 until", $label) };
+        } elsif(    $type =~ /(?:int|float|double|decimal|numeric)/
+                && !$column->refers_to) {
+            $args->{"${field}_gt"} = { %$info, label => _("%1 greater than", $label) };
+            $args->{"${field}_lt"} = { %$info, label => _("%1 less than", $label) };
+            $args->{"${field}_ge"} = { %$info, label => _("%1 greater or equal to", $label) };
+            $args->{"${field}_le"} = { %$info, label => _("%1 less or equal to", $label) };
+            $args->{"${field}_dwim"} = { %$info, hints => _('!=>< allowed') };
+        }
+    }
+
+    $args->{contains} = { type => 'text', label => _('Any field contains') };
+    $args->{lacks} = { type => 'text', label => _('No field contains') };
+
+    return $self->_cached_arguments($args);
+}
+
+=head2 take_action
+
+Return a collection with the result of the search specified by the
+given arguments.
+
+We interpret a C<undef> argument as SQL C<NULL>, and ignore empty or
+non-present arguments.
+
+=cut
+
+sub take_action {
+    my $self = shift;
+
+    my $collection = Jifty::Collection->new(
+        record_class => $self->record_class,
+        current_user => $self->record->current_user
+    );
+
+    $collection->unlimit;
+
+    for my $field (grep {$self->has_argument($_)} $self->argument_names) {
+        next if $field eq 'contains';
+        my $value = $self->argument_value($field);
+        
+        my $column = $self->record->column($field);
+        my $op = undef;
+        
+        if (!$column) {
+            # If we don't have a column, this is a comparison or
+            # substring search. Skip undef values for those, since
+            # NULL makes no sense.
+            next unless defined($value);
+            next if $value =~ /^\s*$/;
+
+            if ($field =~ m{^(.*)_([[:alpha:]]+)$}) {
+                $field = $1;
+                $op = $2;
+                if($op eq 'not') {
+                    $op = '!=';
+                } elsif($op eq 'contains') {
+                    $op = 'LIKE';
+                    $value = "%$value%";
+                } elsif($op eq 'lacks') {
+                    $op = 'NOT LIKE';
+                    $value = "%$value%";
+                } elsif($op eq 'after' || $op eq 'gt') {
+                    $op = '>';
+                } elsif($op eq 'before' || $op eq 'lt') {
+                    $op = '<';
+                } elsif($op eq 'since' || $op eq 'ge') {
+                    $op = '>=';
+                } elsif($op eq 'until' || $op eq 'le') {
+                    $op = '<=';
+                } elsif($op eq 'dwim') {
+                    $op = '=';
+                    if (defined($value) and $value =~ s/^\s*([<>!=]{1,2})\s*//) {
+                        $op = $1;
+                        $op = '!=' if $op eq '!';
+                        $op = '=' if $op eq '==';
+                    }
+                }
+            } else {
+                next;
+            }
+        }
+        
+        if(defined($value)) {
+            next if $value =~ /^\s*$/;
+           
+            if ($op && $op =~ /^(?:!=|NOT LIKE)$/) {
+                $collection->limit( column   => $field, value    => $value, operator => $op || "=", entry_aggregator => 'OR', $op ? (case_sensitive => 0) : (),);
+                $collection->limit( column   => $field, value    => 'NULL', operator => 'IS');
+            } else { 
+
+            
+            $collection->limit(
+                column   => $field,
+                value    => $value,
+                operator => $op || "=",
+                entry_aggregator => 'AND',
+                $op ? (case_sensitive => 0) : (),
+               );
+
+            } 
+
+
+        } else {
+            $collection->limit(
+                column   => $field,
+                value    => 'NULL',
+                operator => 'IS'
+               );
+        }
+    }
+
+    if($self->has_argument('contains')) {
+        my $any = $self->argument_value('contains');
+        for my $col ($self->record->columns) {
+            if($col->type =~ /(?:text|varchar)/) {
+                $collection->limit(column   => $col->name,
+                                   value    => "%$any%",
+                                   operator => 'LIKE',
+                                   entry_aggregator => 'OR',
+                                   subclause => 'contains');
+            }
+        }
+    }
+
+    $self->result->content(search => $collection);
+    $self->result->success;
+}
+
+=head1 SEE ALSO
+
+L<Jifty::Action::Record>, L<Jifty::Collection>
+
+=cut
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Action/Record/Update.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Action/Record/Update.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,159 @@
+use warnings;
+use strict;
+
+package Jifty::Action::Record::Update;
+
+=head1 NAME
+
+Jifty::Action::Record::Update - Automagic update action
+
+=head1 DESCRIPTION
+
+This class is used as the base class for L<Jifty::Action>s that are
+merely updating L<Jifty::Record> objects.  To use it, subclass it and
+override the C<record_class> method to return the name of the
+L<Jifty::Record> subclass that this action should update.
+
+=cut
+
+use base qw/Jifty::Action::Record/;
+
+=head1 METHODS
+
+=head2 arguments
+
+Overrides the L<Jifty::Action::Record/arguments> method to further
+specify that all of the primary keys B<must> have values when
+submitted; that is, they are
+L<constructors|Jifty::Manual::Glossary/constructors>.
+
+=cut
+
+sub arguments {
+    my $self = shift;
+    my $arguments = $self->SUPER::arguments(@_);
+
+    for my $column ( $self->record->columns ) {
+        if ( not $column->writable and $column->readable ) {
+            $arguments->{$column->name}{'render_mode'} = 'read';
+        }
+    }
+
+    for my $pk (@{ $self->record->_primary_keys }) {
+        $arguments->{$pk}{'constructor'} = 1;
+        $arguments->{$pk}{'mandatory'} = 1;
+        # XXX TODO IS THERE A BETTER WAY TO NOT RENDER AN ITEM IN arguments
+        $arguments->{$pk}{'render_as'} = 'Unrendered'; 
+        # primary key fields should always be hidden fields
+    }
+    return $arguments;
+}
+
+=head2 validate_arguments
+
+We only need to validate arguments that got B<submitted> -- thus, a
+mandatory argument that isn't submitted isn't invalid, as it's not
+going to change the record.  This is opposed to the behavior inherited
+from L<Jifty::Action>, where mandatory arguments B<must> be present
+for the action to run.
+
+However, constructor arguments are still required.
+
+=cut
+
+sub _validate_arguments {
+    my $self = shift;
+
+    $self->_validate_argument($_) for grep {
+        $self->has_argument($_)
+            or $self->arguments->{$_}->{constructor}
+    } $self->argument_names;
+
+    return $self->result->success;
+}
+
+=head2 take_action
+
+Overrides the virtual C<take_action> method on L<Jifty::Action> to
+call the appropriate C<Jifty::Record>'s C<set_> methods when the
+action is run, thus updating the object in the database.
+
+=cut
+
+sub take_action {
+    my $self = shift;
+    my $changed = 0;
+
+    my $event_info = $self->_setup_event_before_action();
+
+    for my $field ( $self->argument_names ) {
+        # Skip values that weren't submitted
+        next unless $self->has_argument($field);
+
+        my $column = $self->record->column($field);
+
+        # Skip nonexistent fields
+        next unless $column;
+
+        # Grab the value
+        my $value = $self->argument_value($field);
+
+        # Boolean and integer fields should be set to NULL if blank.
+        # (This logic should be moved into SB or something.)
+        $value = undef
+            if ( defined $column->type and ( $column->type =~ /^bool/i || $column->type =~ /^int/i )
+            and defined $value and $value eq '' );
+
+        # Skip file uploads if blank
+        next if lc $self->arguments->{$field}{render_as} eq "upload"
+          and (not defined $value or not ref $value);
+
+        if (ref $value eq "Fh") { # CGI.pm's "lightweight filehandle class"
+            local $/;
+            binmode $value;
+            $value = scalar <$value>;
+        }
+
+        # Skip fields that have not changed
+        my $old = $self->record->$field;
+        # XXX TODO: This ignore "by" on columns
+        $old = $old->id if ref($old) and $old->isa( 'Jifty::Record' );
+    
+        # if both the new and old values are defined and equal, we don't want to change em
+        # XXX TODO "$old" is a cheap hack to scalarize datetime objects
+        next if ( defined $old and defined $value and "$old" eq "$value" );
+
+        # If _both_ the values are ''
+        next if (  (not defined $old or not length $old)
+                    and ( not defined $value or not length $value ));
+
+        my $setter = "set_$field";
+        my ( $val, $msg ) = $self->record->$setter( $value );
+        $self->result->field_error($field, $msg)
+          if not $val and $msg;
+
+        $changed = 1 if $val;
+    }
+
+    $self->report_success
+      if $changed and not $self->result->failure;
+
+    $self->_setup_event_after_action($event_info);
+
+    return 1;
+}
+
+=head2 report_success
+
+Sets the L<Jifty::Result/message> to default success message,
+"Updated". Override this if you want to report some other more
+user-friendly result.
+
+=cut
+
+sub report_success {
+    my $self = shift;
+    $self->result->message(_("Updated"))
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Action/Redirect.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Action/Redirect.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,66 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Jifty::Action::Redirect - Redirect the browser
+
+=cut
+
+package Jifty::Action::Redirect;
+use base qw/Jifty::Action/;
+
+=head2 new
+
+By default, redirect actions happen as late as possible in the run
+order.  Defaults the L<Jifty::Action/order> to be 100 so it runs later
+than most actions.
+
+=cut
+
+sub new {
+    my $class = shift;
+    my $self = $class->SUPER::new(@_);
+
+    # XXX TODO This is wrong -- it should be -1 or some equivilent, so
+    # it is sorted last all the time.
+    $self->order(100) unless defined $self->order;
+    return $self;
+}
+
+=head2 arguments
+
+The only argument to redirect is the C<url> to redirect to.
+
+=cut
+
+sub arguments {
+        {
+            url => { constructor => 1 },
+        }
+
+}
+
+=head2 take_action
+
+If the other actions in the request have been a success so far,
+redirects to the provided C<url>.  The redirect preserves all of the
+L<Jifty::Result>s for this action, in case the destination page wishes
+to inspect them.
+
+=cut
+
+sub take_action {
+    my $self = shift;
+    return 1 unless ($self->argument_value('url'));
+    return 0 unless Jifty->web->response->success;
+
+    my $page = $self->argument_value('url');
+
+    Jifty->web->next_page($page);
+    Jifty->web->force_redirect(1);
+    return 1;
+}
+
+1;
+

Added: jifty/branches/schema-plugins/lib/Jifty/Bootstrap.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Bootstrap.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,33 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Jifty::Bootstrap - Insert initial data into your database
+
+=head1 DESCRIPTION
+
+C<Jifty::Bootstrap> is an abstract base class for your application's
+bootstrapping.  Use it to set up initial data in your database when
+your application is first installed.
+
+=cut
+
+package Jifty::Bootstrap;
+
+use base qw/Jifty::Object/;
+
+=head2 run
+
+C<run> is the workhorse method for the Bootstrap class.  This takes care of
+setting up internal data structures and initializing things in an
+application-dependent manner.
+
+=cut
+
+sub run { 
+    1;
+}
+
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/ClassLoader.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/ClassLoader.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,306 @@
+use warnings;
+use strict;
+
+package Jifty::ClassLoader;
+
+=head1 NAME
+
+Jifty::ClassLoader - Loads the application classes
+
+=head1 DESCRIPTION
+
+C<Jifty::ClassLoader> loads all of the application's model and action
+classes, generating classes on the fly for Collections of pre-existing
+models.
+
+=head2 new
+
+Returns a new ClassLoader object.  Doing this installs a hook into
+C<@INC> that allows L<Jifty::ClassLoader> to dynamically create
+needed classes if they do not exist already.  This works because if
+use/require encounters a blessed reference in C<@INC>, it will
+invoke the INC method with the name of the module it is searching
+for on the reference.
+
+Takes one mandatory argument, C<base>, which should be the the
+application's base path; all of the classes under this will be
+automatically loaded.
+
+=cut
+
+sub new {
+    my $class = shift;
+    my $self = bless {@_}, $class;
+
+    push @INC, $self;
+    return $self;
+}
+
+=head2 INC
+
+The hook that is called when a module has been C<require>'d that
+cannot be found on disk.  The following stub classes are
+auto-generated:
+
+=over
+
+=item I<Application>
+
+An empty application base class is created that doen't provide any
+methods or inherit from anything.
+
+=item I<Application>::Record
+
+An empty class that descends from L<Jifty::Record> is created.
+
+=item I<Application>::Event
+
+An empty class that descends from L<Jifty::Event> is created.
+
+=item I<Application>::Collection
+
+An empty class that descends from L<Jifty::Collection> is created.
+
+=item I<Application>::Notification
+
+An empty class that descends from L<Jifty::Notification>.
+
+=item I<Application>::Dispatcher
+
+An empty class that descends from L<Jifty::Dispatcher>.
+
+=item I<Application>::Handle
+
+An empty class that descends from L<Jifty::Handle> is created.
+
+=item I<Application>::Bootstrap
+
+An empty class that descends from L<Jifty::Bootstrap>.
+
+=item I<Application>::Upgrade
+
+An empty class that descends from L<Jifty::Upgrade>.
+
+=item I<Application>::CurrentUser
+
+An empty class that descends from L<Jifty::CurrentUser>.
+
+=item I<Application>::Model::I<Anything>Collection
+
+If C<I<Application>::Model::I<Something>> is a valid model class, then
+it creates a subclass of L<Jifty::Collection> whose C<record_class> is
+C<I<Application>::Model::I<Something>>.
+
+=item I<Application>::Action::(Create or Update or Delete)I<Anything>
+
+If C<I<Application>::Model::I<Something>> is a valid model class, then
+it creates a subclass of L<Jifty::Action::Record::Create>,
+L<Jifty::Action::Record::Update>, or L<Jifty::Action::Record::Delete>
+whose I<record_class> is C<I<Application>::Model::I<Something>>.
+
+=back
+
+=cut
+
+# This subroutine's name is fully qualified, as perl will ignore a 'sub INC'
+sub Jifty::ClassLoader::INC {
+    my ( $self, $module ) = @_;
+    my $base = $self->{base};
+    return undef unless ( $module and $base );
+
+    # Canonicalize $module to :: style rather than / and .pm style;
+    $module =~ s/.pm$//;
+    $module =~ s{/}{::}g;
+
+    # The quick check
+    return undef unless $module =~ m!^$base!;
+
+    if ( $module =~ m!^(?:$base)$! ) {
+        return $self->return_class(
+            "use warnings; use strict; package " . $base . ";\n" . " 1;" );
+    }
+#    elsif ( $module =~ m!^(?:$base)::Action$! ) {
+#        return $self->return_class(
+#                  "use warnings; use strict; package $module;\n"
+#                . "use base qw/Jifty::Action/; sub _autogenerated { 1 };\n"
+#                . "1;" );
+#    }
+    elsif ( $module =~ m!^(?:$base)::(Record|Collection|Notification|Dispatcher|Bootstrap|Upgrade|Handle|Event|Event::Model|Action::Record::\w+)$! ) {
+        return $self->return_class(
+                  "use warnings; use strict; package $module;\n"
+                . "use base qw/Jifty::$1/; sub _autogenerated { 1 };\n"
+                . "1;" );
+    } elsif ( $module =~ m!^(?:$base)::CurrentUser$! ) {
+        return $self->return_class(
+                  "use warnings; use strict; package $module;\n"
+                . "use base qw/Jifty::CurrentUser/; sub _autogenerated { 1 };\n"
+                . "1;" );
+    } elsif ( $module =~ m!^(?:$base)::Model::(\w+)Collection$! ) {
+        return $self->return_class(
+                  "use warnings; use strict; package $module;\n"
+                . "use base qw/@{[$base]}::Collection/;\n"
+                . "sub record_class { '@{[$base]}::Model::$1' }\n"
+                . "1;" );
+    } elsif ( $module =~ m!^(?:$base)::Event::Model::([^\.]+)$! ) {
+        my $modelclass = $base . "::Model::" . $1;
+        Jifty::Util->require($modelclass);
+
+        return undef unless eval { $modelclass->table };
+
+        return $self->return_class(
+                  "use warnings; use strict; package $module;\n"
+                . "use base qw/${base}::Event::Model/;\n"
+                . "sub record_class { '$modelclass' };\n"
+                . "sub autogenerated { 1 };\n"
+                . "1;" );
+    } elsif ( $module =~ m!^(?:$base)::Action::(Create|Update|Delete|Search)([^\.]+)$! ) {
+        my $modelclass = $base . "::Model::" . $2;
+
+        Jifty::Util->require($modelclass);
+
+        return undef unless eval { $modelclass->table };
+
+        return $self->return_class(
+                  "use warnings; use strict; package $module;\n"
+                . "use base qw/$base\::Action::Record::$1/;\n"
+                . "sub record_class { '$modelclass' };\n"
+                . "sub autogenerated { 1 };\n"
+                . "1;" );
+    }
+    return undef;
+}
+
+=head2 return_class CODE
+
+A helper method; takes CODE as a string and returns an open filehandle
+containing that CODE.
+
+=cut
+
+sub return_class {
+    my $self = shift;
+    my $content = shift;
+
+
+    open my $fh, '<', \$content;
+    return $fh;
+
+}
+
+=head2 require
+
+Loads all of the application's Actions and Models.  It additionally
+C<require>'s all Collections and Create/Update actions for each Model
+base class -- which will auto-create them using the above code if they
+do not exist on disk.
+
+=cut
+
+sub require {
+    my $self = shift;
+    
+    my $base = $self->{base};
+    # if we don't even have an application class, this trick will not work
+    return unless ($base); 
+    Jifty::Util->require($base);
+    Jifty::Util->require($base."::CurrentUser");
+
+    my %models;
+    
+
+    Jifty::Module::Pluggable->import(
+        search_path =>
+          [ map { $base . "::" . $_ } 'Model', 'Action', 'Notification', 'Event' ],
+        require => 1,
+        except  => qr/\.#/,
+        inner   => 0
+    );
+    $models{$_} = 1 for grep {/^($base)::Model::(.*)$/ and not /Collection$/} $self->plugins;
+    $self->models(sort keys %models);
+    for my $full ($self->models) {
+        $self->_require_model_related_classes($full);
+    }
+        
+}
+
+sub _require_model_related_classes {
+    my $self = shift;
+    my $full = shift;
+    my $base = $self->{base};
+        my($short) = $full =~ /::Model::(.*)/;
+        Jifty::Util->require($full . "Collection");
+        Jifty::Util->require($base . "::Action::" . $_ . $short)
+            for qw/Create Update Delete Search/;
+
+}
+
+
+=head2 require_classes_from_database
+
+Jifty supports model classes that aren't files on disk but instead records
+in your database. It's a little bit mind bending, but basically, you can
+build an application entirely out of the database without ever writing a 
+line of code(*). 
+
+* As of early 2007, this forward looking statement is mostly a lie. But we're 
+working on it.
+
+This method finds all database-backed models and instantiates jifty classes for 
+them it returns a list of classnames of the models it created.
+
+=cut
+
+sub require_classes_from_database {
+    my $self = shift;
+    my @instantiated;
+
+    require Jifty::Model::ModelClassCollection;
+    require Jifty::Model::ModelClass;
+    my $models = Jifty::Model::ModelClassCollection->new(current_user => Jifty::CurrentUser->superuser);
+    $models->unlimit();
+    while (my $model = $models->next) {
+        $model->instantiate();
+        $self->_require_model_related_classes($model->qualified_class);
+    }
+}
+
+=head2 models
+
+Accessor to the list of models this application has loaded.
+
+In scalar context, returns a mutable array reference; in list context,
+return the content of the array.
+
+=cut
+
+sub models {
+    my $self = shift;
+    if (@_) {
+        $self->{models} = ref($_[0]) ? $_[0] : \@_;
+    }
+    wantarray ? @{ $self->{models} ||= [] } : $self->{models};
+}
+
+
+=head1 Writing your own classes
+
+If you require more functionality than is provided by the classes
+created by ClassLoader then you should create a class with the
+appropriate name and add your extra logic to it.
+
+For example you will almost certainly want to write your own
+dispatcher, so something like:
+
+ package MyApp::Dispatcher;
+ use Jifty::Dispatcher -base;
+
+If you want to add some application specific behaviour to a model's
+collection class, say for the User model, create F<UserCollection.pm>
+in your applications Model directory.
+
+ package MyApp::Model::UserCollection;
+ use base 'MyApp::Collection';
+
+=cut
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Client.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Client.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,345 @@
+use strict;
+use warnings;
+
+package Jifty::Client;
+use base qw/WWW::Mechanize/;
+
+$ENV{'http_proxy'} = ''; # Otherwise WWW::Mechanize tries to go through your HTTP proxy
+
+use Jifty::YAML;
+use HTTP::Cookies;
+use XML::XPath;
+use Hook::LexWrap;
+use List::Util qw(first);
+use Carp;
+
+
+=head1 NAME
+
+Jifty::Client --- Subclass of L<WWW::Mechanize> with extra Jifty features
+
+=head1 DESCRIPTION
+
+This module is a base for building robots to interact with Jifty applications.
+It currently contains much overlapping code with C<Jifty::Test::WWW::Mechanize>,
+except that it does not inherit from C<Test::WWW::Mechanize>.
+
+Expect this code to be refactored in the near future.
+
+=head1 METHODS
+
+=head2 new
+
+Overrides L<WWW::Mechanize>'s C<new> to automatically give the
+bot a cookie jar.
+
+=cut
+
+sub new {
+    my $class = shift;
+    my $self = $class->SUPER::new(@_);
+    $self->cookie_jar(HTTP::Cookies->new);
+    return $self;
+} 
+
+=head2 moniker_for ACTION, FIELD1 => VALUE1, FIELD2 => VALUE2
+
+Finds the moniker of the first action of type I<ACTION> whose
+"constructor" field I<FIELD1> is I<VALUE1>, and so on.
+
+=cut
+
+sub moniker_for {
+  my $self = shift;
+  my $action = Jifty->api->qualify(shift);
+  my %args = @_;
+
+  for my $f ($self->forms) {
+  INPUT: 
+    for my $input ($f->inputs) {
+      if ($input->type eq "hidden" and $input->name =~ /^J:A-(?:\d+-)?(.*)/ and $input->value eq $action) {
+
+        my $moniker = $1;
+
+        for my $id (keys %args) {
+          my $idfield = $f->find_input("J:A:F:F-$id-$moniker");
+          next INPUT unless $idfield and $idfield->value eq $args{$id};
+        }
+
+        return $1;
+      }
+    }
+  }
+  return undef;
+}
+
+=head2 fill_in_action MONIKER, FIELD1 => VALUE1, FIELD2 => VALUE2, ...
+
+Finds the fields on the current page with the names FIELD1, FIELD2,
+etc in the MONIKER action, and fills them in.  Returns the
+L<HTML::Form> object of the form that the action is in, or undef if it
+can't find all the fields.
+
+=cut
+
+sub fill_in_action {
+    my $self = shift;
+    my $moniker = shift;
+    my %args = @_;
+
+    my $action_form = $self->action_form($moniker, keys %args);
+    return unless $action_form;
+
+    for my $arg (keys %args) {
+        my $input = $action_form->find_input("J:A:F-$arg-$moniker");
+        unless ($input) {
+            return;
+        } 
+        $input->value($args{$arg});
+    } 
+
+    return $action_form;
+}
+
+=head2 action_form MONIKER [ARGUMENTNAMES]
+
+Returns the form (as an L<HTML::Form> object) corresponding to the
+given moniker (which also contains inputs for the given
+argumentnames), and also selects it as the current form.  Returns
+undef if it can't be found.
+
+=cut
+
+sub action_form {
+    my $self = shift;
+    my $moniker = shift;
+    my @fields = @_;
+    Carp::confess("No moniker") unless $moniker;
+
+    my $i;
+    for my $form ($self->forms) {
+        no warnings 'uninitialized';
+
+        $i++;
+        next unless first {   $_->name =~ /J:A-(?:\d+-)?$moniker/
+                           && $_->type eq "hidden" }
+                        $form->inputs;
+        next if grep {not $form->find_input("J:A:F-$_-$moniker")} @fields;
+
+        $self->form_number($i); #select it, for $mech->submit etc
+        return $form;
+    } 
+    return;
+} 
+
+=head2 action_field_value MONIKER, FIELD
+
+Finds the fields on the current page with the names FIELD in the
+action MONIKER, and returns its value, or undef if it can't be found.
+
+=cut
+
+sub action_field_value {
+    my $self = shift;
+    my $moniker = shift;
+    my $field = shift;
+
+    my $action_form = $self->action_form($moniker, $field);
+    return unless $action_form;
+    
+    my $input = $action_form->find_input("J:A:F-$field-$moniker");
+    return unless $input;
+    return $input->value;
+}
+
+=head2 send_action CLASS ARGUMENT => VALUE, [ ... ]
+
+Sends a request to the server via the webservices API, and returns the
+L<Jifty::Result> of the action.  C<CLASS> specifies the class of the
+action, and all parameters thereafter supply argument keys and values.
+
+The URI of the page is unchanged after this; this is accomplished by
+using the "back button" after making the webservice request.
+
+=cut
+
+sub send_action {
+    my $self = shift;
+    my $class = shift;
+    my %args = @_;
+
+
+    my $uri = $self->uri->clone;
+    $uri->path("__jifty/webservices/yaml");
+
+    my $request = HTTP::Request->new(
+        POST => $uri,
+        [ 'Content-Type' => 'text/x-yaml' ],
+        Jifty::YAML::Dump(
+            {   path => $uri->path,
+                actions => {
+                    action => {
+                        moniker => 'action',
+                        class   => $class,
+                        fields  => \%args
+                    }
+                }
+            }
+        )
+    );
+    my $result = $self->request( $request );
+    my $content = eval { Jifty::YAML::Load($result->content)->{action} } || undef;
+    $self->back;
+    return $content;
+}
+
+=head2 fragment_request PATH ARGUMENT => VALUE, [ ... ]
+
+Makes a request for the fragment at PATH, using the webservices API,
+and returns the string of the result.
+
+=cut
+
+sub fragment_request {
+    my $self = shift;
+    my $path = shift;
+    my %args = @_;
+
+    my $uri = $self->uri->clone;
+    $uri->path("__jifty/webservices/xml");
+
+    my $request = HTTP::Request->new(
+        POST => $uri,
+        [ 'Content-Type' => 'text/x-yaml' ],
+        Jifty::YAML::Dump(
+            {   path => $uri->path,
+                fragments => {
+                    fragment => {
+                        name  => 'fragment',
+                        path  => $path,
+                        args  => \%args
+                    }
+                }
+            }
+        )
+    );
+    my $result = $self->request( $request );
+    use XML::Simple;
+    my $content = eval { XML::Simple::XMLin($result->content, SuppressEmpty => '')->{fragment}{content} } || '';
+    $self->back;
+    return $content;
+}
+
+
+# When it sees something like
+# http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd as a DOCTYPE, this will make
+# it open static/dtd/xhtml1-strict.dtd instead -- great for offline hacking!
+ 
+# This "require" is just to give us something to hook on to, and to prevent a
+# future require from taking effect.
+require 'XML/Parser/LWPExternEnt.pl';
+wrap 'XML::Parser::lwp_ext_ent_handler', pre => sub {
+    my $root = Jifty::Util->share_root;
+    $_[2] =~ s{ \A .+ / ([^/]+) \z }{$root/dtd/$1}xms;
+    open my $fh, '<', $_[2] or die "can't open $_[2]: $!";
+    my $content = do {local $/; <$fh>};
+    close $fh;
+    $_[-1] = $content; # override return value
+};
+wrap 'XML::Parser::lwp_ext_ent_cleanup', pre => sub {
+    $_[-1] = 1; # just return please
+};
+
+=head2 field_error_text MONIKER, FIELD
+
+Finds the error span on the current page for the name FIELD in the
+action MONIKER, and returns the text (tags stripped) from it.  (If the
+field can't be found.
+
+=cut
+
+sub field_error_text {
+    my $self = shift;
+    my $moniker = shift;
+    my $field = shift;
+
+    my $xp = XML::XPath->new( xml => $self->content );
+
+    my $id = "errors-J:A:F-$field-$moniker";
+
+    my $nodeset = $xp->findnodes(qq{//span[\@id = "$id"]});
+    return unless $nodeset->size == 1;
+    
+    # Note that $xp->getNodeText does not actually return undef for nodes that
+    # aren't found, even though it's documented to.  Thus the workaround above.
+    return $xp->getNodeText(qq{//span[\@id = "$id" ]});
+} 
+
+=head2 uri
+
+L<WWW::Mechanize> has a bug where it returns the wrong value for
+C<uri> after redirect.  This fixes that.  See
+http://rt.cpan.org/NoAuth/Bug.html?id=9059
+
+=cut
+
+sub uri { shift->response->request->uri }
+
+=head2 session
+
+Returns the server-side L<Jifty::Web::Session> object associated with
+this Mechanize object.
+
+=cut
+
+sub session {
+    my $self = shift;
+
+    return undef unless $self->cookie_jar->as_string =~ /JIFTY_SID_\d+=([^;]+)/;
+
+    my $session = Jifty::Web::Session->new;
+    $session->load($1);
+    return $session;
+}
+
+=head2 continuation [ID]
+
+Returns the current continuation of the Mechanize object, if any.  Or,
+given an ID, returns the continuation with that ID.
+
+=cut
+
+sub continuation {
+    my $self = shift;
+
+    my $session = $self->session;
+    return undef unless $session;
+    
+    my $id = shift;
+    ($id) = $self->uri =~ /J:(?:C|CALL|RETURN)=([^&;]+)/ unless $id;
+
+    return $session->get_continuation($id);
+}
+
+=head2 current_user
+
+Returns the L<Jifty::CurrentUser> object or descendant, if any.
+
+=cut
+
+sub current_user {
+    my $self = shift;
+
+    my $session = $self->session;
+    return undef unless $session;
+
+    my $id = $session->get('user_id');
+    my $object = Jifty->app_class("CurrentUser")->new();
+    my $user = $session->get('user_ref')->new( current_user => $object );
+    $user->load_by_cols( id => $id );
+    $object->user_object($user);
+
+    return $object;
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Collection.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Collection.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,121 @@
+use warnings;
+use strict;
+
+package Jifty::Collection;
+
+=head1 NAME
+
+Jifty::Collection - Collection of Jifty::Record objects
+
+=head1 SYNOPSIS
+
+  package Foo::Model::BarCollection
+  use base qw/Jifty::Collection/;
+
+=head1 DESCRIPTION
+
+This is a wrapper over L<Jifty::DBI::Collection> that at the same time
+is a L<Jifty::Object>.  To use it, subclass it.
+
+Alternatively, an 'anonymous' collection can be made by creating a new
+C<Jifty::Collection> object, and calling
+C<record_class('Application::Model::Foo')> or similar on it.
+
+In addition, each L<Jifty::Collection> includes a L<Data::Page> object
+to help with calculations related to paged data.  You should B<not>
+call the C<first_row> and C<rows_per_page> methods from
+L<Jifty::DBI::Collection> on a L<Jifty::Collection>.  Instead, if
+you'd like to use paging, you should use the C<set_page_info> method
+to B<set> the number of records per page and first record on the
+current page, and you should use the L<Data::Page> object returned by
+the C<pager> method to B<get> information related to paging.
+
+=cut
+
+use base qw/Jifty::Object Jifty::DBI::Collection Class::Accessor::Fast/;
+use Data::Page;
+
+=head1 MODEL
+
+=head2 pager
+
+Returns a L<Data::Page> object associated with this collection.  This
+object defaults to 10 entries per page.  You should use only use
+L<Data::Page>  methods on this object to B<get> information about paging,
+not to B<set> it; use C<set_page_info> to set paging information.
+
+=head2 results_are_readable
+
+If your results from the query is guaranteed to be readable by
+current_user, you can create the collection with
+C<results_are_readable => 1>.  This is cause check_read_rights to
+bypass normal current_user_can checks.
+
+=cut
+
+__PACKAGE__->mk_accessors(qw(pager results_are_readable));
+
+=head2 add_record
+
+Only add records to the collection that we can read
+
+=cut
+
+sub add_record {
+    my $self = shift;
+    my($record) = (@_);
+
+    $record->_is_readable(1)
+        if $self->results_are_readable;
+
+    $self->SUPER::add_record($record)
+        if $self->results_are_readable || $record->check_read_rights;
+}
+
+sub _init {
+    my $self = shift;
+
+    my %args = (
+        record_class => undef,
+        current_user => undef,
+        results_are_readable => undef,
+        @_
+    );
+
+    $self->_get_current_user(%args);
+    $self->record_class($args{record_class}) if defined $args{record_class};
+    $self->results_are_readable($args{results_are_readable});
+    unless ($self->current_user) {
+        Carp::confess("Collection created without a current user");
+    }
+    $self->table($self->new_item->table());
+    $self->SUPER::_init(%args);
+}
+
+=head2 implicit_clauses
+
+Defaults to ordering by the C<id> column.
+
+=cut
+
+sub implicit_clauses {
+    my $self = shift;
+    $self->order_by( column => 'id',order => 'asc');
+}
+
+=head2 new_item
+
+Overrides L<Jifty::DBI::Collection>'s new_item to pass in the current
+user.
+
+=cut
+
+sub new_item {
+    my $self = shift;
+    my $class =$self->record_class();
+
+    # We do this as a performance optimization, so we don't need to do the stackwalking to find it
+    return $class->new(current_user => $self->current_user);
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Config.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Config.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,364 @@
+use warnings;
+use strict;
+
+package Jifty::Config;
+
+=head1 NAME
+
+Jifty::Config -- wrap a jifty configuration file
+
+=head1 DESCRIPTION
+
+
+=cut
+
+use Jifty::Util;
+use Jifty::YAML;
+use File::Spec;
+use File::Basename;
+use Log::Log4perl;
+use Hash::Merge;
+Hash::Merge::set_behavior('RIGHT_PRECEDENT');
+
+use File::Basename();
+use base qw/Class::Accessor::Fast/;
+
+use vars qw/$CONFIG/;
+
+__PACKAGE__->mk_accessors(qw/stash/);
+
+=head1 METHODS
+
+=head2 new PARAMHASH
+
+This class method instantiates a new C<Jifty::Config> object. This
+object deals with configuration files.  
+
+PARAMHASH currently takes a single option
+
+=over
+
+=item load_config
+
+This boolean defaults to true. If true, L</load> will be called upon initialization.
+
+=back
+
+
+=cut
+
+sub new {
+    my $proto = shift;
+    my %args = ( load_config => 1,
+                 @_ 
+             );
+    my $self  = {};
+    bless $self, $proto;
+    $self->stash( {} );
+
+    $self->load() if ($args{'load_config'});
+    return $self;
+}
+
+=head2 load
+
+
+Jifty first loads the main
+configuration file for the application, looking for the
+C<JIFTY_CONFIG> environment variable or C<etc/config.yml> under the
+application's base directory.
+
+It uses the main configuration file to find a vendor configuration
+file -- if it doesn't find a framework variable named 'VendorConfig',
+it will use the C<JIFTY_VENDOR_CONFIG> environment variable.
+
+After loading the vendor configuration file (if it exists), the
+framework will look for a site configuration file, specified in either
+the framework's C<SiteConfig> or the C<JIFTY_SITE_CONFIG> environment
+variable. (Usually in C<etc/site_config.yml>.)
+
+After loading the site configuration file (if it exists), the
+framework will look for a test configuration file, specified in either
+the framework's C<TestConfig> or the C<JIFTY_TEST_CONFIG> environment
+variable.
+
+Values in the test configuration will clobber the site configuration.
+Values in the site configuration file clobber those in the vendor
+configuration file. Values in the vendor configuration file clobber
+those in the application configuration file.
+
+Once we're all done loading from files, several defaults are
+assumed based on the name of the application -- see L</guess>. 
+
+After we have the config file, we call the coderef in C<$Jifty::Config::postload>,
+if it exists.
+
+If the value begins and ends with %, converts it with
+C<Jifty::Util/absolute_path> to an absolute path.  (This is
+unnecessary for most configuration variables which specify files, but
+is needed for variables such as C<MailerArgs> that only sometimes
+specify files.)
+
+=cut
+
+sub load {
+    my $self = shift;
+
+    $self->stash( Hash::Merge::merge( $self->_default_config_files, $self->stash ));
+
+    my $file = $ENV{'JIFTY_CONFIG'} || Jifty::Util->app_root . '/etc/config.yml';
+
+    my $app;
+
+    # Override anything in the default guessed config with anything from a config file
+    if ( -f $file and -r $file ) {
+        $app = $self->load_file($file);
+        $app = Hash::Merge::merge( $self->stash, $app );
+
+        # Load the $app so we know where to find the vendor config file
+        $self->stash($app);
+    }
+    my $vendor = $self->load_file(
+        Jifty::Util->absolute_path(
+            $self->framework('VendorConfig') || $ENV{'JIFTY_VENDOR_CONFIG'}
+        )
+    );
+
+    # First, we load the app and vendor configs. This way, we can
+    # figure out if we have a special name for the siteconfig file
+    my $config = Hash::Merge::merge( $self->stash, $vendor );
+    $self->stash($config);
+
+
+    my $site = $self->load_file(
+        Jifty::Util->absolute_path(
+            $self->framework('SiteConfig') || $ENV{'JIFTY_SITE_CONFIG'}
+        )
+    );
+
+    $config = Hash::Merge::merge( $self->stash, $site );
+    $self->stash($config);
+
+    my $test = $self->load_file(
+        Jifty::Util->absolute_path(
+            $self->framework('TestConfig') || $ENV{'JIFTY_TEST_CONFIG'}
+        )
+    );
+    $config = Hash::Merge::merge( $self->stash, $test );
+    $self->stash($config);
+
+    # Merge guessed values in for anything we didn't explicitly define
+    # Whatever's in the stash overrides anything we guess
+    $self->stash( Hash::Merge::merge( $self->guess, $self->stash ));
+    
+    # There are a couple things we want to guess that we don't want
+    # getting stuck in a default config file for an app
+    $self->stash( Hash::Merge::merge( $self->defaults, $self->stash));
+
+    # Finally, check for global postload hooks (these are used by the
+    # test harness)
+    $self->$Jifty::Config::postload()
+      if $Jifty::Config::postload;
+}
+
+=head2 framework VARIABLE
+
+Get the framework configuration variable C<VARIABLE>.  
+
+=cut
+
+sub framework {
+    my $self = shift;
+    my $var  = shift;
+
+    $self->_get( 'framework', $var );
+}
+
+=head2 app VARIABLE
+
+Get the application configuration variable C<VARIABLE>.
+
+=cut
+
+sub app {
+    my $self = shift;
+    my $var  = shift;
+
+    $self->_get( 'application', $var );
+}
+
+sub _get {
+    my $self    = shift;
+    my $section = shift;
+    my $var     = shift;
+
+    return  $self->stash->{$section}->{$var} 
+}
+
+
+sub _default_config_files {
+    my $self = shift;
+    my $config  = {
+        framework => {
+            SiteConfig => 'etc/site_config.yml'
+        }
+    };
+    return $self->_expand_relative_paths($config);
+}
+
+
+=head2 guess
+
+Attempts to guess (and return) a configuration hash based solely
+on what we already know. (Often, in the complete absence of
+a configuration file).  It uses the name of the directory containing
+the Jifty binary as a default for C<ApplicationName> if it can't find one.
+
+=cut
+
+sub guess {
+    my $self = shift;
+
+    # Walk around a potential loop by calling guess to get the app name
+    my $app_name;
+    if (@_) {
+        $app_name = shift;
+    } elsif ($self->stash->{framework}->{ApplicationName}) {
+        $app_name =  $self->stash->{framework}->{ApplicationName};
+    } else {
+        $app_name =  Jifty::Util->default_app_name;
+    }
+
+    my $app_class =  $self->stash->{framework}->{ApplicationClass} ||$app_name;
+    $app_class =~ s/-/::/g;
+    my $db_name = lc $app_name;
+    $db_name =~ s/-/_/g;
+    my $app_uuid = Jifty::Util->generate_uuid;
+
+    my $guess = {
+        framework => {
+            AdminMode        => 1,
+            DevelMode        => 1,
+            ApplicationClass => $app_class,
+            ApplicationName  => $app_name,
+            ApplicationUUID  => $app_uuid,
+            LogLevel         => 'INFO',
+            PubSub           => {
+                Enable => undef,
+                Backend => 'Memcached',
+                    },
+            Database         => {
+                Database =>  $db_name,
+                Driver   => "SQLite",
+                Host     => "localhost",
+                Password => "",
+                User     => "",
+                Version  => "0.0.1",
+                RecordBaseClass => 'Jifty::DBI::Record::Cachable',
+                CheckSchema => '1'
+            },
+            Mailer     => 'Sendmail',
+            MailerArgs => [],
+            L10N       => {
+                PoDir => "share/po",
+            },
+            Plugins    => [],
+            Web        => {
+                Port => '8888',
+                BaseURL => 'http://localhost',
+                DataDir     => "var/mason",
+                StaticRoot   => "share/web/static",
+                TemplateRoot => "share/web/templates",
+                ServeStaticFiles => 1,
+                MasonConfig => {
+                    autoflush    => 0,
+                    error_mode   => 'fatal',
+                    error_format => 'text',
+                    default_escape_flags => 'h',
+                },
+                Globals      => [],
+            },
+        },
+    };
+
+    return $self->_expand_relative_paths($guess);
+
+}
+
+
+=head2 defaults
+
+We have a couple default values that shouldn't be included in the
+"guessed" config, as that routine is used when initializing a new 
+application. Generally, these are platform-specific file locations.
+
+=cut
+
+sub defaults {
+    my $self = shift;
+    return {
+        framework => {
+            L10N => {
+                DefaultPoDir => Jifty::Util->share_root . '/po',
+            },
+            Web => {
+                DefaultStaticRoot => Jifty::Util->share_root . '/web/static',
+                DefaultTemplateRoot => Jifty::Util->share_root . '/web/templates',
+            },
+        }
+    };
+
+}
+
+=head2 load_file PATH
+
+Loads a YAML configuration file and returns a hashref to that file's
+data.
+
+=cut
+
+sub load_file {
+
+    my $self = shift;
+    my $file = shift;
+
+    # only try to load files that exist
+    return {} unless ( $file && -f $file );
+    my $hashref = Jifty::YAML::LoadFile($file)
+        or die "I couldn't load config file $file: $!";
+
+    $hashref = $self->_expand_relative_paths($hashref);
+    return $hashref;
+}
+
+
+# Does a DFS, turning all leaves that look like C<%paths%> into absolute paths.
+sub _expand_relative_paths {
+    my $self  = shift;
+    my $datum = shift;
+
+    if ( ref $datum eq 'ARRAY' ) {
+        return [ map { $self->_expand_relative_paths($_) } @$datum ];
+    } elsif ( ref $datum eq 'HASH' ) {
+        for my $key ( keys %$datum ) {
+            my $new_val = $self->_expand_relative_paths( $datum->{$key} );
+            $datum->{$key} = $new_val;
+        }
+        return $datum;
+    } elsif ( ref $datum ) {
+        return $datum;
+    } else {
+        if ( defined $datum and $datum =~ /^%(.+)%$/ ) {
+            $datum = Jifty::Util->absolute_path($1);
+        }
+        return $datum;
+    }
+}
+
+=head1 AUTHOR
+
+Various folks at BestPractical Solutions, LLC.
+
+=cut
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Continuation.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Continuation.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,237 @@
+use warnings;
+use strict;
+
+package Jifty::Continuation;
+
+=head1 NAME
+
+Jifty::Continuation - Allows for basic continuation-based programming
+
+=head1 DESCRIPTION
+
+C<Jifty::Continuation> wraps up the information about a context that
+might have been expecting some sort of answer.  It allows one to
+re-visit that context later by providing the continuation again.
+Continuations are stored on the user's session.
+
+Continuations store a L<Jifty::Request> object and the
+L<Jifty::Response> object for the request.  They can also store
+arbitrary code to be run when the continuation is called.
+
+Continuations can also be arbitrarily nested.  This means that
+returning from one continuation will drop you into the continuation
+that is one higher in the stack.
+
+Continuations are generally created just before their request would
+take effect, activated by the presence of certain query parameters.
+The rest of the request is saved, its execution to be continued at a
+later time.
+
+Continuations are run after any actions have run.  When a continuation
+is run, it restores the request that it has saved away into it, and
+pulls into that request the values any return values that were
+specified when it was created.  The continuations code block, if any,
+is then called, and then the filled-in request is then passed to the
+L<Jifty::Dispatcher>.
+
+=cut
+
+
+use Storable 'dclone';
+
+use base qw/Class::Accessor::Fast/;
+
+__PACKAGE__->mk_accessors(qw(id parent
+                             request response code
+                             ));
+
+=head2 new PARAMHASH
+
+Saves a continuation at the current state.  Possible arguments in the
+C<PARAMHASH>:
+
+=over
+
+=item parent
+
+A L<Jifty::Continuation> object, or the C<id> of one.  This represents
+the continuation that this continuation should return to when it is
+called.  Defaults to the current continuation of the current
+L<Jifty::Request>.
+
+=item request
+
+The L<Jifty::Request> object to save away.  Defaults to an empty
+L<Jifty::Request> object.
+
+=item response
+
+The L<Jifty::Response> object that will be loaded up when the
+continuation is run.  Most of the time, the response isn't stored in
+the continuation, since the continuation was saved away B<before> the
+actions got run.  In the case when continuations are used to preserve
+state across a redirect, however, we tuck the L<Jifty::Response> value
+of the previous request into the continuation as well.  Defaults to an
+empty L<Jifty::Response> object.
+
+=item code
+
+An optional subroutine reference to evaluate when the continuation is
+called.
+
+=back
+
+=cut
+
+sub new {
+    my $class = shift;
+    my $self = bless { }, $class;
+
+    my %args = (
+                parent   => Jifty->web->request->continuation,
+                request  => Jifty::Request->new(),
+                response => Jifty::Response->new(),
+                code     => undef,
+                @_
+               );
+
+    # We don't want refs
+    $args{parent} = $args{parent}->id
+      if $args{parent} and ref $args{parent};
+
+    # We're getting most of our properties from the arguments
+    for (keys %args) {
+        $self->$_($args{$_}) if $self->can($_);
+    }
+
+    # Generate a hopefully unique ID
+    # FIXME: use a real ID
+    my $key = Jifty->web->serial . "_" . int(rand(10)) . int(rand(10)) . int(rand(10)) . int(rand(10)) . int(rand(10)) . int(rand(10));
+    $self->id($key);
+
+    # Save it into the session
+    Jifty->web->session->set_continuation($key => $self);
+
+    return $self;
+}
+
+=head2 return_path_matches
+
+Returns true if the continuation matches the current request's path,
+and it would return to its caller in this context.  This can be used
+to ask "are we about to call a continuation?"
+
+=cut
+
+sub return_path_matches {
+    my $self = shift;
+    my $called_uri = $ENV{'REQUEST_URI'};
+    my $request_path = $self->request->path;
+
+    # XXX TODO: WE should be using URI canonicalization
+
+    my $escape;
+    $called_uri =~ s{/+}{/}g;
+    $called_uri = Encode::encode_utf8($called_uri);
+    $called_uri = $escape while $called_uri ne ($escape = URI::Escape::uri_unescape($called_uri));
+    $request_path =~ s{/+}{/}g; 
+    $request_path = Encode::encode_utf8($request_path);
+    $request_path = $escape while $request_path ne ($escape = URI::Escape::uri_unescape($request_path));
+
+    return $called_uri =~ /^\Q$request_path\E[?&;]J:RETURN=@{[$self->id]}$/;
+}
+
+=head2 call
+
+Call the continuation; this is generally done during request
+processing, after an actions have been run.
+L<Jifty::Request::Mapper>-controlled values are filled into the stored
+request based on the current request and response.  During the
+process, another continuation is created, with the filled-in results
+of the current actions included, and the browser is redirected to the
+proper path, with that continuation.
+
+=cut
+
+sub call {
+    my $self = shift;
+
+    # If we needed to fix up the path (it contains invalid
+    # characters) then warn, because this may cause infinite
+    # redirects
+    Jifty->log->warn("Redirect to '@{[$self->request->path]}' contains unsafe characters")
+      if $self->request->path =~ m{[^A-Za-z0-9\-_.!~*'()/?&;+]};
+
+    # Clone our request
+    my $request = $self->request->clone;
+
+    # Fill in return value(s) into correct part of $request
+    $request->do_mapping;
+
+    my $response = $self->response;
+    # If the current response has results, we need to pull them
+    # in.  For safety, monikers from the saved continuation
+    # override those from the request prior to the call
+    if (Jifty->web->response->results) {
+        $response = dclone(Jifty->web->response);
+        my %results = $self->response->results;
+        $response->result($_ => $results{$_}) for keys %results;
+    }
+
+    # Make a new continuation with that request
+    my $next = Jifty::Continuation->new(parent => $self->parent, 
+                                        request => $request,
+                                        response => $response,
+                                        code => $self->code,
+                                       );
+    $next->request->continuation(Jifty->web->session->get_continuation($next->parent))
+      if defined $next->parent;
+
+    # Redirect to right page if we're not there already
+    Jifty->web->_redirect($next->request->path . "?J:RETURN=" . $next->id);
+}
+
+=head2 return
+
+Returns from the continuation by pulling out the stored request, and
+setting that to be the active request.  This shouldn't need to be
+called by hand -- use L<Jifty::Request/return_from_continuation>,
+which ensures that all requirements are ment before it calls this.
+
+=cut
+
+sub return {
+    my $self = shift;
+
+    # Pull state information out of the continuation and set it
+    # up; we use clone so that the continuation itself is
+    # immutable.
+    Jifty->web->response(dclone($self->response));
+
+    # Run any code in the continuation
+    $self->code->(Jifty->web->request)
+      if $self->code;
+
+    # Set the current request to the one in the continuation
+    return Jifty->web->request($self->request->clone);
+}
+
+=head2 delete
+
+Remove the continuation, and any continuations that would return to
+its scope, from the session.
+
+=cut
+
+sub delete {
+    my $self = shift;
+
+    # Remove all continuations that point to me
+    $_->delete for grep {$_->parent eq $self->id} values %{Jifty->web->session->continuations};
+
+    # Finally, remove me from the list of continuations
+    Jifty->web->session->remove_continuation($self->id);
+
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/CurrentUser.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/CurrentUser.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,215 @@
+use warnings;
+use strict;
+
+package Jifty::CurrentUser;
+
+use base qw/Jifty::Object Class::Accessor::Fast/;
+
+__PACKAGE__->mk_accessors(qw(is_superuser is_bootstrap_user user_object));
+
+
+=head1 NAME
+
+Jifty::CurrentUser
+
+=head1 DESCRIPTION
+
+Most applications need to have a concept of who the current user
+is. So Jifty supports this concept internally. Every L<Jifty::Object>
+(which most things in Jifty are descended from) except the CurrentUser
+itself is instantiated with a L<Jifty::CurrentUser> subclass as a
+parameter to the creator.
+
+This class describes (and implements a trivial version) of the access
+control API that a Jifty application needs to implement to provide
+user-based access control
+
+It's generally expected that your application will override this class
+if you want any sort of access control.
+
+=cut
+
+=head2 new
+
+Creates a new L<Jifty::CurrentUser> object.  Calls L<_init>, an
+app-specific initialization routine.
+
+=cut
+
+sub new {
+    my $class = shift;
+    my $self  = {};
+    bless $self, $class;
+    my %args = (@_);
+    if ( delete $args{'_bootstrap'} ) { $self->is_bootstrap_user(1); }
+    $self->_init(%args);
+    return $self;
+}
+
+sub _init { 1}
+
+=head2 superuser
+
+A convenience constructor that returns a new CurrentUser object that's
+marked as a superuser.
+
+=cut
+
+sub superuser {
+    my $class = shift;
+    my $self = $class->new();
+    $self->is_superuser(1);
+    return $self;
+}
+
+=head2 user_object 
+
+This gets or sets your application's user object for the current
+user. Generally, you're expected to set and load it in the _init method
+in your L<Jifty::CurrentUser> subclass.
+
+Example:  
+
+	sub _init {
+	    my $self = shift;
+	    my %args = (@_);
+	
+            if (keys %args) {
+	        $self->user_object(Wifty::Model::User->new(current_user => $self));
+	        $self->user_object->load_by_cols(%args);
+	    }
+	    $self->SUPER::_init(%args);
+	}
+	
+
+=cut
+
+=head2 id
+
+Returns C<0> if we don't have a L<user_object>.  When we I<do> have a
+user_object, return that user's id.
+
+=cut
+
+sub id {
+    my $self = shift;
+    if ($self->user_object) {
+        return ($self->user_object->id());
+    } else {
+        return '0';
+    }
+
+}
+
+=head2 current_user
+
+Every class in a Jifty application has a L</current_user> method that
+returns the user who's doing things, in the form of a
+L<Jifty::CurrentUser> object a subclass thereof.  For the somewhat
+obvious reason that you can't actually lift yourself up by tugging on
+your own bootstraps, a L<Jifty::CurrentUser> object return I<itself>
+rather than another C<Jifty::CurrentUser object>
+
+=cut
+
+sub current_user {
+    my $self = shift;
+    return $self;
+}
+
+=head1 AUTHENTICATION AND AUTHORIZATION
+
+To use Jifty's built-in authentication and authorization system, your
+user objects need to implement the following API methods:
+
+=head2 password_is STRING
+
+Your L<user_object> should have a method called C<password_is> which
+returns true if passed a string that matches the user's current
+password.
+
+=cut
+
+sub password_is {
+    my $self = shift;
+    return undef unless ($self->user_object);
+    return($self->user_object->password_is(@_));
+
+}
+
+=head2 username
+
+Return a string which identifies the user in some way.
+
+=cut
+
+sub username {
+    my $self = shift;
+    return undef unless ($self->user_object);
+    return($self->user_object->username(@_));
+}
+
+=head2 auth_token
+
+Return a string which proves that the user is who they claim to be.  A
+simple way to do this, for example, would be to hash the username and
+some server-side secret.
+
+=cut
+
+sub auth_token {
+    my $self = shift;
+    return undef unless ($self->user_object);
+    return ($self->user_object->auth_token);
+
+}
+
+=head1 RIGHTS AND ACCESS CONTROL
+
+In any system that relies on users' rights to perform actions, it's
+sometimes necessary to walk around the access control system. There
+are two primary cases for this:
+
+=cut
+
+=head2 is_superuser 
+
+Sometimes, while the system is running, you need to do something on
+behalf of a user that they shouldn't be able to do themselves. Maybe
+you need to let a new user sign up for your service (You don't want to
+let any user create more users, right?) or to write an entry to a
+changelog. If the user has the C<is_superuser> flag set, things still
+get read from the database, but the user can walk around any and all
+ACL checks. Think "Neo" from the Matrix. The superuser can walk
+through walls, stop bullets and so on.
+
+
+=cut
+
+=head2 is_bootstrap_user
+
+When your system is first getting going, you can't assume
+B<anything>. There probably aren't any rights in the system to
+check. A user with the L</is_bootstrap_user> flag set is a
+self-reliant superuser. Nothing is read from the database, no ACLs are
+checked.  You probably never need to do anything with bootstrap users.
+
+=cut
+
+
+=head2 current_user_can ACTION
+
+For a current user object, the current user can always C<read>, but
+never write or do anything else.
+
+=cut
+
+
+sub current_user_can {
+    my $self = shift;
+    my $action = shift;
+    return (1) if $action eq 'read';
+    return (0);
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/DateTime.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/DateTime.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,98 @@
+use warnings;
+use strict;
+
+package Jifty::DateTime;
+
+=head1 NAME
+
+Jifty::DateTime - a DateTime subclass that knows about Jifty users
+
+
+=head1 DESCRIPTION
+
+Jifty natively stores timestamps in the database in GMT.  Dates are stored
+without timezone. This class loads and parses dates and sets them
+into the proper timezone.
+
+=cut
+
+use base qw(Jifty::Object DateTime);
+
+
+=head2 new ARGS
+
+See L<DateTime/new>.  After calling that method, set this object's
+timezone to the current user's time zone, if the current user has a
+method called C<time_zone>.
+
+=cut
+
+sub new {
+    my $class = shift;
+    my %args  = (@_);
+    my $self  = $class->SUPER::new(%args);
+
+    # Unless the user has explicitly said they want a floating time,
+    # we want to convert to the end-user's timezone.  This is
+    # complicated by the fact that DateTime auto-appends
+    if (!$args{time_zone} and my $tz = $self->current_user_has_timezone) {
+        $self->set_time_zone("UTC");
+        $self->set_time_zone( $tz );
+    }
+    return $self;
+}
+
+=head2 current_user_has_timezone
+
+Return timezone if the current user has it
+
+=cut
+
+sub current_user_has_timezone {
+    my $self = shift;
+    $self->_get_current_user();
+    my $user_obj = $self->current_user->user_object or return;
+    my $f = $user_obj->can('time_zone') or return;
+    return $f->($user_obj);
+}
+
+=head2 new_from_string STRING
+
+Take some user defined string like "tomorrow" and turn it into a
+C<Jifty::Datetime> object.  If the string appears to be a _date_, keep
+it in the floating timezone, otherwise, set it to the current user's
+timezone.
+
+=cut
+
+sub new_from_string {
+    my $class  = shift;
+    my $string = shift;
+    my $now;
+
+    {
+        # Date::Manip interprets days of the week (eg, ''monday'') as
+        # days within the *current* week. Detect these and prepend
+        # ``next''
+        # XXX TODO: Find a real solution (better date-parsing library?)
+        if($string =~ /^\s* (?:monday|tuesday|wednesday|thursday|friday|saturday|sunday)$/xi) {
+            $string = "next $string";
+        }
+        
+        # Why are we parsing this as GMT? This feels really wrong.  It will get the wrong answer
+        # if the current user is in another tz.
+        require Date::Manip;
+        Date::Manip::Date_Init("TZ=GMT");
+        $now = Date::Manip::UnixDate( $string, "%o" );
+    }
+    return undef unless $now;
+    my $self = $class->from_epoch( epoch => $now, time_zone => 'gmt' );
+    if (my $tz = $self->current_user_has_timezone) {
+        $self->set_time_zone("floating")
+            unless ( $self->hour or $self->minute or $self->second );
+        $self->set_time_zone( $tz );
+    }
+    return $self;
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Dispatcher.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Dispatcher.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,1309 @@
+package Jifty::Dispatcher;
+use strict;
+use warnings;
+use Exporter;
+use Jifty::YAML;
+use base qw/Exporter Jifty::Object/;
+           
+
+=head1 NAME
+
+Jifty::Dispatcher - The Jifty Dispatcher
+
+=head1 SYNOPSIS
+
+In B<MyApp::Dispatcher>:
+
+    package MyApp::Dispatcher;
+    use Jifty::Dispatcher -base;
+
+    under ['blog', 'wiki'] => [
+        run {
+            default model => "MyApp::Model::\u$1"
+        },
+        on PUT 'entries/*' => run {
+            set entry_id => $1;
+            show '/display/entry';
+        },
+        on '*/*' => run {
+            my ($page, $op) = ($1, $2);
+            my $item = get('model')->load($page) or next_rule;
+
+            set item => $item;
+            set page => $page;
+            set op   => $op;
+
+            show "/display/$op";
+        },
+        on '*' => run { dispatch "$1/view" },
+        on ''  => show '/display/list',
+    ];
+    under qr{logs/(\d+)} => [
+        when { $1 > 100 } => show '/error',
+        set model => 'MyApp::Model::Log',
+        run { dispatch "/wiki/LogPage-$1" },
+    ];
+    # ... more rules ...
+
+=head1 DESCRIPTION
+
+C<Jifty::Dispatcher> takes requests for pages, walks through a
+dispatch table, possibly running code or transforming the request
+before finally handing off control to the templating system to display
+the page the user requested or whatever else the system has decided to
+display instead.
+
+Generally, this is B<not> the place to be performing model and user
+specific access control checks or updating your database based on what
+the user has sent in. You want to do that in your model
+classes. (Well, I<we> want you to do that, but you're free to ignore
+our advice).
+
+The Dispatcher runs rules in several stages:
+
+=over
+
+=item before
+
+B<before> rules are run before Jifty evaluates actions. They're the
+perfect place to enable or disable L<Jifty::Action>s using
+L<Jifty::API/allow> and L<Jifty::API/deny> or to completely disallow
+user access to private I<component> templates such as the F<_elements>
+directory in a default Jifty application.  They're also the right way
+to enable L<Jifty::LetMe> actions.
+
+You can entirely stop processing with the C<redirect>, C<tangent> and
+C<abort> directives, though L</after> rules will still run.
+
+=item on
+
+L<on> rules are run after Jifty evaluates actions, so they have full
+access to the results actions users have performed. They're the right
+place to set up view-specific objects or load up values for your
+templates.
+
+Dispatcher directives are evaluated in order until we get to either a
+C<show>, C<redirect>, C<tangent> or C<abort>.
+
+=item after
+
+L<after> rules let you clean up after rendering your page. Delete your
+cache files, write your transaction logs, whatever.
+
+At this point, it's too late to C<show>, C<redirect>, C<tangent> or C<abort>
+page display.
+
+=back
+
+C<Jifty::Dispatcher> is intended to replace all the F<autohandler>,
+F<dhandler> and C<index.html> boilerplate code commonly found in Mason
+applications, but there's nothing stopping you from using those
+features in your application when they're more convenient.
+
+Each directive's code block runs in its own scope, but all share a
+common C<$Dispatcher> object.
+
+=cut
+
+=head1 Plugins and rule ordering
+
+By default, L<Jifty::Plugin> dispatcher rules are added in the order
+they are specified in the application's configuration file; that is,
+after all the plugin dispatchers have run in order, then the
+application's dispatcher runs.  It is possible to specify rules which
+should be reordered with respect to this rule, however.  This is done
+by using a variant on the C<before> and C<after> syntax:
+
+    before plugin NAME =>
+        RULE(S);
+    
+    after plugin NAME =>
+        RULE(S);
+
+C<NAME> may either be a string, which must match the plugin name
+exactly, or a regular expression, which is matched against the plugin
+name.  The rule will be placed at the first boundary that it matches --
+that is, given a C<before plugin qr/^Jifty::Plugin::Auth::/> and both
+a C<Jifty::Plugin::Auth::Basic> and a C<Jifty::Plugin::Auth::Complex>,
+the rules will be placed before the first.
+
+C<RULES> may either be a single C<before>, C<on>, C<under>, or
+C<after> rule to change the ordering of, or an array reference of
+rules to reorder.
+
+=cut
+
+=head1 Data your dispatch routines has access to
+
+=head2 request
+
+The current L<Jifty::Request> object.
+
+=head2 $Dispatcher
+
+The current dispatcher object.
+
+=head2 get $arg
+
+Return the argument value. 
+
+=head1 Things your dispatch routine might do
+
+=head2 under $match => $rule
+
+Match against the current requested path.  If matched, set the current
+context to the directory and process the rule.
+
+The C<$rule> may be an array reference of more rules, a code reference, a
+method name of your dispatcher class, or a fully qualified subroutine name.
+
+All wildcards in the C<$match> string becomes capturing regex patterns.  You
+can also pass in an array reference of matches, or a regex pattern.
+
+The C<$match> string may be qualified with a HTTP method name, such as
+
+=over
+
+=item GET
+
+=item POST
+
+=item PUT
+
+=item OPTIONS
+
+=item DELETE
+
+=item HEAD
+
+=back
+
+=head2 on $match => $rule
+
+Like C<under>, except it has to match the whole path instead of just the prefix.
+Does not set current directory context for its rules.
+
+=head2 before $match => $rule
+
+Just like C<on>, except it runs I<before> actions are evaluated.
+
+=head2 after $match => $rule
+
+Just like C<on>, except it runs I<after> the page is rendered.
+
+
+=head2 when {...} => $rule
+
+Like C<under>, except using an user-supplied test condition.  You can stick 
+any Perl you want inside the {...}; it's just an anonymous subroutine.
+
+=head2 run {...}
+
+Run a block of code unconditionally; all rules are allowed inside a C<run>
+block, as well as user code.  You can think of the {...} as an anonymous 
+subroutine.
+
+=head2 set $arg => $val
+
+Adds an argument to what we're passing to our template, overriding 
+any value the user sent or we've already set.
+
+=head2 default $arg => $val
+
+Adds an argument to what we're passing to our template,
+but only if it is not defined currently.
+
+=head2 del $arg
+
+Deletes an argument we were passing to our template.
+
+=head2 show $component
+
+Display the presentation component.  If not specified, use the
+request path as the default page.
+
+=head2 dispatch $path
+
+Dispatch again using $path as the request path, preserving args.
+
+=head2 next_rule
+
+Break out from the current C<run> block and go on the next rule.
+
+=head2 last_rule
+
+Break out from the current C<run> block and stop running rules in this stage.
+
+=head2 abort $code
+
+Abort the request; this skips straight to the cleanup stage.
+
+If C<$code> is specified, it's used as the HTTP status code.
+
+=head2 redirect $uri
+
+Redirect to another URI.
+
+=head2 tangent $uri
+
+Take a continuation here, and tangent to another URI.
+
+=head2 plugin
+
+See L</Plugins and rule ordering>, above.
+
+=cut
+
+our @EXPORT = qw<
+    under run when set del default
+
+    before on after
+
+    show dispatch abort redirect tangent
+
+    GET POST PUT HEAD DELETE OPTIONS
+
+    plugin
+
+    get next_rule last_rule
+
+    already_run
+
+    $Dispatcher
+>;
+
+our $Dispatcher;
+
+sub request       { Jifty->web->request }
+sub _ret (@);
+sub under ($$@)   { _ret @_ }    # partial match at beginning of path component
+sub before ($$@)  { _ret @_ }    # exact match on the path component
+sub on ($$@)      { _ret @_ }    # exact match on the path component
+sub after ($$@)   { _ret @_ }    # exact match on the path component
+sub when (&@)     { _ret @_ }    # exact match on the path component
+sub run (&@)      { _ret @_ }    # execute a block of code
+sub show (;$@)    { _ret @_ }    # render a page
+sub dispatch ($@) { _ret @_ }    # run dispatch again with another URI
+sub redirect ($@) { _ret @_ }    # web redirect
+sub tangent ($@)  { _ret @_ }    # web tangent
+sub abort (;$@)   { _ret @_ }    # abort request
+sub default ($$@) { _ret @_ }    # set parameter if it's not yet set
+sub set ($$@)     { _ret @_ }    # set parameter
+sub del ($@)      { _ret @_ }    # remove parameter
+sub get ($) { request->argument( $_[0] ) }
+
+sub _qualify ($@);
+sub GET ($)     { _qualify method => @_ }
+sub POST ($)    { _qualify method => @_ }
+sub PUT ($)     { _qualify method => @_ }
+sub HEAD ($)    { _qualify method => @_ }
+sub DELETE ($)  { _qualify method => @_ }
+sub OPTIONS ($) { _qualify method => @_ }
+
+sub plugin ($) { return { plugin => @_ } }
+
+our $CURRENT_STAGE;
+
+=head2 import
+
+Jifty::Dispatcher is an L<Exporter>, that is, part of its role is to
+blast a bunch of symbols into another package. In this case, that other
+package is the dispatcher for your application.
+
+You never call import directly. Just:
+
+    use Jifty::Dispatcher -base;
+
+in C<MyApp::Dispatcher>
+
+=cut
+
+sub import {
+    my $class = shift;
+    my $pkg   = caller;
+    my @args  = grep { !/^-[Bb]ase/ } @_;
+
+    no strict 'refs';
+    no warnings 'once';
+    for (qw(RULES_RUN RULES_SETUP RULES_CLEANUP RULES_DEFERRED)) {
+        @{ $pkg . '::' . $_ } = ();
+    }
+    if ( @args != @_ ) {
+
+        # User said "-base", let's push ourselves into their @ISA.
+        push @{ $pkg . '::ISA' }, $class;
+    }
+
+    $class->export_to_level( 1, @args );
+}
+
+###################################################
+# Magically figure out the arity based on caller info.
+sub _ret (@) {
+    my $pkg   = caller(1);
+    my $sub   = ( caller(1) )[3];
+    my $proto = prototype($sub);
+    my $op    = $sub;
+
+    $proto =~ tr/@;//d;
+    if ( my $idx = rindex( $op, '::' ) ) {
+        $op = substr( $op, $idx + 2 );
+    }
+
+    if ($Dispatcher) {
+
+        # We are under an operation -- carry the rule forward
+        foreach my $rule ( [ $op => splice( @_, 0, length($proto) ) ], @_ ) {
+            $Dispatcher->_handle_rule($rule);
+        }
+    } elsif (wantarray) {
+        ( [ $op => splice( @_, 0, length($proto) ) ], @_ );
+    } elsif ( defined wantarray ) {
+        [ [ $op => splice( @_, 0, length($proto) ) ], @_ ];
+    } else {
+        _push_rule($pkg, [ $op => splice( @_, 0, length($proto) ) ] );
+    }
+}
+
+sub _push_rule($$) {
+    my($pkg, $rule) = @_;
+    my $op = $rule->[0];
+    my $ruleset;
+    if ( ($op eq "before" or $op eq "after") and ref $rule->[1] and ref $rule->[1] eq 'HASH' and $rule->[1]{plugin} ) {
+        $ruleset = 'RULES_DEFERRED';
+    } elsif ( $op eq 'before' ) {
+        $ruleset = 'RULES_SETUP';
+    } elsif ( $op eq 'after' ) {
+        $ruleset = 'RULES_CLEANUP';
+    } else {
+        $ruleset = 'RULES_RUN';
+    }
+    no strict 'refs';
+    # XXX TODO, need to spec stage here.
+    push @{ $pkg . '::' . $ruleset }, $rule;
+}
+
+sub _qualify ($@) {
+    my $key = shift;
+    my $op  = ( caller(1) )[3];
+    $op =~ s/.*:://;
+    return { $key => $op, '' => $_[0] };
+}
+
+=head2 rules STAGE
+
+Returns an array of all the rules for the stage STAGE.
+
+Valid values for STAGE are
+
+=over
+
+=item SETUP
+
+=item RUN
+
+=item CLEANUP
+
+=back
+
+=cut
+
+sub rules {
+    my $self  = shift;
+    my $stage = shift;
+    my $pkg   = ref($self) || $self;
+    no strict 'refs';
+    @{ $pkg . '::RULES_' . $stage };
+}
+
+=head2 new
+
+Creates a new Jifty::Dispatcher object. You probably don't ever want
+to do this. (Jifty.pm does it for you)
+
+=cut
+
+sub new {
+    my $self = shift;
+    return $self if ref($self);
+
+    bless(
+        {   cwd  => '',
+            path => '',
+            rule => undef,
+            @_,
+        } => $self
+    );
+}
+
+=head2 handle_request
+
+Actually do what your dispatcher does. For now, the right thing
+to do is to put the following two lines first:
+
+    require MyApp::Dispatcher;
+    MyApp::Dispatcher->handle_request;
+
+
+=cut
+
+sub handle_request {
+    my $self = shift;
+
+    local $Dispatcher = $self->new();
+
+    # XXX TODO: refactor this out somehow?
+    # We don't want the previous mason request hanging aroudn once we start dispatching
+    no warnings 'once';
+    local $HTML::Mason::Commands::m = undef;
+    # Mason introduces a DIE handler that generates a mason exception
+    # which in turn generates a backtrace. That's fine when you only
+    # do it once per request. But it's really, really painful when you
+    # do it often, as is the case with fragments
+    local $SIG{__DIE__} = 'DEFAULT';
+
+    eval {
+        $Dispatcher->_do_dispatch( Jifty->web->request->path);
+    };
+    if ( my $err = $@ ) {
+        $self->log->warn(ref($err) . " " ."'$err'") if ( $err !~ /^ABORT/ );
+    }
+}
+
+=head2 _handle_stage NAME, EXTRA_RULES
+
+Handles the all rules in the stage named C<NAME>.  Additionally, any
+other arguments passed after the stage C<NAME> are added to the end of
+the rules for that stage.
+
+This is the unit which calling L</last_rule> skips to the end of.
+
+=cut
+
+sub _handle_stage {
+    my ($self, $stage, @rules) = @_;
+
+    # Set the current stage so that rules can make smarter choices;
+    local $CURRENT_STAGE = $stage;
+
+    eval { $self->_handle_rules( [ $self->rules($stage), @rules ] ); };
+    if ( my $err = $@ ) {
+        $self->log->warn( ref($err) . " " . "'$err'" )
+            if ( $err !~ /^(LAST RULE|ABORT)/ );
+        return $err =~ /^ABORT/ ? 0 : 1;
+    }
+    return 1;
+}
+
+=head2 _handle_rules RULESET
+
+When handed an arrayref or array of rules (RULESET), walks through the 
+rules in order, executing as it goes.
+
+
+=cut
+
+sub _handle_rules ($) {
+    my ( $self, $rules ) = @_;
+
+    my @rules;
+    {
+        local $@;
+        eval { @rules = @$rules };
+        @rules = $rules if $@;
+    }
+RULE: foreach my $rule (@rules) {
+        $self->_handle_rule($rule);
+    }
+}
+
+=head2 _handle_rule RULE
+
+When handed a single rule in the form of a coderef, C<_handle_rule>, 
+calls C<_do_run> on that rule and returns the result. When handed a 
+rule that turns out to be an array of subrules, recursively calls
+itself and evaluates the subrules in order.
+
+=cut
+
+sub _handle_rule {
+    my ( $self, $rule ) = @_;
+    my ( $op,   @args );
+
+    # Handle the case where $rule is an array reference.
+    if (ref($rule) eq 'ARRAY') {
+        ( $op, @args ) = @$rule;
+    } else {
+        ( $op, @args ) = ( run => $rule );
+    }
+
+    # Handle the case where $op is an array.
+    my $sub_rules;
+    if (ref($op) eq 'ARRAY' ) {
+         $sub_rules = [ @$op, @args ];
+    }
+
+    if ($sub_rules) {
+        for my $sub_rule (@$sub_rules) {
+            $self->_handle_rule($sub_rule);
+        }
+    }
+
+    # Now we know op is a scalar.
+    local $self->{rule} = $op;
+    my $meth = "_do_$op";
+    $self->$meth(@args);
+
+}
+
+no warnings 'exiting';
+
+sub next_rule { next RULE }
+sub last_rule { die "LAST RULE" }
+
+=head2 _do_under
+
+This method is called by the dispatcher internally. You shouldn't need to.
+
+=cut
+
+sub _do_under {
+    my ( $self, $cond, $rules ) = @_;
+    if ( my $regex = $self->_match($cond) ) {
+
+        # match again to establish $1 $2 etc in the dynamic scope
+        $self->{path} =~ $regex;
+
+        # enter the matched directory
+        local $self->{cwd} = substr( $self->{path}, 0, $+[0] );
+        chop $self->{cwd} if substr( $self->{cwd}, -1 ) eq '/';
+
+        $self->_handle_rules($rules);
+    }
+}
+
+=head2 _do_when
+
+This method is called by the dispatcher internally. You shouldn't need to.
+
+=cut
+
+sub _do_when {
+    my ( $self, $code, $rules ) = @_;
+    if ( $code->() ) {
+        $self->_handle_rules($rules);
+    }
+}
+
+=head2 _do_before
+
+This method is called by the dispatcher internally. You shouldn't need to.
+
+=cut
+
+sub _do_before {
+    my ( $self, $cond, $rules ) = @_;
+    if ( my $regex = $self->_match($cond) ) {
+
+        $self->log->debug("Matched 'before' rule $regex for ".$self->{'path'});
+        # match again to establish $1 $2 etc in the dynamic scope
+        $self->{path} =~ $regex;
+        $self->_handle_rules($rules);
+    }
+
+}
+
+=head2 _do_on
+
+This method is called by the dispatcher internally. You shouldn't need to.
+
+=cut
+
+sub _do_on {
+    my ( $self, $cond, $rules ) = @_;
+    if ( my $regex = $self->_match($cond) ) {
+
+        $self->log->debug("Matched 'on' rule $regex for ".$self->{'path'});
+        # match again to establish $1 $2 etc in the dynamic scope
+        $self->{path} =~ $regex;
+        $self->_handle_rules($rules);
+    }
+}
+
+=head2 _do_after
+
+This method is called by the dispatcher internally. You shouldn't need to.
+
+=cut
+
+sub _do_after {
+    my ( $self, $cond, $rules ) = @_;
+    if ( my $regex = $self->_match($cond) ) {
+        $self->log->debug("Matched 'after' rule $regex for ".$self->{'path'});
+        # match again to establish $1 $2 etc in the dynamic scope
+        $self->{path} =~ $regex;
+        $self->_handle_rules($rules);
+    }
+}
+
+=head2 already_run
+
+Returns true if the code block has run once already in this request.
+This can be useful for 'after' rules to ensure that they only run
+once, even if there is a sub-dispatch which would cause it to run more
+than once.  The idiom is:
+
+    after '/some/path/*' => run {
+        return if already_run;
+        # ...
+    };
+
+=cut
+
+sub already_run {
+    my $id = $Dispatcher->{call_rule};
+    return 1 if get "__seen_$id";
+    set "__seen_$id" => 1;
+    return 0;
+}
+
+sub _do_run {
+    my ( $self, $code ) = @_;
+
+    # Keep track of the coderef being run, so we can know about
+    # already_run
+    local $self->{call_rule} = $code;
+
+    # establish void context and make a call
+    ( $self->can($code) || $code )->();
+
+    # XXX maybe call with all the $1..$x as @_ too? or is it too gonzo?
+    # $code->(map { substr($PATH, $-[$_], ($+[$_]-$-[$_])) } 1..$#-));
+
+    return;
+}
+
+=head2 _do_redirect PATH
+
+This method is called by the dispatcher internally. You shouldn't need to.
+
+Redirect the user to the URL provided in the mandatory PATH argument.
+
+=cut
+
+sub _do_redirect {
+    my ( $self, $path ) = @_;
+    $self->log->debug("Redirecting to $path");
+    Jifty->web->redirect($path);
+}
+
+=head2 _do_tangent PATH
+
+This method is called by the dispatcher internally. You shouldn't need to.
+
+Take a tangent to the URL provided in the mandatory PATH argument.
+(See L<Jifty::Manual::Continuation> for more about tangents.)
+
+=cut
+
+sub _do_tangent {
+    my ( $self, $path ) = @_;
+    $self->log->debug("Taking a tangent to $path");
+    Jifty->web->tangent(url => $path);
+}
+
+=head2 _do_abort 
+
+This method is called by the dispatcher internally. You shouldn't need to.
+
+Don't display any page. just stop.
+
+=cut
+
+sub _do_abort {
+    my $self = shift;
+    $self->log->debug("Aborting processing");
+    if (@_) {
+        # This is the status code
+        my $status = shift;
+        my $apache = Jifty->handler->apache;
+        $apache->header_out(Status => $status);
+        $apache->send_http_header;
+
+        require HTTP::Status;
+        print STDOUT $status, ' ' , HTTP::Status::status_message($status);
+    }
+    $self->_abort;
+}
+
+sub _abort { die "ABORT" }
+
+=head2 _do_show [PATH]
+
+This method is called by the dispatcher internally. You shouldn't need to.
+
+Render a template. If the scalar argument "PATH" is given, render that component.
+Otherwise, just render whatever we were going to anyway.
+
+=cut
+
+sub _do_show {
+    my $self = shift;
+    my $path;
+
+    # Fix up the path
+    $path = shift if (@_);
+    $path ||= $self->{path};
+
+    unless ($CURRENT_STAGE eq 'RUN') {
+        die "You can't call a 'show' rule in a 'before' or 'after' block in the dispatcher.  Not showing path $path";
+    }
+
+    $self->log->debug("Showing path $path");
+
+    # If we've got a working directory (from an "under" rule) and we have
+    # a relative path, prepend the working directory
+    $path = "$self->{cwd}/$path" unless $path =~ m{^/};
+
+    # When we're requesting a directory, go looking for the index.html
+    if ( $self->template_exists( $path . "/index.html" ) ) {
+        $path .= "/index.html";
+    }
+
+    my $abs_template_path = Jifty::Util->absolute_path(
+        Jifty->config->framework('Web')->{'TemplateRoot'} . $path );
+    my $abs_root_path = Jifty::Util->absolute_path(
+        Jifty->config->framework('Web')->{'TemplateRoot'} );
+
+    if ( $abs_template_path !~ /^\Q$abs_root_path\E/ ) {
+        request->path('/__jifty/errors/500');
+    } else {
+        # Set the request path
+        request->path($path);
+    }
+    $self->render_template( request->path );
+
+    last_rule;
+}
+
+sub _do_set {
+    my ( $self, $key, $value ) = @_;
+    $self->log->debug("Setting argument $key to ".($value||''));
+    request->argument($key, $value);
+}
+
+sub _do_del {
+    my ( $self, $key ) = @_;
+    $self->log->debug("Deleting argument $key");
+    request->delete($key);
+}
+
+sub _do_default {
+    my ( $self, $key, $value ) = @_;
+    $self->log->debug("Setting argument default $key to ".($value||''));
+    request->argument($key, $value)
+        unless defined request->argument($key);
+}
+
+=head2 _do_dispatch [PATH]
+
+First, this routine runs all the C<before> dispatcher rules, then it runs
+Jifty->web->handle_request(), then it runs all the main C<on> rules,
+evaluating each one in turn.  If it gets through all the rules without
+running an C<abort>, C<redirect> or C<show> directive, it C<shows>
+the template originally requested.
+
+Once it's done with that, it runs all the cleanup rules defined with C<after>.
+
+=cut
+
+sub _do_dispatch {
+    my $self = shift;
+
+    # Requests should always start with a leading /
+    $self->{path} = "/".shift;
+    $self->{cwd}  = '';
+
+    # Normalize the path.
+    $self->{path} =~ s{/+}{/}g;
+
+    $self->log->debug("Dispatching request to ".$self->{path});
+
+    # Setup -- we we don't abort out of setup, then run the
+    # actions and then the RUN stage.
+    if ($self->_handle_stage('SETUP')) {
+        # Run actions
+        Jifty->web->handle_request();
+
+        # Run, and show the page
+        $self->_handle_stage('RUN' => 'show');
+    }
+
+    # Cleanup
+    $self->_handle_stage('CLEANUP');
+
+    # Out to the next dispatcher's cleanup
+    $self->_abort;
+}
+
+=head2 _match CONDITION
+
+Returns the regular expression matched if the current request fits
+the condition defined by CONDITION. 
+
+C<CONDITION> can be a regular expression, a "simple string" with shell
+wildcard characters (C<*>, C<?>, C<#>, C<[]>, C<{}>) to match against,
+or an arrayref or hashref of those. It should even be nestable.
+
+Arrayref conditions represents alternatives: the match succeeds as soon
+as the first match is found.
+
+Hashref conditions are conjunctions: each non-empty hash key triggers a
+separate C<_match_$keyname> call on the dispatcher object. For example, a
+C<method> key would call C<_match_method> with its value to be matched against.
+After each subcondition is tried (in lexographical order) and succeeded,
+the value associated with the C<''> key is matched again as the condition.
+
+=cut
+
+sub _match {
+    my ( $self, $cond ) = @_;
+
+    # Handle the case where $cond is an array.
+    if ( ref($cond) eq 'ARRAY' ) {
+        local $@;
+        my $rv = eval {
+            for my $sub_cond (@$cond)
+            {
+                return ( $self->_match($sub_cond) or next );
+            }
+        };
+        if ( my $err = $@ ) {
+            warn "$self _match failed: $err";
+        } else {
+            return $rv;
+        }
+    }
+
+    # Handle the case where $cond is a hash.
+    elsif ( ref($cond) eq 'HASH' ) {
+        local $@;
+        my $rv = eval {
+            for my $key ( sort keys %$cond )
+            {
+                next if $key eq '';
+                my $meth = "_match_$key";
+                $self->$meth( $cond->{$key} ) or return;
+            }
+
+            # All precondition passed, get original condition literal
+            return $self->_match( $cond->{''} ) if $cond->{''};
+
+            # Or, if we don't have a literal, we win.
+            return 1;
+        };
+        if ( my $err = $@ ) {
+            warn "$self _match failed: $err";
+        } else {
+            return $rv;
+        }
+    }
+
+    # Now we know $cond is a scalar, match against it.
+    else {
+        my $regex = $self->_compile_condition($cond) or return;
+        $self->{path} =~ $regex or return;
+        return $regex;
+    }
+}
+
+=head2 _match_method METHOD
+
+Takes an HTTP method. Returns true if the current request
+came in with that method.
+
+=cut
+
+sub _match_method {
+    my ( $self, $method ) = @_;
+    $self->log->debug("Matching URL $ENV{REQUEST_METHOD} against ".$method);
+    lc( $ENV{REQUEST_METHOD} ) eq lc($method);
+}
+
+sub _match_plugin {
+    my ( $self, $plugin ) = @_;
+    warn "Deferred check shouldn't happen";
+    return 0;
+}
+
+=head2 _compile_condition CONDITION
+
+Takes a condition defined as a simple string and return it as a regex
+condition.
+
+=cut
+
+sub _compile_condition {
+    my ( $self, $cond ) = @_;
+
+    # Previously compiled (eg. a qr{} -- return it verbatim)
+    return $cond if ref $cond;
+
+    # Escape and normalize
+    $cond = quotemeta($cond);
+    $cond =~ s{(?:\\\/)+}{/}g;
+    $cond =~ s{/$}{};
+
+    my $has_capture = ( $cond =~ / \\ [*?#] /x);
+    if ($has_capture or $cond =~ / \\ [[{] /x) {
+        $cond = $self->_compile_glob($cond);
+    }
+
+    if ( $cond =~ m{^/} ) {
+
+        # '/foo' => qr{^/foo}
+        $cond = "\\A$cond";
+    } elsif ( length($cond) ) {
+
+        # 'foo' => qr{^$cwd/foo}
+        $cond = "(?<=\\A$self->{cwd}/)$cond";
+    } else {
+
+        # empty path -- just match $cwd itself
+        $cond = "(?<=\\A$self->{cwd})";
+    }
+
+    if ( $Dispatcher->{rule} eq 'on' ) {
+
+        # "on" anchors on complete match only
+        $cond .= '/?\\z';
+    } else {
+
+        # "in" anchors on prefix match in directory boundary
+        $cond .= '(?=/|\\z)';
+    }
+
+    # Make all metachars into capturing submatches
+    if (!$has_capture) {
+        $cond = "($cond)";
+    }
+
+    return qr{$cond};
+}
+
+=head2 _compile_glob METAEXPRESSION
+
+Private function.
+
+Turns a metaexpression containing C<*>, C<?> and C<#> into a capturing regex pattern.
+
+Also supports the non-capturing C<[]>,and C<{}> notation.
+
+The rules are:
+
+=over 4
+
+=item *
+
+A C<*> between two C</> characters, or between a C</> and end of string,
+should match one or more non-slash characters:
+
+    /foo/*/bar
+    /foo/*/
+    /foo/*
+    /*
+
+=item *
+
+All other C<*> can match zero or more non-slash characters: 
+
+    /*bar
+    /foo*bar
+    *
+
+=item *
+
+Two stars (C<**>) can match zero or more characters, including slash:
+
+    /**/bar
+    /foo/**
+    **
+
+=item *
+
+Consecutive C<?> marks are captured together:
+
+    /foo???bar      # One capture for ???
+    /foo??*         # Two captures, one for ?? and one for *
+
+=item *
+
+The C<#> character captures one or more digit characters.
+
+=item *
+
+Brackets such as C<[a-z]> denote character classes; they are not captured.
+
+=item *
+
+Braces such as C<{xxx,yyy}]> denote alternations; they are not captured.
+
+=back
+
+=cut
+
+sub _compile_glob {
+    my ( $self, $glob ) = @_;
+    $glob =~ s{
+        # Stars between two slashes, or between a slash and end-of-string,
+        # should at match one or more non-slash characters.
+        (?<= /)      # lookbehind for slash
+        \\ \*        # star
+        (?= / | \z)  # lookahead for slash or end-of-string
+    }{([^/]+)}gx;
+    $glob =~ s{
+        # Two stars can match zero or more characters, including slash.
+        \\ \* \\ \*
+    }{(.*)}gx;
+    $glob =~ s{
+        # All other stars can match zero or more non-slash character.
+        \\ \*
+    }{([^/]*)}gx;
+    $glob =~ s{
+        # The number-sign character matches one or more digits.
+        \\ \#
+    }{(\\d+)}gx;
+    $glob =~ s{
+        # Consecutive question marks are captured as one unit;
+        # we do this by capturing them and then repeat the result pattern
+        # for that many times.  The divide-by-two takes care of the
+        # extra backslashes.
+        ( (?: \\ \? )+ )
+    }{([^/]{${ \( length($1)/2 ) }})}gx;
+    $glob =~ s{
+        # Brackets denote character classes
+        (
+            \\ \[           # opening
+            (?:             # one or more characters:
+                \\ \\ \\ \] # ...escaped closing bracket
+            |
+                \\ [^\]]    # ...escaped (but not the closing bracket)
+            |
+                [^\\]       # ...normal
+            )+
+            \\ \]           # closing
+        )
+    }{$self->_unescape($1)}egx;
+    $glob =~ s{
+        # Braces denote alternations
+        \\ \{ (         # opening (not part of expression)
+            (?:             # zero or more characters:
+                \\ \\ \\ \} # ...escaped closing brace
+            |
+                \\ [^\}]    # ...escaped (but not the closing brace)
+            |
+                [^\\]       # ...normal
+            )+
+        ) \\ \}         # closing (not part of expression)
+    }{'(?:'.join('|', split(/\\,/, $1, -1)).')'}egx;
+    $glob;
+}
+
+sub _unescape {
+    my $self = shift;
+    my $text = shift;
+    $text =~ s{\\(.)}{$1}g;
+    return $text;
+}
+
+=head2 template_exists PATH
+
+Returns true if PATH is a valid template inside your template root.
+
+=cut
+
+sub template_exists {
+    my $self = shift;
+    my $template = shift;
+
+    return  Jifty->handler->mason->interp->comp_exists( $template);
+}
+
+
+=head2 render_template PATH
+
+Use our templating system to render a template. If there's an error, do the right thing.
+
+
+=cut
+
+sub render_template {
+    my $self = shift;
+    my $template = shift;
+
+    $self->log->debug( "Handling template " . $template );
+    eval { Jifty->handler->mason->handle_comp( $template ); };
+    my $err = $@;
+
+    # Handle parse errors
+    if ( $err and not eval { $err->isa('HTML::Mason::Exception::Abort') } ) {
+
+        # Save the request away, and redirect to an error page
+        Jifty->web->response->error($err);
+        my $c = Jifty::Continuation->new(
+            request  => Jifty->web->request->top_request,
+            response => Jifty->web->response,
+            parent   => Jifty->web->request->continuation,
+        );
+
+        warn "$err";
+
+        # Redirect with a continuation
+        Jifty->web->_redirect(
+            "/__jifty/error/mason_internal_error?J:C=" . $c->id );
+    }
+    elsif ($err) {
+        die $err;
+    }
+
+}
+
+
+=head2 import_plugins
+
+Imports rules from L<Jifty/plugins> into the main dispatcher's space.
+
+=cut
+
+sub import_plugins {
+    my $self = shift;
+
+    # Find the deferred rules
+    my @deferred;
+    push @deferred, $_->dispatcher->rules('DEFERRED') for Jifty->plugins;
+    push @deferred, $self->rules('DEFERRED');
+
+    # XXX TODO: Examine @deferred and find rles that cannot fire
+    # because they match no plugins; they should become un-deferred in
+    # the appropriate group.  This is so 'before plugin qr/Auth/' runs
+    # even if we have no auth plugin
+
+    for my $stage (qw/SETUP RUN CLEANUP/) {
+        my @groups;
+        push @groups, {name => ref $_,  rules => [$_->dispatcher->rules($stage)]} for Jifty->plugins;
+        push @groups, {name => 'Jifty', rules => [$self->rules($stage)]};
+
+        my @left;
+        my @rules;
+        for (@groups) {
+            my $name        = $_->{name};
+            my @group_rules = @{$_->{rules}};
+
+            # XXX TODO: 'after' rules should possibly be placed after
+            # the *last* thing they could match
+            push @rules, $self->_match_deferred(\@deferred, before => $name, $stage);
+            push @rules, @group_rules;
+            push @rules, $self->_match_deferred(\@deferred, after => $name, $stage);
+        }
+
+        no strict 'refs';
+        @{ $self . "::RULES_$stage" } = @rules;
+    }
+    if (@deferred) {
+        warn "Leftover unmatched deferred rules: ".Jifty::YAML::Dump(\@deferred);
+    }
+}
+
+sub _match_deferred {
+    my $self = shift;
+    my ($deferred, $time, $name, $stage) = @_;
+    my %stages = (SETUP => "before", RUN => "on", CLEANUP => "after");
+    $stage = $stages{$stage};
+
+    my @matches;
+    for my $op (@{$deferred}) {
+        # Only care if we're on the correct side of the correct plugin
+        next unless $op->[0] eq $time;
+
+        # Regex or string match, appropriately
+        next unless (
+            ref $op->[1]{plugin}
+            ? ( $name =~ $op->[1]{plugin} )
+            : ( $op->[1]{plugin} eq $name ) );
+
+        # Find the list of subrules
+        my @subrules = ref $op->[2] eq "ARRAY" ? @{$op->[2]} : ($op->[2]);
+
+        # Only toplevel rules make sense (before, after, on)
+        warn "Invalid subrule ".$_->[0] for grep {$_->[0] !~ /^(before|on|after)$/} @subrules;
+        @subrules = grep {$_->[0] =~ /^(before|on|after)$/} @subrules;
+
+        # Only match if the stage matches
+        push @matches, grep {$_->[0] eq $stage} @subrules;
+        @subrules = grep {$_->[0] ne $stage} @subrules;
+
+        $op->[2] = [@subrules];
+    }
+
+    # Clean out any completely matched rules
+    @$deferred = grep {@{$_->[2]}} @$deferred;
+
+    return @matches;
+}
+
+=head2 dump_rules
+
+Dump all defined rules in debug log. It will be called by Jifty on startup.
+
+=cut
+
+sub dump_rules {
+    my $self = shift;
+
+    no strict 'refs';
+    foreach my $stage ( qw/SETUP RUN CLEANUP/ ) {
+
+        my $log = '';
+        foreach my $r ( @{ $self . '::RULES_' . $stage } ) {
+            $log .= _unroll_dumpable_rules( 0,$r );
+        }
+
+        Jifty->log->debug( "Rules in stage $stage:\n", $log) if ($log);
+    }
+};
+
+=head2 _unroll_dumpable_rules LEVEL,RULE
+
+Walk all rules defined in dispatcher starting at rule
+C<RULE> and indentation level C<LEVEL>
+
+=cut
+
+sub _unroll_dumpable_rules {
+    my ($level, $rule) = @_;
+    my $log =
+        # indentation
+        ( "    " x $level ) .
+        # op
+        ( $rule->[0] || "undef op" ) . ' ' .
+        # arguments
+        (
+            ! defined( $rule->[1] )   ? ""                                          :
+            ref $rule->[1] eq 'ARRAY' ? "'" . join("','", @{ $rule->[1] }) . "'" :
+            ref $rule->[1] eq 'HASH'  ? $rule->[1]->{method} . " '" . $rule->[1]->{""} ."'" :
+            ref $rule->[1] eq 'CODE'  ? '{...}' :
+                                        "'" . $rule->[1] . "'"
+        ) .
+        "\n";
+
+    if (ref $rule->[2] eq 'ARRAY') {
+        $level++;
+        foreach my $sr ( @{ $rule->[2] } ) {
+            $log .= _unroll_dumpable_rules( $level, $sr );
+        }
+    }
+
+    return $log;
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Event.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Event.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,143 @@
+use warnings;
+use strict;
+
+package Jifty::Event;
+
+use Storable 'nfreeze';
+use Digest::MD5 qw(md5_hex);
+use vars qw/%PUBLISHER/;
+
+=head1 NAME
+
+Jifty::Event
+
+=head1 DESCRIPTION
+
+An event object from the Jifty::PubSub stream.
+
+=head1 METHODS
+
+=head2 new($payload)
+
+Constructor.  Takes any kind of payload and blesses a scalar reference to it
+into an Event object.
+
+=cut
+
+sub new {
+    my $class   = shift;
+    my $payload = shift;
+    bless \$payload, $class;
+}
+
+=head2 publish()
+
+Inserts the event into the pubsub stream.  If Jifty is configured into
+synchronous republishing, then this method runs a C<republish> on itself
+with all current subscriptions implicitly.  If not, it's simply inserted
+into its main channel for asynchronous republishing later.  
+
+=cut
+
+sub publish {
+    my $self  = shift;
+    my $class = ref($self) || $self;
+
+    return undef unless (Jifty->config->framework('PubSub')->{'Enable'});
+
+    # Always publish to the main stream (needed for async & debugging)
+    # if ($ASYNC || $DEBUGGING) {
+    #    ($PUBLISHER{$class} ||= Jifty->bus->new_publisher($class))->msg($$self);
+    #    return;
+    # }
+
+    # Synchronized auto-republishing
+    # TODO - Prioritize current-user subscriptions first?
+    my $subscriptions = Jifty->bus->modify("$class-subscriptions") || {};
+    while (my ($channel, $queries) = each %$subscriptions) {
+        if ($self->filter(@$queries)) {
+            ($PUBLISHER{$channel} ||= Jifty->bus->new_publisher($channel))->msg($$self);
+        }
+    }
+}
+
+=head2 filter(@query)
+
+Takes multiple class-specific queries, which are evaluated in order by calling L</match>.
+
+=cut
+
+sub filter {
+    my $self = shift;
+    $self->match($_) or return 0 for @_;
+    return 1;
+}
+
+=head2 republish(@query)
+
+Run C<filter> with the queries; if they all succeed, the event is republished
+into that query-specific channel.
+
+=cut
+
+sub republish {
+    my $self = shift;
+    $self->filter(@_) or return;
+
+    my $channel = $self->encode_queries(@_);
+    ($PUBLISHER{$channel} ||= Jifty->bus->new_publisher($channel))->msg($$self);
+}
+
+
+=head2 encode_queries(@query)
+
+Encode queries into some sort of canonical md5 encoding.
+
+=cut
+
+sub encode_queries {
+    my $self    = shift;
+    my $class   = ref($self) || $self;
+    return $class unless @_;
+
+    local $Storable::canonical = 1;
+    return $class . '-' . md5_hex(join('', sort map { Storable::nfreeze($_) } @_));
+}
+
+
+=head2 match($query)
+
+Takes a class-specific query and returns whether it matches.
+
+You almost always want to override this; the default implementation
+simply always return true;
+
+=cut
+
+sub match {
+    1;
+}
+
+=head2 render_arguments()
+
+A list of additional things to push into the C<%ARGS> of the region that
+is about to render this event; see L<Jifty::Subs::Render> for more information.
+
+=cut
+
+sub render_arguments {
+    ();
+}
+
+=head2 data()
+
+This event's payload as a scalar value.
+
+=cut
+
+sub data {
+    ${$_[0]}
+}
+
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Event/Model.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Event/Model.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,52 @@
+package Jifty::Event::Model;
+
+use warnings;
+use strict;
+use Carp;
+use base qw/Jifty::Event/;
+
+
+
+=head1 NAME
+
+Jifty::Event::Model
+
+=head1 DESCRIPTION
+
+Objects in this class represent changes to Jifty::Record classes (any action on a model class)
+as Jifty::Events.
+
+As yet, this functionality is unused.
+
+
+=cut
+
+=head2 new
+
+creates a new L<Jifty::Event::Model> object.  If C<PubSub> is enabled for your application, 
+checks to make sure that this event has the following (underdocumented) parameters:
+
+ record_id record_class action_class action_arguments timestamp result as_hash_before as_hash_after current_user_id
+
+=cut
+
+
+sub new {
+    my $class = shift;
+    my $self = $class->SUPER::new(@_);
+    $self->_check() if (Jifty->config->framework('PubSub')->{'Enable'});
+    return $self;
+}
+
+
+sub _check {
+    my $self = shift;
+    for (qw(record_id record_class action_class
+        action_arguments timestamp result as_hash_before
+        as_hash_after current_user_id)) {
+        Carp::confess("$self missing required parameter $_ ")
+            unless ( defined $$self->{$_} );
+        };
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Everything.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Everything.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,85 @@
+use warnings;
+use strict;
+
+package Jifty::Everything;
+
+=head1 NAME
+
+Jifty::Everything - Load all of the important Jifty modules at once.
+
+=cut
+
+use Cwd ();
+BEGIN {
+    # Cwd::cwd() insists doing `pwd`, which is a few hundres of shell
+    # outs just in the BEGIN time for Module::Pluggable to load things.
+    if ($^O ne 'MSWin32') {
+        require POSIX;
+        *Cwd::cwd = *POSIX::getcwd;
+    }
+}
+
+use Jifty ();
+use Jifty::I18N ();
+use Jifty::Dispatcher ();
+use Jifty::Object ();
+use Jifty::Config ();
+use Jifty::Handle ();
+use Jifty::ClassLoader ();
+use Jifty::Util ();
+use Jifty::API ();
+use Jifty::DateTime ();
+use Jifty::Record ();
+use Jifty::Collection ();
+use Jifty::Action ();
+use Jifty::Action::Record ();
+use Jifty::Action::Record::Create ();
+use Jifty::Action::Record::Update ();
+use Jifty::Action::Record::Delete ();
+
+
+use Jifty::Continuation ();
+
+use Jifty::LetMe ();
+
+use Jifty::Logger ();
+use Jifty::Handler ();
+use Jifty::View::Static::Handler ();
+use Jifty::View::Mason::Handler ();
+
+use Jifty::Model::Metadata ();
+use Jifty::Model::Session ();
+use Jifty::Model::SessionCollection ();
+
+
+use Jifty::Request ();
+use Jifty::Request::Mapper ();
+use Jifty::Result ();
+use Jifty::Response ();
+use Jifty::CurrentUser ();
+
+# We can _not_ load Server.pm unless we're in a Server context because
+# HTTP::Server::Simple::Mason bastardizes HTML::Mason::FakeApache::send_http_header
+# with hook::lexwrap
+#use Jifty::Server;
+
+use Jifty::Web ();
+use Jifty::Web::Session ();
+use Jifty::Web::PageRegion ();
+use Jifty::Web::Form ();
+use Jifty::Web::Form::Clickable ();
+use Jifty::Web::Form::Element ();
+use Jifty::Web::Form::Link ();
+use Jifty::Web::Form::Field ();
+use Jifty::Web::Menu ();
+
+use Jifty::Subs ();
+use Jifty::Subs::Render ();
+
+use Jifty::Module::Pluggable;
+Jifty::Module::Pluggable->import(search_path => ['Jifty::Web::Form::Field'],
+                          require     => 1,
+                          except      => qr/\.#/);
+__PACKAGE__->plugins;
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Filter/DateTime.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Filter/DateTime.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,63 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Jifty::Filter::DateTime -- A Jifty::DBI filter to work with
+                          Jifty::DateTime objects
+
+=head1 DESCRIPTION
+
+Jifty::Filter::DateTime promotes DateTime objects to Jifty::DateTime
+objects on load. This has the side effect of setting their time zone
+based on the record's current user's preferred time zone, when
+available.
+
+This is intended to be combined with C<Jifty::DBI::Filter::Date> or
+C<Jifty::DBI::Filter::DateTime>, e.g.
+
+    column created =>
+      type is 'timestamp',
+      filters are qw( Jifty::Filter::DateTime Jifty::DBI::Filter::DateTime),
+      label is 'Created',
+      is immutable;
+
+=cut
+
+package Jifty::Filter::DateTime;
+use base qw(Jifty::DBI::Filter);
+
+
+=head2 decode
+
+If the value is a DateTime, replace it with a Jifty::DateTime
+representing the same time, setting the time zone in the process.
+
+=cut
+
+
+sub decode {
+    my $self = shift;
+    my $value_ref = $self->value_ref;
+
+    return unless ref($$value_ref) && $$value_ref->isa('DateTime');
+
+    # XXX There has to be a better way to do this
+    my %args;
+    for (qw(year month day hour minute second nanosecond formatter)) {
+        $args{$_} = $$value_ref->$_ if(defined($$value_ref->$_));
+    }
+
+    my $dt = Jifty::DateTime->new(%args);
+
+    $$value_ref = $dt;
+}
+
+=head1 SEE ALSO
+
+L<Jifty::DBI::Filter::Date>, L<Jifty::DBI::Filter::DateTime>,
+L<Jifty::DateTime>
+
+=cut
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Handle.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Handle.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,217 @@
+use warnings;
+use strict;
+
+package Jifty::Handle;
+
+=head1 NAME
+
+Jifty::Handle -- A database handle class for Jifty
+
+=head1 DESCRIPTION
+
+A wrapper around Jifty::DBI::Handle which is aware of versions in the
+database
+
+=cut
+
+use Jifty::Util;
+our @ISA;
+
+=head1 METHODS
+
+=head2 new PARAMHASH
+
+This class method instantiates a new L<Jifty::Handle> object. This
+object deals with database handles for the system.  After it is
+created, it will be a subclass of L<Jifty::DBI::Handle>.
+
+=cut
+
+# Setup database handle based on config data
+sub new {
+    my $class = shift;
+
+    my $driver = Jifty->config->framework('Database')->{'Driver'};
+    if ($driver eq 'Oracle') {
+        $ENV{'NLS_LANG'} = "AMERICAN_AMERICA.AL32UTF8";
+        $ENV{'NLS_NCHAR'} = "AL32UTF8";
+    }
+   
+    # We do this to avoid Jifty::DBI::Handle's magic reblessing, because
+    # it breaks subclass methods.
+    my $driver_class  = "Jifty::DBI::Handle::".  $driver;
+    Jifty::Util->require($driver_class);
+
+    unshift @ISA, $driver_class;
+    return $class->SUPER::new();
+}
+
+=head2 canonical_database_name
+
+Returns the canonical name of the application's database (the actual name that will
+be given to the database driver).  This name is a lower-case version of the C<Database>
+argument in the C<Database> section of the framework config.
+
+For SQLite databases (where the database name is actually a filename), this also converts
+a relative path into an absolute path based at the application root.
+
+=cut
+
+sub canonical_database_name {
+    my $self_or_class = shift;
+    my $db_config = Jifty->config->framework('Database');
+
+    # XXX TODO consider canonicalizing to all-lowercase, once there are no
+    # legacy databases
+    my $db = $db_config->{'Database'};
+
+    if ($db_config->{'Driver'} =~ /SQLite/) {
+        $db = Jifty::Util->absolute_path($db);
+    } 
+
+    return $db;
+} 
+
+=head2 connect ARGS
+
+Like L<Jifty::DBI>'s connect method but pulls the name of the database
+from the current L<Jifty::Config>.
+
+=cut
+
+sub connect {
+    my $self = shift;
+    my %args = (@_);
+    my %db_config =  (%{Jifty->config->framework('Database')}, Database => $self->canonical_database_name);
+
+    my %lc_db_config;
+    # Skip the non-dsn keys, but not anything else
+    for (grep {!/^checkschema|version|recordbaseclass$/i} keys %db_config) {
+        $lc_db_config{lc($_)} = $db_config{$_};
+    }
+    $self->SUPER::connect( %lc_db_config , %args);
+    $self->{db_config} = { %lc_db_config , %args };
+    $self->dbh->{LongReadLen} = Jifty->config->framework('MaxAttachmentSize') || '10000000';
+}
+
+
+=head2 check_schema_version
+
+Make sure that we have a recent enough database schema.  If we don't,
+then error out.
+
+=cut
+
+sub check_schema_version {
+    require Jifty::Model::Metadata;
+
+    # Application db version check
+    {
+        my $dbv  = Jifty::Model::Metadata->load("application_db_version");
+        my $appv = Jifty->config->framework('Database')->{'Version'};
+
+        if ( not defined $dbv ) {
+            # First layer of backwards compatibility -- it used to be in _db_version
+            my @v;
+            eval {
+                local $SIG{__WARN__} = sub { };
+                @v = Jifty->handle->fetch_result(
+                    "SELECT major, minor, rev FROM _db_version");
+            };
+            $dbv = join( ".", @v ) if @v == 3;
+        }
+        if ( not defined $dbv ) {
+            # It was also called the 'key' column, not the data_key column
+            eval {
+                local $SIG{__WARN__} = sub { };
+                $dbv = Jifty->handle->fetch_result(
+                    "SELECT value FROM _jifty_metadata WHERE key = 'application_db_version'");
+            } or undef($dbv);
+        }
+
+        die
+            "Application schema has no version in the database; perhaps you need to run this:\n"
+            . "\t bin/jifty schema --setup\n"
+            unless defined $dbv;
+
+        die
+            "Application schema version in database ($dbv) doesn't match application schema version ($appv)\n"
+            . "Please run `bin/jifty schema --setup` to upgrade the database.\n"
+            unless version->new($appv) == version->new($dbv);
+    }
+
+    # Jifty db version check
+    {
+
+        # If we got here, the application had a version (somehow) so
+        # this is an upgrade.  If $dbv is undef, it's because it's
+        # from before when the _jifty_metadata table existed.
+        my $dbv
+            = version->new( Jifty::Model::Metadata->load("jifty_db_version")
+                || '0.60426' );
+        my $appv = version->new($Jifty::VERSION);
+        die
+            "Internal jifty schema version in database ($dbv) doesn't match running jifty version ($appv)\n"
+            . "Please run `bin/jifty schema --setup` to upgrade the database.\n"
+            unless $appv == $dbv;
+    }
+
+}
+
+
+=head2 create_database MODE
+
+C<MODE> is either "print" or "execute".
+
+This method either prints the commands necessary to create the database
+or actually creates it, depending on the value of MODE.
+
+=cut
+
+sub create_database {
+    my $self = shift;
+    my $mode = shift || 'execute';
+    my $database = $self->canonical_database_name;
+    my $driver   = Jifty->config->framework('Database')->{'Driver'};
+    my $query = "CREATE DATABASE $database;\n";
+    if ( $mode eq 'print') {
+        print $query;
+    } elsif ( $driver !~ /SQLite/ ) {
+        $self->simple_query($query);
+    }
+}
+
+=head2 drop_database MODE
+
+C<MODE> is either "print" or "execute".
+
+This method either prints the commands necessary to drop the database
+or actually drops it, depending on the value of MODE.
+
+=cut
+
+sub drop_database {
+    my $self = shift;
+    my $mode = shift || 'execute';
+    my $database = $self->canonical_database_name;
+    my $driver   = Jifty->config->framework('Database')->{'Driver'};
+    if ( $mode eq 'print' ) {
+        print "DROP DATABASE $database;\n";
+    } elsif ( $driver =~ /SQLite/ ) {
+
+        # Win32 complains when you try to unlink open DB
+        $self->disconnect if $^O eq 'MSWin32';
+        unlink($database);
+    } else {
+        $self->simple_query("DROP DATABASE $database");
+    }
+}
+
+
+=head1 AUTHOR
+
+Various folks at BestPractical Solutions, LLC.
+
+=cut
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Handler.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Handler.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,237 @@
+use strict;
+use warnings;
+
+package Jifty::Handler;
+
+=head1 NAME
+
+Jifty::Handler - Methods related to the Mason handler
+
+=head1 SYNOPSIS
+
+  use Jifty;
+  Jifty->new();
+
+  my $handler = Jifty::Handler->handle_request( cgi => $cgi );
+
+  # after each request is handled
+  Jifty::Handler->cleanup_request;
+
+=head1 DESCRIPTION
+
+L<Jifty::Handler> provides methods required to deal with Mason CGI
+handlers.  
+
+=cut
+
+use base qw/Class::Accessor::Fast/;
+use Module::Refresh ();
+
+BEGIN {
+    # Creating a new CGI object breaks FastCGI in all sorts of painful
+    # ways.  So wrap the call and preempt it if we already have one
+    use CGI ();
+
+    # If this file gets reloaded using Module::Refresh, don't do this
+    # magic again, or we'll get infinite recursion
+    unless (CGI->can('__jifty_real_new')) {
+        *CGI::__jifty_real_new = \&CGI::new;
+
+        no warnings qw(redefine);
+        *CGI::new = sub {
+            return Jifty->handler->cgi if Jifty->handler->cgi;
+            CGI::__jifty_real_new(@_);	
+        }
+    }
+};
+
+
+
+__PACKAGE__->mk_accessors(qw(mason dispatcher static_handler cgi apache stash));
+
+=head2 new
+
+Create a new Jifty::Handler object. Generally, Jifty.pm does this only once at startup.
+
+=cut
+
+sub new {
+    my $class = shift;
+    my $self  = {};
+    bless $self, $class;
+
+    $self->create_cache_directories();
+#    wrap 'CGI::new', pre => sub {
+#        $_[-1] = Jifty->handler->cgi if Jifty->handler->cgi;
+#    };
+
+    $self->dispatcher( Jifty->app_class( "Dispatcher" ) );
+    Jifty::Util->require( $self->dispatcher );
+    $self->dispatcher->import_plugins;
+    $self->dispatcher->dump_rules;
+    
+    $self->mason( Jifty::View::Mason::Handler->new( $self->mason_config ) );
+
+    $self->static_handler(Jifty::View::Static::Handler->new());
+
+    return $self;
+}
+
+
+=head2 create_cache_directories
+
+Attempts to create our app's mason cache directory.
+
+=cut
+
+sub create_cache_directories {
+    my $self = shift;
+
+    for ( Jifty->config->framework('Web')->{'DataDir'} ) {
+        Jifty::Util->make_path( Jifty::Util->absolute_path($_) );
+    }
+}
+
+
+=head2 mason_config
+
+Returns our Mason config.  We use the component root specified in the
+C<Web/TemplateRoot> framework configuration variable (or C<html> by
+default).  Additionally, we set up a C<jifty> component root, as
+specified by the C<Web/DefaultTemplateRoot> configuration.  All
+interpolations are HTML-escaped by default, and we use the fatal error
+mode.
+
+=cut
+
+sub mason_config {
+    my %config = (
+        static_source => 1,
+        use_object_files => 1,
+        preprocess => sub {
+            # Force UTF-8 semantics on all our components by
+            # prepending this block to all components as Mason
+            # components defaults to parse the text as Latin-1
+            ${$_[0]} =~ s!^!<\%INIT>use utf8;</\%INIT>\n!;
+        },
+        data_dir =>  Jifty::Util->absolute_path( Jifty->config->framework('Web')->{'DataDir'} ),
+        allow_globals => [
+            qw[ $JiftyWeb ],
+            @{Jifty->config->framework('Web')->{'Globals'} || []},
+        ],
+        comp_root     => [ 
+                          [application =>  Jifty::Util->absolute_path( Jifty->config->framework('Web')->{'TemplateRoot'} )],
+                          [jifty => Jifty->config->framework('Web')->{'DefaultTemplateRoot'}],
+                         ],
+        %{ Jifty->config->framework('Web')->{'MasonConfig'} },
+    );
+
+    for my $plugin (Jifty->plugins) {
+        my $comp_root = $plugin->template_root;
+        next unless $comp_root;
+        push @{ $config{comp_root} }, [ ref($plugin)."-".Jifty->web->serial => $comp_root ];
+    }
+
+    # In developer mode, we want halos, refreshing and all that other good stuff. 
+    if (Jifty->config->framework('DevelMode') ) {
+        push @{$config{'plugins'}}, 'Jifty::Mason::Halo';
+        $config{static_source}    = 0;
+        $config{use_object_files} = 0;
+    }
+    return (%config);
+        
+}
+
+=head2 cgi
+
+Returns the L<CGI> object for the current request, or C<undef> if
+there is none.
+
+=head2 apache
+
+Returns the L<HTML::Mason::FakeApache> or L<Apache> object for the
+current request, ot C<undef> if there is none.
+
+=head2 handle_request
+
+When your server processs (be it Jifty-internal, FastCGI or anything
+else) wants to handle a request coming in from the outside world, you
+should call C<handle_request>.
+
+=over
+
+=item cgi
+
+A L<CGI> object that your server has already set up and loaded with
+your request's data.
+
+=back
+
+=cut
+
+
+sub handle_request {
+    my $self = shift;
+    my %args = (
+        cgi => undef,
+        @_
+    );
+
+    if ( Jifty->config->framework('DevelMode') ) {
+        Module::Refresh->refresh;
+        Jifty::I18N->refresh;
+    }
+
+    Jifty::I18N->get_language_handle;
+
+    $self->cgi( $args{cgi} );
+    $self->apache( HTML::Mason::FakeApache->new( cgi => $self->cgi ) );
+
+    # Build a new stash for the life of this request
+    $self->stash({});
+    local $HTML::Mason::Commands::JiftyWeb = Jifty::Web->new();
+
+    Jifty->web->request( Jifty::Request->new()->fill( $self->cgi ) );
+    Jifty->web->response( Jifty::Response->new );
+    Jifty->api->reset;
+    $_->new_request for Jifty->plugins;
+
+    Jifty->log->debug( "Received request for " . Jifty->web->request->path );
+    my $sent_response = 0;
+    $sent_response
+        = $self->static_handler->handle_request( Jifty->web->request->path )
+        if ( Jifty->config->framework('Web')->{'ServeStaticFiles'} );
+
+    Jifty->web->setup_session unless $sent_response;
+
+    # Return from the continuation if need be
+    Jifty->web->request->return_from_continuation;
+
+    unless ($sent_response) {
+        Jifty->web->session->set_cookie;
+        $self->dispatcher->handle_request()
+    }
+
+    $self->cleanup_request();
+
+}
+
+=head2 cleanup_request
+
+Dispatchers should call this at the end of each request, as a class method.
+It flushes the session to disk, as well as flushing L<Jifty::DBI>'s cache. 
+
+=cut
+
+sub cleanup_request {
+    my $self = shift;
+    # Clean out the cache. the performance impact should be marginal.
+    # Consistency is improved, too.
+    Jifty->web->session->unload();
+    Jifty::Record->flush_cache if Jifty::Record->can('flush_cache');
+    $self->cgi(undef);
+    $self->apache(undef);
+    $self->stash(undef);
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/I18N.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/I18N.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,208 @@
+use strict;
+use warnings;
+
+package Jifty::I18N;
+use base 'Locale::Maketext';
+use Locale::Maketext::Lexicon ();
+use Email::MIME::ContentType;
+use Encode::Guess qw(iso-8859-1);
+use File::ShareDir ':ALL';
+
+=head1 NAME
+
+Jifty::I18N - Internationalization framework for Jifty
+
+=head1 METHODS
+
+=head2 C<_>
+
+This module exports the C<loc> method, which it inherits from
+L<Locale::Maketext::Simple>. Jifty aliases this method to C<_()> for 
+your convenience.
+
+=cut
+
+=head2 new
+
+Set up Jifty's internationalization for your application.  This pulls
+in Jifty's PO files, your PO files and then exports the _ function into
+the wider world.
+
+=cut
+
+my $DynamicLH;
+
+sub new {
+    my $class = shift;
+    my $self  = {};
+    bless $self, $class;
+
+    my @import = (
+        'Gettext',Jifty->config->framework('L10N')->{'PoDir'}. '/*.po',
+        'Gettext',Jifty->config->framework('L10N')->{'DefaultPoDir'}. '/*.po'
+        );
+
+    foreach my $plugin (Jifty->plugins) {
+        local $@;
+        my $dir = eval { module_dir(ref($plugin)); };
+        next unless $dir;
+        push @import, 'Gettext';
+        push @import, $dir . '/po/*.po';
+    };
+
+    Locale::Maketext::Lexicon->import(
+        {   '*' => \@import,
+            _decode => 1,
+            _auto   => 1,
+            _style  => 'gettext',
+        }
+    );
+
+    # Allow hard-coded languages in the config file
+    my $lang = Jifty->config->framework('L10N')->{'Lang'};
+    $lang = [defined $lang ? $lang : ()] unless ref($lang) eq 'ARRAY';
+
+    my $lh = $class->get_handle(@$lang);
+    $DynamicLH = \$lh unless @$lang; 
+    $self->init;
+
+    my $loc_method = sub {
+        # Retain compatibility with people using "-e _" etc.
+        return \*_ unless @_;
+
+        # When $_[0] is undef, return undef.  When it is '', return ''.
+        no warnings 'uninitialized';
+        return $_[0] unless (length $_[0]);
+
+        local $@;
+        # Force stringification to stop Locale::Maketext from choking on
+        # things like DateTime objects.
+        my @stringified_args = map {"$_"} @_;
+        my $result = eval { $lh->maketext(@stringified_args) };
+        if ($@) {
+            # Sometimes Locale::Maketext fails to localize a string and throws
+            # an exception instead.  In that case, we just return the input.
+            return join(' ', @stringified_args);
+        }
+        return $result;
+    };
+
+    {
+        no strict 'refs';
+        no warnings 'redefine';
+        *_ = $loc_method;
+    }
+    return $self;
+}
+
+=head2 get_language_handle
+
+Get the lanauge language for this request.
+
+=cut
+
+sub get_language_handle {
+    my $self = shift;
+    $$DynamicLH = $self->get_handle() if $DynamicLH;
+}
+
+=head2 refresh
+
+Used by L<Jifty::Handler> in DevelMode to reload F<.po> files whenever they
+are modified on disk.
+
+=cut
+
+my $last_modified = '';
+sub refresh {
+    my $modified = join(
+        ',',
+        sort map { $_ => -M $_ } map { glob("$_/*.po") } (
+            Jifty->config->framework('L10N')->{'PoDir'},
+            Jifty->config->framework('L10N')->{'DefaultPoDir'}
+        )
+    );
+    if ($modified ne $last_modified) {
+        Jifty::I18N->new;
+        $last_modified = $modified;
+    }
+}
+
+
+
+=head2 promote_encoding STRING [CONTENT-TYPE]
+
+Return STRING promoted to our best-guess of an appropriate
+encoding. STRING should B<not> have the UTF-8 flag set when passed in.
+
+Optionally, you can pass a MIME content-type string as a second
+argument. If it contains a charset= parameter, we will use that
+encoding. Failing that, we use Encode::Guess to guess between UTF-8
+and iso-latin-1. If that fails, and the string validates as UTF-8, we
+assume that. Finally, we fall back on returning the string as is.
+
+=cut
+
+# XXX TODO This possibly needs to be more clever and/or configurable
+
+sub promote_encoding {
+    my $class = shift;
+    my $string = shift;
+    my $content_type = shift;
+
+    $content_type = Email::MIME::ContentType::parse_content_type($content_type) if $content_type;
+    my $charset = $content_type->{attributes}->{charset} if $content_type;
+
+    # XXX TODO Is this the right thing? Maybe we should just return
+    # the string as-is.
+    Encode::_utf8_off($string);
+
+    if($charset) {
+        $string = Encode::decode($charset, $string);
+    } else {
+        my $encoding = Encode::Guess->guess($string);
+        if(!ref($encoding)) {
+            local $@;
+            eval {
+                # Try utf8
+                $string = Encode::decode_utf8($string, 1);
+            };
+            if($@) {
+                warn "Unknown encoding -- none specified, couldn't guess, not valid UTF-8";
+            }
+        } else {
+            $string = $encoding->decode($string) if $encoding;
+        }
+    }
+
+    return $string;
+}
+
+=head2 maybe_decode_utf8 STRING
+
+Attempt to decode STRING as UTF-8. If STRING is not valid UTF-8, or
+already contains wide characters, return it undecoded.
+
+N.B: In an ideal world, we wouldn't need this function, since we would
+know whether any given piece of input is UTF-8. However, the world is
+not ideal.
+
+=cut
+
+sub maybe_decode_utf8 {
+    my $class = shift;
+    my $string = shift;
+
+    local $@;
+    eval {
+        $string =  Encode::decode_utf8($string);
+    };
+    Carp::carp "Couldn't decode UTF-8: $@" if $@;
+    return $string;
+}
+
+package Jifty::I18N::en;
+use base 'Locale::Maketext';
+our %Lexicon = ( _fallback => 1, _AUTO => 1 );
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/JSON.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/JSON.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,110 @@
+use warnings;
+use strict;
+
+package Jifty::JSON;
+
+=head1 NAME
+
+Jifty::JSON -- Wrapper around L<JSON>
+
+=head1 DESCRIPTION
+
+Provides a wrapper around the L<JSON> library.
+
+The JSON specification at L<http://www.json.org/> states that only
+double-quotes are possible for specifying strings.  However, for the purposes
+of embedding Javascript-compatible objects in XHTML attributes (which use
+double-quotes), we sometimes want to provide strings in single quotes.
+This provides a version of L<JSON/objToJson> which allows
+single-quoted string output.
+
+If the faster L<JSON::Syck> is available, it is preferred over the pure-perl
+L<JSON>, as it provides native support for single-quoted strings.
+
+=head1 METHODS
+
+=cut
+
+BEGIN {
+    local $@;
+    no strict 'refs';
+    no warnings 'once';
+    if (eval { require JSON::Syck; JSON::Syck->VERSION(0.05) }) {
+        *jsonToObj = *_jsonToObj_syck;
+        *objToJson = *_objToJson_syck;
+        $JSON::Syck::ImplicitUnicode = 1;
+    }
+    else {
+        require JSON;
+        *jsonToObj = *_jsonToObj_pp;
+        *objToJson = *_objToJson_pp;
+        $JSON::UTF8 = 1;
+    }
+}
+
+=head2 jsonToObj JSON, [ARGUMENTS]
+
+For completeness, C<Jifty::JSON> provides a C<jsonToObj>.  It is
+identical to L<JSON/jsonToObj>.
+
+=cut
+
+sub _jsonToObj_syck {
+    local $JSON::Syck::SingleQuote = 0;
+    JSON::Syck::Load($_[0]);
+}
+
+sub _jsonToObj_pp {
+    return JSON::jsonToObj(@_);
+}
+
+=head2 objToJson OBJECT, [ARGUMENTS]
+
+This method is identical to L<JSON/objToJson>, except it has an
+additional possible option.  The C<singlequote> option, if set to a
+true value in the C<ARGUMENTS> hashref, overrides L<JSON::Converter>'s
+string output method to output single quotes as delimters instead of
+double quotes.
+
+=cut
+
+sub _objToJson_syck {
+    my ($obj, $args) = @_;
+
+    local $JSON::Syck::SingleQuote = $args->{singlequote};
+    local $JSON::Syck::ImplicitUnicode = 1;
+    JSON::Syck::Dump($obj);
+}
+
+# We should escape double-quotes somehow, so that we can guarantee
+# that double-quotes *never* appear in the JSON string that is
+# returned.
+sub _objToJson_pp {
+    my ($obj, $args) = @_;
+
+    # Unless we're asking for single-quoting, just do what JSON.pm
+    # does
+    return JSON::Converter::objToJson($obj)
+      unless delete $args->{singlequote};
+
+    # Otherwise, insert our own stringify sub
+    no warnings 'redefine';
+    my %esc = (
+        "\n" => '\n',
+        "\r" => '\r',
+        "\t" => '\t',
+        "\f" => '\f',
+        "\b" => '\b',
+        "'"  => '\\\'',
+        "\\" => '\\\\',
+    );
+    local *JSON::Converter::_stringfy = sub {
+        my $arg = shift;
+        $arg =~ s/([\\\n'\r\t\f\b])/$esc{$1}/eg;
+        $arg =~ s/([\x00-\x07\x0b\x0e-\x1f])/'\\u00' . unpack('H2',$1)/eg;
+        return "'" . $arg ."'";
+    };
+    return JSON::objToJson($obj, $args);
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/LetMe.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/LetMe.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,308 @@
+use warnings;
+use strict;
+
+package Jifty::LetMe;
+use Digest::MD5 ();
+use Math::BigInt::Calc;
+use String::Koremutake ();
+
+use base qw/Jifty::Object Class::Accessor::Fast/;
+
+__PACKAGE__->mk_accessors ( qw/checksum_provided email path args until user/);
+
+=head1 NAME
+
+Jifty::LetMe - A way to expose single-link URLs to your applications
+
+=head2 new
+
+Create a new "LetMe" authentication object; it takes no parameters.
+It calls L</_init> to do any initialization.
+
+A LetMe is a way to provide a one-time-use URL for a particular purpose.
+All LetMe objects give you a way to validate someone's identity and to
+allow them a very small set of possible actions or page-access permissions.
+
+For example, you can put a LetMe URL in an email to a new user,
+so that when they click on the URL you know that their email address
+is valid.
+
+=cut
+
+sub new {
+    my $class = shift;
+    my $self = {};
+    bless $self, $class;
+    $self->args({});
+    $self->_init(@_);
+    return $self;
+
+}
+
+=head2 _init @_
+
+Called with whatever L</new> was called with.  By default, does nothing.
+
+=cut
+
+sub _init { return shift }
+
+=head2 user
+
+Contains an app-specific "user" object.
+
+=cut
+
+=head2 validated_current_user
+
+If the user has presented a valid token, returns an (app-specific
+subclass of the) L<Jifty::CurrentUser> object for the user who has the
+email address in $self->email.  If no user has that email address,
+returns undef.
+
+=cut
+
+sub validated_current_user {
+    my $self = shift;
+    return undef unless ( $self->validate );
+    return $self->_user_from_email($self->email);
+
+}
+
+
+=head2 _user_from_email ADDRESS
+
+Returns an (app-specific subclass of the) L<Jifty::CurrentUser> object
+for the user who has the email address I<ADDRESS>.
+
+=cut
+
+sub _user_from_email {
+    my $self = shift;
+    my $email = shift;
+    my $currentuser_object_class = Jifty->app_class("CurrentUser");
+    return $currentuser_object_class->new( email => $email );
+}
+
+sub _generate_digest {
+    my $self = shift;
+
+    # get user's generic secret
+    my $user;
+    return '' unless ( $user = $self->_user_from_email($self->email) );
+    return '' unless ($user->auth_token);
+
+
+
+    # build an md5sum of the email token and until and our secret
+    my $digest = Digest::MD5->new();
+    $digest->add( $user->auth_token );
+    $digest->add( $self->path );
+    my %args = %{$self->args};
+    $digest->add( Encode::encode_utf8($_), Encode::encode_utf8($args{$_})) for sort keys %args;
+    $digest->add( $self->until ) if ($self->until);
+    return $digest->hexdigest();
+}
+
+
+
+=head2 generate_checksum
+
+Returns an auth checksum for the current combination of
+
+    user
+    path
+    arguments
+    until
+
+=cut
+
+sub generate_checksum {
+    my $self = shift;
+
+    return substr( $self->_generate_digest, 0, 16 );
+}
+
+=head2 generate_koremutake_checksum
+
+Generate a slightly more pronouncable version of the checksum using
+L<String::Koremutake>.  Due to hex -> integer limitations, this is
+imporecise and may vary depending on the platform it is used on; as
+such, it is deprecated.
+
+=cut
+
+sub generate_koremutake_checksum {
+    my $self = shift;
+
+    # Only take the first 16 characters. We're really just trying to
+    # get something reasonably short, memorable and unguessable. Also,
+    # don't use Math::BigInt->new directly for simple computation,
+    # because it insists exporting overload to us, which makes
+    # devel::cover and devel::dprof very sad.  This is deprecated in
+    # favor of generate_checksum, which returns a straight hex digest.
+    my $integer_digest = Math::BigInt::Calc->_str(
+        Math::BigInt::Calc->_from_hex(
+            substr( $self->_generate_digest, 0, 16 )
+        )
+    );
+
+    # koremutake it.  This loses precision, since most perls can't
+    # deal with 64 bits with precision.  Thus, $integer_digest ends up
+    # being rounded, possibly in unpredicatable ways.
+    my $k = String::Koremutake->new;
+    return( $k->integer_to_koremutake($integer_digest));
+
+}
+
+=head2 from_token PATH
+
+Parse a string of the form 
+
+mylongusername at example.com/update_task/23/until/20050101/bekidrikufryvagygefuba
+
+into 
+
+      email => mylongusername at example.com,
+      token => 'update_task/23'
+      until => 20050101,
+      checksum_provided => bekidrikufryvagygefuba
+
+=cut
+
+sub from_token {
+    my $self = shift;
+    my $token = shift;
+
+    my @atoms = split('/',$token);
+
+    $self->email( Jifty::I18N->maybe_decode_utf8(URI::Escape::uri_unescape( shift @atoms )) );
+    $self->path( shift @atoms );
+    $self->checksum_provided( pop @atoms );
+
+    # If they don't even have the right number of items in the path, then we know that it's not valid
+    return undef unless (scalar @atoms % 2 == 0); 
+
+    my %args = map { Jifty::I18N->maybe_decode_utf8(URI::Escape::uri_unescape($_)) } @atoms;
+    $self->until( delete $args{until} ) if $args{until};
+
+    $self->args(\%args);
+}
+
+
+=head2 as_token
+
+Returns the "letme" token for this set of credentials. This should round
+trip cleanly with from_token
+
+=cut
+
+sub as_token {
+    my $self = shift;
+    $self->_generate_token( email => $self->email );
+}
+
+=head2 as_encoded_token
+
+A variant of as_token that encodes the user's email address suitably
+for passing in a URL
+
+=cut
+
+sub as_encoded_token {
+    my $self = shift;
+    $self->_generate_token( email => URI::Escape::uri_escape_utf8($self->email) );
+}
+
+sub _generate_token {
+    my $self = shift;
+    my %args = (email => undef, @_);
+    return join ('/', 
+        $args{'email'},
+        $self->path,
+        (map {URI::Escape::uri_escape_utf8($_)} %{$self->args}),
+        (defined $self->until ? ( 'until', $self->until ) : () ), #?
+        $self->generate_checksum  
+        );
+
+}
+
+
+=head2 as_url
+
+Returns the fully qualified URL for this LetMe. It's composed of
+Jifty->web->url, L</base_path> and L</as_encoded_token>
+
+=cut
+
+sub as_url {
+    my $self = shift;
+    return Jifty->web->url(path => $self->base_path . $self->as_encoded_token);
+
+}
+
+
+=head2 base_path
+
+By default, all "LetMe" actions live at URLs under '/let' inside your
+application.  Override this subroutine to change that.
+
+By default, it returns '/let/'
+
+=cut
+
+sub base_path {
+    return '/let/';
+
+}
+
+
+=head2 validate
+
+Returns true if the credentials the user presented validate ok.
+Returns false otherwise.
+
+=cut
+
+sub validate {
+    my $self = shift;
+
+    # email must exist
+
+    unless ($self->_user_from_email($self->email)) {
+        return undef;
+    }
+
+    unless ($self->path) {
+        return undef;
+    }
+    unless ($self->checksum_provided) {
+        return undef;
+    }
+
+
+    unless ($self->_correct_checksum_provided) {
+        return undef;
+    }
+
+    return 1;
+}
+
+
+=head2 _correct_checksum_provided
+
+Returns true if the checksum the user provided is correct. Doesn't
+actually do much input checking. You want to call "validate"
+
+=cut
+
+sub _correct_checksum_provided {
+    my $self = shift;
+    return undef
+        unless ( $self->checksum_provided eq $self->generate_checksum )
+        or
+        ( $self->checksum_provided eq $self->generate_koremutake_checksum );
+
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Logger.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Logger.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,174 @@
+use warnings;
+use strict;
+
+package Jifty::Logger;
+
+=head1 NAME
+
+Jifty::Logger -- A master class for Jifty's logging framwork
+
+=head1 DESCRIPTION
+
+Jifty uses the Log4perl module to log error messages. In Jifty
+programs there's two ways you can get something logged:
+
+Firstly, Jifty::Logger captures all standard warnings that Perl
+emmits.  So in addtion to everying output from perl via the 
+warnings pragmas, you can also log messages like so:
+
+    warn("The WHAM is overheating!");
+
+This doesn't give you much control however.  The second way
+allows you to specify the level that you want logging to
+occur at:
+
+    Jifty->log->debug("Checking the WHAM");
+    Jifty->log->info("Potential WHAM problem detected");
+    Jifty->log->warn("The WHAM is overheating");
+    Jifty->log->error("PANIC!");
+    Jifty->log->fatal("Someone call Eddie Murphy!");
+
+=head2 Configuring Log4perl
+
+Unless you specify otherwise in the configuration file, Jifty will
+supply a default Log4perl configuration.
+
+The default log configuration that logs all messages to the screen
+(i.e. to STDERR, be that directly to the terminal or to fastcgi's
+log file.)  It will log all messages of equal or higher priority
+to he LogLevel configuration option.
+
+    --- 
+    framework: 
+      LogLevel: DEBUG
+
+You can tell Jifty to use an entirely different Logging
+configuration by specifying the filename of a standard Log4perl
+config file in the LogConfig config option (see L<Log::Log4perl> for
+the format of this config file.)
+
+    --- 
+    framework: 
+      LogConfig: etc/log4perl.conf
+
+Note that specifying your own config file prevents the LogLevel
+config option from having any effect.
+
+You can tell Log4perl to check that file perodically for changes.
+This costs you a little in application performance, but allows
+you to change the logging level of a running application.  You
+need to set LogReload to the frequency, in seconds, that the
+file should be checked.
+
+    --- 
+    framework: 
+      LogConfig: etc/log4perl.conf
+      LogReload: 10
+
+(This is implemented with Log4perl's init_and_watch functionality)
+
+=cut
+
+use Log::Log4perl;
+use Carp;
+
+use base qw/Jifty::Object/;
+
+=head1 METHODS
+
+=head2 new COMPONENT
+
+This class method instantiates a new C<Jifty::Logger> object. This
+object deals with logging for the system.
+
+Takes an optional name for this Jifty's logging "component" - See
+L<Log::Log4perl> for some detail about what that is.  It sets up a "warn"
+handler which logs warnings to the specified component.
+
+=cut
+
+sub new {
+    my $class     = shift;
+    my $component = shift;
+
+    my $self = {};
+    bless $self, $class;
+
+    $component = '' unless defined $component;
+
+    # configure Log::Log4perl unless we've done it already
+    if (not Log::Log4perl->initialized) {
+       $class->_initialize_log4perl;
+    }
+    
+    # create a log4perl object that answers to this component name
+    my $logger = Log::Log4perl->get_logger($component);
+    
+    # whenever Perl wants to warn something out capture it with a signal
+    # handler and pass it to log4perl
+    my $previous_warning_handler = $SIG{__WARN__};
+    $SIG{__WARN__} = sub {
+
+        # This caller_depth line tells Log4perl to report
+        # the error as coming from on step further up the
+        # caller chain (ie, where the warning originated)
+        # instead of from the $logger->warn line.
+        local $Log::Log4perl::caller_depth = $Log::Log4perl::caller_depth + 1;
+
+        # If the logger has been taken apart by global destruction,
+        # don't try to use it to log warnings
+        if (Log::Log4perl->initialized) {
+            # @_ often has read-only scalars, so we need to break
+            # the aliasing so we can remove trailing newlines
+            my @lines = map {"$_"} @_;
+            $logger->warn(map {chomp; $_} @lines);
+            carp (map {chomp; $_} @lines);
+        }
+        elsif ($previous_warning_handler) {
+            # Fallback to the old handler
+            goto &$previous_warning_handler;
+        }
+        else {
+            # Now handler - just carp about it for now
+            local $SIG{__WARN__};
+            carp(@_);
+        }
+    };
+
+    return $self;
+}
+
+sub _initialize_log4perl {
+    my $class = shift;
+  
+    my $log_config
+        = Jifty::Util->absolute_path( Jifty->config->framework('LogConfig') );
+
+    if ( defined Jifty->config->framework('LogReload') ) {
+        Log::Log4perl->init_and_watch( $log_config,
+            Jifty->config->framework('LogReload') );
+    } elsif ( -f $log_config and -r $log_config ) {
+        Log::Log4perl->init($log_config);
+    } else {
+        my $log_level = uc Jifty->config->framework('LogLevel');
+        my %default = (
+            'log4perl.rootLogger'        => "$log_level,Screen",
+            '#log4perl.logger.SchemaTool' => "$log_level,Screen",
+            'log4perl.appender.Screen'   => 'Log::Log4perl::Appender::Screen',
+            'log4perl.appender.Screen.stderr' => 1,
+            'log4perl.appender.Screen.layout' =>
+                'Log::Log4perl::Layout::SimpleLayout'
+        );
+        Log::Log4perl->init( \%default );
+  }
+}
+
+=head1 AUTHOR
+
+Various folks at Best Practical Solutions, LLC.
+
+Mark Fowler <mark at twoshortplanks.com> fiddled a bit.
+
+=cut
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Manual/AccessControl.pod
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Manual/AccessControl.pod	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,149 @@
+=head1 NAME
+
+Jifty::Manual::AccessControl - Using Jifty's default ACL system
+
+=head1 DESCRIPTION
+
+
+Out of the box Jifty-based applications have an ACL system.  The system 
+automatically validates ACLs on L<Jifty::Record> objects by calling the method
+C<current_user_can> before any create, read, update, or delete operation.
+In all cases, the arguments passed to the CRUD operation are passed as 
+extra arguments to C<current_user_can>.
+
+On C<create()>, we reject the operation if C<current_user_can('create')>
+returns FALSE.
+
+On C<_value()> or C<I<somefieldname>>, we reject the operation
+if C<current_user_can('read')> returns false.
+
+On C<_set()> or C<I<set_somefieldname>>, we reject the operation
+if C<current_user_can('write')> returns false.
+
+
+On C<delete()>, we reject the operation  if C<current_user_can('delete')>
+returns false.
+
+Out of the box, C<current_user_can> returns 1. When you want to actually 
+check ACLs, you'll need to override C<current_user_can()> in your
+C<Jifty::Record> subclass.
+
+It's likely that at some point, you'll decide you want to ask other
+questions on certain types of operations.  Say, you only want to let
+administrators update the C<paid_account> field. In that case, you'd override
+C<check_update_rights()> to look for the C<admin> right rather than the 
+C<update> right, if the C<FIELD> is C<paid_account>.
+
+=head1 ENABLING ACCESS CONTROL USING THE LOGIN PLUGIN
+
+To painlessly enable the AccessControl subsystem, the
+C<Login> plugin may get enabled. This is done in the
+F<etc/config.yml> configuration file.
+
+    Plugins:
+      - Login: {}
+
+Then, create an C<App::Model::User> class that derives from
+C<Jifty::Plugin::Login::Model::User>, for example:
+
+    use strict;
+    use warnings;
+
+    package App::Model::User;
+    use base 'Jifty::Plugin::Login::Model::User';
+
+    # Your model-specific methods go here.
+
+    1;
+
+Next, create the table in your database using the F<jifty> executable
+like C<./bin/jifty schema --setup>.
+
+=head2 Expanding the Model
+
+The model that manages C<User> Records is not limited to the plugin's
+definition. It can be expanded by providing an additional schema
+definition. Every column here will be added to the plugin's
+columns. Simply add a schema definition block like this:
+
+    use Jifty::DBI::Schema;
+    use App::Record schema {
+        column 'extra_column_name';
+
+        # more columns if necessary
+    };
+
+The full syntax for defining a schema can be found in
+L<Jifty::Manual::Models> or in L<Jifty::DBI::Schema>.
+
+Defining a method C<_init> in your C<App::CurrentUser> class gives you
+a chance to add more data to the C<CurrentUser> object. This method
+will automatically get called after the Plugin's C<_init> is done.
+
+=head2 Templates defined by the C<Login> plugin
+
+To avoid the need for repetitive work, the C<Login> plugin already
+defines a couple of usable templates:
+
+=over 4
+
+=item F</login>
+
+provides a login screen with a signup option. After
+successful login, the current continuation is called. If no
+continuation exists, the template sitting at the base URL (F</>) is called.
+
+=item F</logout>
+
+logs out the current user.
+
+=item F</signup>
+
+allows a user to sign up himself/herself. By default
+a confirmation mail is sent out that has to get followed by
+the user.
+
+=item F</chgpasswd>
+
+allows a user to change his/her password.
+
+=item F</passwordreminder>
+
+after entering his/her mail address, the user will receive a mail that
+contains a link to F</let/reset_lost_password>.
+
+=item F</let/confirm_email>
+
+is called in the mail and results in accepting the user.
+
+=item F</let/reset_lost_password>
+
+enabled by the passwordreminder template, this template allows a user
+to reenter a password for future use.
+
+=back
+
+=head2 Doing checks at other places in your code
+
+If you need to check more than Model-based record operations you will
+have to do some coding on your own. C<< Jifty->web->current_user >> provides a
+C<App::CurrentUser> object that can get queried about the current user.
+This object provides some convenience methods:
+
+=over 4
+
+=item C<username>
+
+returns the name of the current user or C<undef> if not logged in.
+
+=item C<id>
+
+returns the id of the current user or C<undef> if not logged in.
+
+=back
+
+=head1 SEE ALSO
+
+L<Jifty::CurrentUser>, L<Jifty::Record>, L<Jifty::RightsFrom>
+
+=cut

Added: jifty/branches/schema-plugins/lib/Jifty/Manual/Actions.pod
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Manual/Actions.pod	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,386 @@
+=head1 NAME
+
+Jifty::Manual::Actions - Doing Stuff With Jifty
+
+=head1 DESCRIPTION
+
+C<Jifty::Action> abstracts around the idea of declaring named
+(L<"parameters"|Jifty::Manual::Glossary/parameter>) at compile time.
+At runtime, the action collects user input as
+(L<"arguments"|Jifty::Manual::Glossary/argument>), does something with them,
+and returns some result to the user.  If this sounds incredibly
+general, that's because it is -- actions do nearly B<everything> in Jifty.
+
+C<Jifty::Action> will also generate HTML for you from its parameters --
+no more manually writing C<< <input> >> tags and extracting GET and
+POST arguments by hand and dispatching them where they belong --
+C<Jifty::Action> does it all for you.
+
+=head1 WRITING ACTIONS
+
+Jifty provides some actions for you out of the box -- see
+L<Jifty::Manual::ObjectModel> and L<Jifty::Action::Record> for
+autogenerated actions, as well as L<Jifty::Action::Redirect>, but any
+non-trivial application will want to define actions of its own. This
+is how you do it.
+
+Every action is a subclass of Jifty::Action, as well as typically
+I<AppName>::Action. Actions usually live in the I<AppName>::Action::
+namespace; while that's just a convention, it will make your life easier
+if you follow it.
+
+This, the simplest possible action, is:
+
+    use warnings;
+    use strict;
+
+    package MyApp::Action::DoNothing;
+    use base qw/MyApp::Action Jifty::Action/;
+
+    1;
+
+(Instead of copying-and-pasting that, or typing it in, though, you
+could just run:
+
+    jifty action --name DoNothing
+
+in your application's directory, and Jifty would create a skeleton for
+you. )
+
+However, if you want to actually do something with your actions, you
+need to define two things: their L<parameters|/parameters>, and a
+L</take_action> method.
+
+=head2 parameters
+
+Every C<Jifty::Action> subclass should define a C<schema>, which contains
+some C<param> declarations that describes what arguments it takes.
+Supposing we were writing an action to post a blog article, we might start
+out with parameters like thus:
+
+    use Jifty::Param::Schema;
+    use Jifty::Action schema {
+
+    param 'title';
+    param 'category';
+    param 'body';
+
+    };
+
+However, we've only scratched the surface of the power the
+C<param> API offers.  Parameters can have types, labels,
+validators, canonicalizers, and even more. To start with, let's add
+some types and labels:
+
+    use Jifty::Param::Schema;
+    use Jifty::Action schema {
+
+    param title =>
+        label is 'Title',
+        max_length is 50,
+        is mandatory;
+
+    param category => 
+        label is 'Category',
+        max_length is 30;
+
+    param body =>
+        label is 'Entry',
+        render as 'Textarea';
+
+    };
+
+Now, we can ask the action to render form fields, and it will know how
+to display them. But, we can do even better. Let's improve the look of
+that C<category> field, by making it a combobox (a combination
+dropdown/text field), with some default values available:
+
+    # ...
+    param category => 
+        label is 'Category',
+        render as 'Combobox',
+        available are qw( Personal Work Block );
+    # ...
+
+But a static list is lame. What we really want is a C<Category> model,
+and to keep track of all the categories users have entered:
+
+    # ...
+    param categories => 
+        label is 'Category',
+        render as 'Select',
+        available are defer {
+            my $categories = MyBlog::Model::CategoryCollection->new;
+            $categories->unlimit;
+            [{
+                display_from => 'name',
+                value_from   => 'name',
+                collection   => $categories,
+            }];
+        }
+    ...
+
+Now, Jifty will populate the combobox with the result of calling C<name>
+on each element in C<$categories>. Alternatively, if you set
+C<< value_from => 'id' >>, Jifty would automatically return the C<id> of
+the category, for easy database reference. We don't do this with the
+combobox, however, since a combobox displays the selected value in its
+text field.
+
+See L<Jifty::Action> and L<Jifty::Web::Form::Field> for more fields
+you can set in the C<param> declaration, and see L<Jifty::Param::Schema>
+for more about the syntax.
+
+=head2 validation
+
+C<Jifty::Action> can automatically validate arguments for you, as
+appropriate. If an argument has C<valid_values>, then C<Jifty::Action>
+will automatically verify if the given value matches one of
+them. However, you can also write your own validators. Just write a
+C<< sub validate_<parameter> >>, and it will be called as appropriate:
+
+    use Regexp::Common 'profanity_us';
+
+    sub validate_body {
+       my $self = shift;
+       my $body = shift;
+       if ( $body =~ /$RE{profanity}/i) {
+           return $self->validation_error(
+               body => 'Would you speak like that in front of your mother? *cough*'
+           );
+       }
+       return $self->validation_ok('body');
+    }
+
+You can also do validation in the model -- see
+L<Jifty::Action::Record> 
+
+=head2 canonicalization
+
+If, instead of failing, you want to automatically modify
+invalid content to be valid, you want a
+L<canonicalizer|Jifty::Manual::Glossary/canonicalize>, not a
+validator.
+
+    use Regexp::Common 'profanity_us';
+
+    sub canonicalize_body {
+       my $self = shift;
+       my $body = shift;
+       $body =~ s/$RE{profanity}/**expletives**/gi;
+       return $body;
+    }
+
+A L<canonicalizer|Jifty::Manual::Glossary/canonicalize> can also 
+change other parts of the action.  This lets you update the display
+dynamically in an L<AJAX|Jifty::Manual::Glossary/ajax>-enabled browser
+based on what the user has entered.  For example, we can let a user
+use magic syntax to provide tags for their blog post by surrounding the 
+tags with square brackets.  You can also let the user know you're
+doing something magical by using C<canonicalization_note> which 
+will display a message to the user.
+
+    use Jifty::Param::Schema;
+    use Jifty::Action schema {
+        param title =>
+            label is 'Title',
+            hints is "You can provide tags like this [tag1 tag2]",
+            ajax canonicalizes;
+
+        param tags =>
+            label is 'Tags';
+    };
+
+    sub canonicalize_title {
+        my $self = shift;
+        my $value = shift;
+
+        if ($value =~ s/\[(.*?)\]//) {
+            # this clobbers, may want to merge
+            $self->argument_value( tags => $1 );
+            $self->canonicalization_note(
+                title => 'Removed tags from your title'
+            );
+        }
+
+        return $value;
+    }
+
+
+If you set C<ajax validates> or C<ajax canonicalizes>
+for an argument, then Jifty will automatically validate or
+canonicalize it in an L<AJAX|Jifty::Manual::Glossary/ajax>-enabled
+browser when the user stops typing and puts the focus out of
+the corresponding form field.
+
+=head2 take_action
+
+Once an action has arguments, it needs to do something with them. An
+action does so in its C<take_action> sub, which will be called when an
+action is submitted, and only if its arguments
+L<validate|/validation>. 
+
+Inside C<sub take_action>, subclasses can access their arguments via
+C<< $self->argument_value('foo') >>. If you need to check whether you've
+been passed an argument or not (as opposed to being passed a true
+argument or not), use C<< $self->has_argument('foo') >>.
+
+Once an action has done its task, it needs to inform the caller
+whether or not it has succeeded, possibly with some status message. To
+this end, every C<Jifty::Action> has a C<Jifty::Result> associated
+with. C<Jifty::Result> carries both a failure/sucess code, and a
+textual message describing the result of running the action.
+
+Thus, if your action failed for some reason, you would, in
+C<take_action>, write code like:
+
+    $self->result->error('Couldn't write blog post');
+    return;
+
+If, however, the action completed successfully, you might write:
+
+    $self->result->message('Posted to your blog');
+
+Actions will default to successful with an empty message if you don't
+do anything with the result object. Additionally, if you need to return more semantic
+information than a simple message, you can set arbitrary content on
+the result, using $self->result->content, e.g:
+
+    $self->result->content( id => $new_post->id);
+
+This information can be then used elsewhere to, for example,
+automatically redirect you to a view page for that new blog post. The
+view page template may have the following piece of code in it:
+
+    <%args>
+    $id => undef
+    </%args>
+    <%init>
+    my $result = Jifty->web->response->result('post_blog');
+    $id = $result->content('id') if $result and !defined $id;
+    # load the record by $id and other stuff go here...
+   </%init>
+
+where C<'post_blog'> is the moniker for your post page action object.
+In fact, that's exactly how actions "return" values to other components 
+in your application.
+
+Mutiple action "return values" are possible and arbitrary data structures
+can be passed too:
+
+    $self->result->content( keys   => $keys );
+    $self->result->content( result => $collection);
+
+It should also be mentioned that the response object is "per request". That is,
+it usually can't live up to another user request. Therefore, when paging mechanism 
+is applied to your view page, for example, you have to either pass some data 
+to the link constructor or explicitly tell Jifty to preserve states for you.
+
+See L</monikers>, the Jifty Pony site's source, and L<Jifty::Request::Mapper> for some 
+more information.
+
+=head1 USING ACTIONS
+
+At their simplest, you can create and run actions yourself, e.g.:
+
+    Jifty->web->new_action(
+        class     => 'PostBlogEntry',
+        arguments => {
+            title    => 'A boring blog entry',
+            category => 'Jifty',
+            body     => 'This blog entry is lame.'
+        }
+    )->run;
+
+Note that C<< Jifty->web->new_action >>, and all similar methods
+(e.g. L<Jifty::Request::add_action|Jifty::Request/add_action>,
+L<Jifty::Web::Form::add_action|Jifty::Web::Form/new_action>), will
+automatically qualify the C<class> with either C<Jifty::Action::> or
+C<I<AppName>::Action::> as necessary (I've told you putting actions in
+I<AppName::Action::> would make your life easier!)
+
+In practice, you'll rarely provide actions with arguments
+yourself. Instead, you'll create an action with no or partial
+arguments, often in the L<dispatcher|Jifty::Dispatcher>, or a Mason
+component's C<< <%init%> >> block (See L</constructor arguments> for
+details about passing arguments to actions on creation).
+
+    my $create = Jifty->web->new_action(
+        class   => 'PostBlogEntry',
+        moniker => 'post_blog'
+    );
+
+Having created the action, you will, in one of your Mason components,
+output a form where the user can fill in the action's arguments:
+
+    <% Jifty->web->form->start %>
+    <div class="post-metadata">
+      <% $create->form_field('title') %>
+      <% $create->form_field('category') %>
+    </div>
+      <% $create->form_field('body') %>
+    <% Jifty->web->form->submit(label => "Post") %>
+    %# or <% Jifty->web->form->link(label => "Post", submit => $create) %>
+    %# or <% $action->button(label => "Post"); %>
+    <% Jifty->web->form->end %>
+
+C<form_field> will render the field, along with the C<label> as an
+HTML C<< <input> >> tag that Jifty knows how to interpret to feed back
+to your action as an argument when the form is submitted. If you need
+to change the appearance of the field, Jifty outputs classes on the
+fields, as well as providing some semantic C<< <div> >>s you can style
+using CSS. (See L<Jifty::Manual::UsingCSSandJS> for some more details.)
+
+See L<Jifty::Web::Form/submit>, L<Jifty::Web/link> and
+L<Jifty::Action/button> for details on the different ways to generate
+a submit button.
+
+Additionally, instead of C<form_field>, you can use C<hidden> to
+generate a C<hidden> input, which will not be viewable or editable in
+a web browser. (Note that a knowledgeable user I<can> still submit a
+form with a different value for that hidden input; If this concerns
+you, make sure you have appropriate
+L<ACLs|Jifty::Manual::AccessControl> in place. If it still worries
+you, you probably want a L<continuation|Jifty::Continuation> here.)
+
+=head2 monikers
+
+You probably noticed the C<< moniker => 'post_blog' >>. Every action you
+create in Jifty has an associated
+L<moniker|Jifty::Manual::Glossary>. A C<moniker> is simply a unique
+identifier for the action (unique per request, which in practice
+typically means per HTML page). Since actions are constantly being
+serialized (over HTTP, or Javascript AJAX calls, and so on), and
+unpacked, we need a way refer to specific actions other than just
+object identity, e.g. to extract its arguments or results in the
+L<dispatcher|Jifty::Dispatcher> or a template. Monikers give us that. Given a
+moniker, you can pull information about the associated action out of a
+L<request|Jifty::Request> or L<response|Jifty::Response>.
+
+If a moniker is unspecified, it will be autogenerated.
+
+(XXX TODO Note about action registration here)
+
+=head2 Argument Folding
+
+If you write out more than one C<form_field> for a given argument in
+the same form, and more than one is filled in, Jifty will C<fold> the
+arguments into an array before filling them in to the action. This
+provides a way to do, e.g. a C<BulkEdit> action that applies some set
+of changes to many records at once.
+
+(XXX TODO Note about C<constructor> parameters)
+
+=head1 ACTIONS AS WEB SERVICES
+
+Your actions are also automatically published as web services.
+Clients can POST requsets, usually using the YAML or JSON request
+format.  See C<bin/service> for a trivial generic webservice client.
+
+(XXX TODO More about webservices)
+
+=head1 SEE ALSO
+
+L<Jifty::Action>, L<Jifty::Manual::Tutorial>
+
+=cut

Added: jifty/branches/schema-plugins/lib/Jifty/Manual/Continuations.pod
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Manual/Continuations.pod	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,250 @@
+=head1 NAME
+
+Jifty::Manual::Continuations - There And Back Again
+
+=head1 DESCRIPTION
+
+Continuations are a powerful concept in computer science -- in a
+nutshell, they allow you to store away the state of of the interpreter
+at any given point.  More importantly, they allow you to return to
+that state at any later time, by calling the continuation with, and
+evaluation of that interpreter state will resume.  They're a concept
+that first arose in LISP, but has implementations these days in Ruby,
+Scheme, Haskell, Smalltalk, to name a few.
+
+Thus, continuations allow you to preserve context, and return to it
+later.  This is amazingly useful in web programming, which is limited
+to C<HTTP>, which is an inherently stateless protocol.  By passing
+around continuations, we can keep track of the context that got us to
+the current page.
+
+While we can't construct I<full continuations> at the interpreter level
+-- because Perl doesn't support them -- we can implement them at the
+level of HTTP requests.  In technical terms, because they capture the
+control stack up from the beginning of a user's session, they are
+called I<delimited continuations>.
+
+Continuations are more useful than session because sessions store
+information across browser windows; sessions may also break in the
+presence of the back button, as the information displayed on the
+screen, and the information stored in the session may differ.  Since
+continuations are immutable, and a new one is produced every time a
+change is made, the information displayed in the browser cannot get
+out of sync with the information contained in any associated
+continuation.
+
+=head1 USING CONTINUATIONS
+
+=head2 As simple links in templates
+
+The simplest form of continuation use is in a template, using
+L<Jifty::Web/tangent>, as follows:
+
+    <% Jifty->web->tangent( url   => "/someplace",
+                            label => "Go someplace") %>
+
+This will create a link, which, when clicked, will store the current
+request into a continuation, and jump to the url C</someplace>.  In the
+C</someplace> template, you can display information, and possibly have
+the user navigate between multiple pages before returning to the previous page:
+
+    <% Jifty->web->return( label => "Back to whence you came" ) %>
+
+Because this C<return> does not carry a result value, you can think of
+it as a form of C<gosub>.  In comparison, ordinary hyperlinks are akin to
+C<goto> staements.
+
+Sometimes, it may be possible for the user to get to a location
+without having a continuation set.  In that case, clicking on the
+"Back to whence you came" link will appear to do nothing -- which may
+be slightly confusing to the user.  To remedy this, Jifty provides a
+way to specify a default location to return to:
+
+    <% Jifty->web->return( to => "/default", label => "Go back" ) %>
+
+=head2 Using return values
+
+All of the above examples generate links, which means that they don't
+interact at all with actions.  However, continuations can also be
+useful in creating complex multi-page actions.
+
+Continuations are saved -- and the browser is redirected to the new
+URL -- just after all actions have been checked for validation but
+before any of them are run.  This means that the new request has
+access to the full validation state of its parent's actions.
+
+When a continuation is called, it first checks that all actions in the
+request were successful; if any failed, then the continuation is
+B<not> called.  If the request's actions were all successful, it
+merges together the L<Jifty::Result>s of current L<Jifty::Response>
+with those in the L<Jifty::Response> stored in the continuation.  In
+doing so, parameters are mapped using L<Jifty::Request::Mapper>.  This
+makes it possible to return values from continuations into arbitrary
+places.  For example:
+
+    % my $action = Jifty->web->new_action(class => 'AddTwoNumbers');
+    <% Jifty->web->form->start %>
+    <% $action->form_field( 'first_number' ) %>
+    <% $action->form_field( 'second_number',
+           default_value => {
+               request_argument => "number",
+           }
+       ) %>
+    <% Jifty->web->tangent(
+            url    => '/pagetwo',
+            label  => 'Enter a second number',
+            submit => $action
+       ) %>
+    <% Jifty->web->form->end %>
+
+..and in C</pagetwo>:
+
+    <% Jifty->web->form->start %>
+    <input type="text" name="number" />
+    %# We use as_button to tell Jifty that we want a button, not a link
+    <% Jifty->web->return( label => 'Pick', as_button => 1 ) %>
+    <% Jifty->web->form->end %>
+
+..and assuming that C<AddTwoNumbers>'s C<take_action> resembles:
+
+    sub take_action {
+        my $self = shift;
+        my $one = $self->argument_value("first_number");
+        my $two = $self->argument_value("second_number");
+        $self->result->message("Got " . ($one + $two));
+    }
+
+The first page renders the entry box for the first number; the second
+input is hidden because Jifty notices that it is based on a mapped
+value: i.e., its default is set to C<< {request_argument => "number"} >>
+instead of a plain scalar value.
+
+Pressing the button validates the action but does not complete
+running it.  At this point, the C<second_number> argument to the
+C<AddTwoNumbers> action has no real value -- however, it knows that it
+will, at the earliest possible opportunity, fill in its value from the
+C<number> request parameter.
+
+Jifty tangents to C</pagetwo>, where we enter and submit a C<number>
+argument.  Control then returns to the original page, where the request
+mapper maps the C<number> value into the C<second_number> argument of the
+C<AddTwoNumbers> action, which then runs because it has received all
+arguments it requires.
+
+Note that in the example above, the C<number> argument is a plain request 
+argument, not part of another action.  More complex mappings are possible,
+including grabbing the results of or arguments to actions.  This would make
+it possible, for instance, to use an action on the second page to validate the
+number before returning.  This is slightly different from placing a validator
+on the C<AddTwoNumbers> action, as that validator only gets called I<after>
+control has already returned to the first page.
+
+=head2 As dispatcher rules
+
+The L<Jifty::Web/tangent> function is context-aware -- if it is called
+in void context, it immediately saves the continuation and redirects to
+the new url.  This is particularly useful, say, for authentication
+protection in C<before> blocks:
+
+    before '/protected' => sub {
+        # shorthand for: Jifty->web->tangent( url => '/login' )
+        tangent('/login') unless Jifty->web->current_user->id;
+    };
+
+And in the C</login> template:
+
+    % my $action = Jifty->web->new_action(class   => 'Login',
+    %                                     moniker => 'loginbox' );
+    <% Jifty->web->form->start %>
+    <% $action->form_field('username') %>
+    <% $action->form_field('password') %>
+    <% Jifty->web->return( to     => "/protected",
+                           label  => 'Login',
+                           submit =>  $action) %>
+    <% Jifty->web->form->end %>
+
+This establishes a button, which, if the C<Login> action is
+successful, calls the stored continuation, or, lacking one, redirects
+to C</protected>.
+
+As currently impelented, these redirect-from-dispatcher tangents works
+exactly like rendered-as-links tangents, in that when they return,
+I<all> rules in the dispatcher are still executed from the start.
+Therefore the C<unless> guard in the C<before '/protected'> rule above
+is neccessary to prevent recursion.
+
+=head1 GORY DETAILS
+
+Jifty's continuations are implemented in L<Jifty::Continuation>, which
+is very little more than a place to store a L<Jifty::Request> and its
+associated L<Jifty::Response>.
+
+The following diagram diagrams the stages of continuation handling,
+and their interaction with the dispatcher.  For clarity, the page
+region handling code is included, but page regions do not currently
+interact with continuation processing.
+
+                                /--------------\
+          +---------------------v-+            |
+          |........Request........|            |
+          +-|-------------------|-+            |
+            |                   |  RETURN  +---|---------------------+
+    /----\  |                   \----------> Replace request with    |
+    |  +-|--|-+ +==============+           | request in continuation |
+    |  |.v..v.---> SETUP rules |           +-------------------------+
+    |  |......| +==============+
+    |  |..D...|
+    |  |..I...| +~~~~~~~~~~~~~~~~~~~+      +-------------------------+
+    |  |..S...---> Validate actions |      | Store current request   |
+    |  |..P...| +~~~~~|~~~~~~~~~|~~~+ SAVE | and response, redirect  |
+    |  |..A...|       |         \----------> to new scope and URL    |
+    |  |..T...|       |                    +-------------------------+
+    |  |..C...| +~~~~~v~~~~~~~~~~~~~+
+    |  |..E...| |  Run actions      |      +-------------------------+
+    |  |..R...| +~~~~~~~~~~~~~~~|~~~+ CALL | Merge results into the  |
+    |  |......|                 \----------> continuation's results; |
+    |  |......|                            | redirect to return URL  |
+    |  |......| +==============+           +-------------------------+
+    |  |......---> RUN rules   |
+    |  |......| +=====|========+
+    |  |......|       |
+    |  |......|    +--v---------------+
+    |  |......|    | Show templates   |
+    |  |......|    +-------|----------+
+    |  |......|            |
+    |  |......|    +-------v----------+
+    |  |......|    | Show page region ---------------------\
+    |  |......|    +------------------+                    |
+    |  |......|                                            |
+    |  |......| +==============+                           |
+    |  |......---> AFTER rules |                           |
+    |  +------+ +==============+                           |
+    |                                                      |
+    \------------------------------------------------------/
+
+As shown in the diagram above, there are three different operations
+that continuations use.  The first is C<SAVE>, which is triggered by
+the query parameter L<J:CREATE>.  Continuations are saved after
+validating actions; the continuation itself is attached to the user's
+session object.
+
+The current saved continuation is automatically preserved across
+requests.  When the time comes to call the continuation, the C<CALL>
+operation is performed; this is usually triggered by the presence of
+the L<J:CALL> query parameter.  This causes the stored request to be
+query-mapped using L<Jifty::Request::Mapper>, but using the B<current>
+request and response (I<not> the continuation!) as the sources for mapping
+values.  Then, the result objects are merged, with results from the
+stored response taking precedence.  This new mapped request and new
+merged response are formed into a new continuation.
+
+In order to ensure that the browser's URL matches the URL of the
+request in the continuation, Jifty then does a redirect to the URL of
+the request stored in the continuation, starting the last continuation
+operation, the C<RETURN>.  When Jifty detects the C<RETURN> operation,
+most often by the presence of C<J:RETURN>, it loads the continuation
+and reads the stored request and response into the current request and
+response.
+
+=cut
+

Added: jifty/branches/schema-plugins/lib/Jifty/Manual/Cookbook.pod
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Manual/Cookbook.pod	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,323 @@
+=head1 NAME
+
+Jifty::Manual::Cookbook
+
+=head1 DESCRIPTION
+
+This document aims to provide solutions to common questions of "How do
+I do I<x> with Jifty?" While the solutions here certainly aren't the
+only way to do things, they're generally the solutions the developers
+of Jifty use, and ones that work pretty well.
+
+=head1 HOW DO I ...
+
+=head2 Create an LDAP autocomplete field
+
+You need an action in your application. Then run
+
+  jifty action --name LdapSearch
+
+in C<lib/myApp/Action/LdapSearch.pm> add the C<search> field
+
+  use Jifty::Action schema {
+    param search =>
+        autocompleter is \&ldap_search;
+  }
+
+we need L<Net::LDAP> and an accessor to our LDAP value.
+
+  use Net::LDAP;
+
+  __PACKAGE__->mk_accessors(qw(LDAP));
+
+and we can write our C<ldap_search> fonction. 
+Search need at least 3 characters and return an array of C<DisplayName (login)>
+
+  sub ldap_search {
+    my $self = shift;
+    my $search = shift;
+    my @res;
+    if (length $search > 2) {
+         if (! $self->LDAP() ) {
+            $self->LDAP( Net::LDAP->new('ldap.myorg.org');
+            $self->LDAP()->bind( );
+        }
+
+        $self->LDAP()->search(
+          base    => 'ou=people,dc=myorg,dc=org',
+          filter => '(cn='.$filter.')',
+          attrs   =>  ['uid','cn','givenname','displayname'],
+          sizelimit => 10
+          );
+
+        foreach my $entr ( $result->sorted('cn') ) {
+            push @res, $entr->get_value('displayname').' ('.$entr->get_value('uid').')';
+        }
+    }
+    return @res;
+  }
+
+=head2 Add Atom/RSS Feeds ?
+
+You could generate atom/rss feeds for virtually any model in your application.
+For instance, suppose there's a "Post" model (like a blog entry), you could use
+L<XML::Feed> to do this:
+
+    # In '/feed' template
+    <%args>
+    $type
+    </%args>
+    <%init>
+    use XML::Feed;
+    my $posts = MyApp::Model::PostCollection->new();
+    $posts->unlimit();
+
+    my $feed = XML::Feed->new( $type );
+    $feed->title( Jifty->config->framework('ApplicationName') . " Feed" );
+    $feed->link( Jifty->web->url );
+    $feed->description( $feed->title );
+
+    while( my $post = $posts->next ) {
+        my $feed_entry = XML::Feed::Entry->new($type);
+        $feed_entry->title($post->title);
+        $feed_entry->author($post->author->username);
+        $feed_entry->link( Jifty->web->url . "/posts/" . $post->id );
+        $feed_entry->issued( $post->created_on );
+        $feed_entry->summary( $post->body );
+        $feed->add_entry( $feed_entry );
+    }
+    </%init>
+    <% $feed->as_xml |n %>
+
+And add this in F<MyApp/Dispatcher.pm> to make URI look prettier:
+
+    on qr{^/feed/(atom|rss|rss2)}, run {
+        set type => $1;
+        show('/feed');
+    };
+
+And of course, you need to put these in your HTML header template
+(conventionally that's C</_elements/header>):
+
+    <link rel="alternate" type="application/atom+xml" title="Atom" href="/feed/atom" />
+    <link rel="alternate" type="application/rss+xml" title="RSS" href="/feed/rss" />
+
+=head2 Use date or time objects with the database?
+
+On your columns, specify either
+
+    filters are 'Jifty::DBI::Filter::DateTime'
+
+for a timestamp (date and time), or
+
+    filters are 'Jifty::DBI::Filter::Date'
+
+for just a date. Jifty will then automatically translate to and from
+DateTime objects for you when you access the column on your
+model. Additionally, if you add:
+
+    filters are qw(Jifty::Filter::DateTime Jifty::DBI::Filter::Date)
+
+Jifty will inspect the model's
+L<current_user|Jifty::Manual::AccessControl> for a C<time_zone>
+method, and, if it exists, set the retrieved DateTime object's time
+zone appropriately. All dates are stored in UTC in the database, to
+ensure consistency.
+
+=head2 How do I emulate 'created_on' field like Rails ?
+
+In Rails, if you have a field named 'created_on', it's automatically
+set to the creation time of the record. How can I emulate this
+behaviour in Jifty ?
+
+The trick here is to use L<Scalar::Defer>. And declare your column
+like this:
+
+    column created_on =>
+        type is 'timestamp',
+        label is 'Created On',
+        default is defer { DateTime->now },
+        filters are 'Jifty::DBI::Filter::DateTime';
+
+This approach is not really accurate, if you render this field in a
+form, then the defer value is evaluated by the time of rendering,
+which might be way eariler then the creation of record. However, it is
+the easiest one.
+
+If you're using the newly recommeded C<JIfty::DBI::Record schema {}>
+to declare schemas, you might find this trick not working at the moment.
+Please override model's C<before_create> method instead:
+
+    sub before_create {
+        my ($self, $attr) = @_;
+        $attr->{'created_on'} = DateTime->now;
+    };
+
+=head2 Limit access to pages to logged-in users
+
+The best place to do this is probably in your application's
+L<Dispatcher|Jifty::Dispatcher>. If, for example, you wanted to limit
+access to C</secret> to logged-in users, you could write:
+
+    before qr'^/secret' => run {
+        unless(Jifty->web->current_user->id) {
+            Jifty->web->tangent('/login');
+        }
+    };
+
+Then, in your login form component, you would write something like:
+
+    <% Jifty->web->return(to => '/', submit => $login_action) $>
+
+The combination of the C<tangent> and C<return> will cause the user to
+be returned to wherever they came from. See L<Jifty::Continuation> for
+more information.
+
+If you want model-level access control, Jifty provides a ready-built
+ACL system for its models; See L<Jifty::Manual::AccessControl> for
+details.
+
+Finally, you can also allow or deny specific actions in the
+dispatcher, to limit who is able to perform what actions -- see
+L<Jifty::API>.
+
+=head2 Run my Jifty app as fascgi in Apache/Lighttpd ?
+
+Jifty provides a really simple way to run the application as a fastcgi
+server. The complete instructions and examples are in C<'jifty help
+fastcgi'> for both Apache servers and Lighttpd servers. (Please C<cd> to
+your app dir before running this command.)
+
+You'll have to install C<CGI::Fast> and C<FCGI> module for this.
+
+=head2 Take actions based on data in URLs
+
+You can add actions to the request based on data in URLs, or anything
+else, using
+L<Jifty::Request::add_action|Jifty::Request/add_action>. For example,
+suppose you wanted to make the path C</logout> log the user out, and
+redirect them to the home page. You could write:
+
+    before '/logout' => {
+        Jifty->web->request->add_action( class => 'Logout' );
+        Jifty->web->request->add_action( class     => 'Redirect',
+                                         arguments => { url => '/' });
+    };
+
+=head2 Pass HTML form input directly to components
+
+Sometimes you don't want to take an action based on input from HTML
+forms, but just want to change how the page is displayed, or do
+something similarly transient.
+
+C<Jifty::Action> is great, but it doesn't have to be the answer to
+everything. For cases like this, it's fine to use typical HTML C<<
+<input>s >>. Their values will be accessible as request arguments, so
+you can fetch them with C<get> in the dispatcher, and they will be
+passed as arguments to top-level Mason components that list them in
+C<< <%args> >>. And don't worry about namespace conflicts with Jifty's
+auto-generated argument fields -- Jifty prefixes all its C<name>s with
+C<J:> so there won't be a problem.
+
+=head2 Perform database migration
+
+Edit etc/config.yaml and change Database->Version to a proper value
+(say, 0.0.2). Then run
+
+    jifty schema --setup
+
+Jifty would inspect the current database and perform proper actions.
+You could give a C<--print> option to see the actual SQL statements:
+
+    jifty schema --setup --print
+
+
+=head2 Use different table names than the ones Jifty automatically creates
+
+In YourApp::Record, define a C<_guess_table_name> sub that doesn't
+pluralise or pluralises differently.
+
+
+=head2 How do I perform ajax canonicalization on a given field ?
+
+Asking user to input something in a form is really common in a web
+app. For some certain form fields you want them to have a certain
+normalized/canonicalized form in the database, and you could do an ajax
+canonicalization in Jifty very easily. Let's say your User model needs a
+canonicalized C<username> field to make sure those names are in
+lowercase.  All you have to do is to define a method named
+C<canonicalize_username> in your Model class, like this:
+
+    package MyApp::Model::User;
+    use base qw(MyApp::Record);
+
+    sub canonicalize_username {
+        my $class = shift;
+        my $value = shift;
+        return lc($value);
+    }
+
+If the form is generated by a C<Jifty::Action::Record>-based action
+(all those autogenerated CRUD actions), then this is all you need to
+do. And that is probably 90% of the case.  C<Jifty::Action::Record>
+would check if there is a method named like C<canonicalize_fieldname>
+when it is rendering form fields. If found, related javascript code is
+generated. You do not have to modify any code in your view. Jifty does
+it for you.
+
+The ajax canonicalization happens when the input focus leaves that
+field. You would see the effect a bit later than the value in the
+field is changed.
+
+Of course, you can apply the same trick to your own Action classes.
+
+=head2 Get the "Edit" links to work in the "Page info" tool
+
+If you click on "Page info", follow an "Edit" link for a fragment,
+and you get an error message saying something like
+
+	You got to a page that we don't think exists...
+
+then you need to ensure that the C<Jifty::Plugin::EditInPlace> has
+been installed correctly. It should be in the F<Jifty/Plugin> directory
+installed with the rest of the Jifty modules.
+
+	perl -MJifty::Util -e 'print Jifty::Util->jifty_root'
+
+will tell you where to look. If it's not there you will need to install
+it manually. If you still have your build files from installing Jifty
+you can find the module in the F<plugins> directory.
+
+Once you have the modules in place, or if they were already present
+you simply need to enable the plugin in your project config file. Edit 
+F<etc/config.yml> and find the "Plugins:" section. It will probably look
+like this
+
+	  Plugins: []
+
+Change it to 
+
+	  Plugins: 
+	    - EditInPlace: {}
+
+Take care the indentation is significant. Use spaces, not tabs.
+Once you have done that you can restart your Jifty application and the
+edit links should then function correctly.
+
+=head2 Use iepngfix.htc to add PNG support in IE5.5+
+
+Jifty has included iepngfix.htc by Angus Turnbull. The HTC file will
+automatically add PNG support to IMG elements and also supports any
+element with a "background-image" CSS property.
+
+If you want to use this fix, please include this one line in your CSS
+file, whit tag names to which you want the script applied:
+
+    img, div { behavior: url(/static/js/iepngfix.htc) }
+
+Alternatively, you can specify that this will apply to all tags like so:
+
+    * { behavior: url(/static/js/iepngfix.htc) }
+
+Check details from Angus himself. ( http://www.twinhelix.com/ )
+

Added: jifty/branches/schema-plugins/lib/Jifty/Manual/FAQ.pod
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Manual/FAQ.pod	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,26 @@
+=head1 NAME
+
+Jifty::Manual::FAQ - Frequently Answered Questions
+
+=head1 General
+
+=head2  I need to make a web based program, is jifty mature enough?
+
+We built hiveminder.com with it.
+
+=head2 What databases does Jifty support?
+
+Jifty uses Jifty::DBI as its ORM, which uses DBI to connect to the
+underlying database.  SQLite is the default, but Postgres, MySQL, and
+maybe others work great.
+
+=head2 Does Jifty run on Windows?
+
+Yes.  There have been some hiccups in the dependencies, but those are
+being resolved.
+
+=head2 Where is the subversion repository?
+
+  http://svn.jifty.org/svn/jifty.org/jifty/trunk
+
+=cut

Added: jifty/branches/schema-plugins/lib/Jifty/Manual/Glossary.pod
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Manual/Glossary.pod	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,144 @@
+=head1 NAME
+
+Jifty::Manual::Glossary
+
+=head1 GLOSSARY
+
+We use words.  This is what they mean.
+
+=over 4
+
+=item action
+
+An B<action> is a specifically designed RPC call that can do something to the
+system, with any number of declared L</parameter>s.  At runtime, an action
+can take L</argument>s, which it L</canonicalize>s, L</validate>s, and then
+uses to do something useful.  Each action has a L</result>.  See
+L<Jifty::Action> and L<Jifty::Manual::Actions>.
+
+=item active
+
+For an L</action> to run, it needs to be B<active>.  Most of the time, all
+actions submitted are active, but it is possible to specify only a specific
+action as active; any non-active actions are ignored.  See L<Jifty::Request>.
+
+=item AJAX
+
+An acronym standing for B<Asynchronous Javascript And XML>.  Though technically
+incorrect, it is the buzzword that describes doing asynchronous requests to the
+server while the user waits.  This can lead to very "dynamic" pages, as the
+browser does not need to refresh the entire page to update a small section of
+the screen.  In Jifty, the sections of the screen are called L</region>s.  See
+L<Jifty::Web::PageRegion>.
+
+=item argument
+
+An B<argument> is a user-supplied input to fill in a L</parameter> in an
+L</action>.  See L<Jifty::Action>.
+
+=item canonicalize
+
+To turn an L</argument> into a more standard form.  For instance, a
+canonicalizer could translate a user-typed date into a date object or a
+SQL-formatted date string.  See L<Jifty::Action>.
+
+=item constructor
+
+A property of a L</parameter>; the action B<must> have an argument value for
+this paramater in order to be constructed.  This is different from
+L</mandatory>, in that the user can leave mandatory fields empty.  For
+instance, the C<id> of a L<Jifty::Action::Record::Update> is a constructor.
+See L<Jifty::Action>.
+
+=item continuation
+
+A concept stolen from Lisp, Scheme, Smalltalk, and Perl 6.  The continuation of
+any particular piece of code is the deferred operations that care about the
+return value at that point.  In the context of Jifty, a continuation is a
+deferred L</request> that may pull L</argument>s and the like from the
+L</result>s of the current request.  Continuations can be arbitrarily nested,
+so they are often useful to keep track of tangents that the user went on.  See
+L<Jifty::Continuation>.
+
+=item form field
+
+A widget which the browser renders.  These are generally useful to ask the user
+for a value for an L</argument> to an L</action>.  See
+L<Jifty::Web::Form::Field>.
+
+=item fragment
+
+A section of HTML (at present, a Mason component) contained in a L</region>.
+Fragments are a kind of standalone Mason component which the browser can request
+individually.  Because of this, they can only take strings and scalars as
+arguments, not references or objects!
+
+=item element
+
+A Mason component used by one or more other pages, which is not a whole page of
+itself. As opposed to L<fragments|/fragment> C<elements> are strictly internal,
+and never visible to the outside world by themselves. Elements typically live
+under a path beginning with or containing '/_elements'. This, and the whole idea
+of an element is strictly convention, but Jifty contains elements for things
+like page headers, menus, and showing keybindings out of the box to make your
+life easier.
+
+=item mandatory
+
+A property of a L</parameter>; the user must enter a value for the action to
+validate.  This is the simplest level of L<validation|/validate>.
+
+=item moniker
+
+Every instance of a L<Jifty::Action> has a B<moniker>.  A moniker is
+an arbitrary-length nonempty string containing no semicolons.
+Monikers serve as identifiers for actions, to associate arguments with
+actions and to access specific actions "by name".  Monikers need not
+be globally unique, but they must be unique within a single request.
+Monikers have no semantic meaning. See L<Jifty::Action/monikers>
+
+=item parameter
+
+A B<parameter> is a named parameter to an L</action>.  Jifty generally renders
+these on the screen as L</form field>s.  See L<Jifty::Param> and
+L<Jifty::Param::Schema>.
+
+=item region
+
+An area of the page which JavaScript can replace.  The content in the region is
+a L</fragment>.  Think of the region as the box and the fragment as the content
+in the box.  See L<Jifty::PageRegion>.
+
+=item request
+
+A single query which lists L</action>s to run, together with a page or list of
+L</fragment>s to return.  This most often comes from the browser as query
+parameters, but may come from other sources as a L<JSON> or L<YAML> POST
+request.  The answer to a request is a L</response>.  See L<Jifty::Request>.
+
+=item response
+
+The answer to a L</request>, it contains a L</result> for every action that
+ran.  See L<Jifty::Response>.
+
+=item result
+
+The answer to a L</action>, it contains information about if the action was a
+success or failure, and further detail about why or how.  It can also contain
+arbitrary "content".  See L<Jifty::Result>.
+
+=item sticky
+
+A property of L</form field>s.  If a field is "sticky," values that the user
+entered appear there again when the page is rendered again, avoiding making the
+user type them again.  Most L</action>s have form fields which are sticky on
+failure, so the user can update the information and try again.
+
+=item validate
+
+To check that the provided value of an L</argument> is a possible value for it
+to have.  See L<Jifty::Web::Form::Field>.
+
+=back
+
+=cut

Added: jifty/branches/schema-plugins/lib/Jifty/Manual/Models.pod
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Manual/Models.pod	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,356 @@
+=head1 NAME
+
+Jifty::Manual::Models - Managing your datastore
+
+=head1 DESCRIPTION
+
+The idea behing a model is to give the user a database-independent way
+of defining how the data looks alike and how different parts of the
+data relate to each other. In database terms, you might think of a
+schema definition.
+
+Besides the pure definition of a model, creation, updating and lookup
+of data is also possible in a comfortable way.
+
+=head2 Creating a model
+
+Every model consists of two classes: I<AppName>::Model::I<ModelName>
+and I<AppName>::Model::I<ModelName>::Schema. Behind the scenes, a
+class named I<AppName>::Model::I<ModelName>Collection is created by
+L<Jifty::ClassLoader>.
+
+A simple model to store just one line of text might look like this:
+
+    use strict;
+    use warnings;
+
+    package MyApp::Model::TextLine;
+    use Jifty::DBI::Schema;
+
+    use MyApp::Record schema {
+        column 'textline';
+    };
+
+    # Your model-specific methods go here.
+
+    1;
+
+To create the database schema for a model inside an application you
+could simply run:
+
+    jifty model --name TextLine
+
+from inside your application's directory and Jifty will create exactly
+this class structure for you (minus the I<column> line, to be
+precise).
+
+=head3 Schema definition language
+
+Creating a model has important side effects:
+
+=over 2
+
+=item * correctly type your data inside the data-store
+
+=item * let Jifty create (and update) your database schema for you
+
+=item * tell Jifty the behaviour in terms of form display
+
+=item * allow to work with multiple records (referred to as Collections) without effort
+
+=back
+
+To get all these things done, Jifty allows to describe the schema
+definition in a simply comprehendable but powerful syntax that looks
+more like written text than a programming language. The schema
+definition is made inside the C<MyApp::Model::XXX::Schema> package and
+every single column to get created starts with the word C<column>
+followed by the column's name.
+
+A simple definition could look like this:
+
+    column name =>
+        type is 'text',
+        label is 'Name',
+        render as 'Text',
+        since '0.0.1';
+
+The following BNF shows the full syntax supported (omitting
+non-terminals that are self-explanatory to perl-developers):
+
+    schema_definition ::= column_definition+
+
+    column_definition ::= 'column' string_columnname '=>'
+                          column_info [ ',' column_info ]+ ';'
+
+    column_info ::= 'type' 'is' string
+       | 'label' 'is' string
+       | 'render_as' string
+       | 'render' 'as' string
+       | 'hints' 'is' string
+       | 'refers_to' class_name 'by' string_columnname
+       | 'default' 'is' string
+       | 'literal' 'is' string
+       | 'validator' 'is' subroutine_reference
+       | 'immutable'
+       | 'unreadable'
+       | 'length' 'is' number
+       | 'mandatory'
+       | 'not_null'
+       | 'distinct'
+       | 'virtual'
+       | 'sort_order' 'is' number
+       | 'input_filters' 'are' string_classname
+       | 'output_filters' 'are' string_classname
+       | 'filters' 'are' string_classname
+       | 'since' string_version_number
+       | 'valid_values' 'are' array_of_valid_values
+       | 'valid' 'are' array_of_valid_values
+       | 'hints' 'are' string
+
+    * 'is', 'by', 'on', 'as' and 'are' are fill-words that may get omitted.
+
+For a full description of all parameter's meaning, look at
+L<Jifty::DBI::Schema>.
+
+=head3 Versioning
+
+Every time you run the jifty utility with C<schema> as an argument,
+Jifty will keep track on what it has done for you. To get that done,
+the version-number being stored in your application's config file
+C<etc/config.yml> under the key named C<framework/Database/Verson> is
+matched agains your schema definition.
+
+To force an update of your schema, simple create a new version number
+in your config file and modify your schema definition by using exactly
+this version number for every modified entry. After running
+
+    jifty schema --setup
+
+your database structure will be in sync to your schema definition.
+See L<Jifty::Manual::Upgrading> for more information on model upgrading.
+
+=head2 Testing a model
+
+After having created a schema, you might use the B<ADMINISTRATION>
+Menu entry in Jifty's web view (i.e. the "pony") to browse through
+your models and add, edit or delete records in your database.
+
+=head2 The classes behind a model
+
+=over 2
+
+=item * MyApp::Model::Xxx
+
+This is the model-class you created to access individual records of your desired
+type. You will directly deal with objects of this class.
+
+=item * MyApp::Record
+
+All records of C<MyApp::Model::Xxx> will have this class as their base
+class. Usually, this class will be automatically created by
+L<Jifty::ClassLoader> for you. But, if you want to automatically
+enable all your records to do something, you will have a chance to do
+so by manually creating this class.
+
+=item * L<Jifty::Record>
+
+This is the super-class of C<MyApp::Record>. Inside this class, loading
+of records as well as the checking of user capabilities is done before
+going one level down to the database layer.
+
+=item * L<Jifty::DBI::Record>
+
+This is the lowest-level class that the database stack provides. It
+directly deals with the underlying database.
+
+=item * App::Model::XxxCollection
+
+As the name applies, a collection is a set of typically more than one
+record. Every collection of this class conists of multiple
+C<App::Model::Xxx> objects that can get retrieved from your data-store
+without explicit SQL statements, ordered by any criteria you give,
+paged in the fashion you like,
+and iterated sequentially or accessed at random order.
+
+=item * App::Collection
+
+Every collection of your schemata will have this class as its
+base. Usually this class is autocreated by L<Jifty::ClassLoader>. If
+you intend to create new features for all of your collection this will
+be your chance to do.
+
+=item * L<Jifty::Collection>
+
+This is the base class of an C<App::Collection>, managing user
+capabilities on records it will keep track of.
+
+=item * L<Jifty::DBI::Collection>
+
+This is the lowest-level base class that directly manages the access to
+the underlying database.
+
+=back
+
+=head2 Working with a single record
+
+Working with a single record means working with objects of classes like
+C<MyApp::Model::Xxx>. The typical creation and usage of a single
+record is:
+
+    # create an object to allow data access
+    my $object = new MyApp::Model::Xxx;
+
+    # either create a representation in the DB
+    $object->create(column => 'value', ...);
+
+    # or load the data from DB somehow
+    $object->load($id); # by a matching ID
+    $object->load_by_cols(column => 'value', other_column => 'secondvalue');
+
+    # try to load and if failed, create a record
+    $object->load_or_create(column => 'value');
+
+    # get the record's ID in the database
+    # results in 'undef' if record is not valid (which usually means not found)
+    my $id = $object->id;
+
+    # delete the record from the database
+    $object->delete;
+
+To access data stored in different columns of a record you may use
+some of the automagically created methods on the object:
+
+    # read some column named 'colname'
+    my $value = $object->colname;
+
+    # write some value to a column named 'colname'
+    $object->set_colname($value);
+
+    # get all columns in a single hash (not a reference!)
+    my %record = $object->as_hash;
+
+Especially, when writing to a record, you need not worry about how to
+write back the data to the database, the object will manage this step
+on its own.
+
+=head2 Working with multiple records
+
+Working with more than one record of the same object-class brings
+collections into the game. Usually, a collection you deal with is of a
+type that conforms to your model name, C<MyApp::Model::XxxCollection>
+and usually holds records of class C<MyApp::Model::Xxx>. You typically
+use a collection like this:
+
+    # create a collection object
+    my $collection = new MyApp::Model::XxxCollection;
+
+    # get all items of the model into the collection
+    $collection->unlimit;
+
+    # or restrict items to match some condition
+    $collection->limit(column => 'colname', operator => '=', value => 42);
+
+    # bring the items into some sorting order
+    $collection->order_by(column => 'colname');
+
+    # if neccesarry, directly jump to some record from the set
+    $collection->goto_first_item;
+
+    $collection->goto_item(42);
+
+    # iterate through the result set
+    while (my $record = $collection->next) {
+          # do something with $record
+    }
+
+    # directly access the first or last item
+    # be careful: this will set the current position also!
+    my $first = $collection->first;
+    my $last  = $collection->last;
+
+    # get back an array-ref containing all items
+    my $records = $collection->items_array_ref;
+
+=head3 Some options provided by C<limit>
+
+In order to construct more complex restrictions the C<limit> method
+may get called more than once, specifying one single condition with
+each call.
+
+Every use of C<limit> constructs either a I<clause> or a I<subclause>.
+A subclause is built either if the C<subclause> attribute is used or a
+column is used repeatedly.
+
+Every clause is built up by combining its subclauses (if
+any) using the C<entry_aggregator> operator (whose default is I<OR>)
+as a combining operator. Clauses are then I<AND>ed together to yield
+the final restriction that is finally used to retrieve the records in
+question.
+
+The C<operator> (whose default is '=') can be any legal SQL operator
+like C<=>, C<< <= >>, C<< >= >>, C<< != >>, C<LIKE>, C<IS>, C<IS
+NOT> as well as some convenience operators that silently use C<LIKE>
+with properly set wildcards (C<MATCHES>, C<STARTSWITH> or
+C<ENDSWITH>).
+
+    # combining restrictions with "AND"
+    # note that "AND" is implicit here unless a column name is repeated
+    $collection->limit(column => 'col1', value => '...');
+    $collection->limit(column => 'col2', value => '...');
+
+    # combining restrictions with "OR"
+    # note that the 'subclause' has the same value
+    $collection->limit(column => 'col1', value => '...',
+                       entry_aggregator => 'OR', # is already default
+                       subclause => 'some_id');
+    $collection->limit(column => 'col2', value => '...',
+                       entry_aggregator => 'OR', # is already default
+                       subclause => 'some_id');
+
+For debugging purposes, you might want to examine the SQL statement
+generated behind the scene:
+
+    warn $collection->build_select_query;
+
+See L<Jifty::DBI::Collection> about more ways or ordering and limiting
+collections.
+
+=head2 Action - Model relationship
+
+When writing templates you often simply access some record from a
+model and want to operate on this very record by modifying it or you
+might want to add a new record of some type. To do this, our faithful
+L<Jifty::ClassLoader> will create classes named
+C<MyApp::Action::CreateXxx>, C<MyApp::Action::UpdateXxx> or
+C<MyApp::Action::DeleteXxx> for you. This enables you to write a
+template to operate on a single record like this:
+
+    <%init>
+    my $id = some_value_obtained_somehow;
+    my $record = new MyApp::Model::Xxx;
+    $record->load($id);
+
+    my $action = Jifty->web->new_action(class   => 'UpdateXxx',
+                                        moniker => 'mymoniker',
+                                        record  => $record);
+    </%init>
+    ...
+    <% $action->form_field('colname') %>
+    ...
+    <% Jifty->web->link(label  => 'Update',
+                        submit => $action,
+                        ... ) %>
+
+The elegant thing around here is that you could write the class name
+of your action-class simply as C<UpdateXxx> instead of the full
+package name C<MyApp::Action::UpdateXxx> and there is no need to write
+a repeating update procedure for every record class that comes along.
+DRY - don't repeat yourself :-)
+
+=head1 SEE ALSO
+
+L<Jifty::Record>, L<Jifty::DBI::Record>, L<Jifty::Collection>, L<Jifty::DBI::Collection>, L<Jifty::Manual::Actions>, L<Jifty::Manual::Tutorial>
+
+
+=cut

Added: jifty/branches/schema-plugins/lib/Jifty/Manual/ObjectModel.pod
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Manual/ObjectModel.pod	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,198 @@
+=head1 NAME
+
+Jifty::Manual::ObjectModel -- An overview of the Jifty object model
+
+=head1 OVERVIEW
+
+Jifty applications are generally built in a similar way. There's no reason
+you I<need> to use the model we've built, but we find it a reasonably
+ok way to do things.
+
+This document should serve as a roadmap to the Jifty class library, as 
+well as an introduction to the way Jifty applications are put together.
+
+We start with the classes in I<your> application and move on to the bits
+of Jifty itself.
+
+If you create a brand new application, let's call it C<MyWeblog>, and
+create one model class called C<MyWeblog::Post>, you'll end up with the
+following files and directories:
+
+    MyWeblog/
+        etc/
+            config.yml
+        lib/
+            MyWeblog/
+                Model/
+                    Post.pm
+                Action/
+        bin/
+            jifty
+    
+        web/
+            templates/
+            static/
+
+        t/
+            #some test files.
+
+At least that's the scaffolding Jifty creates for you. Behind the
+scenes, Jifty is actually doing a lot more. Rather than create a bunch
+of little "stub" classes and libraries for you, Jifty generates them
+on the fly. It's always possible to I<actually> create these libraries
+when you need to customize the default behavior, but we work really
+hard to make sure you don't need to.
+
+Right now, Jifty is autocreating libraries, static web pages and web
+templates.
+
+We're not 100% satisfied with how Jifty autocreates web templates and
+static pages and are working to redesign that.
+
+The library you I<see> when creating a Jifty app is:
+
+=over
+
+=item MyWeblog::Model::Post
+
+C<MyWeblog::Model::Post> describes the schema and business logic of your
+post class. It uses two namespaces, C<MyWeblog::Model::Post::Schema>
+that has actual column definitions and C<MyWeblog::Model::Post> that
+contains the (optional) business logic, access control and so on.
+
+=back
+
+That's it. But if you look carefully at C<MyWeblog::Model::Post>, you'll see
+the line:
+
+    use base qw/MyWeblog::Record/;
+
+How can that possibly work? There is no C<MyWeblog::Record> class in your
+application. And Jifty, while it tries to be a comprehensive framework,
+draws the line somewhat short of including application-specific base
+classes for every application you might possibly contrive.
+
+The answer lies in L<Jifty::ClassLoader>, a utility module Jifty uses
+to create the boring stuff for you when you need it.
+
+It'd certainly be possible for Jifty to create every class you might need
+as a file on disk when you first create your application (and indeed
+we may decide to do so if enough people yell at us), but when the stub
+classes we'd provide are just little shims that inherit from or call
+to the Jifty core, it doesn't make much sense to create them before you
+need them.  You could build a Jifty application without these shims by
+having your model classes inherit directly from Jifty::Record, but then
+you'll run into trouble the second you want to add application-specific
+code and have to go back and retrofit each and every one of your classes
+to use your new base class. It's a little thing, but one that can save
+you a bunch of pain and suffering later on.
+
+C<MyWeblog::Record> is the first autogenerated class you'll run into but
+probably not the last. A full list of everything Jifty provides for your
+new application follows:
+
+You get one each of the these:
+
+=over
+
+=item MyWeblog::Record
+
+This class is, as discussed above, a thin wrapper around
+L<Jifty::Record>. You might choose to create your own C<MyWeblog::Record>
+if you want to build in custom access control by overriding
+L<Jifty::Record/current_user_can> or want to implement methods that
+every model class should have access to.
+
+=item MyWeblog::Collection
+
+We haven't talked much about collections yet, but as their name implies,
+collections are bundles of L<Jifty::Record> objects that match some set
+of criteria. It's relatively uncommon that you'll want to override this,
+but if you want the rope, it's here.
+
+=item MyWeblog::Notification 
+
+C<MyWeblog::Notification> is an app-specific implementation of the
+L<Jifty::Notification> email driver. You might want to override this
+class if you want to set an application-specific header or footer for
+all outgoing email.
+
+=item MyWeblog::Dispatcher
+
+C<MyWeblog::Dispatcher> is an application-specific "dispatcher" class that
+allows you to write code that runs when a client makes a request to the server
+I<before> Jifty runs I<actions> or renders I<templates>.  See L<Jifty::Dispatcher>
+for more information about the dispatcher.
+
+=item MyWeblog::CurrentUser
+
+Most every web application that grows past a personal hack eventually
+starts to provide personalization, require access control or otherwise
+want to know who's currently in the driver's seat. The "current user"
+for an application is a first-class object in Jifty. To get user-based
+authentication working out of the box, you'll have to override the
+default C<MyWeblog::CurrentUser>. (Out of the box, it treats everyone
+as the same user.) We're working to generalize the authentication system
+we've used in a few Jifty apps so far to the point where it feels "right"
+as a core Jifty component, but we're not quite there just yet.
+
+Most of what you'll need to override in C<MyWeblog::CurrentUser> is
+the C<_init> function, which needs to load up an application-specific
+model class that represents one of your users into its C<user_object>
+accessor.  To make all this work, you'll also need an
+application-specific C<MyWeblog::Action::Login> and likely also a
+passel of user-management code.
+
+(And yes, this is the topic of a future generalization and a future
+tutorial. At that point, a bunch of this documentation will be extracted
+to L<Jifty::CurrentUser>.)
+
+=back
+
+But wait! There's more!  You also get one each of these for your default
+model class:
+
+=over
+
+=item MyWeblog::Model::PostCollection
+
+It's no fun having a weblog that only shows you one post at a time,
+is it?  Jifty provides you with default L<Jifty::Collection> classes
+for every L<Jifty::Record> subclass in your model.  You get all the
+standard C<limit>, C<order_by>, C<columns>, paging support and so-on out
+of the box, but sometimes when you're going to be getting collections
+matching certain criteria often, it makes sense to actually create your
+own subclass and start dropping methods in.
+
+=item MyWeblog::Action::CreatePost, MyWeblog::Action::UpdatePost,
+MyWeblog::Action::DeletePost
+
+One of Jifty's strengths is that it makes it easy to build applications by
+tying application-specific controller functions to your model classes and
+intuiting what parameters they take by having a look inside the models.
+
+For each class in your model, Jifty creates three
+actions, C<Create>,C<Update> and C<Delete>.  They're named,
+perhaps a bit unadventureously, C<MyWeblog::Action::CreatePost>,
+C<MyWeblog::Action::UpdatePost>, C<MyWeblog::Action::DeletePost>
+and inherit directly from L<Jifty::Action::Record::Create>,
+L<Jifty::Action::Record::Update> and L<Jifty::Action::Record::Delete>,
+respectively. Sometimes, it makes sense to override these default actions
+when you want to change the behaviour of one or more of the actions.
+One common use is to add or remove AJAX validation or autocompletion
+for an argument or to change an argument's default value for webforms.
+This, isn't, however the place to start inserting business logic or
+access control. That belongs in your model class, which these wrappers
+will hand things off to. By putting logic in your actions, you make your
+model classes less useful and run into trouble when you want to start
+scripting your model outside a web environment.
+
+=back
+
+There's no reason you need to stick with these default
+"implementations" if they're not meeting your needs. Just create your
+own classes and Jifty will use your real classes instead.
+
+=cut
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Manual/PageRegions.pod
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Manual/PageRegions.pod	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,171 @@
+
+=head1 NAME
+
+Jifty::Manual::PageRegions - Using page regions
+
+=head1 DESCRIPTION
+
+Page regions are a way of doing the new trend of automatic in-page
+replacement with JavaScript -- while at the same time providing the
+same user experience for non-JavaScript enabled browsers.  Sections
+are chunked into nestable "page regions", which can be refreshed
+independently.
+
+=head1 USING PAGE REGIONS
+
+=head2 Constructing Page Regions
+
+From inside any template, a region may get constructed via something like
+this:
+
+    <% Jifty->web->region( name     => 'regionname',
+                           path     => '/path/of/component',
+                           defaults => { argname => 'some value', ... },
+                         ) %>
+
+This call will pass all arguments to the C<new> constructor of
+L<Jifty::Web::PageRegion>. The most often used parameters are:
+
+=over
+
+=item name
+
+The mandatory region's name given here is used to embed the region's
+content into a C<< <div> >> tag which is marked with a fully qualified
+name for that region. The qualified name represents the nesting
+structure of regions inside a page and is automatically built inside.
+The qualified name for a given L<Jifty::Web::PageRegion> object can
+be obtained by calling C<< Jifty->web->qualified_region >>.
+
+=item path (optional)
+
+If a path is given, the component's rendered result under this path is
+embedded inside the region. If no path is given, C</__jifty/empty>
+will be used resulting in an empty region inside.
+
+=item defaults (optional)
+
+Every argument given here (as a hash-ref) will be transported to the
+component that displays the region inside. The values are accessible
+by building a C<< <%args> >> block in the component (Mason template)
+specifying the arguments.
+
+=back
+
+=head2 Using Page Regions
+
+Given a template with regions, any region can influence itself or any
+other region it knows about. Doing this is typically done with
+JavaScript handlers like C<onclick>. The examples below will
+demonstrate some typical scenarios:
+
+    %# replace this region with some other component
+    <% Jifty->web->link( label   => 'click me',
+                         onclick => {
+                             replace_with => '/new/path/component',
+                             args         => { argname => 'some value' },
+                                    },
+                       ) %>
+
+    %# insert a new region in front of a given region
+    %# use an HTML-entity as the link-text and a CSS class
+    <% Jifty->web->link( label        => '%#9997;',
+                         escape_label => 0,
+                         class        => 'blue_button',
+                         onclick => {
+                             region  => 'regionname',
+                             prepend => '/new/path/component',
+                             args    => { argname => 'some value' },
+                                    },
+                       ) %>
+
+    %# insert a new region after a given CSS selector inside $region
+    <% Jifty->web->link( label   => 'add something',
+                         onclick => {
+                             element => $region->parent->get_element('div.list'),
+                             append  => '/new/path/component',
+                             args    => { argname => 'some value' },
+                                    },
+                       ) %>
+
+    %# a button to replace the current region with empty content
+    <% Jifty->web->link( label   => 'clear',
+                         onclick => {
+                             refresh_self => 1,
+                             toggle       => 1,
+                                    },
+                         as_button => 1,
+                       ) %>
+
+    %# a button to delete some region with JavaScript confirmation alert
+    <% Jifty->web->link( label   => 'delete',
+                         onclick => {
+                             delete  => 'regionname',
+                             confirm => 'really delete this?',
+                                    },
+                         as_button => 1,
+                       ) %>
+
+    %# refresh the parent region which holds the current one
+    <% $search->button(
+        label   => 'Search!',
+        onclick => {
+            submit  => $search_action,
+            refresh => Jifty->web->current_region->parent,
+            args    => { page => 1 }
+                   }
+      ) %>
+
+=head1 GORY DETAILS
+
+There is a bit of complication involved in making sure that the
+server-side Perl implementation of page regions, and, more importantly,
+how they preserve state, interacts with the client-side JavaScript
+implementation.  What follows is an attempt to describe the process.
+
+Regions, when they are created, have a default path and a default set
+of arguments.  These are "defaults" because they can be overridden by
+the browser -- this is what enables the browser to say "...and that
+region has this other path, in reality."  The same is true for
+arguments; for example, a paging widget could have a default C<page>
+argument of 1, but could be actually being rendered with a C<page> of
+2.
+
+These overrides are kept track of using state variables.  When a
+region is entered, it peers at the current state variables, and
+overrides the default path and arguments before rendering the region.
+
+When a L<Jifty::Web::Form::Clickable> object with an C<onclick> is
+L<generated|Jifty::Web::Form::Clickable/generate>, it examines the
+C<onclick> and determines how to emulate it without JavaScript.  It
+determines which actions need to be run, as well as how to manipulate
+the future state variables to change the display of the appropriate
+regions.  It encodes all of this in the button or link; since the
+JavaScript usually returns false, the fallback mode is never seen by
+the browser.
+
+When a region is output, it is output with a tiny "region wrapper",
+which serves two purposes: to inform the JavaScript of the existance
+of the page region and its default path and variables, and to create a
+unique C<< <div> >> for the fragment to reside in.  The browser reads
+the JavaScript and creates, on the client-side, a model of the nested
+PageRegions.  This allows the JavaScript to model the state variable
+changes correctly.
+
+When the JavaScript C<Update> function is called, it is passed a list of
+fragments that needs to be updated, as well as a list of actions that
+need to be run.  As it does so, it builds up an up-to-date list of
+state variables, to more closely imitate the state of a non-javascript
+enabled client.  It constructs a JSON request based on that
+information, and passes it off to the XML web-service endpoint on the
+server.
+
+When the request comes back, it parses the XML.  For each fragment
+that was requested, it finds the correct bit of the response, and
+replaces the content of the DOM with the response.  As it does so, it
+re-updates the client-side view of the fragments with the server's
+information -- this is particularly key for dealing with parameters
+which were mapped by the request mapper.  Finally, it displays
+any messages and errors from actions.
+
+=cut

Added: jifty/branches/schema-plugins/lib/Jifty/Manual/RequestHandling.pod
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Manual/RequestHandling.pod	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,124 @@
+=head1 NAME
+
+Jifty::Manual::RequestHandling - Jifty's request handling process
+
+=head1 DESCRIPTION
+
+This document outlines some of Jifty's inside in order to help you to understand
+what happens during the request processing phase.
+
+=head1 THE HANDLER
+
+As soon as a http request (whatever the method might be, like GET,
+POST, PUT, ...) arrives at Jifty's border, the request is forwarded to
+a handler. By default, C<< Jifty->handler >> points to a L<Jifty::Handler>
+object that is responsible for handling an incoming request. The
+handler receives a L<CGI> object on which it operates.
+
+=head2 The major steps in the request handling process are:
+
+=over
+
+=item refresh eventually modified modules in develop mode
+
+This allows a perl developer to change perl modules being used in a
+Jifty application without the need to restart the server which would
+otherwise become necessary. This is never done on a live environment.
+
+=item build a stash
+
+The stash is a storage area that can be reached by simply accessing
+C<< Jifty->handler->stash->{some_key} >>. The stash will start fresh with
+every request and lives for the entire lifetime of a request. Using
+the stash, transporting data between otherwise unconnected modules
+will become possible.
+
+=item construct a request and response object
+
+Using the L<CGI> object, a L<Jifty::Request> object is constructed and
+its data is populated with the CGI's data. The request can be reached
+later using C<< Jifty->web->request >>. The request holds information
+about all actions involved, all page fragments, contains state
+variables and arguments (usually GET/POST parameters).
+
+Also, an empty L<Jifty::Response> object is constructed that contains
+one or more L<Jifty::Result> objects, each of which holds one
+L<Jifty::Action>'s result. The response object can be retrieved with
+the C<< Jifty->web->response >> method.
+
+=item setup plugins
+
+For every registered L<Jifty::Plugin>, some kind of per-request
+initialization is performed allowing the actions provided by each
+plugin to run.
+
+=item handle static content
+
+If the requested URI points to some existing static content being
+housed in a C<static> directory, this content is handled.
+
+=item setup the session
+
+Based on a cookie that is sent with every http response, the current
+user is assigned a unique session. The session is stored in a
+L<Jifty::Web::Session> object and can be accessed using the
+C<< Jifty->web->session >> method.
+
+=item return from a continuation if requested
+
+If there is an open continuation on the stack (e.g. from a
+C<< Jifty->web->tangent >> link) and the return has been requested
+(e.g. by a C<< Jifty->web->return >> link), the return will execute at
+this stage.
+
+=item handle dynamic request unless already served
+
+First, the user is given a cookie containing the session-id. Then, the
+request is forwarded to C<< Jifty->handler->dispatcher >>, a
+L<Jifty::Dispatcher> object to handle the request. The dispatcher
+works through the following steps:
+
+=over 4
+
+=item setup
+
+In this stage, all rules in the dispatcher that are marked with the
+word C<before> are run.
+
+=item run the actions involved
+
+Every L<Jifty::Action> that is registered in a form or involved in a
+link or button is run in this stage.
+
+=item run dispatching rules
+
+This stage is responsible for working through all rules marked by
+words like C<under>, C<on>, C<when> and so on. This is a point where
+based on the URI or parameters the template to get displayed may still
+be modified, data get retrieved, additional actions run or the template's
+parameters get adjusted.
+
+=item show the page
+
+Here, the template displaying the page is run.
+
+=item cleanup
+
+This final stage of the dispatcher will run all rules marked with the
+word C<after>.
+
+=back
+
+=item cleanup several things
+
+Finally, the eventually modified session-record is flushed and some
+internally allocated structures get deallocated.
+
+=back
+
+=head1 SEE ALSO
+
+L<Jifty::Handler>, L<Jifty::Dispatcher>, L<Jifty::Request>, L<Jifty::Response>
+
+=cut
+

Added: jifty/branches/schema-plugins/lib/Jifty/Manual/Style.pod
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Manual/Style.pod	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,72 @@
+=head1 NAME
+
+Jifty::Manual::Style - Jifty coding style guide
+
+
+=head3 Default style
+
+When in doubt, default to whatever Damian Conway's I<Perl Best
+Practices> says.
+
+
+=head3 Private documentation
+
+When documenting a private method, or providing documentation which is
+not useful to the user of the module (and is presumably useful to the
+developer), wrap it in =begin/end private.  This way it does not show
+up in perldoc where a user would see it and yet is still available and
+well formatted (that is, not just a lump comment) when looking at the
+code.
+
+  =begin private
+
+  =head2 import_extra
+
+  Called by L<Test::More>'s C<import> code when L<Jifty::Test> is first
+  C<use>'d, it calls L</setup>, and asks Test::More to export its
+  symbols to the namespace that C<use>'d this one.
+
+  =end private
+
+  sub import_extra {
+        ...
+  }
+
+
+=head3 Test temp files
+
+Files created by tests should be declared as such using
+Jifty::Test->test_file() so they are cleaned up on a successful test
+run.
+
+
+=head3 Use Shell::Command
+
+Shell::Command has a number of functions which work like common shell
+file commands such as touch, cp and mv.  They are battle tested and
+cross-platform.  Use them instead of coding your own.
+
+For example, instead of this:
+
+    open my $file, ">foo";
+    close $file;
+
+Do this:
+
+    use Shell::Command;
+    touch $file;
+
+
+=head3 Case insensitive matching
+
+To check if a string equals another string case insensitively, do this
+
+    lc $foo eq lc $bar;
+    lc $foo eq 'bar';
+
+not this:
+
+    $foo =~ /^\Q$bar\E/i;
+    $foo =~ /^bar$/i;
+
+=cut

Added: jifty/branches/schema-plugins/lib/Jifty/Manual/Tutorial.pod
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Manual/Tutorial.pod	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,497 @@
+=head1 NAME
+
+Jifty::Manual::Tutorial - Zero to Jifty in a Jiffy
+
+=head1 DESCRIPTION
+
+This tutorial should give you everything you need to 
+build your first application with Jifty.
+
+=cut
+
+=head1 HOW TO
+
+=head2 The requirements
+
+Here's what you need to have installed -- at least when we write it.
+
+=head2 Installing Jifty
+
+No bones about it. We believe pretty strongly in the DRY (Don't Repeat
+Yourself) principle. That's one of the big reasons we love Perl and
+CPAN. Jifty makes use of lots of amazing code from CPAN.  At last count,
+it directly depended on 60 packages from CPAN.  Most of these libraries
+are cross-platform pure-Perl packages and should run great out of the
+box on any platform you can get Perl onto.
+
+We've gone to lengths to make sure you don't spend your day
+downloading library after library by bundling everything we can inside
+the Jifty package.  The Jifty installer is capable of determining what
+modules your system needs, and downloading and installing them all in
+one go.  Don't worry, it will ask you first before it makes any changes.
+
+On most systems you can Perl's bundled CPAN module to download
+and install Jifty:
+
+  # perl -MCPAN -e'install Jifty'       # Unix-like systems
+  # perl -MCPAN -e"install Jifty"       # Win32 systems
+
+If you've downloaded a C<.tar.gz> of Jifty, you can do a
+manual install:
+
+  # tar xzvf jifty-<version>.tgz
+  # cd jifty-<version>
+  # perl Makefile.PL
+  # make
+  # make test
+  # make install
+
+If the tests don't pass, we want to hear about it. Please join us
+on C<jifty-devel at lists.jifty.org> and report the failure. (See 
+L</GETTING HELP> below for info on how to join the list.)
+
+=head2 Setting up the Scaffolding
+
+Once you have Jifty happily installed, you're ready to 
+create your first application. 
+
+Jifty is intentionally a bit minimalist. All you I<really>
+need to make an application go is a copy of the F<jifty> commandline
+tool (inside your new application's F<bin/> directory.  
+
+Of course, it's often helpful to have a bit more structure around to
+help guide your work. Jifty comes with tools to build that structure for
+you.
+
+Change directory to some place it will be safe to create a new
+Jifty application. (Jifty will create a subdirectory for you).
+
+  # jifty app --name MyWeblog
+  Can't guess application root from current path (/tmp) or bin path (/usr/bin)
+  Creating new application MyWeblog
+  Creating directory lib
+  Creating directory lib/MyWeblog
+  Creating directory bin
+  Creating directory etc
+  Creating directory doc
+  Creating directory log
+  Creating directory var
+  Creating directory var/mason
+  Creating directory share
+  Creating directory share/po
+  Creating directory share/web
+  Creating directory share/web/templates
+  Creating directory share/web/static
+  Creating directory lib/MyWeblog/Model
+  Creating directory lib/MyWeblog/Action
+  Creating directory t
+  Creating configuration file MyWeblog/etc/config.yml
+
+Let's take those one by one.
+
+=over
+
+=item bin
+
+Inside F<bin/> is F<jifty>, the Jifty command dispatcher. Some
+of the most important commands are C<schema>, which sets up or updates
+your database schema and C<server>, which starts a standalone
+webserver. To find out what commands your F<jifty> comes with, run:
+
+    jifty help
+
+=item etc
+
+Configuration files live in F<etc/>, though if you don't have a config
+file, Jifty will supply some sane defaults.
+
+=item doc
+
+Jifty won't magically write your documentation for you, but when B<you>
+write your docs, put them in F<doc/>.
+
+=item log
+
+Jifty uses L<Log::Log4perl> to configure its logging. By default, it
+dumps logs named F<server.log> and F<error.log> into the F<log> directory.
+
+=item share/web/templates
+
+Jifty uses L<HTML::Mason> as its primary templating system.  Put
+your application's templates into F<share/web/templates/>.  Out of
+the box, Jifty comes with an application I<skeleton> that it installs
+in F<share/web/templates/>.  This default application is a convenient
+way to get a basic application up and running quickly, but probably
+needs some customization as you build a more advanced application.
+
+You can find where Perl stuck Jifty's default templates with:
+
+  perl -MJifty::Util -e 'print Jifty::Util->share_root'
+
+=item share/web/static
+
+Some nontrivial percentage of the I<stuff> your web application serves
+out doesn't need to (or I<shouldn't>) pass through your templating
+engine.
+
+Just drop your static files into F<share/web/static/> and Jifty will serve
+them out if it can't find a template with the right name.
+
+Out of the box, Jifty comes with a CSS style, Javascript libraries and a
+Pony. Look in F<share/web/static> in the Jifty distribution, or in the same
+place Jifty stuck its default templates.
+
+=item lib/MyWeblog
+
+For a full treatment of the Jifty object model see
+L<Jifty::Manual::ObjectModel>.
+
+To build a basic Jifty application, you only need to worry about two
+sorts of classes, B<Models> and B<Actions>.
+
+=item lib/MyWeblog/Model
+
+The real base of your application lives in
+C<lib/B<ApplicationName>/Model>. Classes here define your application's data
+structures and how they relate to each other.  Jifty will use your model
+classes to set up and upgrade your database's schema when it needs to.
+
+=item lib/MyWeblog/Action
+
+When we said you only need to worry about B<Models> and B<Actions>,
+we weren't telling the whole truth.  Jifty will take care of basic
+database-interaction (C<CREATE, READ, UPDATE, DELETE>) B<Actions> for
+your B<Models>, but they're there if you want to change anything.
+
+=item t
+
+Jifty starts off your application with a basic harness, but can't yet write 
+all your tests for you. (It does, however, build simple tests for model
+classes you generate.)
+
+=item var
+
+Jifty stores cache files here while the server is running. You shouldn't ever
+have to touch this directory.
+
+=back
+
+=head2 Building your data model
+
+As you might imagine by the fact that this tutorial application is named
+B<MyWeblog>, the example here is a simple weblog application.  Future
+tutorials will add authentication, comments, and RSS and Atom feeds.
+
+=head3 Posts
+
+Weblogs tend to center around posts, so it's no surprise that the first model
+to create is the C<post>:
+
+  # cd MyWeblog
+  # jifty model --name Post
+  Writing file /tmp/MyWeblog/t/00-model-Post.t
+  Writing file /tmp/MyWeblog/lib/MyWeblog/Model/Post.pm
+
+Great! Now you have a B<Post> model (not that it models anything yet).
+
+Open F<lib/MyWeblog/Model/Post.pm> in your favorite text editor.
+
+You should see something like this:
+
+  use strict;
+  use warnings;
+  
+  package MyWeblog::Model::Post;
+  use Jifty::DBI::Schema;
+  
+  use MyWeblog::Record schema {
+  
+  };
+  
+  # Your model-specific methods go here.
+  
+  1;
+
+
+Now it's time to tell the model class about posts. Start by giving our post a
+C<body> and a C<title>. (In a future tutorial, the application will become fully
+folksonomy-compliant by adding a C<category> and upgrading that C<category> to a
+C<tags> table.)
+
+Position your cursor right after:
+
+  use MyWeblog::Record schema {
+
+Add the lines:
+
+  column title =>
+        type is 'text',
+        label is 'Title',
+        default is 'Untitled post';
+
+  column body => 
+        type is 'text',
+        label is 'Content',
+        render_as 'Textarea';
+
+Save your model class.
+
+=head2 Setting up the database
+
+Ok. It's time to initialize MyWeblog's database. By default, Jifty sets up your
+application with the SQLite database engine.  If you'd rather use PostgreSQL or
+MySQL, you need to add some content to F<etc/jifty.yml>. (See C<Jifty::Config>
+for a bit more information).
+
+  # jifty schema --setup
+  INFO - Generating SQL for application MyWeblog...
+  INFO - Using MyWeblog::Model::Post
+  INFO - Using Jifty::Model::Session
+  INFO - Using Jifty::Model::Metadata
+  INFO - Using Jifty::Model::Schema
+  INFO - Set up version v0.0.1, jifty version 0.607280
+
+=head2 Starting the Jifty application server
+
+Ok. You have a working, if simplistic, application.  Start up a webserver and
+have a look around.  Be sure to check out the AJAX-enabled administrative UI,
+the online documentation browser, and the Pony.
+
+  # ./bin/jifty server
+  INFO - You can connect to your server at http://localhost:8888/
+
+Please always run this command at the root directory of your Jifty applications,
+or you'll be caught by a lot of errors.
+
+For many platforms, a simple "jifty server" command works too. :)
+
+=head2 Building a user interface
+
+The administrative web does give you everything you need to work with your
+application's data, but it's not much of a weblog.
+
+=head3 Posting
+
+Create a page to post a new weblog entry:
+
+  # cd share/web/templates/
+
+Open a new file called F<post> in your text editor. Make it look like this:
+
+  <%init>
+  my $action = Jifty->web->new_action(class => 'CreatePost');
+  </%init>
+
+  <&| /_elements/wrapper, title => "Post to your weblog" &>
+  <% Jifty->web->form->start() %>
+  <% Jifty->web->form->next_page( url => '/') %>
+  <% $action->form_field('title') %>
+  <% $action->form_field('body') %>
+  <% Jifty->web->form->submit( label => 'Post' ) %>
+  <% Jifty->web->form->end() %>
+  </&>
+
+Yes, it's a template file in L<HTML::Mason> syntax. If you're not familiar with Mason,
+we recommend you to consult its online documentation for details. I<Mason templates 
+should start in the first column of the file. Particularly importantly, the C<E<lt>%initE<gt>>
+and C<E<lt>/%initE<gt>> blocks must be flush left.>
+
+=head3 Viewing
+
+It's really easy to get a I<basic> listing of entries and a little bit more
+complex to get a pretty AJAXified paged list.  Here's how to do both; you can
+decide which one works best for you.
+
+(If you cut and paste the code in the examples below, make sure it's 
+lined up in column 1, or it won't work.)
+
+=head4 The quick and dirty way
+
+Open a new file called F<index.html> in the F<share/web/templates> directory
+in your text editor. (Your webserver will
+treat the URL C</index.html> as the I<default> page for your site).  Make it
+look like this:
+
+  <%init>
+  my $posts = MyWeblog::Model::PostCollection->new();
+  $posts->unlimit();
+  </%init>
+
+  <&| /_elements/wrapper, title => Jifty->config->framework('ApplicationName') &>
+  <dl>
+  % while (my $post = $posts->next) {
+   <dt><% $post->title %></dt>
+   <dd><% $post->body %></dd>
+  % }
+  </dl>
+  </&>
+
+(Make sure to remove that leading whitespace!)
+
+=head4 The complex way that gets you lots of cool toys
+
+The I<complex way> involves using one of Jifty's advanced features: I<Page
+regions>. These regions let your application reload page sections
+independently, either using AJAX on modern high-end browsers or regular GET
+requests with downlevel browsers such as C<lynx>, C<w3m>, or the browser on
+your mobile phone.
+
+The downside of this approach is that each separate region needs to live in
+its own I<fragment> file.
+
+The complex way starts off about the same as the easy way.  Open a new file
+called F<share/web/templates/index.html> in your text editor.  Fill it with this:
+
+  <&| /_elements/wrapper, title => Jifty->config->framework('ApplicationName') &>
+
+  <% Jifty->web->region(name => "myweblog-posts",
+                        path => "/fragments/page_of_posts") %>
+  </&>
+
+If you're on the ball, you've probably already guessed that you need to create
+a file called F<share/web/templates/fragments/page_of_posts> containing:
+
+  <%args>
+  $page => 1
+  </%args>
+  <%init>
+  my $posts = MyWeblog::Model::PostCollection->new();
+  $posts->unlimit();
+  $posts->set_page_info( current_page => $page,
+                         per_page     => 15
+                       );
+  $m->out("No items found.") if ($posts->pager->total_entries == 0);
+
+  </%init>
+  % if ($posts->pager->last_page > 1) {
+     Page <% $page %> of <% $posts->pager->last_page %>
+  % }
+  <dl class="list">
+  % while (my $post = $posts->next) {
+   <dt><% $post->title %></dt>
+   <dd><% $post->body %></dd>
+  % }
+  </dl>
+
+  % if ($posts->pager->previous_page) {
+    <% Jifty->web->link( label => "Previous Page", onclick => { args => { page => $posts->pager->previous_page } } ) %>
+  % }
+  % if ($posts->pager->next_page) {
+    <% Jifty->web->link( label => "Next Page", onclick => { args => { page => $posts->pager->next_page } } ) %>
+  % }
+
+Now fire up your Jifty webserver again. Go create a post by browsing C</post>
+on your webserver. Create more than 15 posts, and notice how Jifty gives you
+AJAX C<Next Page> and C<Previous Page> buttons for you. Turn off javascript or
+view the page in lynx, and notice how the AJAX automatically falls-back to page
+loads for you. All for free, thanks to Jifty!
+
+=head3 Hey, where'd that class come from?
+
+If you're paying attention, you may have wondered about C<MyWeblog::Model::PostCollection>,
+since there's no file called F<PostCollection.pm>. By default, Jifty uses 
+C<Jifty::ClassLoader> to auto-generate a bunch of classes for you. Of course,
+you can override these definitions if you like. See L<Jifty::ClassLoader> for more details.
+
+=head2 Navigation
+
+Of course, having to remember the URL to get to the posting page is a bit
+annoying.  To get a B<Post> button in the menu, you need to override the default
+menus.
+
+Jifty's I<default> menus live in F<_elements/nav> in the default application
+template (along with the Pony).  For now, override F<_elements/nav>. (We're
+working on ways to make this better.)
+
+Inside your applications F<share/web/templates> directory, create a directory called
+F<_elements>.
+
+  mkdir share/web/templates/_elements
+
+You may want to start with the stock jifty template, in which case you
+only need to add the C<< $top->child( Post... >> part
+
+  cat $(perl -MJifty::Util -e 'print Jifty::Util->share_root' \
+    )/web/templates/_elements/nav > share/web/templates/_elements/nav
+
+Otherwise, inside F<_elements>, open up a new file called C<nav> in your
+text editor and insert:
+
+  <%init>
+  my $top = Jifty->web->navigation;
+  $top->child( Home => url => "/");
+  $top->child( Post => url => "/post", 
+                       label => "Post Article");
+  </%init>
+
+For more information about the menu system, see the documentation in
+L<Jifty::Web::Menu>.
+
+=head2 That's it!
+
+That's just about everything you need to get started building Jifty
+applications.  We're working hard to make Jifty even easier to use and
+to obsolete the I<hard bits> of this tutorial as quickly as we can.
+
+Please join us on the C<jifty-devel> mailing list to talk about how you're 
+using Jifty or what you find difficult or hard to use about it.
+
+=head1 GETTING HELP
+
+=head2 Online Help
+
+The C<jifty> command-line application comes with builtin help.
+
+  jifty help
+
+  jifty help <command>
+
+If your server is running with administration mode enabled (the
+configuration file C<AdminMode> setting is missing or non-zero), you can
+click the "Online Docs" link in your browser for an extensive list of
+per-module Jifty documentation.
+
+=head2 Joining the mailing list
+
+C<jifty-devel at lists.jifty.org> is where we discuss how we're building Jifty,
+what we're having trouble with and so on.
+
+To join the list, send mail to C<jifty-devel-subscribe at lists.jifty.org>.
+
+=head2 Browsing the wiki
+
+We have a wiki! (Actually, the wiki is Jifty's primary website)
+
+Please visit L<http://jifty.org/>, browse and contribute.
+
+The wiki is powered by I<Wifty>, a Wiki built on Jifty. Its code is freely 
+available from the jifty subversion repository.
+
+=head1 REPORTING BUGS
+
+At this incredibly early stage in its life, please report bugs in Jifty
+to C<jifty-devel at lists.jifty.org>.
+
+=head1 FUTURE TUTORIALS
+
+Future tutorials include:
+
+=over 4
+
+=item * Access Control and Security
+
+=item * Upgrading your application's data model
+
+=item * The dispatcher in depth
+
+=item * Deploying your application in production
+
+=item * Web Services
+
+=item * Continuations in depth
+
+=item * Customized view (user-defined wrappers and css)
+
+=back
+
+=cut

Added: jifty/branches/schema-plugins/lib/Jifty/Manual/Tutorial_de.pod
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Manual/Tutorial_de.pod	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,549 @@
+=head1 NAME
+
+Jifty::Manual::Tutorial_de - Einführung in Jifty
+
+=head1 BESCHREIBUNG
+
+Dieses Tutorial sollte Ihnen alles Wichtige mit auf den Weg geben, um
+erfolgreich eine erste Applikation mit Jifty zu bauen.
+
+=cut
+
+=head1 WIE?
+
+=head2 Anforderungen
+
+Hier steht, was Sie installiert haben sollten -- wenigstens wenn wir
+es schreiben.
+
+=head2 Jifty installieren
+
+Nicht wirklich eine große Sache. Wir glauben stark an das DRY (Don't
+Repeat Yourself -- Keine Wiederholungen) Prinzip. Das ist einer der
+großen Gründe, warum wir Perl und das CPAN lieben. Jifty benutzt viel
+Code aus dem CPAN. Bei der letzten Zählung waren es wenigstens 60
+Pakete. Die meisten Module sind auf vielen Plattformen einsetzbare
+nur in Perl geschriebene Module und sollten ohne Probleme funktionieren.
+
+Wir haben einige Anstrengungen unternommen, damit Sie nicht Ihre Zeit
+damit verbringen müssen, Abhängigkeiten aufzulösen, nur um Jifty zu
+installieren. Das Jifty Installationsprogramm stellt fest, welche
+Module Ihr System benötigt und lädt bei Bedarf die erforderlichen
+Module nach. Also machen Sie sich keine Sorgen, Sie werden immer
+gefragt, bevor Änderungen vorgenommen werden.
+
+Auf den meisten Systemen wird Perl mit dem CPAN Modul ausgeliefert,
+womit die Jifty Installation einfach wird:
+
+  # perl -MCPAN -e'install Jifty'       # auf Unix-ähnlichen Systemen
+  # perl -MCPAN -e"install Jifty"       # auf Win32 Systemen
+
+Falls Sie lieber eine C<.tar.gz> Datei von Jifty herunterladen wollen,
+können Sie diese wie folgt installieren:
+
+  # tar xzvf jifty-<version>.tgz
+  # cd jifty-<version>
+  # perl Makefile.PL
+  # make
+  # make test
+  # make install
+
+Falls die Tests nicht erfolgreich verlaufen, wäre eine Rückmeldung an
+uns interessannt. Bitte melden Sie sich bei der Liste
+C<jifty-devel at lists.jifty.org> und melden den Fehler. (Oder sehen Sie
+nach unter L</HILFE> weiter unten wo beschrieben ist, wie Sie sich uns
+anschließen können.)
+
+=head2 Erstellung eines Rumpfes
+
+Wenn Sie Jifty erfolgreich installiert haben, können Sie Ihre erste
+Applikation erstellen.
+
+Jifty ist geplantermaßen Minimalistisch. Wenn Sie eine Applikation
+erstellen möchten, kopieren Sie einfach das F<jifty>
+Kommandozeilenprogramm in das F<bin/> Unterverzeichnis in Ihr
+Applikations-Verzeichnis.
+
+OK, es ist natürlich hilfreich, ein wenig strukturierter
+vorzugehen. Jifty wird mit Werkzeugen geliefert, die Ihnen helfen, die
+notwendigen Strukturen anzulegen.
+
+Wechseln Sie in ein sicheres Verzeichnis, in dem Sie Ihre Applikation
+anlegen möchten (Jifty erstellt das notwendige Unterverzeichnis für Sie).
+
+  # jifty app --name MyWeblog
+  Can't guess application root from current path (/tmp) or bin path (/usr/bin)
+  Creating new application MyWeblog
+  Creating directory lib
+  Creating directory lib/MyWeblog
+  Creating directory bin
+  Creating directory etc
+  Creating directory doc
+  Creating directory log
+  Creating directory var
+  Creating directory var/mason
+  Creating directory share
+  Creating directory share/po
+  Creating directory share/web
+  Creating directory share/web/templates
+  Creating directory share/web/static
+  Creating directory lib/MyWeblog/Model
+  Creating directory lib/MyWeblog/Action
+  Creating directory t
+  Creating configuration file MyWeblog/etc/config.yml
+
+Sehen wir uns die einzelnen Verzeichnisse genauer an.
+
+=over
+
+=item bin
+
+Innerhalb des F<bin/> Verzeichnisses liegt F<jifty>, die Jifty
+Kommandozentrale. Einige der wichtigsten Befehle sind C<schema>,
+welches ein Datenbank-Schema erstellt oder erneuert und C<server>,
+womit ein funktionsfähiger Webserver gestartet wird. Um die von
+F<jifty> verstandenen Kommandos herauszufinden, tippen Sie:
+
+    jifty help
+
+=item etc
+
+Konfigurationsdateien leben in F<etc/> und Jifty wird vernünftige
+Standardwerte verwenden, sollte keine Konfigurationsdatei vorhanden
+sein oder benötigte Einträge fehlen.
+
+=item doc
+
+Leider erstellt Jifty keine magische Dokumentation, aber wenn Sie
+dokumentieren, dann bitte in das Verzeichnis F<doc/>.
+
+=item log
+
+Jifty setzt L<Log::Log4perl> ein, um
+mitzuprotokollieren. Voreingestellt ist das Erstellen der Logs
+F<server.log> und F<error.log> in das F<log/> Verzeichnis.
+
+=item share/web/templates
+
+Jifty nutzt L<HTML::Mason> als sein primäres Template System. Legen
+Sie die Templates Ihrer Appkikation in F<share/web/templates/>. Von
+Haus aus wird Jifty mit einer vordefinierten Applikation geliefert,
+die im Verzeichnis F<share/web/templates/> installiert wird. Diese
+vordefinierte Applikation ist ein bequemer Weg um eine einfache eigene
+Applikation schnell zum Laufen zu bringen, jedoch sind sicher
+Anpassungen notwendig, wenn Ihre Applikation komplexer wird.
+
+Sie können schnell herausfinden, wo Jifty seine vordefinierten
+Templates ablegt:
+
+  perl -MJifty::Util -e 'print Jifty::Util->share_root'
+
+=item share/web/static
+
+Einige einfache Dinge, die zu Ihrer Applikation gehören, brauchen
+nicht (oder I<sollten nicht>) durch das Template-System laufen.
+
+Stecken Sie solche Dateien einfach in das F<share/web/static/>
+Verzeichnis und Jifty wird diese verwenden, wenn es nicht gleichzeitig
+ein Template mit identischen Namen gibt.
+
+Von Hause aus wird Jifty mit einigen CSS Stilen, JavaScript
+Bibliotheken und einem Pony geliefert. Sehen Sie einfach im
+Verzeichnis F<share/web/static> der Jifty Distribution nach oder dort
+wo Jifty seine Standard-Templates ablegt.
+
+=item lib/MyWeblog
+
+Eine nähere Beschreibung des Objekt Modells und der
+Verzeichnishierarchie finden Sie unter L<Jifty::Manual::ObjectModel>
+
+Zum Bau einer funktionsfähigen einfachen Applikation brauchen Sie sich
+nur Gedanken über zwei Arten von Klassen, B<Models> und B<Actions> zu
+machen.
+
+=item lib/MyWeblog/Model
+
+Die wirkliche Basis Ihrer Applikation lebt in
+C<lib/B<AppName>/Model>. Alle Klassen hier definieren die
+Daten(bank)strukturen Ihrer Applikation und wie diese miteinander
+verbunden sind. Jifty benutzt diese Model-Klassen zum Erstellen oder
+Aktualisieren des Datenbank-Schemas, wenn dies notwendig sein sollte.
+
+=item lib/MyWeblog/Action
+
+Als wir gesagt haben, daß Sie sich nur über B<Models> und B<Actions>
+den Kopf zerbrechen müssen, haben wir nicht die ganze Wahrheit
+gesagt. Jifty kümmert sich um grundlegende Datenbank Interaktionen
+(C<CREATE (Anlegen), READ (Lesen), UPDATE (Aktualisieren), DELETE
+(Löschen)>). Entsprechend benötigte B<Actions> werden für die
+jeweiligen B<Models> automatisch erzeugt und können jederzeit
+verändert werden, sollte dies notwendig werden.
+
+=item t
+
+Jifty legt bereits Test-Routinen für Ihre Applikation an, kann
+allerdings nicht die Tests für Sie schreiben. (Es erstellt jedoch
+einfache Tests für die Model-Klassen, die Sie anlegen.)
+
+=item var
+
+Jifty speichert Cache-Dateien hier, während der Server
+läuft. Normalerweise werden Sie hier nichts tun müssen.
+
+=back
+
+=head2 Erstellung eines Datenmodells
+
+Wie Sie sich aus der Tatsache, daß diese Tutorial-Applikation
+B<MyWeblog> heißt, vorstellen können, ist das hier vorgestellte
+Beispiel ein einfaches Weblog. Künftige Tutorials werden
+Authentifizierung, Kommentare sowie RSS und Atom Feeds beschreiben.
+
+=head3 Posts
+
+Weblogs konzentrieren sich rund um Posts, daher ist es keine
+Überraschung, daß unser erstes Model, das wir erzeugen werden, ein
+C<Post> ist.
+
+  # cd MyWeblog
+  # jifty model --name Post
+  Writing file /tmp/MyWeblog/t/00-model-Post.t
+  Writing file /tmp/MyWeblog/lib/MyWeblog/Model/Post.pm
+
+Großartig! Nun haben Sie ein B<Post> Model (Nur, daß es im Augenblick
+noch nichts modelliert).
+
+Öffnen Sie F<lib/MyWeblog/Model/Post.pm> in Ihrem Lieblings-Editor.
+
+Sie sollten etwas sehen wie:
+
+  use strict;
+  use warnings;
+  
+  package MyWeblog::Model::Post;
+  use Jifty::DBI::Schema;
+  
+  use MyWeblog::Record schema {
+  
+  };
+  
+  # Your model-specific methods go here.
+  
+  1;
+
+Nun ist es an der Zeit, der Model Klasse etwas über Posts zu
+erzählen. Fangen wir damit an, einem Post einen C<body> (Rumpf) und
+einen C<title> (Titel) zu geben. (In einem kommenden Tutorial wird die
+Applikation voll "Folksonomy"-tauglich werden, was wir durch Anfügen
+einer C<category> (Kategorie) und Erweitern der C<category> zu einer
+C<tags> (Indexierung, Etikett) Tabelle erreichen könnten.)
+
+Editieren Sie unter dieser Zeile:
+
+  use MyWeblog::Record schema {
+
+Fügen Sie diese Zeilen an:
+
+  column title =>
+        type is 'text',
+        label is 'Title',
+        default is 'Ohne Titel';
+
+  column body => 
+        type is 'text',
+        label is 'Content',
+        render_as 'Textarea';
+
+Abspeichern bitte nicht vergessen.
+
+=head2 Erstellung der Datenbank
+
+Gut. Nun ist es an der Zeit, die Datenbank zu erstellen. Standardgemäß
+benutzt Jifty eine SQLite Datenbank. Wenn Sie lieber PostgreSQL oder
+MySQL verwenden möchten, dann können Sie Anpassungen an
+F<etc/jifty.yml> vornehmen. (Sehen Sie dazu in C<Jifty::Config> nach,
+um mehr darüber zu erfahren.)
+
+  # jifty schema --setup
+  INFO - Generating SQL for application MyWeblog...
+  INFO - Using MyWeblog::Model::Post
+  INFO - Using Jifty::Model::Session
+  INFO - Using Jifty::Model::Metadata
+  INFO - Using Jifty::Model::Schema
+  INFO - Set up version v0.0.1, jifty version 0.607280
+
+=head2 Start des Jifty Application Server
+
+OK. Nun haben Sie eine lauffähige, aber noch einfache
+Applikation. Starten Sie den Webserver und schauen Sie sich um. Werfen
+Sie einen Blick auf die AJAX gesteuerte Administrations-Umgebung, die
+Online Dokumentation und das Pony.
+
+  # ./bin/jifty server
+  INFO - You can connect to your server at http://localhost:8888/
+
+Starten Sie diese Kommandofolge immer im Hauptverzeichnis Ihrer Jifty
+Applikation, andernfalls werden etliche Fehlermeldungen die Folge
+sein.
+
+Auf den meisten Plattformen wird ein einfaches "jifty server" Kommando
+ebenfalls funktionieren. :)
+
+=head2 Erstellung der Benutzeroberfläche
+
+Die Administrations-Umgebung gibt Ihnen zwar alles, was Sie brauchen,
+um mit den Daten der Applikation zu arbeiten, ist aber noch lange kein
+Weblog.
+
+=head3 Erstellung von Posts
+
+Erstellen Sie eine Seite zum Posten eines neuen Weblog Eintrages:
+
+  # cd share/web/templates/
+
+Erstellen Sie eine neue Datei namens F<post> in Ihrem Editor. Stellen
+Sie sicher, daß diese so aussieht:
+
+  <%init>
+  my $action = Jifty->web->new_action(class => 'CreatePost');
+  </%init>
+
+  <&| /_elements/wrapper, title => "Post Eintrag Erstellen" &>
+  <% Jifty->web->form->start() %>
+  <% Jifty->web->form->next_page( url => '/') %>
+  <% $action->form_field('title') %>
+  <% $action->form_field('body') %>
+  <% Jifty->web->form->submit( label => 'Post' ) %>
+  <% Jifty->web->form->end() %>
+  </&>
+
+Ja, dies ist eine Template Datei in L<HTML::Mason> Syntax. Wenn Sie
+noch nicht genug über Mason wissen, empfehlen wir dessen Online
+Dokumentation für mehr Details. I<Mason Templates> sollten jeweils in
+der ersten Spalte der Datei beginnen. Besonders wichtig sind die
+C<E<lt>%initE<gt>> und C<E<lt>/%initE<gt>> Blöcke, die unbedingt
+lingsbündig geschrieben werden müssen.
+
+=head3 Anzeige
+
+Es ist relativ einfach, eine I<einfache> Auflistung von Einträgen zu
+erhalten und ein wenig aufwändiger, eine AJAX gesteuerte seitenweise
+Darstellung zu bekommen. Hier beschreiben wir, wie man beides macht;
+Sie können entscheiden, welches Ihnen besser gefällt.
+
+(Falls Sie per Kopieren und Einfügen den Beispiel-Code von unten
+übertragen, stellen Sie sicher, daß er in einer Spalte erscheint,
+ansonsten wird es nicht funktionieren.)
+
+=head4 Der schnelle Weg
+
+Erstellen Sie eine neue Datei F<index.html> im F<share/web/templates>
+Verzeichnis in Ihrem Editor. (Der Webserver wird die URL
+C</index.html> als die I<Standard> Seite für Ihre Applikation
+annehmen.) Befüllen Sie die Datei wie folgt:
+
+  <%init>
+  my $posts = MyWeblog::Model::PostCollection->new();
+  $posts->unlimit();
+  </%init>
+
+  <&| /_elements/wrapper, title => Jifty->config->framework('ApplicationName') &>
+  <dl>
+  % while (my $post = $posts->next) {
+   <dt><% $post->title %></dt>
+   <dd><% $post->body %></dd>
+  % }
+  </dl>
+  </&>
+
+(Entfernen Sie die Leerzeichen am Zeilenanfang!)
+
+=head4 Die aufwändige Art, mit der Sie viele coole Dinge bekommen
+
+Die I<aufwändige Art> benutzt eines von Jifty's fortgeschrittenen
+Eigenschaften: I<PageRegions> (Seitenbereiche). Diese Bereiche
+erlauben Ihrer Applikation einzelne Bestandteile einer Seite
+unabhängig voneinander neu zu laden. Das funktioniert sowohl unter
+Benutzung von AJAX auf modernen Browsern, als auch mit GET Requests
+auf nicht JavaScript fähigen Browsern wie C<lynx>, C<w3m> oder dem
+Browser Ihres Handys.
+
+Der Nachteil dieser Methode ist, daß jeder Seiten-Bereich in einer
+eigenen I<Fragment> Datei vorliegen muß.
+
+Die aufwändige Art beginnt zunächst genauso wie der schnelle
+Weg. Erstellen Sie eine neue Datei mit dem Namen
+F<share/web/templates/index.html> in Ihrem Editor. Befüllen Sie die
+Datei mit:
+
+  <&| /_elements/wrapper, title => Jifty->config->framework('ApplicationName') &>
+
+  <% Jifty->web->region(name => "myweblog-posts",
+                        path => "/fragments/page_of_posts") %>
+  </&>
+
+Wenn Sie mitgedacht haben, dann haben Sie bestimmt schon erraten, daß
+Sie als nächstes eine Datei
+F<share/web/templates/fragments/page_of_posts> mit dem nachfolgenden
+Inhalt anlegen dürfen:
+
+  <%args>
+  $page => 1
+  </%args>
+  <%init>
+  my $posts = MyWeblog::Model::PostCollection->new();
+  $posts->unlimit();
+  $posts->set_page_info( current_page => $page,
+                         per_page     => 15
+                       );
+  $m->out("Keine Einträge.") if ($posts->pager->total_entries == 0);
+
+  </%init>
+  % if ($posts->pager->last_page > 1) {
+     Seite <% $page %> von <% $posts->pager->last_page %>
+  % }
+  <dl class="list">
+  % while (my $post = $posts->next) {
+   <dt><% $post->title %></dt>
+   <dd><% $post->body %></dd>
+  % }
+  </dl>
+
+  % if ($posts->pager->previous_page) {
+    <% Jifty->web->link( label => "vorherige Seite", onclick => { args => { page => $posts->pager->previous_page } } ) %>
+  % }
+  % if ($posts->pager->next_page) {
+    <% Jifty->web->link( label => "nächste Seite", onclick => { args => { page => $posts->pager->next_page } } ) %>
+  % }
+
+Nun starten Sie den Jifty Webserver erneut. Erstellen Sie einen Post
+indem Sie die URL C</post> auf Ihrem Webserver anfahren. Erstellen Sie
+wenigstens 15 Einträge und beobachten Sie dabei, wie Jifty Ihnen die
+AJAX Knöpfe C<vorherige Seite> und C<nächste Seite> erstellt. Schalten
+Sie JavaScript aus oder verwenden Sie lynx und beobachten Sie wie AJAX
+automatisch zurückfällt zum vollständigen Laden der jeweils neuen
+Seite. Alles umsonst. Danke, Jifty!
+
+=head3 Hey, woher kam diese Klasse?
+
+Wenn Sie genau aufgepasst haben, dann haben Sie sich sicher gefragt,
+woher die C<MyWeblog::Model::PostCollection> Klasse kam. Es gibt keine
+Datei mit dem Namen F<PostCollection.pm>. Jifty setzt
+C<Jifty::ClassLoader> ein, um einige Klassen für Sie zu
+erstellen. Selbstverständlich könnten Sie das ebenfalls tun. Lesen Sie
+unter L<Jifty::ClassLoader> mehr darüber.
+
+=head2 Navigation
+
+Natürlich ist es unschön, sich die URL der Post Seite merken zu
+müssen. Um einen B<Post> Eintrag im Menü zu bekommen, müssen Sie die
+Standard Menüs überschreiben.
+
+Jifty's I<Standard> Menüs werden durch F<_elements/nav> in den
+Standard Applikations Templates (die mit dem Pony) erzeugt. Im
+Augenblick bleibt Ihnen nichts anderes übrig, als F<_elements/nav> zu
+überschreiben. (Wir arbeiten an Methoden, dies besser zu gestalten.)
+
+Legen Sie innerhalb des Verzeichnisses F<share/web/templates> Ihrer
+Applikation ein Verzeichnis F<_elements> an.
+
+  mkdir share/web/templates/_elements
+
+Sie möchten bestimmt mit den normalen Jifty Templates arbeiten, also
+müssen wir nur den C<< $top->child( Post... ) >> Teil anfügen.
+
+  cat $(perl -MJifty::Util -e 'print Jifty::Util->share_root' \
+    )/web/templates/_elements/nav > share/web/templates/_elements/nav
+
+Ansonsten können Sie innerhalb F<_elements> auch gerne eine neue Datei
+namens C<nav> anlegen, die so aussehen könnte:
+
+  <%init>
+  my $top = Jifty->web->navigation;
+  $top->child( Home => url => "/");
+  $top->child( Post => url => "/post",
+                       label => "Neuen Eintrag");
+  </%init>
+
+Mehr Information über das Menü System können Sie der Dokumentation
+unter L<Jifty::Web::Menu> entnehmen.
+
+=head2 Das war's
+
+Das war alles, das Sie benötigen, um anzufangen, Jifty Applikationen
+zu erstellen. Wir arbeiten hart daran, Jifty noch einfacher einsetzbar
+zu machen und die I<dicken Brocken> dieses Tutorials so bald als
+möglich zu vereinfachen.
+
+Bitte nehmen Sie auf der C<jifty-devel> Mailing Liste Kontakt mit uns
+auf, wenn Sie Fragen haben, wie man Jifty benutzt oder was Sie
+schwierig zu benutzen finden. Dies ist eine Mailing Liste in
+englischer Sprache.
+
+=head1 HILFE
+
+=head2 Online Hilfe
+
+Das C<jifty> Kommandozeilen Programm besitzt eine eingebaute Hilfe
+Funktion.
+
+  jifty help
+
+  jifty help <kommando>
+
+Wenn Ihr Server mit eingeschaltetem Administrations-Modus arbeitet
+(die Konfigurations-Einstellung C<AdminMode> fehlt oder sie ist
+ungleich Null), dann können Sie den "Online Docs" Link in Ihrem
+Browser benutzen, um eine ausführliche modulweise Dokumentation zu
+Jifty zu erhalten.
+
+=head2 Beitreten der Mailing Liste
+
+C<jifty-devel at lists.jifty.org> ist der Ort, an dem wir besprechen, wie
+wir Jifty erstellen, was es an Problemen oder Vorschlägen gibt und so
+weiter.
+
+Um der Liste beizutreten, senden Sie eine Mail an
+C<jifty-devel-subscribe at lists.jifty.org>. Bitte beachten Sie, daß die
+Kommunikation in dieser Liste in englischer Sprache geführt wird.
+
+=head2 Unser Wiki
+
+Wir haben ein Wiki! (Tatsächlich ist das Wiki die hauptsächliche
+Website von Jifty).
+
+Besuchen Sie uns unter L<http://jifty.org/>, lesen Sie und tragen Sie
+etwas bei!
+
+Das Wiki wird von I<Wifty> betrieben, einem Wiki das auf Jifty
+basiert. Sein Code ist frei verfügbar in unserem Subversion
+Repository.
+
+=head1 FEHLER MELDEN
+
+In dieser frühen Phase von Jifty melden Sie bitte alle Fehler, die
+Ihnen auffallen, an C<jifty-devel at lists.jifty.org>.
+
+=head1 KÜNFTIGE TUTORIALS
+
+Künftige Tutorials werden enthalten:
+
+=over 4
+
+=item * Zugangskontrolle und Sicherheit
+
+=item * Erweiterung von Datenmodellen
+
+=item * Umfangreiche Beschreibung des Dispatchers
+
+=item * Einbindung einer Applikation in einen Live Server
+
+=item * Web Services im Detail
+
+=item * Continuations (Fortsetzungen) und deren Einsatz
+
+=item * Anpassungen der Darstellung (Benutzerdefinierte Wrapper und CSS)
+
+=back
+
+=cut

Added: jifty/branches/schema-plugins/lib/Jifty/Manual/Tutorial_ja.pod
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Manual/Tutorial_ja.pod	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,472 @@
+=encoding utf8
+
+=head1 題名
+
+Jifty::Manual::Tutorial - あっという間にゼロからJifty
+
+=head1 説明
+
+このチュートリアルを読めば、Jiftyではじめてのアプリケーションを
+構築するにあたって知っておくべきことはすべてわかるはずです。
+
+=cut
+
+=head1 使い方
+
+=head2 必要なもの
+
+ここでは――少なくともこのチュートリアルの執筆時点で――インス
+トールしておくべきものを紹介します。
+
+=head2 Jiftyのインストール
+
+そんなに構えなくても大丈夫。私たちはDRY (Don't Repeat Yourself) 
+の原則をとても大事に思っていますし、だからこそPerlやCPANを愛し
+ているのですから。JiftyはCPANのすばらしいコードをたくさん利用し
+ています。直接依存しているCPANパッケージは全部あわせて60にもな
+りましたが、そのほとんどはクロス・プラットフォームなピュアPerl
+パッケージですから、Perlを利用できるプラットフォームならインス
+トールするだけでばっちり動くはずです。
+
+それだけではありません。次から次へとライブラリをダウンロードす
+るだけで一日が終わってしまわないよう、バンドルできるライブラリ
+はすべてJiftyパッケージのなかにまとめるようにしています。運が
+よければ、(Perlのデータベース・インタフェースやJiftyがデフォ
+ルトで利用する組み込みSQLiteのように)実際にOSにあわせてコンパ
+イルしなければならないトリッキーなライブラリをいくつかインスト
+ールするだけですみます。
+
+http://download.jifty.org/pub/jifty/ から完全版をダウンロード
+するのでも、CPANからインストールするのでも結構ですが、CPANから
+「スリム」版を入手する場合は、依存パッケージは自分でインストー
+ルする必要があります(その場合でもできる限り簡単にインストール
+できるようにはしてありますが)。すぐに始めたいのであれば、以下
+のURLからどうぞ。
+
+    http://download.jifty.org/pub/jifty/
+
+いずれにしても、インストールの手順は同じです。
+
+  # tar xzvf jifty-<version>.tgz
+  # cd jifty-<version>
+  # perl Makefile.PL
+  # make
+  # make test
+  # make install
+
+テストが通らないようでしたら、お話を聞きたいので、
+C<jifty-devel at lists.jifty.org>に参加して失敗したことを報告して
+ください(メーリング・リストへの参加方法については以下の
+「L</困ったときは>」をご覧ください)。
+
+=head2 足場をつくる(スキャフォールディング)
+
+Jiftyをうまくインストールできたら、準備完了。はじめてのアプリケ
+ーションをつくってみましょう。
+
+Jiftyはわざと少々ミニマリスト的につくってあります。アプリケーシ
+ョンを走らせるにあたって、「本当に」必要なのはF<jifty>というコ
+マンドラインツールのみなのです(新しくつくったアプリケーション
+のF<bin/>ディレクトリに入っています)。
+
+もちろん往々にしてもう少し仕掛けを用意しておいた方が作業は楽に
+なるものですから、そのような仕掛けを構築するツールも用意して
+あります。
+
+どこか、新しいJiftyアプリケーションをつくってもかまわないディレ
+クトリに移動してください(Jiftyはサブディレクトリをつくります)。
+
+  # jifty app --name MyWeblog
+  Can't guess application root from current path (/your/current/directory) or bin path (/usr/bin)
+  Creating new application MyWebLog
+  Creating directory lib
+  Creating directory lib/MyWebLog
+  Creating directory bin
+  Creating directory etc
+  Creating directory doc
+  Creating directory log
+  Creating directory web
+  Creating directory web/templates
+  Creating directory web/static
+  Creating directory lib/MyWebLog/Model
+  Creating directory lib/MyWebLog/Action
+  Creating directory t
+  Creating configuration file MyWeblog/etc/config.yml
+
+では、ひとつひとつ見ていきましょう。
+
+=over
+
+=item bin
+
+F<bin/>のなかにはF<jifty>というコマンド・ディスパッチャが入って
+います。特に重要なコマンドとしては、データベースのスキーマを設定
+したり更新したりするC<schema>と、スタンドアロンのウェブサーバを
+起動するC<server>があります。F<jifty>にどのようなコマンドがある
+か知りたい場合は次のようにタイプしてください。
+
+    jifty help
+
+=item etc
+
+F<etc/>には設定ファイルが入ります。設定ファイルがない場合はJifty
+が適切なデフォルト値を用意します。
+
+=item doc
+
+Jiftyの魔法もドキュメントまでは書いてはくれませんが、「みなさん
+が」ドキュメントを書くときはF<doc/>に入れてください。
+
+=item log
+
+JiftyはL<Log::Log4perl>を使ってログの設定を行います。デフォルト
+ではF<log>ディレクトリにF<server.log>とF<error.log>というログを
+吐き出します。
+
+=item web/templates
+
+JiftyはL<HTML::Mason>をメインのテンプレート・システムとしていま
+す。アプリケーションのテンプレートはF<web/templates/>に入れてく
+ださい。Jiftyには最初からアプリケーションの「スケルトン」が用意
+されています(F<share/web/templates/>にインストールされています)。
+このデフォルト・アプリケーションは、基本的なアプリケーションを
+急いで準備したいときには便利です。ただし、もっと複雑なアプリケ
+ーションを構築するときにはなにがしかのカスタマイズを行う必要が
+あるでしょう。
+
+PerlがどこにJiftyのデフォルト・テンプレートをインストールしたか
+知りたい場合は次のようにします。
+
+  perl -MJifty::Util -e 'print Jifty::Util->share_root'
+
+=item web/static
+
+ウェブ・アプリケーションが提供する「素材」のなかには、テンプレ
+ート・エンジンを通す必要のない(あるいは「通すべきでない」)も
+のも少なからずあります。
+
+そのような静的ファイルについては、F<web/static/>に入れておいて
+ください。 適切な名前のテンプレートが見つからない場合はそこか
+らサーブされます。
+
+また、Jiftyには最初からCSSのスタイルシート、Javascriptライブラ
+リ、Ponyも用意されています。F</usr/local/share/jifty/web/static>
+のなかをご覧ください。
+
+=item lib/MyWebLog
+
+Jiftyのオブジェクト・モデルの詳細については
+L<Jifty::Manual::ObjectModel>をご覧ください。
+
+基本的なJiftyアプリケーションを構築するだけなら、気にかける必
+要があるのは「モデル」と「アクション」という二種類のクラスのみ
+です。
+
+=item lib/MyWebLog/Model
+
+アプリケーションの本当の基礎となるものは
+C<lib/B<アプリケーション名>/Model>に入っています。ここに入って
+いるクラスはアプリケーションのデータ構造とそれらの相関関係を定
+義するものです。Jiftyはこのモデル・クラスを利用して必要なときに
+データベースのスキーマを設定・更新します。
+
+=item lib/MyWebLog/Action
+
+先ほどは「モデル」と「アクション」さえ気にかけていればいいと言
+いましたが、それは真実の一面でしかありません。たしかにJiftyは
+「モデル」となるデータベースに対する基本的なやりとり(C<CREATE、
+READ、UPDATE、DELETE>) をする「アクション」を用意してくれるの
+ですが、なにがしかの変更を加えたければ、変更すべきものはここに
+あります。
+
+=item t
+
+Jiftyはアプリケーションの土台を一式用意しますが、まだあらゆる
+テストを用意することまではできません(もっとも、生成したモデル
+・クラスに対する簡単なテストは用意します)。
+
+=back
+
+=head2 データ・モデルを構築する
+
+このチュートリアル・アプリケーションの名前がB<MyWebLog>である
+ことからも想像がつくかと思いますが、ここでは例題として簡単なウ
+ェブログ・アプリケーションをとりあげます。今後のチュートリアル
+では、認証、コメント、RSSやAtomフィードの機能を追加する予定です。
+
+=head3 記事
+
+ウェブログの中心はふつう記事です。だから、当然最初につくるモデ
+ルはC<post>になります。
+
+  # cd MyWeblog
+  # jifty model --name Post
+  Writing file /tmp/MyWeblog/t/00-model-Post.t
+  Writing file /tmp/MyWeblog/lib/MyWeblog/Model/Post.pm
+
+すごいですね! これでB<Post>というモデルができたのです(もっと
+も、いまのところモデルらしいことはしませんけれどね)。
+
+お好きなエディタでF<lib/MyWeblog/Model/Post.pm>を開いてください。
+
+このような画面になるはずです。
+
+  package MyWeblog::Model::Post::Schema;
+  use Jifty::DBI::Schema;
+
+  # Your column definitions go here.  See L<Jifty::DBI::Schema> for
+  # documentation about how to write column definitions.
+
+  package MyWeblog::Model::Post;
+  use base qw/MyWeblog::Record/;
+
+  # Your model-specific methods go here.
+
+  1;
+
+では、このモデル・クラスに記事の構造を教えましょう。まずは記事
+にC<body>とC<title>、C<category>を用意します(将来的にはこの
+C<category>をC<tags>テーブルにアップグレードして完全なフォーク
+ソノミー対応にする予定です)。
+
+カーソルを以下の行のすぐ下に移動してください。
+
+  # Your column definitions go here.  See L<Jifty::DBI::Schema> for
+  # documentation about how to write column definitions.
+
+以下の行を加えます。
+
+  column title =>
+        type is 'text',
+        label is 'Title',
+        default is 'Untitled post';
+
+  column body => 
+        type is 'text',
+        label is 'Content',
+        render_as 'Textarea';
+
+モデル・クラスを保存しましょう。
+
+=head2 データベースのセットアップ
+
+OK。今度はMyWeblogのデータベースを初期化します。デフォルトで
+はSQLiteデータベース・エンジンを使うようにセットアップされます
+ので、PostgreSQLやMySQLを使いたい場合は、F<etc/jifty.yml>に何
+行か付け足す必要があります(C<Jifty::Config>にもう少し詳しい説
+明があります)。
+
+  # jifty schema --setup
+  INFO - Generating SQL for application MyWeblog...
+  INFO - Using MyWeblog::Model::Post
+  INFO - Using Jifty::Model::Schema
+  INFO - Set up version v0.0.1
+
+=head2 Jiftyのアプリケーション・サーバを起動する
+
+OK。これで簡単ながら動作するアプリケーションができました。
+ウェブサーバを起動して見てみましょう。AJAXのきいた管理UIやオ
+ンライン・ドキュメント・ブラウザ、Ponyを確かめてみてください。
+
+  # ./bin/jifty server
+  INFO - You can connect to your server at http://localhost:8888/
+
+=head2 ユーザ・インタフェースの構築
+
+管理画面を使えばアプリケーションのデータを管理するのに必要な
+ものはすべて手に入りますが、それではあまりウェブログらしくは
+ありません。
+
+=head3 記事を投稿する
+
+新しいエントリを投稿するページをつくりましょう。
+
+  # cd web/templates/
+
+F<post>という新規ファイルをテキスト・エディタで開いて、この
+ようにしてください。
+
+  <%init>
+  my $action = Jifty->web->new_action(class =>'CreatePost');
+  </%init>
+
+  <&|/_elements/wrapper, title => "Post to your weblog" &>
+  <% Jifty->web->form->start() %>
+  <% Jifty->web->form->next_page( url => '/') %>
+  <% $action->form_field('title') %>
+  <% $action->form_field('body') %>
+  <% Jifty->web->form->submit( label => 'Post' ) %>
+  <% Jifty->web->form->end() %>
+  </&>
+
+=head3 閲覧する
+
+「基本的な」エントリ一覧をつくるのは本当に簡単です。AJAXでペー
+ジングするきれいな一覧の方はもう少し複雑ですが、ここでは両方の
+やり方を紹介します。お好みの方を選んでください。
+
+=head4 手っ取り早いやり方
+
+F<web/templates>ディレクトリでF<index.html>という新規ファイル
+をテキスト・エディタで開き(ウェブサーバはC</index.html>という
+URLをサイトの「デフォルト」ページとしてくれるはずです)、この
+ようにしてください。
+
+  <%init>
+  my $posts = MyWeblog::Model::PostCollection->new();
+  $posts->unlimit();
+  </%init>
+
+  <&|/_elements/wrapper, title => Jifty->config->framework('ApplicationName') &>
+  <dl>
+  % while (my $post = $posts->next) {
+   <dt><%$post->title%></dt>
+   <dd><%$post->body%></dd>
+  % }
+  </dl>
+  </&>
+
+=head4 複雑だけどクールなツールをふんだんに使う方法
+
+「複雑な方法」ではJiftyの上級機能のひとつである「ページ領域
+(Page regions)」というものを使います。領域を指定しておくと、
+最近の高性能ブラウザならAJAXで、C<lynx>やC<w3m>、携帯電話のブ
+ラウザのように機能が限定されたブラウザでも通常のGETリクエスト
+を使って、ページの一部だけを独立してリロードすることが
+できるのです。
+
+このアプローチの欠点は、指定する領域ごとに固有の「部品」ファイ
+ルを用意しなければならないことです。
+
+複雑な方法も、最初は簡単な方法とほとんど同じです。テキスト・エ
+ディタでF<web/templates/index.html>という新規ファイルを開いて、
+以下のように書き込んでください。
+
+  <&|/_elements/wrapper, title => Jifty->config->framework('ApplicationName') &>
+
+  <% Jifty->web->region(name => "myweblog-posts",
+                        path => "/fragments/page_of_posts") %>
+  </&>
+
+勘のいい方ならたぶんもうおわかりでしょうが、今度は
+F<web/templates/fragments/page_of_posts>というファイルをつく
+って、以下のようにする必要があります。
+
+  <%args>
+  $page => 1
+  </%args>
+  <%init>
+  my $posts = MyWeblog::Model::PostCollection->new();
+  $posts->unlimit();
+  $posts->set_page_info( current_page => $page,
+                         per_page     => 25
+                       );
+  $m->out("No items found.") if ($posts->pager->total_entries == 0);
+
+  </%init>
+  % if ($posts->pager->last_page > 1) {
+     Page <% $page %> of <% $posts->pager->last_page %>
+  % }
+  <dl class="list">
+  % while (my $post = $posts->next) {
+   <dt><%$post->title%></dt>
+   <dd><%$post->body%></dd>
+  % }
+  </dl>
+
+  % if ($posts->pager->previous_page) {
+    <% Jifty->web->link( label => "Previous Page", onclick => { args => { page => $posts->pager->previous_page } } ) %>
+  % }
+  % if ($posts->pager->next_page) {
+    <% Jifty->web->link( label => "Next Page", onclick => { args => { page => $posts->pager->next_page } } ) %>
+  % }
+
+さて、もう一度Jiftyのウェブサーバを起動してみましょう。ブラウ
+ザでC</post>を表示して、記事を作成してみてください。
+
+=head2 ナビゲーション
+
+当然のことながら、投稿ページのURLを覚えていなければならないと
+いうのはいささか面倒なことです。ただし、メニューに「投稿」ボタ
+ンを用意するには、デフォルトのメニューをオーバーライドしなけれ
+ばなりません。
+
+Jiftyの「デフォルト」メニューは(Ponyともども)アプリケーショ
+ンのデフォルト・テンプレートのなかのF<_elements/nav>にあります。
+さしあたっては、F<_elements/nav>をオーバーライドしましょう(こ
+こは改善中です)。
+
+アプリケーションのF<web/templates>ディレクトリのなかに、
+F<_elements>というディレクトリをつくります。
+
+F<_elements>ディレクトリのなかにテキスト・エディタでC<nav>とい
+う新規ファイルをつくり、以下を挿入してください。
+
+  <%init>
+  my $top = Jifty->web->navigation;
+  $top->child( Home => url => "/");
+  $top->child( Post => url => "/post", 
+                       label => "Post Article");
+  </%init>
+
+メニュー・システムについての詳細はL<Jifty::Web::Menu>をご覧ください。
+
+=head2 おしまい!
+
+おおよそこれだけ知っていればJiftyアプリケーションの構築は始め
+られるはずです。私たちもJiftyをもっと使いやすく、またこのチュ
+ートリアルの「手強い部分」をなくすべく鋭意努力しています。
+
+ぜひC<jifty-devel>メーリング・リストに参加して、Jiftyをどう使
+っているか、使ってみて難しいとか使いづらいと思った点をお知らせ
+ください。
+
+=head1 困ったときは
+
+=head2 メーリング・リストに参加する
+
+C<jifty-devel at lists.jifty.org>ではJiftyをどのように構築するか、
+何に困っているか等が話し合われています。
+
+メーリング・リストに参加したい方は
+C<jifty-devel-subscribe at lists.jifty.org>にメールを送ってください。
+
+=head2 wikiを見る
+
+wikiもあります!(というか、このwikiがJiftyのメイン・サイトなのです)
+
+L<http://jifty.org/>に来て、書き込みしてみてください。
+
+このwikiは、I<Wifty>というJiftyベースのwikiで運用されています。
+ソースコードはjiftyのsubversionリポジトリから自由に入手可能です。
+
+=head1 バグを報告する
+
+Jiftyは信じられないほど初期の段階にありますので、バグが見つかっ
+たらC<jifty-devel at lists.jifty.org>に報告してください。
+
+=head1 予定されているチュートリアル
+
+将来的には以下のチュートリアルが予定されています。
+
+=over 4
+
+=item * アクセス・コントロールとセキュリティ
+
+=item * データ・モデルのアップグレード
+
+=item * ディスパッチャの詳細
+
+=item * 本番環境への移行
+
+=item * ウェブ・サービス
+
+=item * 継続についての詳細
+
+=back
+
+=head1 翻訳者
+
+石垣憲一(C<ishigaki_at_tcool.org>) L<http://www.tcool.org/>
+
+=cut

Added: jifty/branches/schema-plugins/lib/Jifty/Manual/Upgrading.pod
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Manual/Upgrading.pod	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,72 @@
+=head1 NAME
+
+Jifty::Manual::Upgrading
+
+=head2 DESCRIPTION
+
+Jifty provides a way for you to upgrade the database schema and data
+of your application between versions.  If all you are doing is adding
+new models or columns to existing models Jifty will do the upgrade
+almost automatically.  If more extensive changes are required you need
+to write some code to tell Jifty what to do.
+
+=head1 HOW TO
+
+=head2 New models and columns
+
+=head3 Adding a new model
+
+When adding a new model to your application you need to tell Jifty
+at which version of your application the model was created.  To do
+this add a since sub to your new model class.
+
+ sub since { 0.0.5 }
+
+=head3 Adding a new column to an existing model
+
+When you have an existing model and decide that you need to add another
+column to it you also need to tell Jifty about this.  This is done by
+using C<since> as well. However, the C<since> goes into the column
+definition itself.  
+
+ column created_by =>
+    refers_to Wifty::Model::User, 
+    since '0.0.20';
+
+=head2 data migration and schema changes
+
+If a file called F<Upgrade.pm> exists in your application it will be
+run by C<jifty schema --setup>.
+
+F<Upgrade.pm> can be used to make any schema changes or to manipulate
+your applications data.
+
+At the very least your F<Upgrade.pm> should contain the following:
+
+ package MyApp::Upgrade;
+
+ use base qw(Jifty::Upgrade);
+ use Jifty::Upgrade qw( since rename );
+
+ since '0.6.1' => sub {
+    ....
+ };
+
+The C<since> function is where you do all the work.  Each C<since>
+will be run in version order until the application is up to date.
+
+=head3 Renaming a column
+
+To rename a column, simply call C<rename> inside your C<since> code block:
+
+    use MyApp::Model::User;
+
+    since '0.6.1' => sub {
+        rename(
+            table   => 'MyApp::Model::User', 
+            column  => 'zip', 
+            to      => 'postcode'
+        );
+    };
+
+=head3 Migrating data

Added: jifty/branches/schema-plugins/lib/Jifty/Manual/UsingCSSandJS.pod
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Manual/UsingCSSandJS.pod	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,185 @@
+=head1 NAME
+
+Jifty::Manual::UsingCSSandJS - Using CSS and JavaScript
+
+=head1 DESCRIPTION
+
+Jifty comes bundled with a series of separately developed JavaScript
+libraries as well as a set of CSS definitions that both allow Jifty to
+functionally and beautifully work out of the box. This document
+describes the mechanisms behind the scenes as well as some of the
+details inside the included files.
+
+=head1 BUILT-IN FEATURES
+
+Both, CSS and JavaScript (further abbreviated as "JS") files
+typically reside in the C<share/web/static> directory of Jifty,
+keeping separate C<css> and C<js> subdirectories for each of both sets
+of files. When using Jifty without any interference into these files,
+all of those files will get loaded from the Jifty-provided directories.
+
+In both cases, there are hooks for expansion by keeping empty but
+present files in the C<css> and C<js> directories. By simply creating
+and populating these files inside the C<share/web/static/css> and
+C<share/web/static/js> directories brings the predefined hooks to
+work.
+
+Also there is a big difference of the whole operation between an
+application running in C<DevelMode> or a productive application. In
+DevelMode, every single CSS and JS file will get included into every
+single template page being rendered. On the other hand, a productive
+application will merge all CSS and JS definitions upon the first
+request and will only include one file each containing all CSS and JS
+definitions in a single request.
+
+=head1 USING AND EXPANDING CSS
+
+=head2 Assembly of CSS definitions
+
+When Jifty assembles all CSS definitions (which is internally done
+inside L<Jifty::Web> by the method C<include_css>, a single file,
+C<main.css> is included into the generated HTML code of the current
+page. This file consists of a series of C<@include> directives that
+reference every single CSS file to get used.
+
+=head2 Expansion of CSS definitions
+
+Jifty maintains two initially empty files, C<app-base.css> and
+C<app.css> that may get "overloaded" by simply providing these files
+in an application's C<share/web/static/css> directory.
+
+These two files will get included in different order, C<app-base.css>
+being the very first and C<app.css> getting included very late in the
+CSS construction process.
+
+This means that general definitions that should apply to all
+subsequently encountered styles could easily get done in
+C<app-base.css> whereas individual redefinitions, expansions or your
+application's own definitions could go into C<app.css>.
+
+=head2 Jifty's own definitions
+
+Jifty provides a series of definitions that are responsible for a good
+look without any modification. Please note that not all of the used
+CSS classes are already defined, but they will provide a hook for
+modification of the general look. Some of the styles are listed below.
+
+=over
+
+=item form_errors, error
+
+Error messages encountered during validation are displayed inside a
+C<< <div> >> tag of class C<form_errors> which initially is not yet
+defined. Every single error message is marked with a class C<error>.
+
+=item hints, warning, error
+
+These classes are used for displaying additional information for form
+fields.
+
+=item form_field, mandatory, argument-$name
+
+Every form field including its label is packed inside a C<< <div> >>
+tag with these classes (mandatory only if the field is
+mandatory, of course), where C<$name> is the field's name.
+
+=item preamble
+
+This section is a C<< <span> >> tag filled with a form field's
+preamble content that could contain additional instructions for the
+user. The content may be set by the C<preamble> accessor method that
+is available for every C<Jifty::Web::Form::Field> and its successors.
+
+=item widget, button, button_as_link, combobox,
+combo-text, combo-button, combo-list, date, label, password
+submit_button, reset, text, hidden, ajaxvalidation,
+ajaxcanonicalization, ajaxautocompletes
+
+These class names are used depending on the type of widget getting
+rendered.
+
+=item autocomplete
+
+used for the autocomplete div.
+
+=item toplevel, menu, context_menu, submenu, title, expand
+
+These classes are used in navigation bars.
+
+=item jifty, results, messages
+
+These three CSS classes are used to surround a message block displaying
+an action's messages after having run an action.
+
+=item message, error, $moniker
+
+Every single message that is displayed in an action's result box is
+marked with the message's type plus the action's moniker as a CSS
+class name.
+
+=back
+
+=head1 USING AND EXPANDING JAVASCRIPT
+
+Jifty comes bundled with a series of separately developed JavaScript
+libraries, like
+
+=over
+
+=item C<Prototype> L<http://prototype.conio.net>
+
+Prototype is a toolkit for providing AJAX support. This is a library
+some others depend on.
+
+=item C<scriptaculous.js> L<http://script.aculo.us/>
+
+This library provides support for animation with effects, drag & drop,
+AJAX controls and DOM utilities.
+
+=item C<Rico> L<http://openrico.org>
+
+Rico also provides graphical effects.
+
+=item C<Json> L<http://json.org>
+
+Hereby, major support for encoding and decoding data into the JSON
+data format (similar to C<YAML>) is provided.
+
+=item C<behaviour.js> L<http://bennolan.com/behaviour/>
+
+With C<behaviour.js>, intelligent JavaScript handlers can be defined.
+
+=item C<cssQuery.js> L<http://dean.edwards.name/>
+
+This library adds support for querying the DOM using CSS selectors.
+
+=back
+
+=head2 Assembly of JS definitions
+
+Jifty maintains a complete list of JS files to include. This list may
+be retrieved or set by the accessor C<< Jifty->web->javascript_libs >>.
+There should, However, rarely arise a situation to do that, because
+Jifty has already reserved two files that may get added to your application:
+
+=over 4
+
+=item F<app.js>
+
+Initially empty; put all JS functions you need to define here.
+
+=item F<app_behaviour.js>
+
+Reserved for defining behaviors for DOM objects using the C<behaviour.js>
+library.
+
+=back
+
+The assembly process of all JS definitions is done in L<Jifty::Web> by
+the method C<include_javascript>.
+
+=head1 SEE ALSO
+
+L<Jifty::Web>
+
+=cut

Added: jifty/branches/schema-plugins/lib/Jifty/Mason/Halo.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Mason/Halo.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,163 @@
+use warnings;
+use strict;
+package Jifty::Mason::Halo;
+use base qw/HTML::Mason::Plugin/;
+use Time::HiRes ();
+Jifty->handle->log_sql_statements(1);
+
+=head1 NAME
+
+Jifty::Mason::Halo
+
+=head1 DESCRIPTION
+
+
+=cut
+
+
+=head2 start_component_hook CONTEXT_OBJECT
+
+Whenever we start to render a component, check to see if we can draw a halo around the component.
+
+Either way, record halo metadata.
+
+=cut
+
+
+sub start_component_hook {
+    my $self    = shift;
+    my $context = shift;
+
+    return if ($context->comp->path && $context->comp->path eq "/__jifty/halo");
+
+    Jifty->handler->stash->{ '_halo_index_stack' } ||= [];
+
+    my $DEPTH = Jifty->handler->stash->{'_halo_depth'} || 0;
+    my $STACK = Jifty->handler->stash->{'_halo_stack'} ||= [];
+        
+    my $INDEX_STACK = Jifty->handler->stash->{'_halo_index_stack'};
+
+    my $halo_base = Jifty->web->serial;
+
+    Jifty->handler->stash->{'_halo_depth'} = ++$DEPTH;
+    if ($STACK->[-1]) {
+        push @{$STACK->[-1]->{sql_statements}}, Jifty->handle->sql_statement_log;
+        Jifty->handle->clear_sql_statement_log;
+    }
+
+    push @$STACK, {
+        id           => $halo_base,
+        args         => [map { eval { defined $_ and fileno( $_ ) }  ? "*GLOB*" : $_} @{$context->args}],
+        start_time   => Time::HiRes::time(),
+        path         => $context->comp->path || '',
+        subcomponent => (  $context->comp->is_subcomp() ? 1:0),
+        name         => $context->comp->name || '(Unamed component)',
+        proscribed   => ($self->_unrendered_component($context) ? 1 :0 ),
+        depth        => $DEPTH
+    };
+
+    push @$INDEX_STACK, $#{$STACK};
+    return if $self->_unrendered_component($context);
+
+    $context->request->out(qq{<div id="halo-@{[$halo_base]}">});
+}
+
+=head2 end_component_hook CONTEXT_OBJECT
+
+When we're done rendering a component, record how long it took
+and close off the halo C<span> if we have one.
+
+
+=cut
+
+sub end_component_hook {
+    my $self    = shift;
+    my $context = shift;
+
+    return if ($context->comp->path && $context->comp->path =~ "^/__jifty/halo");
+
+    my $STACK = Jifty->handler->stash->{'_halo_stack'};
+    my $INDEX_STACK = Jifty->handler->stash->{'_halo_index_stack'};
+    my $DEPTH = Jifty->handler->stash->{'_halo_depth'};
+
+    my $FRAME_ID = pop @$INDEX_STACK;
+
+    my $frame = $STACK->[$FRAME_ID];
+    $frame->{'render_time'} = int((Time::HiRes::time - $frame->{'start_time'}) * 1000)/1000;
+
+    push @{$frame->{sql_statements}}, Jifty->handle->sql_statement_log;
+    Jifty->handle->clear_sql_statement_log;
+
+
+    Jifty->handler->stash->{'_halo_depth'} = $DEPTH-1 ;
+
+    # If 
+    return if $self->_unrendered_component($context);
+
+    # print out the div with our halo magic actions.
+    # if we didn't render a beginning of the span, don't render an end
+    unless ( $frame->{'proscribed'} ) {
+        my $comp_name = $frame->{'path'};
+        $context->request->out('</div>');
+        $context->request->out(
+            Jifty->web->link(
+                label   => _( 'Edit %1', $comp_name ),
+                class => 'inline_edit',
+                onclick => [
+                    {   element      => "#halo-" . $frame->{id},
+                        replace_with =>
+                            '/__jifty/edit_inline/mason_component/'.$comp_name
+                    }
+                ]
+            )
+            )
+            if 0 and ( $frame->{'path'} and $frame->{'path'} !~ /^\/?__jifty/ );
+    }
+
+}
+
+=head2 _unrendered_component CONTEXT
+
+Returns true if we're not currently inside the "Body" section of the
+webpage OR the current component is a subcomponent. (Rendering halos
+for subcomponents being too "heavy")
+
+=cut
+
+
+sub _unrendered_component {
+    my $self    = shift;
+    my $context = shift;
+    if (   $context->comp->is_subcomp()
+        or not Jifty->handler->stash->{'in_body'})
+    {
+        return 1;
+    } else {
+        return undef;
+    }
+
+}
+
+=head2 render_component_tree
+
+Once we're just about to finish rendering our HTML page (just before
+the C<</body>> tag, we should call render_component_tree to output all
+the halo data and metadata.
+
+
+=cut
+
+sub render_component_tree {
+    my $self  = shift;
+    my @stack = @{ Jifty->handler->stash->{'_halo_stack'} };
+
+    for (@stack) {
+        $_->{'render_time'} = int((Time::HiRes::time - $_->{'start_time'}) * 1000)/1000
+          unless defined $_->{'render_time'};
+    }
+
+    Jifty->web->mason->comp("/__jifty/halo", stack => \@stack );
+}
+
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Model/Metadata.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Model/Metadata.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,98 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Jifty::Model::Metadata - Tracks Jifty-related metadata
+
+=head1 SYNOPSIS
+
+  my $app = Jifty->new(config_file => "$ProjectRoot/etc/config.yml");
+  my $application_version = Jifty::Model::Metadata->load("application_db_version");
+
+=head1 DESCRIPTION
+
+Every Jifty application automatically inherits this table, which
+describes information about the Jifty database.  It uses this
+information to smartly upgrade between application versions, as well
+as versions of Jifty itself, for instance.
+
+=cut
+
+package Jifty::Model::Metadata;
+
+use Jifty::DBI::Schema;
+use Jifty::Record schema {
+
+column data_key => type is 'text';
+column value    => type is 'text';
+};
+
+use version;
+
+use base qw( Jifty::Record );
+
+=head2 table
+
+Schemas are stored in the table C<_jifty_metadata>.
+
+=cut
+
+sub table {'_jifty_metadata'}
+
+=head2 since
+
+The metadata table first appeared in Jifty version 0.60427
+
+=cut
+
+sub since {'0.60427'}
+
+=head2 load KEY
+
+Fetches the given C<KEY> from the metadata store.  Returns undef if
+such a key cannot be found.
+
+=cut
+
+sub load {
+    my $self = shift;
+    $self = $self->new( current_user => Jifty::CurrentUser->superuser )
+        unless ref $self;
+    return undef unless $self->_handle and $self->_handle->dbh->ping;
+
+    my ($key) = @_;
+
+    # This may barf all over the place.  That's almost expected in
+    # some circumstances, so we eat all warnings and errors right
+    # here, right now.
+    eval {
+        local $SIG{__WARN__} = sub { };
+        $self->load_by_cols( data_key => $key );
+    };
+    return undef unless $self->id;
+    return $self->value;
+}
+
+=head2 store KEY => VALUE
+
+Stores the given C<KEY> in the database, overwriting the previous
+value if it existed.
+
+=cut
+
+sub store {
+    my $self = shift;
+    $self = $self->new( current_user => Jifty::CurrentUser->superuser )
+        unless ref $self;
+
+    my ( $key, $value ) = @_;
+    $self->load_by_cols( data_key => $key );
+    if ( $self->id ) {
+        $self->set_value($value);
+    } else {
+        $self->create( data_key => $key, value => $value );
+    }
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Model/Session.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Model/Session.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,103 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Jifty::Model::Session - Jifty session tracking
+
+=head1 DESCRIPTION
+
+Every Jifty application automatically inherits this table, which
+tracks session information for the application.  Individual keys in
+the session structure are stored as rows, making session updates take
+constant time, and also reducing the need for locking.
+
+=cut
+
+package Jifty::Model::Session;
+use Jifty::DBI::Schema;
+use Jifty::Record schema {
+
+column session_id => type is 'varchar(32)';
+column data_key => type is 'text';
+column value => type is 'blob',
+  filters are 'Jifty::DBI::Filter::Storable';
+column created => type is 'timestamp',
+  filters are 'Jifty::DBI::Filter::DateTime';
+column updated => type is 'timestamp',
+  filters are 'Jifty::DBI::Filter::DateTime';
+column key_type => type is 'varchar(32)';
+};
+
+use base qw( Jifty::Record );
+use DateTime ();
+
+=head2 table
+
+Sessions are stored in the table C<_jifty_sessions>.
+
+=cut
+
+sub table {'_jifty_sessions'}
+
+=head2 since
+
+Sessions first started getting stored in the database in Jifty version
+0.60428
+
+=cut
+
+sub since { '0.60428' }
+
+=head2 current_user
+
+Everyone is treated as the superuser when dealing with session
+objects.  This avoids infinite recursion, as otherwise it would try to
+look up the current user in the session object to find out who we
+are...
+
+=cut
+
+sub current_user { return Jifty::CurrentUser->superuser }
+
+=head2 new_session_id
+
+Returns a random new session id.  This is a 32-character hex string.
+
+=cut
+
+sub new_session_id {
+    return Digest::MD5::md5_hex(
+        Digest::MD5::md5_hex( time() . {} . rand() . $$ ) );
+}
+
+=head2 create
+
+Defaults the created and updated times to now.
+
+=cut
+
+sub create {
+    my $self = shift;
+    my %args = (
+        session_id => $self->new_session_id,
+        key_type   => "key",
+        created    => DateTime->now,
+        updated    => DateTime->now,
+        @_,
+    );
+
+    return $self->SUPER::create(%args);
+}
+
+=head2 before_set_value [VALUE]
+
+Updates the C<updated> column, in addition to setting the value.
+
+=cut
+
+sub before_set_value {
+    shift->set_updated( DateTime->now );
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Model/SessionCollection.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Model/SessionCollection.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,28 @@
+use warnings;
+use strict;
+
+package Jifty::Model::SessionCollection;
+
+use base qw/Jifty::Collection/;
+
+=head2 record_class
+
+This deals with collections of L<Jifty::Model::Session>s.
+
+=cut 
+
+sub record_class { 'Jifty::Model::Session' }
+
+
+=head2 current_user
+
+Everyone is treated as the superuser when dealing with session
+objects.  This avoids infinite recursion, as otherwise it would try to
+look up the current user in the session object to find out who we
+are...
+
+=cut
+
+sub current_user { return Jifty::CurrentUser->superuser }
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Module/Pluggable.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Module/Pluggable.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,59 @@
+package Jifty::Module::Pluggable;
+use strict;
+use warnings;
+use base qw/Module::Pluggable/;
+
+=head1 NAME
+
+Jifty::Module::Pluggable
+
+
+=head1 DESCRIPTION
+
+A custom subclass of Module::Pluggable to override the C<require>
+mechanism with one that better fits our own view of the world.
+
+=head2 require
+
+    Date:   October 24, 2006 12:19:31 AM PDT
+    From:     simon at thegestalt.org
+    Subject:    Re: Module::Pluggable and CORE::require
+    To:       jesse at bestpractical.com
+
+On Mon, Oct 23, 2006 at 09:11:22PM -0700, Jesse Vincent said:
+does this thread make any sense to you? It looks like  
+Module::Pluggable is interacting poorly with UNIVERSAL::require?
+
+Module::Pluggable used to to use UNIVERSAL::require but I switched 
+because I was trying to get rid of dependencies.
+
+I farmed the requiring stuff off to it's own _require method in order to 
+make it easy to subclass so that people could ovveride how the require 
+was done. 
+
+
+=head3  But Simon is lying...
+
+What we actually need to override is Module::Pluggable::Object::_require, which we do below.
+
+
+=cut
+
+
+use Module::Pluggable::Object;
+use UNIVERSAL::require;
+
+no warnings 'redefine';
+sub Module::Pluggable::Object::_require {
+    my $self = shift;
+    my $module = shift;
+
+    # Module::Pluggable::Object::_require expects a true value (the error message) on failure
+    # On success, it expects you to return undef.
+
+    local $UNIVERSAL::require::ERROR;
+    $module->require(); # We'd prefer to use Jifty::Util->require() here, but it spews crazy warnings
+    return $UNIVERSAL::require::ERROR;
+} 
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Notification.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Notification.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,297 @@
+use warnings;
+use strict;
+
+package Jifty::Notification;
+
+use base qw/Jifty::Object Class::Accessor::Fast/;
+use Email::Send            ();
+use Email::MIME::Creator;
+
+__PACKAGE__->mk_accessors(
+    qw/body preface footer subject from _recipients _to_list to/);
+
+=head1 USAGE
+
+It is recommended that you subclass L<Jifty::Notification> and
+override C<body>, C<subject>, C<recipients>, and C<from> for each
+message.  (You may want a base class to provide C<from>, C<preface>
+and C<footer> for example.)  This lets you keep all of your
+notifications in the same place.
+
+However, if you really want to make a notification type in code
+without subclassing, you can create a C<Jifty::Notification> and call
+the C<set_body>, C<set_subject>, and so on methods on it.
+
+=head1 METHODS
+
+=cut
+
+=head2 new [KEY1 => VAL1, ...]
+
+Creates a new L<Jifty::Notification>.  Any keyword args given are used
+to call set accessors of the same name.
+
+Then it calls C<setup>.
+
+=cut
+
+sub new {
+    my $class = shift;
+    my $self  = bless {}, $class;
+
+    my %args = @_;
+
+    # initialize message bits to avoid 'undef' warnings
+    #for (qw(body preface footer subject)) { $self->$_(''); }
+    $self->_recipients( [] );
+
+    while ( my ( $arg, $value ) = each %args ) {
+        if ( $self->can($arg) ) {
+            $self->$arg($value);
+        } else {
+            $self->log->error(
+                ( ref $self ) . " called with invalid argument $arg" );
+        }
+    }
+
+    $self->setup;
+
+    return $self;
+}
+
+=head2 setup
+
+Your subclass should override this to set the various field values.
+
+=cut
+
+sub setup { }
+
+=head2 send_one_message
+
+Delivers the notification, using the L<Email::Send> mailer defined in
+the C<Mailer> and C<MailerArgs> configuration arguments.  Returns true
+if mail was actually sent.  Note errors are not the only cause of mail
+not being sent -- for example, the recipients list could be empty.
+
+Be aware that if you haven't set C<recipients>, this will fail silently
+and return without doing anything useful.
+
+=cut
+
+sub send_one_message {
+    my $self       = shift;
+    my @recipients = $self->recipients;
+    my $to         = join( ', ',
+        map { ( $_->can('email') ? $_->email : $_ ) } grep {$_} @recipients );
+    return unless ($to);
+    my $message = Email::MIME->create(
+        header => [
+            From    => $self->from    || 'A Jifty Application <nobody>',
+            To      => $to,
+            Subject => Encode::encode('MIME-Header', $self->subject || 'No subject'),
+        ],
+        attributes => { charset => 'UTF-8' },
+        parts => $self->parts
+    );
+    $message->encoding_set('8bit')
+        if (scalar $message->parts == 1);
+    $self->set_headers($message);
+
+    my $method   = Jifty->config->framework('Mailer');
+    my $args_ref = Jifty->config->framework('MailerArgs');
+    $args_ref = [] unless defined $args_ref;
+
+    my $sender
+        = Email::Send->new( { mailer => $method, mailer_args => $args_ref } );
+
+    my $ret = $sender->send($message);
+
+    unless ($ret) {
+        $self->log->error("Error sending mail: $ret");
+    }
+
+    $ret;
+}
+
+=head2 set_headers MESSAGE
+
+Takes a L<Email::MIME> object C<MESSAGE>, and modifies it as
+necessary before sending it out.  As the method name implies, this is
+usually used to add or modify headers.  By default, does nothing; this
+method is meant to be overridden.
+
+=cut
+
+sub set_headers {}
+
+=head2 body [BODY]
+
+Gets or sets the body of the notification, as a string.
+
+=head2 subject [SUBJECT]
+
+Gets or sets the subject of the notification, as a string.
+
+=head2 from [FROM]
+
+Gets or sets the from address of the notification, as a string.
+
+=head2 recipients [RECIPIENT, ...]
+
+Gets or sets the addresses of the recipients of the notification, as a
+list of strings (not a reference).
+
+=cut
+
+sub recipients {
+    my $self = shift;
+    $self->_recipients( [@_] ) if @_;
+    return @{ $self->_recipients };
+}
+
+=head2 email_from OBJECT
+
+Returns the email address from the given object.  This defaults to
+calling an 'email' method on the object.  This method will be called
+by L</send> to get email addresses (for L</to>) out of the list of
+L</recipients>.
+
+=cut
+
+sub email_from {
+    my $self = shift;
+    my ($obj) = @_;
+    if ( $obj->can('email') ) {
+        return $obj->email;
+    } else {
+        die "No 'email' method on " . ref($obj) . "; override 'email_from'";
+    }
+}
+
+=head2 to_list [OBJECT, OBJECT...]
+
+Gets or sets the list of objects that the message will be sent to.
+Each one is sent a separate copy of the email.  If passed no
+parameters, returns the objects that have been set.  This also
+suppresses duplicates.
+
+=cut
+
+sub to_list {
+    my $self = shift;
+    if (@_) {
+        my %ids = ();
+        $ids{ $self->to->id } = undef if $self->to;
+        $ids{ $_->id } = $_ for @_;
+        $self->_to_list( [ grep defined, values %ids ] );
+    }
+    return @{ $self->_to_list || [] };
+}
+
+=head2 send
+
+Sends an individual email to every user in L</to_list>; it does this by
+setting L</to> and L</recipient> to the first user in L</to_list>
+calling L<Jifty::Notification>'s C<send> method, and progressing down
+the list.
+
+Additionally, if L</to> was set elsewhere, sends an email to that
+person, as well.
+
+=cut
+
+sub send {
+    my $self = shift;
+    my $currentuser_object_class = Jifty->app_class("CurrentUser");
+    for my $to ( grep {defined} ($self->to, $self->to_list) ) {
+        if ($to->can('id')) {
+        next if     $currentuser_object_class->can("nobody")
+                and $to->id == $currentuser_object_class->nobody->id;
+                
+        next if $to->id == $currentuser_object_class->superuser->id;
+        } 
+        $self->to($to);
+        $self->recipients($to);
+        $self->send_one_message(@_);
+    }
+}
+
+=head2 to
+
+Of the list of users that C<to> provided, returns the one which mail
+is currently being sent to.  This is set by the L</send> method, such
+that it is available to all of the methods that
+L<Jifty::Notification>'s C<send> method calls.
+
+=cut
+
+=head2 preface
+
+Print a header for the message. You want to override this to print a message.
+
+Returns the message as a scalar.
+
+=cut
+
+=head2 footer
+
+Print a footer for the message. You want to override this to print a message.
+
+Returns the message as a scalar.
+
+=cut
+
+=head2 full_body
+
+The main, plain-text part of the message.  This is the preface,
+body, and footer joined by newlines.
+
+=cut
+
+sub full_body {
+  my $self = shift;
+  return join( "\n", grep { defined } $self->preface, $self->body, $self->footer );
+}
+
+=head2 parts
+
+The parts of the message.  You want to override this if you want to
+send multi-part mail.  By default, this method returns a single
+part consisting of the result of calling C<< $self->full_body >>.
+
+Returns the parts as an array reference.
+
+=cut
+
+sub parts {
+  my $self = shift;
+  return [
+    Email::MIME->create(
+      attributes => { charset => 'UTF-8' },
+      body       => Encode::encode_utf8($self->full_body),
+    )
+  ];
+
+}
+
+=head2 magic_letme_token_for PATH
+
+Returns a L<Jifty::LetMe> token which allows the current user to access a path on the
+site. 
+
+=cut
+
+sub magic_letme_token_for {
+    my $self = shift;
+    my $path = shift;
+    my %args = @_;
+
+    my $letme = Jifty::LetMe->new();
+    $letme->email( $self->to->email );
+    $letme->path($path);
+    $letme->args( \%args );
+    return ( $letme->as_url );
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Object.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Object.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,121 @@
+use warnings;
+use strict;
+
+package Jifty::Object;
+
+use Log::Log4perl ();
+
+=head1 Jifty::Object
+
+C<Jifty::Object> is the superclass of most of Jifty's objects.  It is
+used to provide convenient accessors to important global objects like
+the database handle or the logger object, while still allowing
+individual classes to overload these methods.
+
+We ought to be able to mix-in C<Jifty::Object> with any other class;
+thus, we will not define C<new> or C<_init> in C<Jifty::Object>.  We
+do assume, however, that C<$self> is a blessed hash reference.
+
+=cut
+
+=head2 current_user [USER]
+
+Gets/sets a user for the current user for this object.  You often do
+not need to call this explicitly; Jifty will inspect your caller's
+C<current_user>, and so on up the call stack.
+
+=cut
+
+
+sub current_user {
+    my $self = shift;
+    unless (ref($self)) {
+        Carp::cluck("Called current_user as a class method. Dealing. Fix your code.");
+    }
+    $self->{'_current_user'} = shift if (@_); 
+    return($self->{'_current_user'});
+}
+
+=head2 PRIVATE _get_current_user
+
+Takes the ARGS paramhash passed to _init.
+Find the current user. First, try to see if it's explicit.
+After that, check the caller's current_user. After that, look
+at Jifty->web->current_user
+
+Fills in current_user with that value
+
+=cut
+
+
+sub _get_current_user {
+    my $self = shift;
+    my %args = (@_);
+
+    return if ( ref($self) && $self->current_user );
+
+    if ( $args{'current_user'} ) {
+        return $self->current_user( $args{'current_user'} );
+    }
+    my $depth = 1;
+    my $caller;
+
+    # Mason introduces a DIE handler that generates a mason exception
+    # which in turn generates a backtrace. That's fine when you only
+    # do it once per request. But it's really, really painful when you do it
+    # often, as is the case with fragments
+    #
+    local $SIG{__DIE__} = 'DEFAULT';
+    eval {
+        package DB;
+
+        # get the caller in array context to populate @DB::args
+        while ( not $self->current_user and $depth < 10 ) {
+
+            #local @DB::args;
+            my ($package,   $filename, $line,       $subroutine, $hasargs,
+                $wantarray, $evaltext, $is_require, $hints,      $bitmask
+                )
+                = CORE::caller( $depth++ );
+            my $caller_self = $DB::args[0];
+            next unless ( ref($caller_self) );    #skip class methods;
+	    next if $caller_self->isa('Jifty::Date'); 
+            next
+                unless ( $caller_self->can('current_user')
+                and $caller_self->current_user
+                and defined $caller_self->current_user->id );
+            $self->current_user( $caller_self->current_user() );
+        }
+    };
+
+    # If we found something, return it
+    return $self->current_user if $self->current_user;
+
+    # Fallback to web ui framework
+    if ( Jifty->web ) {
+        return $self->current_user( Jifty->web->current_user );
+    }
+
+    return undef;
+}
+
+
+sub _handle {
+    return Jifty->handle();
+}
+
+=head2 log
+
+Returns a L<Log::Log4perl> logger object; the category of the logger
+is the same as the class of C<$self>.
+
+=cut
+
+sub log {
+    my $self = shift;
+
+    return Log::Log4perl->get_logger(ref $self);
+}
+
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Param.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Param.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,107 @@
+use warnings;
+use strict;
+
+package Jifty::Param;
+
+=head1 NAME
+
+Jifty::Param - Parameters for Jifty actions
+
+=head1 DESCRIPTION
+
+Describes a parameter to a C<Jifty::Action> object.  Do not construct
+this by hand -- use L<Jifty::Param::Schema> in the action package to
+declare parameters instead.
+
+=head2 accessors
+
+Although this class is not derived from B<Jifty::Web::Form::Field>,
+it does share the accessors from it; see L<Jifty::Web::Form::Field>,
+for the list of possible keys that each parameter can have.
+
+In addition to the list there, you may use these additional keys:
+
+=over
+
+=item constructor
+
+A boolean which, if set, indicates that the argument B<must> be
+present in the C<arguments> passed to create the action, rather than
+being expected to be set later.
+
+Defaults to false.
+
+=item valid_values
+
+An array reference.  Each element should be either:
+
+=over 4
+
+=item *
+
+A hash reference with a C<display> field for the string to display for the
+value, and a C<value> field for the value to actually send to the server.
+
+=item *
+
+A hash reference with a C<collection> field containing a L<Jifty::Collection>,
+and C<display_from> and C<value_from> fields containing the names of methods to
+call on each record in the collection to get C<display> and C<value>.
+
+=item *
+
+A simple string, which is treated as both C<display> and C<value>.
+
+=back
+
+=item available_values
+
+Just like L<valid_values>, but represents the list of suggested values,
+instead of the list of acceptable values.
+
+=item sort_order
+
+An integer of how the parameter sorts relative to other parameters.
+This is usually implicitly generated by its declaration order.
+
+=back
+
+=cut
+
+
+# We share accessors with Jifty::Web::Form::Field, but not its methods,
+# so it's not an inheritance relationsip.
+use Jifty::Web::Form::Field ();
+
+use base qw/Class::Accessor::Fast/;
+use constant ACCESSORS => (
+    Jifty::Web::Form::Field->accessors,
+    qw(constructor valid_values available_values sort_order),
+);
+
+__PACKAGE__->mk_accessors(ACCESSORS);
+
+sub accessors { ACCESSORS }
+
+=head2 new
+
+Creates a new L<Jifty::Param> object.  Note that unlike L<Jifty::Web::Form::Field>,
+the object is never magically reblessed into a subclass.  Should only be called
+implicitly from a L<Jifty::Param::Schema> declaration.
+
+=cut
+
+sub new {
+    my $class = shift;
+    $class->Class::Accessor::Fast::new({
+        type          => 'text',
+        class         => '',
+        input_name    => '',
+        default_value => '',
+        sticky_value  => '',
+        render_mode   => 'update',
+        @_,
+    });
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Param/Schema.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Param/Schema.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,174 @@
+package Jifty::Param::Schema;
+
+=head1 NAME
+
+Jifty::Param::Schema - Declare parameters of a Jifty action with ease.
+
+=head1 SYNOPSIS
+
+    package MyApp::Action::Login;
+    use Jifty::Param::Schema;
+    use Jifty::Action schema {
+
+    param email =>
+        label is 'Email address',
+        is mandatory,
+        ajax validates;
+
+    param password =>
+        type is 'password',
+        label is 'Password',
+        is mandatory;
+
+    param remember =>
+        type is 'checkbox',
+        label is 'Remember me?',
+        hints is 'If you want, your browser can remember your login for you',
+        default is 0;
+
+    };
+
+=head1 DESCRIPTION
+
+This module provides a simple syntax to declare action parameters.
+
+It re-exports C<defer> and C<lazy> from L<Scalar::Defer>, for setting
+parameter fields that must be recomputed at request-time.
+
+=head2 schema
+
+The C<schema> block from a L<Jifty::Action> subclass describes an action
+for a Jifty application.
+
+Within the C<schema> block, the localization function C<_> is redefined
+with C<defer>, so that it resolves into a dynamic value that will be
+recalculated upon each request, according to the user's current language
+preference.
+
+=head2 param
+
+Each C<param> statement inside the C<schema> block sets out the name
+and attributes used to describe one named parameter, which is then used
+to build a L<Jifty::Param> object.  That class defines possible field names
+to use in the declarative syntax here.
+
+The C<param> function is not available outside the C<schema> block.
+
+=head1 ALIASES
+
+In addition to the labels provided by L<Jifty::Web::Form::Field> and
+L<Jifty::Param>, this module offers the following aliases:
+
+    ajax validates,             # ajax_validates is 1
+    ajax canonicalizes,         # ajax_canonicalizes is 1
+    order is -1,                # sort_order is -1
+    default is 0,               # default_value is 0
+    valid are qw( 1 2 3 ),      # valid_values are qw( 1 2 3 )
+    available are qw( 1 2 3 ),  # available_values are qw( 1 2 3 )
+    render as 'select',         # render_as is 'select'
+
+=head1 SEE ALSO
+
+L<Object::Declare>, L<Scalar::Defer>
+
+=cut
+
+use strict;
+use warnings;
+use Jifty::I18N;
+use Jifty::Param;
+use Scalar::Defer;
+use Object::Declare (
+    mapping => {
+        param => 'Jifty::Param',
+    },
+    aliases => {
+        default     => 'default_value',
+        available   => 'available_values',
+        valid       => 'valid_values',
+        render      => 'render_as',
+        order       => 'sort_order',
+    },
+    copula  => {
+        is      => '',
+        are     => '',
+        as      => '',
+        ajax    => 'ajax_',
+    }
+);
+use Exporter::Lite;
+use Class::Data::Inheritable;
+
+our @EXPORT = qw( defer lazy param schema );
+
+sub schema (&) {
+    my $code = shift;
+    my $from = caller;
+
+    no warnings 'redefine';
+    local *_ = sub { my $args = \@_; defer { _(@$args) } };
+
+    Class::Data::Inheritable::mk_classdata($from => qw/PARAMS/);
+    my @params = &declare($code);
+
+    # The .99 here is a flag for Jifty::Action::Record to mark autogenerated orders
+    my $count = 10000.99;
+    foreach my $param (@params) {
+        next if !ref($param) or defined($param->sort_order);
+        $param->sort_order($count);
+        $count += 10;
+    }
+
+    if (my $super_params = $from->can('SUPER::PARAMS')) {
+        $from->PARAMS(merge_params( $super_params->(), { @params } ));
+    }
+    else {
+        $from->PARAMS({ @params });
+    }
+
+    no strict 'refs';
+    push @{$from . '::ISA'}, 'Jifty::Action';
+    return;
+}
+
+use Hash::Merge ();
+
+no warnings 'uninitialized';
+use constant MERGE_PARAM_BEHAVIOUR => {
+    SCALAR => {
+            SCALAR => sub { length($_[1]) ? $_[1] : $_[0] },
+            ARRAY  => sub { [ @{$_[1]} ] },
+            HASH   => sub { $_[1] } },
+    ARRAY => {
+            SCALAR => sub { length($_[1]) ? $_[1] : $_[0] },
+            ARRAY  => sub { [ @{$_[1]} ] },
+            HASH   => sub { $_[1] } },
+    HASH => {
+            SCALAR => sub { length($_[1]) ? $_[1] : $_[0] },
+            ARRAY  => sub { [ @{$_[1]} ] },
+            HASH   => sub { Hash::Merge::_merge_hashes( $_[0], $_[1] ) } }
+};
+
+=head2 merge_params HASHREF HASHREF
+
+Takes two hashrefs. Merges them together and returns the merged hashref.
+
+    - Empty fields in subclasses don't override nonempty fields in superclass anymore.
+    - Arrays don't merge; e.g. if parent class's valid_values is [1,2,3,4], and
+      subclass's valid_values() is [1,2], they don't somehow become [1,2,3,4,1,2].
+
+BUG: This should either be a private routine or factored out into Jifty::Util
+
+
+
+=cut
+
+sub merge_params {
+    my $prev_behaviour = Hash::Merge::get_behavior();
+    Hash::Merge::specify_behavior( MERGE_PARAM_BEHAVIOUR, "merge_params" );
+    my $rv = Hash::Merge::merge(@_);
+    Hash::Merge::set_behavior( $prev_behaviour );
+    return $rv;
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Plugin.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Plugin.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,143 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin;
+
+=head1 NAME
+
+Jifty::Plugin - Describes a plugin to the Jifty framework
+
+=head1 DESCRIPTION
+
+Plugins are like mini-apps.  They come in packages with share
+directories which provide static and template files; they provide
+actions; they have dispatcher rules.  To create the skeleton of a new
+plugin, you can use the command:
+    jifty plugin --name SomePlugin
+
+To use a plugin in your Jifty application, find the C<Plugins:> line
+in the C<config.yml> file:
+
+      Plugins:
+        - SpiffyThing: {}
+        - SomePlugin:
+            arguments: to
+            the: constructor
+
+The dispatcher for a plugin should live in
+C<Jifty::Plugin::I<name>::Disptcher>; it is written like any other
+L<Jifty::Dispatcher>.  Plugin dispatcher rules are checked before the
+application's rules; however, see L<Jifty::Dispatcher/Plugins and rule
+ordering> for how to manually specify exceptions to this.
+
+Actions and models under a plugin's namespace are automatically
+discovered and made available to applications.
+
+=cut
+
+use File::ShareDir;
+
+=head2 new
+
+Sets up a new instance of this plugin.  This is called by L<Jifty>
+after reading the configuration file, and is supplied whatever
+plugin-specific settings were in the config file.  Note that because
+plugins affect Mason's component roots, adding plugins during runtime
+is not supported.
+
+=cut
+
+sub new {
+    my $class = shift;
+    
+    # Get a classloader set up
+    Jifty::ClassLoader->new(base => $class)->require;
+    Jifty::Util->require($class->dispatcher);
+
+    # Start a plugin classloader set up on behalf of the application
+    require Jifty::Plugin::ClassLoader;
+    Jifty::Plugin::ClassLoader->new(
+	base => Jifty->app_class,
+	plugin => $class,
+    )->require;
+
+    # XXX TODO: Add .po path
+
+    my $self = bless {} => $class;
+    $self->init(@_);
+    return $self;
+}
+
+
+=head2 init [ARGS]
+
+Called by L</new>, this does any custom configuration that the plugin
+might need.  It is passed the same parameters as L</new>, gleaned from
+the configuration file.
+
+=cut
+
+sub init {
+    1;
+}
+
+=head2 new_request
+
+Called right before every request.  By default, this adds the plugin's
+actions to the list of allowed actions, using L<Jifty::API/allow>.
+
+=cut
+
+sub new_request {
+    my $self = shift;
+    my $class = ref($self) || $self;
+    Jifty->api->allow(qr/^\Q$class\E::Action/);
+}
+
+=head2 template_root
+
+Returns the root of the template directory for this plugin
+
+=cut
+
+sub template_root {
+    my $self = shift;
+    my $class = ref($self) || $self;
+    unless (exists $self->{share}) {
+        $self->{share} = undef;
+        eval { $self->{share} = File::ShareDir::module_dir($class) };
+    }
+    return unless $self->{share};
+    return $self->{share}."/web/templates";
+}
+
+=head2 static_root
+
+Returns the root of the static directory for this plugin
+
+=cut
+
+sub static_root {
+    my $self = shift;
+    my $class = ref($self) || $self;
+    unless (exists $self->{share}) {
+        $self->{share} = undef;
+        eval { $self->{share} = File::ShareDir::module_dir($class) };
+    }
+    return unless $self->{share};
+    return $self->{share}."/web/static";
+}
+
+=head2 dispatcher
+
+Returns the classname of the dispatcher class for this plugin
+
+=cut
+
+sub dispatcher {
+    my $self = shift;
+    my $class = ref($self) || $self;
+    return $class."::Dispatcher";
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Plugin/ClassLoader.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Plugin/ClassLoader.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,207 @@
+use warnings;
+use strict;
+
+package Jifty::Plugin::ClassLoader;
+
+=head1 NAME
+
+Jifty::Plugin::ClassLoader - Autogenerates application classes
+
+=head1 DESCRIPTION
+
+C<Jifty::Plugin::ClassLoader> loads additional model and action classes on
+behalf of the application out of the configured plugin classes.  Unlike, 
+C<Jifty::ClassLoader>, this class will only autogenerate classes if the
+plugin provides them.  The plugin classes are checked before the base Jifty
+classes, so that a plugin can override the Jifty class, just as any
+existing application classes will be loaded first.
+
+=head2 new
+
+Returns a new ClassLoader object.  Doing this installs a hook into
+C<@INC> that allows L<Jifty::Plugin::ClassLoader> to dynamically create
+needed classes if they do not exist already.  This works because if
+use/require encounters a blessed reference in C<@INC>, it will
+invoke the INC method with the name of the module it is searching
+for on the reference.
+
+Takes two mandatory arguments, C<base>, which should be the 
+application's base path; and C<plugin> which is the plugin classname.
+
+=cut
+
+sub new {
+    my $class = shift;
+    my $self = bless {@_}, $class;
+
+    push @INC, $self;
+    return $self;
+}
+
+=head2 INC
+
+The hook that is called when a module has been C<require>'d that
+cannot be found on disk.  The following stub classes are
+auto-generated:
+
+=over
+
+=item I<Application>
+
+An empty application base class is created that doen't provide any
+methods or inherit from anything.
+
+=item I<Application>::Record
+
+An empty class that descends from L<Jifty::Record> is created.
+
+=item I<Application>::Collection
+
+An empty class that descends from L<Jifty::Collection> is created.
+
+=item I<Application>::Notification
+
+An empty class that descends from L<Jifty::Notification>.
+
+=item I<Application>::Dispatcher
+
+An empty class that descends from L<Jifty::Dispatcher>.
+
+=item I<Application>::Bootstrap
+
+An empty class that descends from L<Jifty::Bootstrap>.
+
+=item I<Application>::Upgrade
+
+An empty class that descends from L<Jifty::Upgrade>.
+
+=item I<Application>::CurrentUser
+
+An empty class that descends from L<Jifty::CurrentUser>.
+
+=item I<Application>::Model::I<Anything>Collection
+
+If C<I<Application>::Model::I<Something>> is a valid model class, then
+it creates a subclass of L<Jifty::Collection> whose C<record_class> is
+C<I<Application>::Model::I<Something>>.
+
+=item I<Application>::Action::(Create or Update or Delete)I<Anything>
+
+If C<I<Application>::Model::I<Something>> is a valid model class, then
+it creates a subclass of L<Jifty::Action::Record::Create>,
+L<Jifty::Action::Record::Update>, or L<Jifty::Action::Record::Delete>
+whose I<record_class> is C<I<Application>::Model::I<Something>>.
+
+=back
+
+=cut
+
+# This subroutine's name is fully qualified, as perl will ignore a 'sub INC'
+sub Jifty::Plugin::ClassLoader::INC {
+    my ( $self, $module ) = @_;
+
+    my $base = $self->{base};
+    my $plugin = $self->{plugin};
+    return undef unless ( $module and $base and $plugin);
+
+
+
+    # Canonicalize $module to :: style rather than / and .pm style;
+    $module =~ s/.pm$//;
+    $module =~ s{/}{::}g;
+
+    # The quick check
+    return undef unless $module =~ m!^$base!;
+
+    # Note that at this point, all of the plugins classes will already be
+    # loaded, so we can just check their presence when deciding whether
+    # this is a class the plugin intends to autocreate
+    if ( $module =~ m{^(?:$base)::CurrentUser$} ) {
+        my $method = "${plugin}::CurrentUser";
+	if ( Jifty::Util->already_required($method) ) {
+	    Jifty->log->debug("Implementing $module using $method");
+            return Jifty::ClassLoader->return_class(
+                  "use warnings; use strict; package $module;\n"
+                . "use base qw/$method/;\n"
+                . "sub _autogenerated { 1 };\n"
+                . "1;" ) 
+	}
+	else {
+	    Jifty->log->debug("Couldn't implement $module using $method");
+	}
+    } elsif ( $module =~ m!^(?:$base)::Action::(Create|Update|Delete|Search)([^\.]+)$! ) {
+        my $model = "::Model::" . $2;
+        my $method = $plugin . "::Action::" . $1 . $2;
+
+        # Check to see if this is an action for a model that this plugin 
+        # doesn't provide
+	return undef unless Jifty::Util->already_required("$plugin$model");
+
+	if ( Jifty::Util->already_required($method) ) {
+	    Jifty->log->debug("Implementing $module using $method");
+	    return Jifty::ClassLoader->return_class(
+                  "use warnings; use strict; package $module;\n"
+                . "use base qw/$method/;\n"
+                . "sub record_class { '$base$model' };\n"
+                . "sub autogenerated { 1 };\n"
+                . "1;" )
+	}
+	else {
+	    Jifty->log->debug("Couldn't implement $module using $method");
+	}
+    } elsif ( $module =~ m{^(?:$base)::(Action|Notification)([^\.]+)$} ) {
+	my $method = $plugin . "::" . $1 . $2;
+	if ( Jifty::Util->already_required($method) ) {
+	    Jifty->log->debug("Implementing $module using $method");
+	    return Jifty::ClassLoader->return_class(
+                  "use warnings; use strict; package $module;\n"
+                . "use base qw/$method/;\n"
+                . "sub autogenerated { 1 };\n"
+                . "1;" )
+	}
+	else {
+	    Jifty->log->debug("Couldn't implement $module using $method");
+	}
+
+    } 
+
+    return undef;
+}
+
+=head2 require
+
+Loads all of the application's Actions and Models.  It additionally
+C<require>'s all Collections and Create/Update actions for each Model
+base class -- which will auto-create them using the above code if they
+do not exist on disk.
+
+=cut
+
+sub require {
+    my $self = shift;
+    
+    my $base = $self->{plugin};
+
+
+    # if we don't even have an application class, this trick will not work
+    return unless ($base); 
+    Jifty::Util->require($base);
+    Jifty::Util->require($base."::CurrentUser");
+
+    Jifty::Module::Pluggable->import(
+        search_path =>
+          [ map { $base . "::" . $_ } 'Model', 'Action', 'Notification' ],
+        require => 1,
+        except  => qr/\.#/,
+        inner   => 0
+    );
+    $self->{models}{$_} = 1 for grep {/^($base)::Model::(.*)$/ and not /Collection$/} $self->plugins;
+    for my $full (keys %{$self->{models}}) {
+        my($short) = $full =~ /::Model::(.*)/;
+        Jifty::Util->require($full . "Collection");
+        Jifty::Util->require($base . "::Action::" . $_ . $short)
+            for qw/Create Update Delete/;
+    }
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Plugin/REST.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Plugin/REST.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,29 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::REST;
+use base qw/Jifty::Plugin/;
+
+our $VERSION = 0.01;
+
+=head1 NAME
+
+Jifty::Plugin::REST
+
+=head1 DESCRIPTION
+
+A RESTian web services API for your Jifty app.
+
+=head1 USAGE
+
+Add the following to your site_config.yml
+
+ framework:
+   Plugins:
+     - REST: {}
+
+See the URL /=/help in your Jifty app for more information about
+how to access the RESTian resources.
+
+=cut
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Plugin/REST/Dispatcher.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Plugin/REST/Dispatcher.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,691 @@
+package Jifty::Plugin::REST::Dispatcher;
+use warnings;
+
+
+
+
+use CGI qw( start_html end_html ol ul li a dl dt dd );
+use Carp;
+use Jifty::Dispatcher -base;
+use Jifty::YAML ();
+use Jifty::JSON ();
+use Data::Dumper ();
+use XML::Simple;
+
+before qr{^ (/=/ .*) \. (js|json|yml|yaml|perl|pl|xml) $}x => run {
+    $ENV{HTTP_ACCEPT} = $2;
+    dispatch $1;
+};
+
+before POST qr{^ (/=/ .*) ! (DELETE|PUT|GET|POST|OPTIONS|HEAD|TRACE|CONNECT) $}x => run {
+    $ENV{REQUEST_METHOD} = $2;
+    dispatch $1;
+};
+
+on GET    '/=/model/*/*/*/*'    => \&show_item_field;
+on GET    '/=/model/*/*/*'      => \&show_item;
+on GET    '/=/model/*/*'        => \&list_model_items;
+on GET    '/=/model/*'          => \&list_model_columns;
+on GET    '/=/model'            => \&list_models;
+
+on PUT    '/=/model/*/*/*'      => \&replace_item;
+on DELETE '/=/model/*/*/*'      => \&delete_item;
+
+on GET    '/=/action/*'         => \&list_action_params;
+on GET    '/=/action'           => \&list_actions;
+on POST   '/=/action/*'         => \&run_action;
+
+on GET    '/=/help'             => \&show_help;
+
+=head2 show_help
+
+Shows basic help about resources and formats available via this RESTian interface.
+
+=cut
+
+sub show_help {
+    my $apache = Jifty->handler->apache;
+
+    $apache->header_out('Content-Type' => 'text/plain; charset=UTF-8');
+    $apache->send_http_header;
+   
+    print qq{
+Accessing resources:
+
+on GET    /=/model                                  list models
+on GET    /=/model/<model>                          list model columns
+on GET    /=/model/<model>/<column>                 list model items
+on GET    /=/model/<model>/<column>/<key>           show item
+on GET    /=/model/<model>/<column>/<key>/<field>   show item field
+
+on PUT    /=/model/<model>/<column>/<key>           replace item
+on DELETE /=/model/<model>/<column>/<key>           delete item
+
+on GET    /=/action                                 list actions
+on GET    /=/action/<action>                        list action params
+on POST   /=/action/<action>                        run action
+
+
+Resources are available in a variety of formats:
+
+    JSON, JS, YAML, XML, Perl, and HTML
+
+and may be requested in such formats by sending an appropriate HTTP Accept: header
+or appending one of the extensions to any resource:
+
+    .json, .js, .yaml, .xml, .pl
+
+HTML is output only if the Accept: header or an extension does not request a
+specific format.
+    };
+    last_rule;
+}
+
+
+=head2 stringify LIST
+
+Takes a list of values and forces them into strings.  Right now all it does
+is concatenate them to an empty string, but future versions might be more
+magical.
+
+=cut
+
+sub stringify {
+    no warnings 'uninitialized';
+    my @r = map { defined $_ ? '' . $_ : undef } @_;
+    return wantarray ? @r : pop @r;
+}
+
+=head2 object_to_data OBJ
+
+Takes an object and converts the known types into simple data structures.
+
+Current known types:
+
+  Jifty::DBI::Collection
+  Jifty::DBI::Record
+
+=cut
+
+sub object_to_data {
+    my $obj = shift;
+    
+    my %types = (
+        'Jifty::DBI::Collection' => \&_collection_to_data,
+        'Jifty::DBI::Record'     => \&_record_to_data,
+    );
+
+    for my $type ( keys %types ) {
+        if ( UNIVERSAL::isa( $obj, $type ) ) {
+            return $types{$type}->( $obj );
+        }
+    }
+
+    # Attempt to stringify as last resort
+    return stringify( $obj );
+}
+
+sub _collection_to_data {
+    my $records = shift->items_array_ref;
+    return [ map { _record_to_data( $_ ) } @$records ];
+}
+
+sub _record_to_data {
+    my $record = shift;
+    # We could use ->as_hash but this method avoids transforming refers_to
+    # columns into JDBI objects
+    my %data   = map {
+                    $_ => (UNIVERSAL::isa( $record->column( $_ )->refers_to,
+                                           'Jifty::DBI::Collection' )
+                             ? undef
+                             : stringify( $record->_value( $_ ) ) )
+                 } $record->readable_attributes;
+    return \%data;
+}
+
+=head2 list PREFIX items
+
+Takes a URL prefix and a set of items to render. passes them on.
+
+=cut
+
+sub list {
+    my $prefix = shift;
+    outs($prefix, \@_)
+}
+
+
+
+=head2 outs PREFIX DATASTRUCTURE
+
+TAkes a url path prefix and a datastructure.  Depending on what content types the other side of the HTTP connection can accept,
+renders the content as yaml, json, javascript, perl, xml or html.
+
+=cut
+
+
+sub outs {
+    my $prefix = shift;
+    my $accept = ($ENV{HTTP_ACCEPT} || '');
+    my $apache = Jifty->handler->apache;
+    my @prefix;
+    my $url;
+
+    if($prefix) {
+        @prefix = map {s/::/./g; $_} @$prefix;
+         $url    = Jifty->web->url(path => join '/', '=', at prefix);
+    }
+
+
+
+    if ($accept =~ /ya?ml/i) {
+        $apache->header_out('Content-Type' => 'text/x-yaml; charset=UTF-8');
+        $apache->send_http_header;
+        print Jifty::YAML::Dump(@_);
+    }
+    elsif ($accept =~ /json/i) {
+        $apache->header_out('Content-Type' => 'application/json; charset=UTF-8');
+        $apache->send_http_header;
+        print Jifty::JSON::objToJson( @_, { singlequote => 1 } );
+    }
+    elsif ($accept =~ /j(?:ava)?s|ecmascript/i) {
+        $apache->header_out('Content-Type' => 'application/javascript; charset=UTF-8');
+        $apache->send_http_header;
+        print 'var $_ = ', Jifty::JSON::objToJson( @_, { singlequote => 1 } );
+    }
+    elsif ($accept =~ qr{^(?:application/x-)?(?:perl|pl)$}i) {
+        $apache->header_out('Content-Type' => 'application/x-perl; charset=UTF-8');
+        $apache->send_http_header;
+        print Data::Dumper::Dumper(@_);
+    }
+    elsif ($accept =~  qr|^(text/)?xml$|i) {
+        $apache->header_out('Content-Type' => 'text/xml; charset=UTF-8');
+        $apache->send_http_header;
+        print render_as_xml(@_);
+    }
+    else {
+        $apache->header_out('Content-Type' => 'text/html; charset=UTF-8');
+        $apache->send_http_header;
+        
+        # Special case showing particular actions to show an HTML form
+        if (    defined $prefix
+            and $prefix->[0] eq 'action'
+            and scalar @$prefix == 2 )
+        {
+            show_action_form($1);
+        }
+        else {
+            print render_as_html($prefix, $url, @_);
+        }
+    }
+
+    last_rule;
+}
+
+our $xml_config = { SuppressEmpty   => '',
+                    NoAttr          => 1,
+                    RootName        => 'data' };
+
+=head2 render_as_xml DATASTRUCTURE
+
+Attempts to render DATASTRUCTURE as simple, tag-based XML.
+
+=cut
+
+sub render_as_xml {
+    my $content = shift;
+
+    if (ref($content) eq 'ARRAY') {
+        return XMLout({value => $content}, %$xml_config);
+    }
+    elsif (ref($content) eq 'HASH') {
+        return XMLout($content, %$xml_config);
+    } else {
+        return XMLout({value => $content}, %$xml_config)
+    }
+}
+
+
+=head2 render_as_html PREFIX URL DATASTRUCTURE
+
+Attempts to render DATASTRUCTURE as simple semantic HTML suitable for humans to look at.
+
+=cut
+
+sub render_as_html {
+    my $prefix = shift;
+    my $url = shift;
+    my $content = shift;
+    if (ref($content) eq 'ARRAY') {
+        return start_html(-encoding => 'UTF-8', -declare_xml => 1, -title => 'models'),
+              ul(map {
+                  li($prefix ?
+                     a({-href => "$url/".Jifty::Web->escape_uri($_)}, Jifty::Web->escape($_))
+                     : Jifty::Web->escape($_) )
+              } @{$content}),
+              end_html();
+    }
+    elsif (ref($content) eq 'HASH') {
+        return start_html(-encoding => 'UTF-8', -declare_xml => 1, -title => 'models'),
+              dl(map {
+                  dt($prefix ?
+                     a({-href => "$url/".Jifty::Web->escape_uri($_)}, Jifty::Web->escape($_))
+                     : Jifty::Web->escape($_)),
+                  dd(html_dump($content->{$_})),
+              } sort keys %{$content}),
+              end_html();
+    }
+    else {
+        return start_html(-encoding => 'UTF-8', -declare_xml => 1, -title => 'models'),
+              Jifty::Web->escape($content),
+              end_html();
+    }
+}
+
+
+=head2 html_dump DATASTRUCTURE
+
+Recursively render DATASTRUCTURE as some simple html dls and ols. 
+
+=cut
+
+
+sub html_dump {
+    my $content = shift;
+    if (ref($content) eq 'ARRAY') {
+        ul(map {
+            li(html_dump($_))
+        } @{$content});
+    }
+    elsif (ref($content) eq 'HASH') {
+        dl(map {
+            dt(Jifty::Web->escape($_)),
+            dd(html_dump($content->{$_})),
+        } sort keys %{$content}),
+    } elsif (ref($content) && $content->isa('Jifty::Collection')) {
+        return  ol( map { li( html_dump_record($_))  } @{$content->items_array_ref});
+        
+    } elsif (ref($content) && $content->isa('Jifty::Record')) {
+          return   html_dump_record($content);
+    }
+    else {
+        Jifty::Web->escape($content);
+    }
+}
+
+=head2 html_dump_record Jifty::Record
+
+Returns a nice simple HTML definition list of the keys and values of a Jifty::Record object.
+
+=cut
+
+
+sub html_dump_record {
+    my $item = shift;
+     my %hash = $item->as_hash;
+
+     return  dl( map {dt($_), dd($hash{$_}) } keys %hash )
+}
+
+=head2 action ACTION
+
+Canonicalizes ACTION into the form preferred by the code. (Cleans up casing, canonicalizing, etc. Returns 404 if it can't work its magic
+
+=cut
+
+
+sub action {  _resolve($_[0], 'Jifty::Action', Jifty->api->actions) }
+
+=head2 model MODEL
+
+Canonicalizes MODEL into the form preferred by the code. (Cleans up casing, canonicalizing, etc. Returns 404 if it can't work its magic
+
+=cut
+
+sub model  { _resolve($_[0], 'Jifty::Record', Jifty->class_loader->models) }
+
+sub _resolve {
+    my $name = shift;
+    my $base = shift;
+    return $name if $name->isa($base);
+
+    $name =~ s/\W+/\\W+/g;
+
+    foreach my $cls (@_) {
+        return $cls if $cls =~ /$name$/i;
+    }
+
+    abort(404);
+}
+
+
+=head2 list_models
+
+Sends the user a list of models in this application, with the names transformed from Perlish::Syntax to Everything.Else.Syntax
+
+=cut
+
+sub list_models {
+    list(['model'], map { s/::/./g; $_ } Jifty->class_loader->models);
+}
+
+our @column_attrs = 
+qw( name
+    type
+    default
+    readable writable
+    max_length
+    mandatory
+    distinct
+    sort_order
+    refers_to
+    alias_for_column
+    aliased_as
+    label hints
+    valid_values
+);
+
+
+=head2 list_model_columns
+
+Sends the user a nice list of all columns in a given model class. Exactly which model is shoved into $1 by the dispatcher. This should probably be improved.
+
+
+=cut
+
+sub list_model_columns {
+    my ($model) = model($1);
+
+    my %cols;
+    for my $col ( $model->new->columns ) {
+        $cols{ $col->name } = { };
+        for ( @column_attrs ) {
+            my $val = $col->$_();
+            $cols{ $col->name }->{ $_ } = $val
+                if defined $val and length $val;
+        }
+    }
+
+    outs( [ 'model', $model ], \%cols );
+}
+
+=head2 list_model_items MODELCLASS COLUMNNAME
+
+Returns a list of items in MODELCLASS sorted by COLUMNNAME, with only COLUMNAME displayed.  (This should have some limiting thrown in)
+
+=cut
+
+
+sub list_model_items {
+    # Normalize model name - fun!
+    my ( $model, $column ) = ( model($1), $2 );
+    my $col = $model->new->collection_class->new;
+    $col->unlimit;
+
+    # If we don't load the PK, we won't get data
+    $col->columns("id", $column);
+    $col->order_by( column => $column );
+
+    list( [ 'model', $model, $column ],
+        map { stringify($_->$column()) } @{ $col->items_array_ref || [] } );
+}
+
+
+=head2 show_item_field $model, $column, $key, $field
+
+Loads up a model of type C<$model> which has a column C<$column> with a value C<$key>. Returns the value of C<$field> for that object. 
+Returns 404 if it doesn't exist.
+
+=cut
+
+sub show_item_field {
+    my ( $model, $column, $key, $field ) = ( model($1), $2, $3, $4 );
+    my $rec = $model->new;
+    $rec->load_by_cols( $column => $key );
+    $rec->id          or abort(404);
+    $rec->can($field) or abort(404);
+
+    # Check that the field is actually a column (and not some other method)
+    abort(404) if not scalar grep { $_->name eq $field } $rec->columns;
+
+    outs( [ 'model', $model, $column, $key, $field ], stringify($rec->$field()) );
+}
+
+=head2 show_item $model, $column, $key
+
+Loads up a model of type C<$model> which has a column C<$column> with a value C<$key>. Returns all columns for the object
+
+Returns 404 if it doesn't exist.
+
+=cut
+
+sub show_item {
+    my ($model, $column, $key) = (model($1), $2, $3);
+    my $rec = $model->new;
+    $rec->load_by_cols( $column => $key );
+    $rec->id or abort(404);
+    outs( ['model', $model, $column, $key],  { map {$_ => stringify($rec->$_())} map {$_->name} $rec->columns});
+}
+
+
+=head2 replace_item
+
+Implemented by redispatching to a CreateModel or UpdateModel action
+
+=cut
+
+sub replace_item { _dispatch_to_action('Update') }
+
+=head2 delete_item
+
+Implemented by redispatching to a DeleteModel action.
+
+=cut
+
+sub delete_item { _dispatch_to_action('Delete') }
+
+sub _dispatch_to_action {
+    my $prefix = shift;
+    my ($model, $class, $column, $key) = (model($1), $1, $2, $3);
+    my $rec = $model->new;
+    $rec->load_by_cols( $column => $key );
+
+    if ( not $rec->id ) {
+        abort(404)         if $prefix eq 'Delete';
+        $prefix = 'Create' if $prefix eq 'Update';
+    }
+
+    $class =~ s/^[\w\.]+\.//;
+
+    $ENV{REQUEST_METHOD} = 'POST';
+    Jifty->web->request->argument( $column => $key );
+    Jifty->web->request->argument( 'id' => $rec->id )
+        if defined $rec->id;
+    
+    # CGI.pm doesn't handle form encoded data in PUT requests (in fact,
+    # it doesn't really handle PUT requests properly at all), so we have
+    # to read the request body ourselves and have CGI.pm parse it
+    if (    $ENV{'CONTENT_TYPE'} =~ m|^application/x-www-form-urlencoded$|
+         or $ENV{'CONTENT_TYPE'} =~ m|^multipart/form-data$| )
+    {
+        my $cgi    = Jifty->handler->cgi;
+        my $length = defined $ENV{'CONTENT_LENGTH'} ? $ENV{'CONTENT_LENGTH'} : 0;
+        my $data;
+
+        $cgi->read_from_client( \$data, $length, 0 )
+            if $length > 0;
+
+        if ( defined $data ) {
+            my @params = $cgi->all_parameters;
+            $cgi->parse_params( $data );
+            push @params, $cgi->all_parameters;
+            
+            my %seen;
+            my @uniq = map { $seen{$_}++ == 0 ? $_ : () } @params;
+
+            # Add only the newly parsed arguments to the Jifty::Request
+            Jifty->web->request->argument( $_ => $cgi->param( $_ ) )
+                for @uniq;
+        }
+    }
+
+    dispatch '/=/action/' . action( $prefix . $class );
+}
+
+=head2 list_actions
+
+Returns a list of all actions allowed to the current user. (Canonicalizes Perl::Style to Everything.Else.Style).
+
+=cut
+
+sub list_actions {
+    list(['action'], map {s/::/./g; $_} Jifty->api->actions);
+}
+
+=head2 list_action_params
+
+Takes a single parameter, $action, supplied by the dispatcher.
+
+Shows the user all possible parameters to the action.
+
+=cut
+
+our @param_attrs = qw(
+    name
+    type
+    default_value
+    label
+    hints
+    mandatory
+    length
+);
+
+sub list_action_params {
+    my ($class) = action($1) or abort(404);
+    Jifty::Util->require($class) or abort(404);
+    my $action = $class->new or abort(404);
+
+    my $arguments = $action->arguments;
+    my %args;
+    for my $arg ( keys %$arguments ) {
+        $args{ $arg } = { };
+        for ( @param_attrs ) {
+            my $val = $arguments->{ $arg }{ $_ };
+            $args{ $arg }->{ $_ } = $val
+                if defined $val and length $val;
+        }
+    }
+
+    outs( ['action', $class], \%args );
+}
+
+=head2 show_action_form $ACTION_CLASS
+
+Takes a single parameter, the class of an action.
+
+Shows the user an HTML form of the action's parameters to run that action.
+
+=cut
+
+sub show_action_form {
+    my ($action) = action(shift) or abort(404);
+    Jifty::Util->require($action) or abort(404);
+    $action = $action->new or abort(404);
+
+    # XXX - Encapsulation?  Someone please think of the encapsulation!
+    no warnings 'redefine';
+    local *Jifty::Web::out = sub { shift; print @_ };
+    local *Jifty::Action::form_field_name = sub { shift; $_[0] };
+    local *Jifty::Action::register = sub { 1 };
+    local *Jifty::Web::Form::Field::Unrendered::render = \&Jifty::Web::Form::Field::render;
+
+    print start_html(-encoding => 'UTF-8', -declare_xml => 1, -title => ref($action));
+    Jifty->web->form->start;
+    for my $name ($action->argument_names) {
+        print $action->form_field($name);
+    }
+    Jifty->web->form->submit( label => 'POST' );
+    Jifty->web->form->end;
+    print end_html;
+    last_rule;
+}
+
+=head2 run_action 
+
+Expects $1 to be the name of an action we want to run.
+
+Runs the action, I<with the HTTP arguments as its arguments>. That is, it's not looking for Jifty-encoded (J:F) arguments.
+If you have an action called "MyApp::Action::Ping" that takes a parameter, C<ip>, this action will look for an HTTP 
+argument called C<ip>, (not J:F-myaction-ip).
+
+Returns the action's result.
+
+TODO, doc the format of the result.
+
+On an invalid action name, throws a C<404>.
+On a disallowed action mame, throws a C<403>. 
+On an internal error, throws a C<500>.
+
+=cut
+
+sub run_action {
+    my ($action_name) = action($1) or abort(404);
+    Jifty::Util->require($action_name) or abort(404);
+    
+    my $args = Jifty->web->request->arguments;
+    delete $args->{''};
+
+    my $action = $action_name->new( arguments => $args ) or abort(404);
+
+    Jifty->api->is_allowed( $action_name ) or abort(403);
+
+    my $params = $action->arguments;
+    for my $key ( keys %$params ) {
+        next if not exists $params->{ $key }{'default_value'};
+        next if $action->has_argument( $key );
+        $action->argument_value( $key => $params->{ $key }{'default_value'} );
+    }
+
+    $action->validate;
+
+    local $@;
+    eval { $action->run };
+
+    if ($@) {
+        abort(500);
+    }
+
+    my $rec = $action->{record};
+    if ($action->result->success && $rec and $rec->isa('Jifty::Record') and $rec->id) {
+        my $url    = Jifty->web->url(path => join '/', '=', map {
+            Jifty::Web->escape_uri($_)
+        } 'model', ref($rec), 'id', $rec->id);
+        Jifty->handler->apache->header_out('Location' => $url);
+    }
+    
+    my $result = $action->result;
+
+    my $out = {};
+    $out->{success} = $result->success;
+    $out->{message} = $result->message;
+    $out->{error} = $result->error;
+    $out->{field_errors} = {$result->field_errors};
+    for (keys %{$out->{field_errors}}) {
+        delete $out->{field_errors}->{$_} unless $out->{field_errors}->{$_};
+    }
+    $out->{field_warnings} = {$result->field_warnings};
+    for (keys %{$out->{field_warnings}}) {
+        delete $out->{field_warnings}->{$_} unless $out->{field_warnings}->{$_};
+    }
+    $out->{content} = $result->content;
+
+    for my $key ( keys %{ $out->{content} } ) {
+        $out->{content}{$key} = object_to_data( $out->{content}{$key} );
+    }
+    
+    outs(undef, $out);
+
+    last_rule;
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Record.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Record.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,489 @@
+use warnings;
+use strict;
+
+package Jifty::Record;
+
+=head1 NAME
+
+Jifty::Record - Represents a Jifty object that lives in the database.
+
+=head1 DESCRIPTION
+
+C<Jifty::Record> is a kind of L<Jifty::Object> that has a database
+representation; that is, it is also a L<Jifty::DBI::Record> as well.
+
+=cut
+
+use base qw(Jifty::Object Jifty::DBI::Record Class::Accessor::Fast);
+__PACKAGE__->mk_accessors('_is_readable');
+
+sub _init {
+    my $self = shift;
+    my %args = (@_);
+    $self->_get_current_user(%args);
+    
+    $self->SUPER::_init(@_);
+
+}
+
+=head1 METHODS
+
+=cut
+
+=head2 create PARAMHASH
+
+C<create> can be called as either a class method or an object method.
+
+Takes an array of key-value pairs and inserts a new row into the database representing
+this object.
+
+Override's L<Jifty::DBI::Record> in these ways:
+
+=over 4
+
+=item Remove C<id> values unless they are truly numeric
+
+=item Automatically load by id after create
+
+=item actually stop creating the new record if a field fails to validate.
+
+=back
+
+=cut 
+
+sub create {
+    my $class    = shift;
+    my $self;
+    if (ref($class)) { 
+        ($self,$class) = ($class,undef);
+    } else {
+        $self = $class->new();
+    }
+
+    my %attribs = @_;
+
+    unless ( $self->check_create_rights(@_) ) {
+        $self->log->error( $self->current_user->id . " tried to create a ",
+            ref $self, " without permission" );
+        wantarray ? return ( 0, _('Permission denied') ) : return (0);
+    }
+
+    foreach my $key ( keys %attribs ) {
+        my $attr = $attribs{$key};
+        my $method = "canonicalize_$key";
+        my $func = $self->can($method) or next;
+        $attribs{$key} = $self->$func( $attr);
+    }
+    foreach my $key ( keys %attribs ) {
+        my $attr = $attribs{$key};
+        my $method = "validate_$key";
+        if (my $func = $self->can($method)) {
+        my ( $val, $msg ) = $func->($self, $attr);
+        unless ($val) {
+            $self->log->error("There was a validation error for $key");
+            if ($class) {
+                return($self);
+            } else {
+                return ( $val, $msg );
+            }
+        }
+        }
+        # remove blank values. We'd rather have nulls
+        if ( exists $attribs{$key} and (! defined $attr || ( not ref( $attr) and $attr eq '' ))) {
+            delete $attribs{$key};
+        }
+    }
+
+
+    my $msg = $self->SUPER::create(%attribs);
+    if (ref($msg)  ) {
+        # It's a Class::ReturnValue
+        return $msg ;
+    }
+    my ($id, $status) = $msg;
+    $self->load_by_cols( id => $id ) if ($id);
+    if ($class) {
+        return $self;
+    } else {
+        return wantarray ? ($id, $status) : $id;
+    }
+}
+
+
+=head2 id
+
+Returns the record id value.
+This routine short-circuits a much heavier call up through Jifty::DBI
+
+=cut
+
+sub _primary_key { 'id' }
+sub id { $_[0]->{'values'}->{'id'} }
+
+
+=head2 load_or_create
+
+C<load_or_create> can be called as either a class method or an object method.
+It attempts to load a record with the named parameters passed in.  If it
+can't do so, it creates a new record.
+
+=cut
+
+sub load_or_create {
+    my $class = shift;
+    my $self;
+    if (ref($class)) {
+       ($self,$class) = ($class,undef); 
+    } else {
+        $self = $class->new();
+    }
+
+    my %args = (@_);
+
+    my ( $id, $msg ) = $self->load_by_cols(%args);
+    unless ( $self->id ) {
+        return $self->create(%args);
+    }
+
+    return ($id,$msg);
+}
+
+
+=head2 current_user_can RIGHT [ATTRIBUTES]
+
+Should return true if the current user (C<< $self->current_user >>) is
+allowed to do I<RIGHT>.  Possible values for I<RIGHT> are:
+
+=over
+
+=item create
+
+Called just before an object's C<create> method is called, as well as
+before parameter validation.  ATTRIBUTES is the attributes that
+the object is trying to be created with, as the attributes aren't on
+the object yet to be inspected.
+
+=item read
+
+Called before any attribute is accessed on the object.
+ATTRIBUTES is a hash with a single key C<column> and a single
+value, the name of the column being queried.
+
+=item update
+
+Called before any attribute is changed on the object.
+ATTRIBUTES is a hash of the arguments passed to _set.
+
+
+
+=item delete
+
+Called before the object is deleted.
+
+=back
+
+The default implementation returns true if the current user is a
+superuser or a boostrap user.  If the user is looking to delegate the
+access control decision to another object (by creating a
+C<delegate_current_user_can> subroutine), it hands off to that
+routine.  Otherwise, it returns false.
+
+=cut
+
+sub current_user_can {
+    my $self  = shift;
+    my $right = shift;
+
+    if (   $self->current_user->is_bootstrap_user
+        or $self->current_user->is_superuser )
+    {
+        return (1);
+    }
+
+    
+    if ($self->can('delegate_current_user_can')) {
+        return $self->delegate_current_user_can($right, @_); 
+    }
+
+    unless ( $self->current_user->isa( 'Jifty::CurrentUser' ) ) {
+        $self->log->error(
+            "Hm. called to authenticate without a currentuser - "
+                . $self->current_user );
+        return (0);
+    }
+    return (0);
+
+}
+
+=head2 check_create_rights ATTRIBUTES
+
+Internal helper to call L</current_user_can> with C<create>.
+
+=cut
+
+sub check_create_rights { return shift->current_user_can('create', @_) }
+
+
+=head2 check_read_rights
+
+Internal helper to call L</current_user_can> with C<read>.
+
+Passes C<column> as a named parameter for the column the user is checking rights
+on.
+
+=cut
+
+sub check_read_rights {
+    my $self = shift;
+    return (1) if $self->_is_readable;
+    return $self->current_user_can( 'read', column => shift );
+}
+
+=head2 check_update_rights
+
+Internal helper to call L</current_user_can> with C<update>.
+
+=cut
+
+sub check_update_rights { return shift->current_user_can('update', @_) } 
+
+
+=head2 check_delete_rights
+
+Internal helper to call L</current_user_can> with C<delete>.
+
+=cut
+
+sub check_delete_rights { return shift->current_user_can('delete', @_) }
+
+
+sub _set {
+    my $self = shift;
+
+    unless ($self->check_update_rights(@_)) {
+        return (0, _('Permission denied'));
+    }
+    $self->SUPER::_set(@_);
+}
+
+    
+sub _value {
+    my $self = shift;
+    my $column = shift;
+
+    unless ($self->check_read_rights( $column => @_ )) {
+        return (undef);
+    }
+    my $value = $self->SUPER::_value( $column => @_ );
+    return $value if ref $value or $self->column($column)->type eq 'blob';
+
+    Encode::_utf8_on($value) if defined $value;
+    $value;
+}
+
+
+=head2 as_superuser
+
+Returns a copy of this object with the current_user set to the
+superuser. This is a convenient way to duck around ACLs if you have
+code that needs to for some reason or another.
+
+=cut
+
+sub as_superuser {
+    my $self = shift;
+
+    my $clone = $self->new(current_user => $self->current_user->superuser);
+    $clone->load($self->id);
+    return $clone;
+}
+
+
+=head2 _collection_value METHOD
+
+A method ripped from the pages of Jifty::DBI::Record 
+so we could change the invocation method of the collection generator to
+add a current_user argument.
+
+=cut
+
+sub _collection_value {
+    my $self = shift;
+
+    my $method_name = shift;
+    return unless defined $method_name;
+
+    my $column    = $self->column($method_name);
+    my $classname = $column->refers_to();
+
+    return undef unless $classname;
+    return unless $classname->isa( 'Jifty::DBI::Collection' );
+
+    if ( my $prefetched_collection = $self->_prefetched_collection($method_name)) {
+        return $prefetched_collection;
+    }
+
+    my $coll = $classname->new( current_user => $self->current_user );
+    if ($column->by and $self->id) { 
+            $coll->limit( column => $column->by(), value => $self->id );
+    }
+    return $coll;
+}
+
+=head2 delete PARAMHASH
+
+Overrides L<Jifty::DBI::Record> to check the delete ACL.
+
+=cut
+
+sub delete {
+    my $self = shift;
+    unless ($self->check_delete_rights(@_)) {
+            Jifty->log->logcluck("Permission denied");
+            return(0, _('Permission denied'));
+        }
+    $self->SUPER::delete(@_); 
+}
+
+=head2 _brief_description
+
+When displaying a list of records, Jifty can display a friendly value 
+rather than the column's unique id.  Out of the box, Jifty always
+tries to display the 'name' field from the record. You can override this
+method to return the name of a method on your record class which will
+return a nice short human readable description for this record.
+
+=cut
+
+sub _brief_description {'name'}
+
+=head2 _to_record
+
+This is the Jifty::DBI function that is called when you fetch a value which C<REFERENCES> a
+Record class.  The only change from the Jifty::DBI code is the arguments to C<new>.
+
+=cut
+
+sub _to_record {
+    my $self  = shift;
+    my $column_name = shift;
+    my $value = shift;
+
+    my $column = $self->column($column_name);
+    my $classname = $column->refers_to();
+
+    return undef unless $classname;
+    return unless $classname->isa( 'Jifty::Record' );
+
+    # XXX TODO FIXME we need to figure out the right way to call new here
+    # perhaps the handle should have an initiializer for records/collections
+    my $object = $classname->new(current_user => $self->current_user);
+    $object->load_by_cols(( $column->by || 'id')  => $value) if ($value);
+    # XXX: an attribute or hook to let model class declare implicit
+    # readable refers_to columns.  $object->_is_readable(1) if $column->blah;
+    return $object;
+}
+
+=head2 cache_key_prefix
+
+Returns a unique key for this application for the Memcached cache.
+This should be global within a given Jifty application instance.
+
+=cut
+
+
+sub cache_key_prefix {
+    Jifty->config->framework('Database')->{'Database'};
+}
+
+sub _cache_config {
+    {   'cache_p'       => 1,
+        'cache_for_sec' => 60,
+    };
+}
+
+=head2 since
+ 
+By default, all models exist since C<undef>, the ur-time when the application was created. Please override it for your midel class.
+ 
+=cut
+ 
+
+
+=head2 printable_table_schema
+
+When called, this method will generate the SQL schema for the current version of this 
+class and return it as a scalar, suitable for printing or execution in your database's command line.
+
+=cut
+
+
+sub printable_table_schema {
+    my $class = shift;
+
+    my $schema_gen = $class->_make_schema();
+    return $schema_gen->create_table_sql_text;
+}
+
+=head2 create_table_in_db
+
+When called, this method will generate the SQL schema for the current version of this 
+class and insert it into the application's currently open database.
+
+=cut
+
+sub create_table_in_db {
+    my $class = shift;
+
+    my $schema_gen = $class->_make_schema();
+
+    # Run all CREATE commands
+    for my $statement ( $schema_gen->create_table_sql_statements ) {
+        my $ret = Jifty->handle->simple_query($statement);
+        $ret or die "error creating table $class: " . $ret->error_message;
+    }
+
+}
+
+sub _make_schema { 
+        my $class = shift;
+
+    my $schema_gen = Jifty::DBI::SchemaGenerator->new( Jifty->handle )
+        or die "Can't make Jifty::DBI::SchemaGenerator";
+    my $ret = $schema_gen->add_model( $class->new );
+    $ret or die "couldn't add model $class: " . $ret->error_message;
+
+        return $schema_gen;
+}
+
+=head2 add_column_sql column_name
+
+Returns the SQL statement neccessary to add C<column_name> to this class's representation in the database
+
+=cut
+
+sub add_column_sql {
+    my $self        = shift;
+    my $column_name = shift;
+
+    my $col        = $self->column($column_name);
+    my $definition = $self->_make_schema()->column_definition_sql($self->table => $col->name);
+    return "ALTER TABLE " . $self->table . " ADD COLUMN " . $definition;
+}
+
+=head2 drop_column_sql column_name
+
+Returns the SQL statement neccessary to remove C<column_name> from this class's representation in the database
+
+=cut
+
+sub drop_column_sql {
+    my $self        = shift;
+    my $column_name = shift;
+
+    my $col = $self->column($column_name);
+    return "ALTER TABLE " . $self->table . " DROP COLUMN " . $col->name;
+}
+
+1;
+

Added: jifty/branches/schema-plugins/lib/Jifty/Request.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Request.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,1024 @@
+use warnings;
+use strict;
+
+package Jifty::Request;
+
+use base qw/Jifty::Object Class::Accessor::Fast/;
+__PACKAGE__->mk_accessors(qw(_top_request arguments just_validating path continuation_id continuation_type continuation_path));
+
+use Jifty::JSON;
+use Jifty::YAML;
+use Storable 'dclone';
+
+=head1 NAME
+
+Jifty::Request - Canonical internal representation of an incoming Jifty request
+
+=head1 DESCRIPTION
+
+This document discusses the ins and outs of getting data from the web
+browser (or any other source) and figuring out what it means.  Most of
+the time, you won't need to worry about the details, but they are
+provided below if you're curious.
+
+This class parses the submission and makes it available as a
+protocol-independent B<Jifty::Request> object.
+
+Each request contains several types of information:
+
+=over 4
+
+=item actions
+
+A request may contain one or more actions; these are represented as
+L<Jifty::Request::Action> objects. Each action request has a
+L<moniker|Jifty::Manual::Glossary/moniker>, a set of submitted
+L<arguments|Jifty::Manual::Glossary/arguments>, and an implementation
+class.  By default, all actions that are submitted are run; it is
+possible to only mark a subset of the submitted actions as "active",
+and only the active actions will be run.  These will eventually become
+full-fledge L<Jifty::Action> objects.
+
+=item state variables
+
+State variables are used to pass around bits of information which are
+needed more than once but not often enough to be stored in the
+session.  Additionally, they are per-browser window, unlike session
+information.
+
+=item continuations
+
+Continuations can be called or created during the course of a request,
+though each request has at most one "current" continuation.  See
+L<Jifty::Continuation>.
+
+=item (optional) fragments
+
+L<Fragments|Jifty::Manual::Glossary/fragments> are standalone bits of
+reusable code.  They are most commonly used in the context of AJAX,
+where fragments are the building blocks that can be updated
+independently.  A request is either for a full page, or for multiple
+independent fragments.  See L<Jifty::Web::PageRegion>.
+
+=back
+
+=head1 METHODS
+
+=head2 new PARAMHASH
+
+Creates a new request object.  For each key in the I<PARAMHASH>, the
+method of that name is called, with the I<PARAMHASH>'s value as its
+sole argument.
+
+=cut
+
+sub new {
+    my $class = shift;
+    my $self = bless {}, $class;
+
+    $self->{'actions'} = {};
+    $self->{'state_variables'} = {};
+    $self->{'fragments'} = {};
+    $self->arguments({});
+
+    my %args = @_;
+    for (keys %args) {
+        $self->$_($args{$_}) if $self->can($_);
+    }
+
+    return $self;
+}
+
+=head2 clone
+
+Return a copy of the request.
+
+=cut
+
+sub clone {
+    my $self = shift;
+    
+    # "Semi-shallow" clone
+    return bless({map {
+        my $val = $self->{$_};
+        $_ => (ref($val) ? { %$val } : $val);
+    } keys %$self}, ref($self));
+}
+
+=head2 fill
+
+Attempt to fill in the request from any number of various methods --
+YAML, JSON, etc.  Falls back to query parameters.  Takes a CGI object.
+
+=cut
+
+sub fill {
+    my $self = shift;
+    my ($cgi) = @_;
+
+    # Grab content type and posted data, if any
+    my $ct   = $ENV{"CONTENT_TYPE"};
+    my $data = $cgi->param('POSTDATA');
+
+    # Check it for something appropriate
+    if ($data) {
+        if ($ct eq "text/x-json") {
+            return $self->from_data_structure(eval{Jifty::JSON::jsonToObj($data)});
+        } elsif ($ct eq "text/x-yaml") {
+            return $self->from_data_structure(eval{Jifty::YAML::Load($data)});
+        }
+    }
+
+    # Fall back on using the mason args
+    return $self->from_cgi($cgi);
+}
+
+=head2 from_data_structure
+
+Fills in the request from a data structure.  This is called once the
+YAML or JSON has been parsed.  See L</SERIALIZATION> for details of
+how to construct a proper data structure.
+
+Returns itself.
+
+=cut
+
+sub from_data_structure {
+    my $self = shift;
+    my $data = shift;
+
+    $self->path(Jifty::Util->canonicalize_path($data->{path} || "/"));
+    $self->just_validating($data->{validating}) if $data->{validating};
+
+    if (ref $data->{continuation} eq "HASH") {
+        $self->continuation_id($data->{continuation}{id});
+        $self->continuation_type($data->{continuation}{type} || "parent");
+        $self->continuation_path($data->{continuation}{create});
+    }
+
+    my %actions = %{$data->{actions} || {}};
+    for my $a (values %actions) {
+        next unless ref $a eq "HASH";
+        my %arguments;
+        for my $arg (keys %{$a->{fields} || {}}) {
+            if (ref $a->{fields}{$arg}) {
+                # Double-fallback exists for historical reasons only;
+                # Jifty applications after July 10th, 2006 should
+                # never generate them.
+                for my $type (qw/doublefallback fallback value/) {
+                    $arguments{$arg} = $a->{fields}{$arg}{$type}
+                      if exists $a->{fields}{$arg}{$type};
+                }
+            } else {
+                $arguments{$arg} = $a->{fields}{$arg};
+            }
+        }
+        $self->add_action(moniker   => $a->{moniker},
+                          class     => $a->{class},
+                          order     => $a->{order},
+                          active    => exists $a->{active} ? $a->{active} : 1,
+                          arguments => \%arguments,
+                         );
+    }
+
+    my %variables = ref $data->{variables} eq "HASH" ? %{$data->{variables}} : ();
+    for my $v (keys %variables) {
+        $self->add_state_variable(key => $v, value => $variables{$v});
+    }
+
+    my %fragments = ref $data->{fragments} eq "HASH" ? %{$data->{fragments}} : ();
+    for my $f (values %fragments) {
+        next unless ref $f eq "HASH";
+        my $current = $self->add_fragment(
+            name      => $f->{name},
+            path      => $f->{path},
+            arguments => $f->{args},
+            wrapper   => $f->{wrapper} || 0,
+        );
+        while (ref $f->{parent} eq "HASH" and $f = $f->{parent}) {
+            $current = $current->parent(Jifty::Request::Fragment->new({
+                name => $f->{name},
+                path => $f->{path},
+                arguments => $f->{args},
+            }));
+        }
+    }
+
+    return $self;
+}
+
+=head2 from_cgi CGI
+
+Calls C<from_webform> with the CGI's parameters, after doing Mason
+parameter mapping, and splitting C<|>'s in argument names.  See
+L</argument munging>.
+
+Returns itself.
+
+=cut
+
+sub from_cgi {
+    my $self = shift;
+    my ($cgi) = @_;
+
+    my $path = $cgi->path_info;
+    $path =~ s/\?.*//;
+    $self->path( $path );
+
+    use HTML::Mason::Utils;
+    my %args = HTML::Mason::Utils::cgi_request_args( $cgi, $cgi->request_method );
+
+    # Either CGI.pm or HTML::Mason should really deal with this for us.
+    for my $k (keys %args) {
+        my $val = $args{$k};
+        if(ref($val) && ref($val) eq 'ARRAY') {
+            $args{$k} = [map {Jifty::I18N->promote_encoding($_, $ENV{CONTENT_TYPE})} @$val];
+        } elsif(!ref($val)) {
+            $args{$k} = Jifty::I18N->promote_encoding($val, $ENV{CONTENT_TYPE});
+        }
+    }
+    
+    my @splittable_names = grep /=|\|/, keys %args;
+    for my $splittable (@splittable_names) {
+        delete $args{$splittable};
+        for my $newarg (split /\|/, $splittable) {
+            # If your key has a '=', you may just lose
+            my ($k, $v) = split /=/, $newarg, 2;
+            $args{$k} = $v;
+            # The following breaks page regions and the like, sadly:
+            #$args{$k} ? (ref $args{$k} ? [@{$args{$k}},$v] : [$args{$k}, $v] ) : $v;
+        }
+    }
+    return $self->from_webform( %args );
+}
+
+=head2 from_webform %QUERY_ARGS
+
+Parses web form arguments into the Jifty::Request data structure.
+Takes in the query arguments. See L</SERIALIZATION> for details of how
+query parameters are parsed.
+
+Returns itself.
+
+=cut
+
+sub from_webform {
+    my $self = shift;
+
+    my %args = (@_);
+
+    # Pull in all of the arguments
+    $self->arguments(\%args);
+
+    # Extract actions and state variables
+    $self->from_data_structure($self->webform_to_data_structure(%args));
+
+    return $self;
+}
+
+=head2 argument KEY [=> VALUE]
+
+Merges a single query parameter into the request.  This may add
+actions, change action arguments, or change state variables.
+
+=cut
+
+sub argument {
+    my $self = shift;
+
+    my $key = shift;
+    if (@_) {
+        my $value = shift;
+        
+        $self->arguments->{$key} = $value;
+
+        # Continuation type is ofetn undef, so give it a sane default
+        # so we can use eq without warnings
+        my $cont_type = $self->continuation_type || "";
+
+        if ($key eq "J:VALIDATE") {
+            $self->{validating} = $value;
+        } elsif ($key eq "J:C" and $cont_type ne "return" and $cont_type ne "call") {
+            # J:C Doesn't take preference over J:CALL or J:RETURN
+            $self->continuation_id($value);
+            $self->continuation_type("parent");
+        } elsif ($key eq "J:CALL" and $cont_type ne "return") {
+            # J:CALL doesn't take preference over J:RETURN
+            $self->continuation_id($value);
+            $self->continuation_type("call");
+        } elsif ($key eq "J:RETURN") {
+            # J:RETURN trumps all
+            $self->continuation_id($value);
+            $self->continuation_type("return");
+        } elsif ($key eq "J:PATH") {
+            $self->continuation_path($value);
+        } elsif ($key =~ /^J:V-(.*)/s) {
+            $self->add_state_variable(key => $1, value => $value);
+        } elsif ($key =~ /^J:A-(?:(\d+)-)?(.+)/s) {
+            $self->add_action(moniker => $2, class => $value, order => $1, arguments => {}, active => 1);
+        } else {
+            # It's possibly a form field
+            my ($t, $a, $m) = $self->parse_form_field_name($key);
+            if ($t and $t eq "J:A:F" and $self->action($m)) {
+                $self->action($m)->argument($a => $value);
+                $self->action($m)->modified(1);
+            }
+        }
+    }
+
+    defined(my $val = $self->arguments->{$key}) or return undef;
+
+    $val;
+}
+
+=head2 delete KEY
+
+Removes the argument supplied -- this is the opposite of L</argument>,
+above.
+
+=cut
+
+sub delete {
+    my $self = shift;
+
+    my $key = shift;
+    delete $self->arguments->{$key};
+    if ($key =~ /^J:A-(?:(\d+)-)?(.+)/s) {
+        $self->remove_action($2);
+    } elsif ($key =~ /^J:A:F-(\w+)-(.+)/s and $self->action($2)) {
+        $self->action($2)->delete($1);
+        $self->action($2)->modified(1);
+    } elsif ($key =~ /^J:V-(.*)/s) {
+        $self->remove_state_variable($1);
+    }
+}
+
+=head2 parse_form_field_name FIELDNAME
+
+Takes a form field name generated by a Jifty action.
+Returns a tuple of
+
+=over 
+
+=item type
+
+A slightly-too-opaque identifier
+
+=item moniker
+
+The moniker for this field's action.
+
+=item argument name
+
+The argument name. 
+
+
+=back
+
+=cut
+
+sub parse_form_field_name {
+    my $self       = shift;
+    my $field_name = shift;
+
+    my ( $type, $argument, $moniker );
+    if ( $field_name =~ /^(.*?)-(\w+)-(.*)$/ ) {
+        $type     = $1;
+        $argument = $2;
+        $moniker  = $3;
+    }
+
+    else {
+        return undef;
+    }
+
+    return ( $type, $argument, $moniker );
+}
+
+=head2 webform_to_data_structure HASHREF
+
+Converts the data from a webform's %args to the datastructure that
+L<Jifty::Request> uses internally.
+
+XXX TODO: ALEX: DOC ME
+
+=cut
+
+sub webform_to_data_structure {
+    my $self = shift;
+    my %args = (@_);
+
+
+    my $data = {actions => {}, variables => {}};
+
+    # Pass path through
+    $data->{path} = $self->path;
+
+    $data->{validating} = $args{'J:VALIDATE'} if defined $args{'J:VALIDATE'} and length $args{'J:VALIDATE'};
+
+    # Continuations
+    if ($args{'J:C'} or $args{'J:CALL'} or $args{'J:RETURN'}) {
+        $data->{continuation}{id} = $args{'J:RETURN'} || $args{'J:CALL'} || $args{'J:C'};
+        $data->{continuation}{type} = "parent" if $args{'J:C'};
+        $data->{continuation}{type} = "call"   if $args{'J:CALL'};
+        $data->{continuation}{type} = "return" if $args{'J:RETURN'};
+    }
+    $data->{continuation}{create} = $args{'J:PATH'} if $args{'J:CREATE'};
+
+    # Are we only setting some actions as active?
+    my $active_actions;
+    if (exists $args{'J:ACTIONS'}) {
+        $active_actions = {};
+        $active_actions->{$_} = 1 for split '!', $args{'J:ACTIONS'};
+    } # else $active_actions stays undef
+
+
+    # Mapping from argument types to data structure names;
+    # Double-fallback exists for historical reasons only; Jifty
+    # applications after July 10th, 2006 should never generate them.
+    my %types = ("J:A:F:F:F" => "doublefallback", "J:A:F:F" => "fallback", "J:A:F" => "value");
+
+    # The "sort" here is key; it ensures that action registrations
+    # come before action arguments
+    for my $key (sort keys %args) {
+        my $value = $args{$key};
+        if( $key  =~ /^J:V-(.*)/s ) {
+            # It's a variable
+            $data->{variables}{$1} = $value;
+        } elsif ($key =~ /^J:A-(?:(\d+)-)?(.+)/s) {
+            # It's an action declatation
+            $data->{actions}{$2} = {
+                order   => $1,
+                moniker => $2,
+                class   => $value,
+                active  => ($active_actions ? ($active_actions->{$2} || 0) : 1),
+            };
+        } else {
+            # It's possibly a form field
+            my ($t, $a, $m) = $self->parse_form_field_name($key);
+            next unless $t and $types{$t} and $data->{actions}{$m};
+            $data->{actions}{$m}{fields}{$a}{$types{$t}} = $value;
+        }
+    }
+
+    return $data;
+}
+
+=head2 continuation_id [CONTINUATION_ID]
+
+Gets or sets the ID of the continuation associated with the request.
+
+=cut
+
+=head2 continuation [CONTINUATION]
+
+Returns the L<Jifty::Continuation> object associated with this
+request, if any.
+
+=cut
+
+sub continuation {
+    my $self = shift;
+
+    $self->continuation_id(ref $_[0] ? $_[0]->id : $_[0])
+      if @_;
+
+    return undef unless $self->continuation_id;
+    return Jifty->web->session->get_continuation($self->continuation_id);
+}
+
+=head2 save_continuation
+
+Saves the current request and response if we've been asked to.  If we
+save the continuation, we redirect to the next page -- the call to
+C<save_continuation> never returns.
+
+=cut
+
+sub save_continuation {
+    my $self = shift;
+    my $path;
+    return unless $path = $self->continuation_path;
+
+    # Clear out the create path so we don't ave the "create a
+    # continuation" into the continuation!
+    $self->continuation_path(undef);
+
+    my $c = Jifty::Continuation->new(
+        request  => $self,
+        response => Jifty->web->response,
+        parent   => $self->continuation,
+    );
+
+    # Set us up with the new continuation
+    Jifty->web->_redirect( Jifty->web->url(path => $path)
+                      . ( $path =~ /\?/ ? "&" : "?" ) . "J:C="
+                      . $c->id );
+}
+
+=head2 call_continuation
+
+Calls the L<Jifty::Continuation> associated with this request, if
+there is one.  Returns true if the continuation was called
+successfully -- if calling the continuation requires a redirect, this
+function will throw an exception to its enclosing dispatcher.
+
+=cut
+
+sub call_continuation {
+    my $self = shift;
+    return if $self->is_subrequest;
+    return unless $self->continuation_type and $self->continuation_type eq "call" and $self->continuation;
+    $self->log->debug("Calling continuation ".$self->continuation->id);
+    $self->continuation->call;
+    return 1;
+}
+
+=head2 return_from_continuation
+
+=cut
+
+sub return_from_continuation {
+    my $self = shift;
+    return unless $self->continuation_type and $self->continuation_type eq "return" and $self->continuation;
+    return $self->continuation->call unless $self->continuation->return_path_matches;
+    $self->log->debug("Returning from continuation ".$self->continuation->id);
+    return $self->continuation->return;
+}
+
+=head2 path
+
+Returns the path that was requested
+
+=cut
+
+=head2 just_validating
+
+This method returns true if the request was merely for validation.  If
+this flag is set, then all active actions are validated, but no
+actions are run.
+
+=cut
+
+=head2 state_variables
+
+Returns an array of all of this request's state variables, as
+L<Jifty::Request::StateVariable>s.
+
+=cut
+
+sub state_variables { 
+    my $self = shift;
+    return values %{$self->{'state_variables'}};
+}
+
+=head2 state_variable NAME
+
+Returns the L<Jifty::Request::StateVariable> object for the variable
+named I<NAME>, or undef of there is no such variable.
+
+=cut
+
+sub state_variable {
+    my $self = shift;
+    my $name = shift;
+    return $self->{'state_variables'}{$name};
+}
+
+=head2 add_state_variable PARAMHASH
+
+Adds a state variable to this request's internal representation.
+Takes a C<key> and a C<value>; returns the newly-added
+L<Jifty::Request::StateVariable>.
+
+=cut
+
+sub add_state_variable {
+    my $self = shift;
+    my %args = (
+                 key => undef,
+                 value => undef,
+                 @_);
+
+    my $state_var = Jifty::Request::StateVariable->new();
+    
+    for my $k (qw/key value/) {
+        $state_var->$k($args{$k}) if defined $args{$k};
+    } 
+    $self->{'state_variables'}{$args{'key'}} = $state_var;
+
+    return $state_var;
+}
+
+=head2 remove_state_variable KEY
+
+Removes the given state variable.  The opposite of
+L</add_state_variable>, above.
+
+=cut
+
+sub remove_state_variable {
+    my $self = shift;
+    my ($key) = @_;
+    delete $self->{'state_variables'}{$key};
+}
+
+=head2 clear_state_variables
+
+Remove all the state variables.
+
+=cut
+
+sub clear_state_variables {
+    my $self = shift;
+
+    $self->{'state_variables'} = {};
+}
+
+
+=head2 actions
+
+Returns a list of the actions in the request, as
+L<Jifty::Request::Action> objects.
+
+=cut
+
+sub actions {
+    my $self = shift;
+    return sort {($a->order || 0) <=> ($b->order || 0)}
+      values %{ $self->{'actions'} };
+}
+
+=head2 action MONIKER
+
+Returns a L<Jifty::Request::Action> object for the action with the
+given moniker, or undef if no such action was sent.
+
+=cut
+
+sub action {
+    my $self = shift;
+    my $moniker = shift;
+    return $self->{'actions'}{$moniker};
+} 
+
+=head2 add_action PARAMHASH
+
+Required argument: C<moniker>.
+
+Optional arguments: C<class>, C<order>, C<active>, C<arguments>.
+
+Adds a L<Jifty::Request::Action> with the given
+L<moniker|Jifty::Manual::Glossary/moniker> to the request.  If the
+request already contains an action with that moniker, it merges it in,
+overriding the implementation class, active state, and B<individual>
+arguments.  Returns the newly added L<Jifty::Request::Action>.
+
+See L<Jifty::Action>.
+
+=cut
+
+sub add_action {
+    my $self = shift;
+    my %args = (
+        moniker => undef,
+        class => undef,
+        order => undef,
+        active => 1,
+        arguments => undef,
+        has_run => 0,
+        @_
+    );
+
+    my $action = $self->{'actions'}->{ $args{'moniker'} } || Jifty::Request::Action->new;
+
+    for my $k (qw/moniker class order active has_run/) {
+        $action->$k($args{$k}) if defined $args{$k};
+    } 
+    
+    if ($args{'arguments'}) {
+        for my $k (keys %{ $args{'arguments'} }) {
+            $action->argument($k, $args{'arguments'}{$k});
+        } 
+    }
+
+    $self->{'actions'}{$args{'moniker'}} = $action;
+
+    return $action;
+} 
+
+
+=head2 clear_actions
+
+Removes all actions from this request
+
+=cut
+
+sub clear_actions {
+    my $self = shift;
+    $self->{'actions'} = {};
+}
+
+=head2 remove_action MONIKER
+
+Removes an action with the given moniker.
+
+=cut
+
+sub remove_action {
+    my $self = shift;
+    my ($moniker) = @_;
+    delete $self->{'actions'}{$moniker};
+}
+
+=head2 fragments
+
+Returns a list of fragments requested, as L<Jifty::Request::Fragment> objects.
+
+=cut
+
+sub fragments {
+    my $self = shift;
+
+    return values %{$self->{'fragments'}}
+}
+
+=head2 fragment NAME
+
+Returns the requested fragment with that name
+
+=cut
+
+sub fragment {
+    my $self = shift;
+    my $name = shift;
+    return $self->{'fragments'}{$name};
+}
+
+=head2 add_fragment PARAMHASH
+
+Required arguments: C<name>, C<path>
+
+Optional arguments: C<arguments>, C<wrapper>
+
+Adds a L<Jifty::Request::Fragment> with the given name to the request.
+If the request already contains a fragment with that name, it merges
+it in.  Returns the newly added L<Jifty::Request::Fragment>.
+
+See L<Jifty::PageRegion>.
+
+=cut
+
+sub add_fragment {
+    my $self = shift;
+
+    my %args = (
+                name      => undef,
+                path      => undef,
+                arguments => undef,
+                wrapper   => undef,
+                @_
+               );
+
+    my $fragment = $self->{'fragments'}->{ $args{'name'} } || Jifty::Request::Fragment->new;
+
+    for my $k (qw/name path wrapper/) {
+        $fragment->$k($args{$k}) if defined $args{$k};
+    } 
+    
+    if ($args{'arguments'}) {
+        for my $k (keys %{ $args{'arguments'} }) {
+            $fragment->argument($k, $args{'arguments'}{$k});
+        } 
+    }
+
+    $self->{'fragments'}{$args{'name'}} = $fragment;
+
+    return $fragment;
+}
+
+=head2 do_mapping PARAMHASH
+
+Takes two possible arguments, C<request> and C<response>; they default
+to the current L<Jifty::Request> and the current L<Jifty::Response>.
+Calls L<Jifty::Request::Mapper/map> on every argument of this request,
+pulling arguments and results from the given C<request> and C<response>.
+
+=cut
+
+sub do_mapping {
+    my $self = shift;
+
+    my %args = (
+                request  => Jifty->web->request,
+                response => Jifty->web->response,
+                @_,
+               );
+
+    for (keys %{$self->arguments}) {
+        my ($key, $value) = Jifty::Request::Mapper->map(destination => $_, source => $self->arguments->{$_}, %args);
+        next unless $key ne $_ or not defined $value or $value ne $self->argument($_);
+        delete $self->arguments->{$_};
+        $self->argument($key => $value);
+    }
+    for ($self->state_variables) {
+        my ($key, $value) = Jifty::Request::Mapper->map(destination => $_->key, source => $_->value, %args);
+        next unless $key ne $_->key or not defined $value or $value ne $_->value;
+        $self->remove_state_variable($_->key);
+        $self->add_state_variable(key => $key, value => $value);
+    }
+}
+
+=head2 is_subrequest
+
+Returns true if this request is a subrequest.
+
+=cut
+
+sub is_subrequest {
+    my $self = shift;
+    return $self->_top_request ? 1 : undef;
+}
+
+=head2 top_request
+
+Returns the top-level request for this request; if this is a
+subrequest, this is the user-created request that the handler got
+originally.  Otherwise, returns itself;
+
+=cut
+
+sub top_request {
+    my $self = shift;
+    $self->_top_request(@_) if @_;
+    return $self->_top_request || $self;
+}
+
+package Jifty::Request::Action;
+use base 'Class::Accessor::Fast';
+__PACKAGE__->mk_accessors( qw/moniker arguments class order active modified has_run/);
+
+=head2 Jifty::Request::Action
+
+A small package that encapsulates the bits of an action request:
+
+=head3 moniker [NAME]
+
+=head3 argument NAME [VALUE]
+
+=head3 arguments
+
+=head3 class [CLASS]
+
+=head3 order [INTEGER]
+
+=head3 active [BOOLEAN]
+
+=head3 has_run [BOOLEAN]
+
+=cut
+
+sub argument {
+    my $self = shift;
+    my $key  = shift;
+
+    $self->arguments({}) unless $self->arguments;
+
+    $self->arguments->{$key} = shift if @_;
+    $self->arguments->{$key};
+}
+
+=head3 delete
+
+=cut
+
+sub delete {
+    my $self = shift;
+    my $argument = shift;
+    delete $self->arguments->{$argument};
+}
+
+
+package Jifty::Request::StateVariable;
+use base 'Class::Accessor::Fast';
+__PACKAGE__->mk_accessors (qw/key value/);
+
+=head2 Jifty::Request::StateVariable
+
+A small package that encapsulates the bits of a state variable:
+
+=head3 key
+
+=head3 value
+
+=cut
+
+package Jifty::Request::Fragment;
+use base 'Class::Accessor::Fast';
+__PACKAGE__->mk_accessors( qw/name path wrapper arguments parent/ );
+
+=head2 Jifty::Request::Fragment
+
+A small package that encapsulates the bits of a fragment request:
+
+=head3 name [NAME]
+
+=head3 path [PATH]
+
+=head3 wrapper [BOOLEAN]
+
+=head3 argument NAME [VALUE]
+
+=head3 arguments
+
+=cut
+
+sub argument {
+    my $self = shift;
+    my $key  = shift;
+
+    $self->arguments({}) unless $self->arguments;
+
+    $self->arguments->{$key} = shift if @_;
+    $self->arguments->{$key};
+}
+
+=head3 delete
+
+=cut
+
+sub delete {
+    my $self = shift;
+    my $argument = shift;
+    delete $self->arguments->{$argument};
+}
+
+=head1 SERIALIZATION
+
+=head2 CGI Query parameters
+
+The primary source of Jifty requests through the website are CGI query
+parameters.  These are requests submitted using CGI GET or POST
+requests to your Jifty application.
+
+=head3 argument munging
+
+In addition to standard Mason argument munging, Jifty also takes
+arguments with a B<name> of
+
+   bla=bap|beep=bop|foo=bar
+
+and an arbitrary value, and makes them appear as if they were actually
+separate arguments.  The purpose is to allow submit buttons to act as
+if they'd sent multiple values, without using JavaScript.
+
+=head3 actions
+
+=head4 registration
+
+For each action, the client sends a query argument whose name is
+C<J:A-I<moniker>> and whose value is the fully qualified class name of
+the action's implementation class.  This is the action "registration."
+The registration may also take the form C<J:A-I<order>-I<moniker>>,
+which also sets the action's run order.
+
+=head4 arguments
+
+The action's arguments are specified with query arguments of the form
+C<J:A:F-I<argumentname>-I<moniker>>.  To cope with checkboxes and the
+like (which don't submit anything when left unchecked) we provide a
+level of fallback, which is checked if the first doesn't exist:
+C<J:A:F:F-I<argumentname>-I<moniker>>.
+
+=head3 state variables
+
+State variables are set via C<J:V-I<name>> being set to the value of
+the state parameter.
+
+=head4 continuations
+
+The current continuation set by passing the parameter C<J:C>, which is
+set to the id of the continuation.  To create a new continuation, the
+parameter C<J:CREATE> is passed.  Calling a continuation is a simple
+as passing C<J:CALL> with the id of the continuation to call; this
+will redirect to the appropriate url, with L<J:RETURN> set.
+
+=head3 request options
+
+The existence of C<J:VALIDATE> says that the request is only
+validating arguments.  C<J:ACTIONS> is set to a semicolon-separated
+list of monikers; the actions with those monikers will be marked
+active, while all other actions are marked inactive.  In the absence
+of C<J:ACTIONS>, all actions are active.
+
+=head2 YAML POST Request Protocol
+
+
+=head2 JSON POST Request Protocol
+
+
+
+=cut
+
+1;
+

Added: jifty/branches/schema-plugins/lib/Jifty/Request/Mapper.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Request/Mapper.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,184 @@
+use warnings;
+use strict;
+
+package Jifty::Request::Mapper;
+
+=head1 NAME
+
+Jifty::Request::Mapper - Maps response values into arbitrary query
+parameters
+
+=head1 DESCRIPTION
+
+C<Jifty::Request::Mapper> is used to insert values into parameters
+that you can't know when you originally constructed the request.  The
+prime example of this is a Create action to a View page -- where you
+can't know what ID to supply to the View page until after the Create
+action has run.  This problem can be fixed by establishing a mapping
+between some part of the L<Jifty::Result> of the Create action, and
+the ID query parameter.
+
+=head1 METHODS
+
+=head2 query_parameters HASH
+
+Extended syntax for generating query parameters.  This is used by
+L<Jifty::Web::Form::Clickable> for its C<parameters> argument, as well
+as for C<results> of continuations.
+
+Possible syntaxes for each key => value pair in the C<HASH> are:
+
+=over
+
+=item C<< KEY => STRING >>
+
+The simplest form -- the C<KEY> will have the literal value of the
+C<STRING> supplied
+
+=item C<< KEY => { result => ACTION } >>
+
+The C<KEY> will take on the value of the content named C<KEY> from the
+result of the C<ACTION>.  C<ACTION> may either be a L<Jifty::Action>
+object, or a moniker thereof.
+
+=item C<< KEY => { result => ACTION, name => STRING } >>
+
+The C<KEY> will take on the value of the content named C<STRING> from
+the result of the C<ACTION>.  C<ACTION> may either be a L<Jifty::Action>
+object, or a moniker thereof.
+
+=item C<< KEY => { request_argument => STRING } >>
+
+The C<KEY> will take on the value of the argument named C<STRING> from
+the request.
+
+=item C<< KEY => { argument => ACTION } >>
+
+The C<KEY> will take on the value of the argument named C<KEY> from
+the C<ACTION>.  C<ACTION> may either be a L<Jifty::Action> object, or
+a moniker thereof.
+
+=item C<< KEY => { argument => ACTION. name => STRING } >>
+
+The C<KEY> will take on the value of the argument named C<STRING> from
+the C<ACTION>.  C<ACTION> may either be a L<Jifty::Action> object, or
+a moniker thereof.
+
+=back
+
+C<result_of> and C<argument_to> are valid synonyms for C<result> and
+C<argument>, above.
+
+=cut
+
+sub query_parameters {
+    my $class = shift;
+
+    my %parameters = @_;
+    my %return;
+    for my $key (keys %parameters) {
+        if (ref $parameters{$key} eq "HASH") {
+            my %mapping = %{$parameters{$key}};
+
+            if ($mapping{request_argument}) {
+                $return{"J:M-$key"} = join("`","A", $mapping{request_argument});
+            }
+
+            for (grep {/^(result(_of)?|argument(_to)?)$/} keys %mapping) {
+                my $action  = $mapping{$_};
+                my $moniker = ref $action ? $action->moniker : $action;
+                my $name = $mapping{name} || $key;
+
+                my $type = ($_ =~ /result/) ? "R" : "A";
+
+                $return{"J:M-$key"} = join("`", $type, $moniker, $name);
+            }
+        } else {
+            $return{$key} = $parameters{$key};
+        }
+    }
+
+    return %return;
+}
+
+
+=head2 map PARAMHASH
+
+Responsible for doing the actual mapping that L</query_parameters>
+above sets up.  That is, takes magical query parameters and extracts
+the values they were ment to have.
+
+=over
+
+=item destination
+
+The C<key> from a query parameter
+
+=item source
+
+The C<value> of a query parameter
+
+=item request
+
+The L<Jifty::Request> object to pull action arguments from.  Defauts
+to the current request.
+
+=item response
+
+The L<Jifty::Response> object to pull results from.  Defaults to the
+current response.
+
+=back
+
+Returns a key => value pair.
+
+=cut
+
+sub map {
+    my $class = shift;
+
+    my %args = (
+        source      => undef,
+        destination => undef,
+        request     => Jifty->web->request,
+        response    => Jifty->web->response,
+        @_
+    );
+
+    my @original = ($args{destination} => $args{source});
+
+    # In case the source is a hashref, we force ourselves to go the
+    # *other* direction first.
+    ($args{destination}, $args{source}) = $class->query_parameters($args{destination} => $args{source});
+
+    # Bail unless it's a mapping
+    return ( @original )
+        unless $args{destination} =~ /^J:M-(.*)/;
+
+    my $destination = $1;
+
+    my @bits = split( /\`/, $args{source} );
+    if ( $bits[0] ) {
+        if ( $bits[0] eq "A" and @bits == 3 ) {
+            # No such action -- value is undef
+            return ( $destination => undef ) unless $args{request}->top_request->action( $bits[1] );
+            # We have a value
+            return ( $destination => $args{request}->top_request->action( $bits[1] )->argument( $bits[2] ) );
+        } elsif ( $bits[0] eq "R" and @bits == 3 ) { 
+            # No such action -- value is undef
+            return ( $destination => undef ) unless $args{request}->top_request->action( $bits[1] );
+            # Action exists but hasn't run yet -- defer until later
+            return ( @original ) unless $args{response}->result( $bits[1] );
+            # We have a value
+            return ( $destination => $args{response}->result( $bits[1] )->content( $bits[2] ) );
+        } elsif ( $bits[0] eq "A" and @bits == 2 ) {
+            return ( $destination => $args{request}->arguments->{ $bits[1] } );
+        }
+    }
+    # As a fallback, just set it to the value
+    return ( $destination => $args{source} );
+
+}
+
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Response.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Response.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,131 @@
+use warnings;
+use strict;
+
+package Jifty::Response;
+
+=head1 NAME
+
+Jifty::Response - Canonical internal representation of the result of a L<Jifty::Action>
+
+=head1 DESCRIPTION
+
+The answer to a L<Jifty::Request> is a C<Jifty::Response> object.
+Currently, the response object exists merely to collect the
+L<Jifty::Result> objects of each L<Jifty::Action> that ran.
+
+=cut
+
+use base qw/Jifty::Object Class::Accessor::Fast/;
+
+__PACKAGE__->mk_accessors(qw(error));
+
+=head2 new
+
+Creates a new L<Jifty::Response> object.
+
+=cut
+
+sub new {
+    my $class = shift;
+    bless {results => {}, headers => []}, $class;
+}
+
+
+=head2 add_header KEY => VALUE
+
+Add an HTTP header to the outgoing HTTP response. 
+
+=cut
+
+
+sub add_header {
+    my $self = shift;
+     Jifty->handler->apache->header_out( @_ ) if Jifty->handler->apache;
+
+    # This one is so we can get jifty's headers into mason
+    # Otherwise we'd have to wrap mason's output layer
+
+    push @{$self->{headers}}, [@_];
+}
+
+=head2 headers
+
+Returns an array of key-value pairs of all the HTTP headers we want to
+stick on the outgoing HTTP request.
+
+
+=cut
+
+sub headers {
+    my $self = shift;
+    return @{$self->{headers}};
+}
+
+=head2 result MONIKER [RESULT]
+
+Gets or sets the L<Jifty::Result> of the L<Jifty::Action> with the given
+I<MONIKER>.
+
+=cut
+
+sub result {
+    my $self = shift;
+    my $moniker = shift;
+    $self->{results}{$moniker} = shift if @_;
+    return $self->{results}{$moniker};
+}
+
+=head2 results
+
+Returns a hash which maps moniker to its L<Jifty::Result>
+
+=cut
+
+sub results {
+    my $self = shift;
+    return %{$self->{results}};
+}
+
+=head2 messages
+
+Returns the aggregate messages of all of the L<Jifty::Result>s.
+
+=cut
+
+sub messages {
+    my $self = shift;
+    my %results = $self->results;
+    return map {$_, $results{$_}->message} grep {defined $results{$_}->message and length $results{$_}->message} sort keys %results;
+}
+
+=head2 error [MESSAGE]
+
+Gets or sets a generalized error response.  Setting an error also
+makes the response a L</failure>.
+
+=head2 success
+
+Returns true if none of the results are failures and there is no
+L</error> set.
+
+=cut
+
+sub success {
+    my $self = shift;
+    return 0 if grep {$_->failure} values %{$self->{results}};
+    return 1;
+}
+
+=head2 failure
+
+Returns true if any of the results failed or there was an L</error>
+set.
+
+=cut
+
+sub failure {
+    my $self = shift;
+    return not $self->success;
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Result.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Result.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,182 @@
+use warnings;
+use strict;
+
+package Jifty::Result;
+
+=head1 NAME
+
+Jifty::Result - Outcome of running a L<Jifty::Action>
+
+=head1 DESCRIPTION
+
+C<Jifty::Result> encapsulates the outcome of running a
+L<Jifty::Action>.  Results are also stored on the framework's
+L<Jifty::Response> object.
+
+=cut
+
+
+
+use base qw/Jifty::Object Class::Accessor::Fast/;
+
+__PACKAGE__->mk_accessors(qw(failure action_class message _content));
+
+
+=head2 new
+
+Construct a new action result.  This is done automatically when the
+action is created, and can be accessed via the
+L<Jifty::Action/result>.
+
+=cut
+
+sub new {
+    my $class = shift;
+    my $self = bless {}, $class;
+
+    $self->failure(0);
+    $self->_content({});
+
+    return $self;
+}
+
+=head2 failure [BOOL]
+
+Gets or sets if the action succeeded or failed.
+
+=head2 success [BOOL]
+
+Gets or sets if the action succeeded or failed -- this is an
+alternate interface from C<failure> but has the same effect.
+
+=cut
+
+sub success {
+    my $self = shift;
+    return not $self->failure(map {not $_} @_);
+}
+
+=head2 action_class [MESSAGE]
+
+Returns the class for the action that this result came from.
+
+=head2 message [MESSAGE]
+
+Gets or sets the action's response message.  This is an informational
+textual description of the outcome of the action.
+
+=head2 error [ERROR]
+
+Gets or sets the action's error response.  This is an informational
+textual description of what went wrong with the action, overall.  This
+also automatically sets the result to be a L</failure>.
+
+=cut
+
+sub error {
+    my $self = shift;
+    
+    $self->failure(1) if @_ and $_[0];
+    $self->{error} = shift if @_;
+    return $self->{error};
+}
+
+=head2 field_error FIELD [ERROR]
+
+Gets or sets the error string for a specific field on the action.
+This also automatically sets the result to be a failure.
+
+=cut
+
+sub field_error {
+    my $self = shift;
+    my $field = shift;
+
+    $self->failure(1) if @_ and $_[0];
+    $self->{field_errors}{ $field } = shift if @_;
+    return $self->{field_errors}{ $field };
+}
+
+=head2 field_errors
+
+Returns a hash which maps L<argument|Jifty::Manual::Glossary/argument>
+name to error.
+
+=cut
+
+sub field_errors {
+    my $self = shift;
+    return %{$self->{field_errors} || {}};
+}
+
+=head2 field_warning FIELD [WARNING]
+
+Gets or sets the warning string for a specific field on the action.
+
+=cut
+
+sub field_warning {
+    my $self = shift;
+    my $field = shift;
+
+    $self->{field_warnings}{ $field } = shift if @_;
+    return $self->{field_warnings}{ $field };
+}
+
+=head2 field_warnings
+
+Returns a hash which maps L<argument|Jifty::Manual::Glossary/argument>
+name to warning.
+
+=cut
+
+sub field_warnings {
+    my $self = shift;
+    return %{$self->{field_warnings} || {}};
+}
+
+=head2 field_canonicalization_note FIELD [NOTE]
+
+Gets or sets a canonicalization note for a specific field on the action.
+
+=cut
+
+sub field_canonicalization_note {
+    my $self = shift;
+    my $field = shift;
+
+    $self->{field_canonicalization_notes}{ $field } = shift if @_;
+    return $self->{field_canonicalization_notes}{ $field };
+}
+
+=head2 field_canonicalization_notes
+
+Returns a hash which maps L<argument|Jifty::Manual::Glossary/argument>
+name to canonicalization notes.
+
+=cut
+
+sub field_canonicalization_notes {
+    my $self = shift;
+    return %{$self->{field_canonicalization_notes} || {}};
+}
+
+=head2 content [KEY [, VALUE]]
+
+Gets or sets the content C<KEY>.  This is used when actions need to
+return values.  If not C<KEY> is passed, it returns an anonymous hash
+of all of the C<KEY> and C<VALUE> pairs.
+
+=cut
+
+sub content {
+    my $self = shift;
+
+    return $self->_content unless @_;
+
+    my $key = shift;
+    $self->_content->{$key} = shift if @_;
+    return $self->_content->{$key};
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/RightsFrom.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/RightsFrom.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,141 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Jifty::RightsFrom
+
+=head1 SYNOPSIS
+
+  package Application::Model::Thing;
+  use Jifty::DBI::Schema;
+  use Application::Record schema {
+    column owner => refers_to Application::Model::Person;
+  }
+
+  use Jifty::RightsFrom column => 'owner';
+
+=head1 DESCRIPTION
+
+Provides a C<delegate_current_user_can> method that various
+task-related objects can use as a base to make their own access
+control decisions based on their
+task. L<Jifty::Record/current_user_can> uses this method to make an
+access control decision if it exists.
+
+Note that this means that you a model class can use Jifty::RightsFrom,
+and still have a custom C<current_user_can> method, and they will not
+interfere with each other.
+
+=cut
+
+package Jifty::RightsFrom;
+use base qw/Exporter/;
+
+
+sub import {
+    my $class = shift;
+    export_curried_sub(
+        sub_name  => 'delegate_current_user_can',
+        as        => 'delegate_current_user_can',
+        export_to => $class,
+        args      => \@_
+    );
+}
+
+
+=head2 export_curried_sub HASHREF
+
+Takes:
+
+=over
+
+=item sub_name
+
+The subroutine in this package that you want to export.
+
+=item export_to
+
+The name of the package you want to export to.
+
+=item as
+
+The name your new curried sub should be exported into in the package
+C<export_to>
+
+
+=item args (arrayref)
+
+The arguments you want to hand to your sub.
+
+
+=back
+
+=cut
+
+sub export_curried_sub {
+    my %args = (
+        sub_name     => undef,
+        export_to => undef,
+        as           => undef,
+        args        =>  undef,
+        @_
+    );
+    no strict 'refs';
+    no warnings 'redefine';
+    local *{ $args{'as'} } = sub { &{ $args{'sub_name'} }(shift @_, @{ $args{'args'} }, @_ ) };
+
+    local @{Jifty::RightsFrom::EXPORT_OK} = ($args{as});
+    Jifty::RightsFrom->export_to_level( 2, $args{export_to}, $args{as} );
+}
+
+=head2 delegate_current_user_can C<'column'>, C<$column_name>, C<$right_name>, C<@attributes>
+
+Make a decision about permissions based on checking permissions on the
+column of this record specified in the call to C<import>. C<create>,
+C<delete>, and C<update> rights all check for the C<update> right on
+the delegated object. On create, we look in the passed attributes for
+an argument with the name of that column.
+
+=cut
+
+sub delegate_current_user_can {
+    my $self        = shift;
+    my $object_type = shift;    #always 'column' for now
+    my $col_name    = shift;
+    my $right       = shift;
+    my %attribs     = @_;
+
+    $right = 'update' if $right ne 'read';
+    my $obj;
+
+    my $column   = $self->column($col_name);
+    my $obj_type = $column->refers_to();
+
+    # XXX TODO: this card is bloody hard to follow. it's my fault. --jesse
+
+    my $foreign_key = $attribs{ $column->name };
+    # We only do the isa if the foreign_key is a reference
+    # We could also do this using eval, but it's an order of magnitude slower
+    if ( ref($foreign_key) and $foreign_key->isa($obj_type) ) {
+        $obj = $foreign_key;    # the fk is actually an object
+    } elsif (
+        my $fk_value = (
+                   $foreign_key
+                || $self->__value( $column->name )
+                || $self->{ $column->name }
+        )
+        )
+    {
+        $obj = $obj_type->new( current_user => $self->current_user );
+        $obj->load_by_cols( ( $column->by || 'id' ) => $fk_value );
+    } else {
+        return 0;
+    }
+
+    return $obj->current_user_can($right);
+}
+
+
+1;
+

Added: jifty/branches/schema-plugins/lib/Jifty/Script.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Script.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,43 @@
+package Jifty::Script;
+use App::CLI;
+use base qw/App::CLI App::CLI::Command/;
+
+=head2 prepare
+
+C<prepare> figures out which command to run. If the user wants
+C<--help> give them help. If they have no command on the commandline,
+but a JIFTY_COMMAND environment variable, try that. If they have
+neither, shows the help. Otherwise, let App::CLI figure it out.
+
+=cut
+
+sub prepare {
+    my $self = shift;
+    if ($ARGV[0] =~ /--?h(elp?)/i) {
+        shift @ARGV; #discard the --help
+        unshift @ARGV, 'help';
+    } elsif (!$ARGV[0] and $ENV{'JIFTY_COMMAND'}) {
+        my $cmd = $ENV{'JIFTY_COMMAND'};
+        unshift @ARGV, $cmd;
+    } elsif (! @ARGV) {
+        unshift @ARGV, 'help';
+    }
+    return $self->SUPER::prepare(@_);
+}
+
+=head2 alias
+
+The alias table lets users type C<fastcgi> in place of C<FastCGI>.
+
+=cut
+
+sub alias {
+    return (
+            fastcgi => "FastCGI",
+           )
+}
+
+
+
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Script/Action.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Script/Action.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,166 @@
+use warnings;
+use strict;
+
+package Jifty::Script::Action;
+use base qw/App::CLI::Command/;
+
+
+
+=head1 NAME
+
+Jifty::Script::Action - Add an action class to your Jifty application
+
+=head1 DESCRIPTION
+
+This creates a skeleton of a new action class for your jifty
+application, complete with a skeleton of a test suite for it,
+as well.
+
+=head1 API
+
+=head2 options
+
+There are only two possible options to this script:
+
+=over
+
+=item --name NAME (required)
+
+Name of the action class.
+
+=item --force
+
+By default, this will stop and warn you if any of the files it is
+going to write already exist.  Passing the --force flag will make it
+overwrite the files.
+
+=back
+
+=cut
+
+sub options {
+    (
+     'n|name=s' => 'name',
+     'force'    => 'force',
+    )
+}
+
+=head2 run
+
+Creates a skeleton file under C<lib/I<ApplicationClass>/Action/I<Action>>, as
+well as a skeleton tests file.
+
+=cut
+
+sub run {
+    my $self = shift;
+    
+    my $action = $self->{name} || '';
+    die "You need to give your new action a --name\n"
+      unless $action =~ /\w+/;
+
+    Jifty->new( no_handle => 1 );
+    my $root = Jifty::Util->app_root;
+    my $appclass = Jifty->config->framework("ApplicationClass");
+    my $appclass_path =  File::Spec->catfile(split(/::/,Jifty->config->framework("ApplicationClass")));
+
+    my $actionFile = <<"EOT";
+use strict;
+use warnings;
+
+=head1 NAME
+
+@{[$appclass]}::Action::@{[$action]}
+
+=cut
+
+package @{[$appclass]}::Action::@{[$action]};
+use base qw/@{[$appclass]}::Action Jifty::Action/;
+
+use Jifty::Param::Schema;
+use Jifty::Action schema {
+
+};
+
+=head2 take_action
+
+=cut
+
+sub take_action {
+    my \$self = shift;
+    
+    # Custom action code
+    
+    \$self->report_success if not \$self->result->failure;
+    
+    return 1;
+}
+
+=head2 report_success
+
+=cut
+
+sub report_success {
+    my \$self = shift;
+    # Your success message here
+    \$self->result->message('Success');
+}
+
+1;
+
+EOT
+
+
+    my $testFile = <<"EOT";
+#!/usr/bin/env perl
+use warnings;
+use strict;
+
+=head1 DESCRIPTION
+
+A (very) basic test harness for the $action action.
+
+=cut
+
+use Jifty::Test tests => 1;
+
+# Make sure we can load the action
+use_ok('@{[$appclass]}::Action::@{[$action]}');
+
+EOT
+
+    $self->_write("$root/lib/$appclass_path/Action/$action.pm" => $actionFile,
+                  "$root/t/00-action-$action.t" => $testFile,
+                 );
+}
+
+sub _write {
+    my $self = shift;
+    my %files = (@_);
+    my $halt;
+    for my $path (keys %files) {
+        my ($volume, $dir, $file) = File::Spec->splitpath($path);
+
+        # Make sure the directories we need are there
+        Jifty::Util->make_path($dir);
+
+        # If it already exists, bail
+        if (-e $path and not $self->{force}) {
+            print "File $path exists already; Use --force to overwrite\n";
+            $halt = 11;
+        }
+    }
+    exit if $halt;
+    
+    # Now that we've san-checked everything, we can write the files
+    for my $path (keys %files) {
+        print "Writing file $path\n";
+        # Actually write the file out
+        open(FILE, ">$path")
+          or die "Can't write to $path: $!";
+        print FILE $files{$path};
+        close FILE;
+    }
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Script/App.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Script/App.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,159 @@
+use warnings;
+use strict;
+
+package Jifty::Script::App;
+use base qw(App::CLI::Command Class::Accessor::Fast);
+
+use File::Copy;
+use Jifty::Config;
+use Jifty::YAML;
+use File::Basename;
+
+__PACKAGE__->mk_accessors(qw/prefix dist_name mod_name/);
+
+
+=head1 NAME
+
+Jifty::Script::App - Create the skeleton of a Jifty application
+
+=head1 DESCRIPTION
+
+Creates a skeleton of a new Jifty application.  See
+L<Jifty::Manual::Tutorial> for an example of its use.
+
+=head2 options
+
+This script only takes one option, C<--name>, which is required; it is
+the name of the application to create.  Jifty will create a directory
+with that name, and place all of the files it creates inside that
+directory.
+
+=cut
+
+sub options {
+    (
+     'n|name=s' => 'name',
+    )
+}
+
+=head2 run
+
+Create a directory for the application, a skeleton directory
+structure, and a C<Makefile.PL> for you application.
+
+=cut
+
+sub run {
+    my $self = shift;
+
+    $self->prefix( $self->{name} ||''); 
+
+    unless ($self->prefix =~ /\w+/ ) { die "You need to give your new Jifty app a --name"."\n";}
+
+    # Turn my-app-name into My::App::Name.
+
+    $self->mod_name (join ("::", map { ucfirst } split (/\-/, $self->prefix)));
+    my $dist = $self->mod_name;
+    $self->dist_name($self->prefix);
+
+    print("Creating new application ".$self->mod_name."\n");
+    $self->_make_directories();
+    $self->_install_jifty_binary();
+    $self->_write_makefile();
+    $self->_write_config();
+
+
+}
+
+sub _install_jifty_binary {
+    my $self = shift;
+    my $prefix = $self->prefix;
+    my $basename = basename($0);
+
+    # Copy our running copy of 'jifty' to bin/jifty
+    copy($0, "$prefix/bin/$basename");
+    # Mark it executable
+    chmod(0555, "$prefix/bin/$basename");
+
+    # Do the same for .bat if we are on a DOSish platform
+    if (-e "$0.bat") {
+        copy("$0.bat", "$prefix/bin/$basename.bat");
+        chmod(0555, "$prefix/bin/$basename.bat");
+    }
+}
+
+
+
+sub _write_makefile {
+    my $self = shift;
+    my $mod_name = $self->mod_name;
+    my $prefix = $self->prefix;
+    # Write a makefile
+    open(MAKEFILE, ">$prefix/Makefile.PL") or die "Can't write Makefile.PL: $!";
+    print MAKEFILE <<"EOT";
+use inc::Module::Install;
+
+name        '$mod_name';
+version     '0.01';
+requires    'Jifty' => '@{[$Jifty::VERSION]}';
+
+WriteAll;
+EOT
+    close MAKEFILE;
+} 
+
+sub _make_directories {
+    my $self = shift;
+
+    mkdir($self->prefix);
+    my @dirs = qw( lib );
+    my @dir_parts = split('::',$self->mod_name);
+    my $lib_dir = "";
+    foreach my $part (@dir_parts) {
+        $lib_dir .= '/' if length $lib_dir;
+        $lib_dir .=  $part;
+        push @dirs, "lib/$lib_dir";
+    }
+
+    @dirs = (@dirs, $self->_directories); 
+
+    foreach my $dir (@dirs) {
+        $dir =~ s/__APP__/$lib_dir/;
+        print("Creating directory @{[$self->prefix]}/$dir\n");
+        mkdir( $self->prefix."/$dir") or die "Can't create ". $self->prefix."/$dir: $!";
+
+    }
+}
+sub _directories {
+    return qw(
+        bin
+        etc
+        doc
+        log
+        var
+        var/mason
+        share
+        share/po
+        share/web
+        share/web/templates
+        share/web/static
+        lib/__APP__/Model
+        lib/__APP__/Action
+        t
+    );
+}
+
+
+sub _write_config {
+    my $self = shift;
+    my $cfg = Jifty::Config->new(load_config => 0);
+    my $default_config = $cfg->guess($self->dist_name);
+    my $file = join("/",$self->prefix, 'etc','config.yml');
+    print("Creating configuration file $file\n");
+    Jifty::YAML::DumpFile($file => $default_config);
+
+}
+
+
+1;
+

Added: jifty/branches/schema-plugins/lib/Jifty/Script/Deps.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Script/Deps.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,119 @@
+use warnings;
+use strict;
+
+package Jifty::Script::Deps;
+use base qw/App::CLI::Command/;
+
+use Config;
+use File::Find::Rule;
+use Module::ScanDeps;
+use PAR::Dist::FromCPAN;
+use Pod::Usage;
+use version;
+use Jifty::Config;
+
+=head2 options
+
+Returns a hash of all the options this script takes. (See the usage message for details)
+
+=cut
+
+
+sub options {
+    return (
+        "setup"             => "setup_deps",
+        "help|?"            => "help",
+    );
+}
+
+=head2 run
+
+Prints a help message if the users want it. If not, goes about its
+business.
+
+Sets up the environment, checks current database state, creates or deletes
+a database as necessary and then creates or updates your models' schema.
+
+=cut
+
+sub run {
+    my $self = shift;
+    local     $ENV{'PERL_MM_USE_DEFAULT'} = 1;
+    Jifty->new( no_handle => 1 );
+
+    my $root = Jifty::Util->app_root;
+    chdir $root;
+
+    # First let's find out our dependencies.
+    # I think we can cache the result in META.yml or something.
+
+    warn "Scanning for dependencies...\n";
+
+    my @files   = _get_files_in(grep { -d } map { $_, "share/$_" } qw( lib html bin ));
+    my $map     = scan_deps(
+        files   => \@files,
+        recurse => 1,
+    );
+
+    my @mod;
+    foreach my $key (sort keys %$map) {
+        my $mod = $map->{$key};
+        next unless $mod->{type} eq 'module';
+        next if $mod->{file} eq "$Config::Config{privlib}/$key";
+        next if $mod->{file} eq "$Config::Config{archlib}/$key";
+        push @mod, _name($key);
+
+        warn "* $mod[-1]\n";
+    }
+
+    warn "Populating share/deps/...\n";
+
+    mkdir "share";
+    mkdir "share/deps";
+
+    my $pat = '/^(?:' . join('|', map { quotemeta($_) } @mod) . ')$/';
+
+    cpan_to_par(
+        pattern => $pat,
+        out     => 'share/deps/',
+        follow  => 1,
+        verbose => 0,
+        test    => 0,
+    );
+}
+
+sub _name {
+    my $str = shift;
+    $str =~ s!/!::!g;
+    $str =~ s!.pm$!!i;
+    $str =~ s!^auto::(.+)::.*!$1!;
+    return $str;
+}
+
+sub _get_files_in {
+  my @dirs = @_;
+  my $rule = File::Find::Rule->new;
+  $rule->or($rule->new
+                 ->directory
+                 ->name('.svn')
+                 ->prune
+                 ->discard,
+            $rule->new
+                 ->directory
+                 ->name('CVS')
+                 ->prune
+                 ->discard,
+            $rule->new
+                 ->name(qr/~$/)
+                 ->discard,
+            $rule->new
+                 ->name(qr/\.pod$/)
+                 ->discard,
+            $rule->new
+                 ->not($rule->new->file)
+                 ->discard,
+            $rule->new);
+  return $rule->in(grep {-e $_} @dirs);
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Script/FastCGI.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Script/FastCGI.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,112 @@
+use strict;
+use warnings;
+
+
+package Jifty::Script::FastCGI;
+use base qw/App::CLI::Command/;
+
+use File::Basename;
+use CGI::Fast;
+
+
+=head1 NAME
+
+Jifty::Script::FastCGI - A FastCGI server for your Jifty application
+
+=head1 DESCRIPTION
+
+When you're ready to move up to something that can handle the increasing load your
+new world-changing application is generating, you'll need something a bit heavier-duty
+than the pure-perl Jifty standalone server.  C<FastCGI> is what you're looking for.
+
+Because Apache's FastCGI dispatcher can't pass commandline flags to your script, you'll need
+to call jifty a bit differently:
+
+ AddHandler fastcgi-script fcgi
+ DocumentRoot /path/to/your/jifty/app/share/web/templates
+ FastCgiServer /path/to/your/jifty/app/bin/jifty -initial-env JIFTY_COMMAND=fastcgi
+ ScriptAlias /  /path/to/your/jifty/app/bin/jifty/
+
+For B<lighttpd> (L<http://www.lighttpd.net/>), use this setting:
+
+ server.modules  = ( "mod_fastcgi" )
+ server.document-root = "/path/to/your/jifty/app/share/web/templates"
+ fastcgi.server = (
+        "" => (
+            "your_jifty_app" => (
+                "socket"       => "/tmp/your_jifty_app.socket",
+                "check-local"  => "disable",
+                "bin-path"     => "/path/to/your/jifty/app/bin/jifty",
+                "bin-environment" => ( "JIFTY_COMMAND" => "fastcgi" ),
+                "min-procs"    => 1,
+                "max-procs"    => 5,
+                "max-load-per-proc" => 1,
+                "idle-timeout" => 20,
+            )
+        )
+    )
+
+If you have MaxRequests options under FastCGI in your config.yml, or
+commandline option C<--maxrequests=N> assigned, the fastcgi process
+will exit after serving N requests. 
+
+An alternative to Apache mod_fastcgi is to use mod_fcgid with mod_rewrite.
+If you use mod_fcgid and mod_rewrite, you can use this in your Apache
+configuration instead:
+
+ DocumentRoot /path/to/your/jifty/app/share/web/templates
+ ScriptAlias /cgi-bin /path/to/your/jifty/app/bin
+ DefaultInitEnv JIFTY_COMMAND fastcgi
+ <Directory /path/to/your/jifty/app/bin>
+     Options ExecCGI
+     SetHandler fcgid-script
+ </Directory>
+ <Directory /path/to/your/jifty/app/share/web/templates>
+     RewriteEngine on
+     RewriteRule ^$ index.html [QSA]
+     RewriteRule ^(.*)$ /cgi-bin/jifty/$1 [QSA,L]
+ </Directory>
+
+It may be possible to do this without using mod_rewrite.
+
+=head2 options
+
+=cut
+
+sub options {
+    (
+        'maxrequests=i' => 'maxrequests',
+    );
+}
+
+=head2 run
+
+Creates a new FastCGI process.
+
+=cut
+
+sub run {
+    my $self = shift;
+    Jifty->new();
+    my $conf = Jifty->config->framework('Web')->{'FastCGI'} || {};
+    $self->{maxrequests} ||= $conf->{MaxRequests};
+
+    my $requests = 0;
+    while ( my $cgi = CGI::Fast->new ) {
+        # the whole point of fastcgi requires the env to get reset here..
+        # So we must squash it again
+        $ENV{'PATH'}   = '/bin:/usr/bin';
+        $ENV{'SHELL'}  = '/bin/sh' if defined $ENV{'SHELL'};
+        $ENV{'PATH_INFO'}   = $ENV{'SCRIPT_NAME'}
+            if $ENV{'SERVER_SOFTWARE'} =~ /^lighttpd\b/;
+        for (qw(CDPATH ENV IFS)) {
+            $ENV{$_} = '' if (defined $ENV{$_} );
+        }
+        Jifty->handler->handle_request( cgi => $cgi );
+	if ($self->{maxrequests} && ++$requests >= $self->{maxrequests}) {
+	    exit 0;
+	}
+    }
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Script/Help.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Script/Help.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,44 @@
+package Jifty::Script::Help;
+use strict;
+use base qw( App::CLI::Command::Help );
+use File::Find qw(find);
+
+sub help_base {
+    return "Jifty::Manual";
+}
+
+1;
+
+__DATA__
+
+=head1 NAME
+
+Jifty::Script::Help - Show help
+
+=head1 SYNOPSIS
+
+ help COMMAND
+
+=head1 OPTIONS
+
+Optionally help can pipe through a pager, to make it easier to
+read the output if it is too long. For using this feature, please
+set environment variable PAGER to some pager program.
+For example:
+
+    # bash, zsh users
+    export PAGER='/usr/bin/less'
+
+    # tcsh users
+    setenv PAGER '/usr/bin/less'
+
+=head2 help_base
+
+Jifty's help system also looks in L<Jifty::Manual> and the
+subdirectories for any help commands that it can't find help for.
+
+=head1 AUTHOR
+
+Various folks at Best Practical Solutions, LLC.
+
+=cut

Added: jifty/branches/schema-plugins/lib/Jifty/Script/Model.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Script/Model.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,170 @@
+use warnings;
+use strict;
+
+package Jifty::Script::Model;
+use base qw/App::CLI::Command/;
+
+
+
+=head1 NAME
+
+Jifty::Script::Model - Add a model class to your Jifty application
+
+=head1 DESCRIPTION
+
+This creates a skeleton of a new model class for your jifty
+application, complete with a skeleton of a test suite for it, as well.
+
+=head1 API
+
+=head2 options
+
+There are only two possible options to this script:
+
+=over
+
+=item --name NAME (required)
+
+Name of the model class.
+
+=item --force
+
+By default, this will stop and warn you if any of the files it is
+going to write already exist.  Passing the --force flag will make it
+overwrite the files.
+
+=back
+
+=cut
+
+sub options {
+    (
+     'n|name=s' => 'name',
+     'force' => 'force',
+    )
+}
+
+=head2 run
+
+Creates a skeleton file under C<lib/I<ApplicationClass>/Model/I<Model>>, as
+well as a skeleton tests file.
+
+=cut
+
+sub run {
+    my $self = shift;
+    
+    my $model = $self->{name} || '';
+    die "You need to give your new model a --name\n"
+      unless $model =~ /\w+/;
+
+    Jifty->new( no_handle => 1 );
+    my $root = Jifty::Util->app_root;
+    my $appclass = Jifty->config->framework("ApplicationClass");
+    my $appclass_path = File::Spec->catfile(split (/::/, $appclass));
+
+    my $modelFile = <<"EOT";
+use strict;
+use warnings;
+
+package @{[$appclass]}::Model::@{[$model]};
+use Jifty::DBI::Schema;
+
+use @{[$appclass]}::Record schema {
+
+};
+
+# Your model-specific methods go here.
+
+1;
+
+EOT
+
+
+    my $testFile = <<"EOT";
+#!/usr/bin/env perl
+use warnings;
+use strict;
+
+=head1 DESCRIPTION
+
+A basic test harness for the $model model.
+
+=cut
+
+use Jifty::Test tests => 11;
+
+# Make sure we can load the model
+use_ok('@{[$appclass]}::Model::@{[$model]}');
+
+# Grab a system user
+my \$system_user = @{[$appclass]}::CurrentUser->superuser;
+ok(\$system_user, "Found a system user");
+
+# Try testing a create
+my \$o = @{[$appclass]}::Model::@{[$model]}->new(current_user => \$system_user);
+my (\$id) = \$o->create();
+ok(\$id, "$model create returned success");
+ok(\$o->id, "New $model has valid id set");
+is(\$o->id, \$id, "Create returned the right id");
+
+# And another
+\$o->create();
+ok(\$o->id, "$model create returned another value");
+isnt(\$o->id, \$id, "And it is different from the previous one");
+
+# Searches in general
+my \$collection =  @{[$appclass]}::Model::@{[$model]}Collection->new(current_user => \$system_user);
+\$collection->unlimit;
+is(\$collection->count, 2, "Finds two records");
+
+# Searches in specific
+\$collection->limit(column => 'id', value => \$o->id);
+is(\$collection->count, 1, "Finds one record with specific id");
+
+# Delete one of them
+\$o->delete;
+\$collection->redo_search;
+is(\$collection->count, 0, "Deleted row is gone");
+
+# And the other one is still there
+\$collection->unlimit;
+is(\$collection->count, 1, "Still one left");
+
+EOT
+
+    $self->_write("$root/lib/$appclass_path/Model/$model.pm" => $modelFile,
+                  "$root/t/00-model-$model.t" => $testFile,
+                 );
+}
+
+sub _write {
+    my $self = shift;
+    my %files = (@_);
+    my $halt;
+    for my $path (keys %files) {
+        my ($volume, $dir, $file) = File::Spec->splitpath($path);
+
+        # Make sure the directories we need are there
+	Jifty::Util->make_path($dir);
+
+        # If it already exists, bail
+        if (-e $path and not $self->{force}) {
+            print "File $path exists already; Use --force to overwrite\n";
+            $halt = 11;
+        }
+    }
+    exit if $halt;
+    
+    # Now that we've san-checked everything, we can write the files
+    for my $path (keys %files) {
+        print "Writing file $path\n";
+        # Actually write the file out
+        open(FILE, ">$path")
+          or die "Can't write to $path: $!";
+        print FILE $files{$path};
+        close FILE;
+    }
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Script/Plugin.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Script/Plugin.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,150 @@
+use warnings;
+use strict;
+
+package Jifty::Script::Plugin;
+use base qw(App::CLI::Command Class::Accessor::Fast);
+
+use File::Copy;
+use Jifty::Config;
+use Jifty::YAML;
+use File::Basename;
+
+__PACKAGE__->mk_accessors(qw/prefix dist_name mod_name lib_dir/);
+
+
+=head1 NAME
+
+Jifty::Script::Plugin - Create the skeleton of a Jifty plugin
+
+=head1 DESCRIPTION
+
+Creates a skeleton of a new L<Jifty::Plugin>.
+
+=head2 options
+
+This script only takes one option, C<--name>, which is required; it is
+the name of the plugin to create; this will be prefixed with
+C<Jifty::Plugin::> automatically.  Jifty will create a directory with
+that name, and place all of the files it creates inside that
+directory.
+
+=cut
+
+sub options {
+    (
+     'n|name=s' => 'name',
+    )
+}
+
+=head2 run
+
+Create a directory for the plugin, a skeleton directory structure, and
+a C<Makefile.PL> for your plugin.
+
+=cut
+
+sub run {
+    my $self = shift;
+
+    $self->prefix( $self->{name} ||''); 
+
+    unless ($self->prefix =~ /\w+/ ) { die "You need to give your new Jifty plugin a --name"."\n";}
+    $self->prefix( $self->prefix );
+
+    # Turn my-plugin-name into My::Plugin::Name.
+    $self->mod_name ("Jifty::Plugin::" . join ("::", map { ucfirst } split (/\-/, $self->prefix)));
+    $self->dist_name("Jifty-Plugin-".$self->prefix);
+    $self->lib_dir(join("/",grep{$_} split '::', $self->mod_name));
+
+    print("Creating new plugin ".$self->mod_name."\n");
+    $self->_make_directories();
+    $self->_write_makefile();
+    $self->_write_default_files();
+}
+
+sub _write_makefile {
+    my $self = shift;
+    my $prefix = $self->prefix;
+    # Write a makefile
+    open(MAKEFILE, ">$prefix/Makefile.PL") or die "Can't write Makefile.PL: $!";
+    print MAKEFILE <<"EOT";
+use inc::Module::Install;
+name('@{[$self->dist_name]}');
+version('0.01');
+requires('Jifty' => '@{[$Jifty::VERSION]}');
+
+install_share;
+
+WriteAll;
+EOT
+    close MAKEFILE;
+} 
+
+sub _write_default_files {
+    my $self = shift;
+    my $mod_name = $self->mod_name;
+    my $prefix = $self->prefix;
+    my $lib = $self->lib_dir;
+    open(PLUGIN, ">$prefix/lib/$lib.pm") or die "Can't write $prefix/$lib.pm: $!";
+    print PLUGIN <<"EOT";
+use strict;
+use warnings;
+
+package $mod_name;
+use base qw/Jifty::Plugin/;
+
+# Your plugin goes here.  If takes any configuration or arguments, you
+# probably want to override L<Jifty::Plugin/init>.
+
+1;
+EOT
+    close PLUGIN;
+
+    open(DISPATCHER, ">$prefix/lib/$lib/Dispatcher.pm") or die "Can't write $prefix/lib/$lib/Dispatcher.pm: $!";
+    print DISPATCHER <<"EOT";
+use strict;
+use warnings;
+
+package @{[$mod_name]}::Dispatcher;
+use Jifty::Dispatcher -base;
+
+# Put any plugin-specific dispatcher rules here.
+
+1;
+EOT
+}
+
+sub _make_directories {
+    my $self = shift;
+
+    mkdir($self->prefix);
+    my @dirs = qw( lib );
+    my @dir_parts = split('/',$self->lib_dir);
+    push @dirs, join('/', 'lib', @dir_parts[0..$_]) for 0.. at dir_parts-1;
+
+    @dirs = (@dirs, $self->_directories); 
+
+    foreach my $dir (@dirs) {
+        $dir =~ s/__LIB__/$self->lib_dir/e;
+        print("Creating directory $dir\n");
+        mkdir( $self->prefix."/$dir") or die "Can't create ". $self->prefix."/$dir: $!";
+    }
+}
+
+sub _directories {
+    return qw(
+        doc
+        share
+        share/po
+        share/web
+        share/web/templates
+        share/web/static
+        lib/__LIB__/Model
+        lib/__LIB__/Action
+        t
+    );
+}
+
+
+1;
+

Added: jifty/branches/schema-plugins/lib/Jifty/Script/Po.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Script/Po.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,140 @@
+use warnings;
+use strict;
+
+package Jifty::Script::Po;
+use base qw(App::CLI::Command Class::Accessor::Fast);
+
+use File::Copy ();
+use Jifty::Config ();
+use Jifty::YAML ();
+use Locale::Maketext::Extract ();
+use File::Find::Rule ();
+use MIME::Types ();
+our $MIME = MIME::Types->new();
+our $LMExtract = Locale::Maketext::Extract->new;
+use constant USE_GETTEXT_STYLE => 1;
+
+__PACKAGE__->mk_accessors(qw/language/);
+
+
+=head1 NAME
+
+Jifty::Script::Po - Extract translatable strings from your application
+
+=head1 DESCRIPTION
+
+Extracts message catalogs for your Jifty app. When run, Jifty will update
+all existing message catalogs, as well as create a new one if you specify a --language flag
+
+=head2 options
+
+This script only takes one option, C<--language>, which is optional; it is
+the name of a message catalog to create.  
+
+=cut
+
+sub options {
+    (
+     'l|language=s' => 'language',
+    )
+}
+
+
+=head2 run
+
+Runs the "update_catalogs" method.
+
+=cut
+
+
+sub run {
+        my $self = shift;
+            Jifty->new(no_handle => 1);
+        $self->update_catalogs;
+}
+
+=head2 _check_mime_type FILENAME
+
+This routine returns a mimetype for the file C<FILENAME>.
+
+=cut
+
+sub _check_mime_type {
+    my $self       = shift;
+    my $local_path = shift;
+    my $mimeobj = $MIME->mimeTypeOf($local_path);
+    my $mime_type = ($mimeobj ? $mimeobj->type : "unknown");
+    return if ( $mime_type =~ /^image/ );
+    return 1;
+}
+
+=head2 update_catalogs
+
+Extracts localizable messages from all files in your application, finds
+all your message catalogs and updates them with new and changed messages.
+
+=cut
+
+sub update_catalogs {
+    my $self = shift;
+    $self->extract_messages();
+    my @catalogs = File::Find::Rule->file->in(
+        Jifty->config->framework('L10N')->{'PoDir'} );
+    if ($self->{'language'}) { 
+        $self->update_catalog( File::Spec->catfile( Jifty->config->framework('L10N')->{'PoDir'}, $self->{'language'} . ".po"));
+    }
+    else {
+        foreach my $catalog (@catalogs) {
+            next if $catalog =~ m{(^|/)\.svn/};
+            $self->update_catalog( $catalog );
+        }
+    }
+}
+
+=head2 update_catalog FILENAME
+
+Reads C<FILENAME>, a message catalog and integrates new or changed 
+translations.
+
+=cut
+
+sub update_catalog {
+    my $self       = shift;
+    my $translation = shift;
+    my $logger =Log::Log4perl->get_logger("main");
+    $logger->info( "Updating message catalog '$translation'");
+
+    $LMExtract->read_po($translation) if ( -f $translation );
+
+    # Reset previously compiled entries before a new compilation
+    $LMExtract->set_compiled_entries;
+    $LMExtract->compile(USE_GETTEXT_STYLE);
+
+    $LMExtract->write_po($translation);
+}
+
+
+=head2 extract_messages
+
+Find all translatable messages in your application, using 
+L<Locale::Maketext::Extract>.
+
+=cut
+
+sub extract_messages {
+    my $self = shift;
+    # find all the .pm files in @INC
+    my @files = File::Find::Rule->file->in( Jifty->config->framework('Web')->{'TemplateRoot'}, 'lib', 'bin' );
+
+    my $logger =Log::Log4perl->get_logger("main");
+    foreach my $file (@files) {
+        next if $file =~ m{(^|/)[\._]svn/};
+        next if $file =~ m{\~$};
+        next unless $self->_check_mime_type($file );
+        $logger->info("Extracting messages from '$file'");
+        $LMExtract->extract_file($file);
+    }
+
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Script/Schema.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Script/Schema.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,613 @@
+use warnings;
+use strict;
+
+package Jifty::Script::Schema;
+use base qw/App::CLI::Command/;
+
+use Pod::Usage;
+use version;
+use Jifty::DBI::SchemaGenerator;
+use Jifty::Config;
+use SQL::ReservedWords;
+
+Jifty::Module::Pluggable->import(
+    require     => 1,
+    search_path => ["SQL::ReservedWords"],
+    sub_name    => '_sql_dialects',
+);
+
+our %_SQL_RESERVED          = ();
+our @_SQL_RESERVED_OVERRIDE = qw(value);
+foreach my $dialect ( 'SQL::ReservedWords', &_sql_dialects ) {
+    foreach my $word ( $dialect->words ) {
+        push @{ $_SQL_RESERVED{ lc($word) } }, $dialect->reserved_by($word);
+    }
+}
+
+# XXX TODO: QUESTIONABLE ENGINEERING DECISION
+# The SQL standard forbids columns named 'value', but just about everone on the planet
+# actually supports it. Rather than going about scaremongering, we choose
+# not to warn people about columns named 'value'
+
+delete $_SQL_RESERVED{ lc($_) } for (@_SQL_RESERVED_OVERRIDE);
+
+=head2 options
+
+Returns a hash of all the options this script takes. (See the usage message for details)
+
+=cut
+
+sub options {
+    return (
+        "setup"                 => "setup_tables",
+        "print|p"               => "print",
+        "create-database|c"     => "create_database",
+        "ignore-reserved-words" => "ignore_reserved",
+        "drop-database"         => "drop_database",
+        "help|?"                => "help",
+        "man"                   => "man"
+    );
+}
+
+=head2 run
+
+Prints a help message if the users want it. If not, goes about its
+business.
+
+Sets up the environment, checks current database state, creates or deletes
+a database as necessary and then creates or updates your models' schema.
+
+=cut
+
+sub run {
+    my $self = shift;
+
+    $self->print_help();
+    $self->setup_environment();
+    $self->probe_database_existence();
+    $self->manage_database_existence();
+    $self->prepare_model_classes();
+    if ( $self->{create_all_tables} ) {
+        $self->create_all_tables();
+    } elsif ( $self->{'setup_tables'} ) {
+        $self->upgrade_jifty_tables();
+        $self->upgrade_application_tables();
+    } else {
+        print "Done.\n";
+    }
+}
+
+=head2 setup_environment
+
+Sets up a minimal Jifty environment.
+
+=cut
+
+sub setup_environment {
+    my $self = shift;
+
+    # Import Jifty
+    Jifty::Util->require("Jifty");
+    Jifty::Util->require("Jifty::Model::Metadata");
+}
+
+=head2 print_help
+
+Prints out help for the package using pod2usage.
+
+If the user specified --help, prints a brief usage message
+
+If the user specified --man, prints out a manpage
+
+=cut
+
+sub print_help {
+    my $self = shift;
+
+    # Option handling
+    my $docs = \*DATA;
+    pod2usage( -exitval => 1, -input => $docs ) if $self->{help};
+    pod2usage( -exitval => 0, -verbose => 2, -input => $docs )
+        if $self->{man};
+}
+
+=head2 prepare_model_classes
+
+Reads in our application class from the config file and finds all our app's models.
+
+=cut
+
+sub prepare_model_classes {
+
+    my $self = shift;
+
+    # This creates a sub "models" which when called, finds packages under
+    # the application's ::Model, requires them, and returns a list of their
+    # names.
+    Jifty::Module::Pluggable->import(
+        require     => 1,
+        except      => qr/\.#/,
+        search_path => [ "Jifty::Model", Jifty->app_class("Model") ],
+        sub_name    => 'models',
+    );
+}
+
+=head2 probe_database_existence
+
+Probes our database to see if it exists and is up to date.
+
+=cut
+
+sub probe_database_existence {
+    my $self = shift;
+
+    my $no_handle = 0;
+    if ( $self->{'create_database'} or $self->{'drop_database'} ) {
+        $no_handle = 1;
+    }
+
+    # Now try to connect.  We trap expected errors and deal with them.
+    eval {
+        Jifty->new(
+            no_handle        => $no_handle,
+            logger_component => 'SchemaTool',
+        );
+    };
+
+    if ( $@ =~ /doesn't match (application schema|running jifty) version/i ) {
+
+        # We found an out-of-date DB.  Upgrade it
+        $self->{setup_tables} = 1;
+    } elsif ( $@ =~ /no version in the database/i ) {
+
+        # No version table.  Assume the DB is empty.
+        $self->{create_all_tables} = 1;
+    } elsif ( $@ =~ /database .*? does not exist/i
+        or $@ =~ /unknown database/ ) {
+
+        # No database exists; we'll need to make one and fill it up
+        $self->{create_database}   = 1;
+        $self->{create_all_tables} = 1;
+    } elsif ($@) {
+
+        # Some other unexpected error; rethrow it
+        die $@;
+    }
+
+    # Setting up tables requires creating the DB if we just dropped it
+    $self->{create_database} = 1
+        if $self->{drop_database} and $self->{setup_tables};
+
+   # Setting up tables on a just-created DB is the same as setting them all up
+    $self->{create_all_tables} = 1
+        if $self->{create_database} and $self->{setup_tables};
+
+    # Give us some kind of handle if we don't have one by now
+    Jifty->handle( Jifty::Handle->new() ) unless Jifty->handle;
+}
+
+=head2 create_all_tables
+
+Create all tables for this application's models. Generally, this
+happens on installation.
+
+=cut
+
+sub create_all_tables {
+    my $self = shift;
+
+    my $log = Log::Log4perl->get_logger("SchemaTool");
+    $log->info("Generating SQL for application @{[Jifty->app_class]}...");
+
+    my $appv = version->new( Jifty->config->framework('Database')->{'Version'} );
+    my $jiftyv = version->new( $Jifty::VERSION  );
+
+    # Start a transaction
+    Jifty->handle->begin_transaction;
+
+    $self->create_tables_for_models (grep {$_->isa('Jifty::DBI::Record')}  __PACKAGE__->models );
+
+    # Update the versions in the database
+    Jifty::Model::Metadata->store( application_db_version => $appv );
+    Jifty::Model::Metadata->store( jifty_db_version       => $jiftyv );
+
+    # Load initial data
+    eval {
+        my $bootstrapper = Jifty->app_class("Bootstrap");
+        Jifty::Util->require($bootstrapper);
+        $bootstrapper->run() if $bootstrapper->can('run');
+    };
+    die $@ if $@;
+
+    # Commit it all
+    Jifty->handle->commit;
+
+    Jifty::Util->require('IPC::PubSub');
+    IPC::PubSub->new(
+        JiftyDBI => (
+            db_config    => Jifty->handle->{db_config},
+            table_prefix => '_jifty_pubsub_',
+            db_init      => 1,
+        )
+    );
+    $log->info("Set up version $appv, jifty version $jiftyv");
+}
+
+=head2 create_tables_for_models TABLEs
+
+Given a list of items that are the scalar names of subclasses of Jifty::Record, 
+either prints SQL or creates all those models in your database.
+
+=cut
+
+sub create_tables_for_models {
+    my $self = shift;
+    my @models = (@_);
+
+    my $log = Log::Log4perl->get_logger("SchemaTool");
+    my $appv = version->new( Jifty->config->framework('Database')->{'Version'} );
+    my $jiftyv = version->new( $Jifty::VERSION  );
+
+    for my $model ( @models) {
+
+       # TODO XXX FIXME:
+       #   This *will* try to generate SQL for abstract base classes you might
+       #   stick in $AC::Model::.
+        if ( $model->can( 'since' ) and ($model =~ /^Jifty::Model::/ ? $jiftyv : $appv) < $model->since ) {
+            $log->info( "Skipping $model, as it should already be in the database");
+            next;
+        }
+        $log->info("Using $model, as it appears to be new.");
+
+            $self->_check_reserved($model)
+        unless ( $self->{'ignore_reserved'}
+            or !Jifty->config->framework('Database')->{'CheckSchema'} );
+
+        if ( $self->{'print'} ) {
+            print $model->printable_table_schema;
+        } else {
+            $model->create_table_in_db;
+        }
+    }
+}
+
+=head2 upgrade_jifty_tables
+
+Upgrade Jifty's internal tables.
+
+=cut
+
+sub upgrade_jifty_tables {
+    my $self = shift;
+    my $dbv  = Jifty::Model::Metadata->load('jifty_db_version');
+    unless ($dbv) {
+        # Backwards combatibility -- it usd to be 'key' not 'data_key';
+        eval {
+            local $SIG{__WARN__} = sub { };
+            $dbv = Jifty->handle->fetch_result(
+                "SELECT value FROM _jifty_metadata WHERE key = 'jifty_db_version'"
+            );
+        };
+    }
+
+    $dbv = version->new($dbv || '0.60426');
+    my $appv = version->new($Jifty::VERSION);
+
+    return unless $self->upgrade_tables( "Jifty" => $dbv, $appv, "Jifty::Upgrade::Internal");
+    if ( $self->{print} ) {
+        warn "Need to upgrade jifty_db_version to $appv here!";
+    } else {
+        Jifty::Model::Metadata->store( jifty_db_version => $appv );
+    }
+}
+
+=head2 upgrade_application_tables
+
+Upgrade the application's tables.
+
+=cut
+
+sub upgrade_application_tables {
+    my $self = shift;
+    my $dbv  = version->new( Jifty::Model::Metadata->load('application_db_version') );
+    my $appv = version->new( Jifty->config->framework('Database')->{'Version'} );
+
+    return unless $self->upgrade_tables( Jifty->app_class, $dbv, $appv );
+    if ( $self->{print} ) {
+        warn "Need to upgrade application_db_version to $appv here!";
+    } else {
+        Jifty::Model::Metadata->store( application_db_version => $appv );
+    }
+}
+
+=head2 upgrade_tables BASECLASS, FROM, TO, [UPGRADECLASS]
+
+Given a C<BASECLASS> to upgrade, and two L<version> objects, C<FROM>
+and C<TO>, performs the needed transforms to the database.
+C<UPGRADECLASS>, if not specified, defaults to C<BASECLASS>::Upgrade
+
+=cut
+
+sub upgrade_tables {
+    my $self = shift;
+    my ( $baseclass, $dbv, $appv, $upgradeclass ) = @_;
+    $upgradeclass ||= $baseclass . "::Upgrade";
+
+    my $log = Log::Log4perl->get_logger("SchemaTool");
+
+    # Find current versions
+
+    if ( $appv < $dbv ) {
+        print "$baseclass version $appv from module older than $dbv in database!\n";
+        return;
+    } elsif ( $appv == $dbv ) {
+        # Shouldn't happen
+        print "$baseclass version $appv up to date.\n";
+        return;
+    }
+    $log->info( "Generating SQL to upgrade $baseclass $dbv database to $appv" );
+
+    # Figure out what versions the upgrade knows about.
+    Jifty::Util->require($upgradeclass) or return;
+    my %UPGRADES;
+    eval {
+        $UPGRADES{$_} = [ $upgradeclass->upgrade_to($_) ]
+            for grep { $appv >= version->new($_) and $dbv < version->new($_) }
+            $upgradeclass->versions();
+    };
+
+    for my $model_class ( grep {/^\Q$baseclass\E::Model::/} __PACKAGE__->models ) {
+
+        # We don't want to get the Collections, for example.
+        next unless $model_class->isa('Jifty::DBI::Record');
+
+        # Set us up the table
+        my $model = $model_class->new;
+
+        # If this whole table is new Create it
+        if (defined $model->since and  $appv >= $model->since and $model->since >$dbv ) {
+            unshift @{ $UPGRADES{ $model->since } }, $model->printable_table_schema();
+        } else {
+            # Go through the columns
+            for my $col  (grep {not $_->virtual} $model->columns ) {
+
+                # If they're old, drop them
+               if ( defined $col->till and $appv >= $col->till and $ $col->till > $dbv ) {
+                   push @{ $UPGRADES{ $col->till } }, $model->drop_column_sql($col->name);
+                }
+
+                # If they're new, add them
+                if (defined $col->since and $appv >= $col->since and $col->since >$dbv ) {
+                    unshift @{ $UPGRADES{ $col->since } }, $model->add_column_sql($col->name);
+                }
+            }
+        }
+    }
+
+    if ( $self->{'print'} ) {
+        $self->_print_upgrades(%UPGRADES);
+
+    } else {
+       eval { $self->_execute_upgrades(%UPGRADES);
+        $log->info("Upgraded to version $appv");
+    }; die $@ if $@;
+    }
+    return 1;
+}
+
+
+
+sub _execute_upgrades {
+    my $self     = shift;
+    my %UPGRADES = (@_);
+    Jifty->handle->begin_transaction;
+    my $log = Log::Log4perl->get_logger("SchemaTool");
+    for my $version ( sort { version->new($a) <=> version->new($b) } keys %UPGRADES) {
+        $log->info("Upgrading through $version");
+        for my $thing ( @{ $UPGRADES{$version} } ) {
+            if ( ref $thing ) {
+                $log->info("Running upgrade script");
+                $thing->();
+            } else {
+                my $ret = Jifty->handle->simple_query($thing);
+                $ret
+                    or die "error updating a table: " . $ret->error_message;
+            }
+        }
+    }
+    Jifty->handle->commit;
+}
+
+sub _print_upgrades {
+    my %UPGRADES = (@_);
+    for (
+        map  { @{ $UPGRADES{$_} } }
+        sort { version->new($a) <=> version->new($b) }
+        keys %UPGRADES
+        ) {
+        if ( ref $_ ) {
+            print "-- Upgrade subroutine:\n";
+            require Data::Dumper;
+            $Data::Dumper::Pad     = "-- ";
+            $Data::Dumper::Deparse = 1;
+            $Data::Dumper::Indent  = 1;
+            $Data::Dumper::Terse   = 1;
+            print Data::Dumper::Dumper($_);
+        } else {
+            print "$_;\n";
+        }
+    }
+}
+
+=head2 manage_database_existence
+
+If the user wants the database created, creates the database. If the
+user wants the old database deleted, does that too.
+
+=cut
+
+sub manage_database_existence {
+    my $self = shift;
+
+    my $handle = $self->_connect_to_db_for_management();
+
+        if ( $self->{'drop_database'} ) {
+        $handle->drop_database($self->{'print'} ? 'print' : 'execute');
+    }
+
+    if ( $self->{'create_database'} ) {
+        $handle->create_database($self->{'print'} ? 'print' : 'execute');
+    }
+
+    $handle->disconnect;
+
+    # If we drop and didn't re-create, then don't reconnect
+    if ( $self->{'drop_database'} and not $self->{'create_database'} ) {
+        return;
+    }
+
+    # Likewise if we didn't get a connection before, and we're just
+    # printing, the connect below will fail
+    elsif ( $self->{'print'}
+        and not( Jifty->handle and Jifty->handle->dbh->ping ) ) {
+        return;
+    } else {
+        $self->_reinit_handle();
+    }
+}
+
+sub _connect_to_db_for_management {
+    my $handle = Jifty::Handle->new();
+
+    my $driver   = Jifty->config->framework('Database')->{'Driver'};
+
+    # Everything but the template1 database is assumed
+    my %connect_args;
+    $connect_args{'database'} = 'template1' if ( $driver eq 'Pg' );
+    $connect_args{'database'} = ''          if ( $driver eq 'mysql' );
+    $handle->connect(%connect_args);
+    return $handle;
+}
+
+sub _reinit_handle {
+    Jifty->handle( Jifty::Handle->new() );
+    Jifty->handle->connect();
+}
+
+sub __parenthesize {
+    if ( not defined $_[0] ) { return () }
+    if ( @_ == 1 )           { return $_[0] }
+    return "(" . ( join ", ", @_ ) . ")";
+}
+
+sub _classify {
+    my %dbs;
+
+    # Guess names of databases + their versions by breaking on last space,
+    # e.g., "SQL Server 7" is ("SQL Server", "7"), not ("SQL", "Server 7").
+    push @{ $dbs{ $_->[0] } }, $_->[1]
+        for map { [ split /\s+(?!.*\s)/, $_, 2 ] } @_;
+    return
+        map { join " ", $_, __parenthesize( @{ $dbs{$_} } ) } sort keys %dbs;
+}
+
+sub _check_reserved {
+    my $self  = shift;
+    my $model = shift;
+    my $log   = Log::Log4perl->get_logger("SchemaTool");
+    foreach my $col ( $model->columns ) {
+        if ( exists $_SQL_RESERVED{ lc( $col->name ) } ) {
+            $log->error(
+                      $model . ": "
+                    . $col->name
+                    . " is a reserved word in these SQL dialects: "
+                    . join( ', ',
+                    _classify( @{ $_SQL_RESERVED{ lc( $col->name ) } } ) )
+            );
+        }
+    }
+}
+
+1;
+
+__DATA__
+
+=head1 NAME
+
+Jifty::Script::Schema - Create SQL to update or create your Jifty app's tables
+
+=head1 SYNOPSIS
+
+  jifty schema --setup      Creates or updates your application's database tables
+
+ Options:
+   --print            Print SQL, rather than executing commands
+
+   --setup            Upgrade or install the database, creating it if need be
+   --create-database  Only creates the database
+   --drop-database    Drops the database
+
+   --help             brief help message
+   --man              full documentation
+
+=head1 OPTIONS
+
+=over 8
+
+=item B<--print>
+
+Rather than actually running the database create/update/drop commands,
+Prints the commands to standard output
+
+=item B<--create-database>
+
+Send a CREATE DATABASE command.  Note that B<--setup>, below, will
+automatically send a CREATE DATABASE if it needs one.  This option is
+useful if you wish to create the database without creating any tables
+in it.
+
+=item B<--drop-database>
+
+Send a DROP DATABASE command.  Use this in conjunction with B<--setup>
+to wipe and re-install the database.
+
+=item B<--setup>
+
+Actually set up your app's tables.  This creates the database if need
+be, and runs any commands needed to bring the tables up to date; these
+may include CREATE TABLE or ALTER TABLE commands.  This option is
+assumed if the database does not exist, or the database version is not
+the same as the application's version.
+
+=item B<--help>
+
+Print a brief help message and exits.
+
+=item B<--man>
+
+Prints the manual page and exits.
+
+=back
+
+=head1 DESCRIPTION
+
+Looks for all model classes of your Jifty application and generates
+SQL statements to create or update database tables for all of the
+models.  It either prints the SQL to standard output (B<--print>) or
+actually issues the C<CREATE TABLE> or C<ALTER TABLE> statements on
+Jifty's database.
+
+(Note that even if you are just displaying the SQL, you need to have
+correctly configured your Jifty database in
+I<ProjectRoot>C</etc/config.yml>, because the SQL generated may depend
+on the database type.)
+
+=head1 BUGS
+
+Due to limitations of L<DBIx::DBSchema>, this probably only works with
+PostgreSQL, MySQL and SQLite.
+
+It is possible that some of this functionality should be rolled into
+L<Jifty::DBI::SchemaGenerator>
+
+=cut

Added: jifty/branches/schema-plugins/lib/Jifty/Script/Server.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Script/Server.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,100 @@
+use warnings;
+use strict;
+
+package Jifty::Script::Server;
+use base qw/App::CLI::Command/;
+
+# XXX: if test::builder is not used, sometimes connection is not
+# properly closed, causing the client to wait for the content for a
+# 302 redirect, see t/06-signup.t, which timeouts after test 24.
+# If we load this after we load the rest of Jifty, its die handler clobbers ours.
+# HATE.  And even worse, Test::Builder clobbers our global SIG{__DIE__} handler.
+# So we work around the work around. The real answer is to figure out how Test::Builder 
+# makes our test server not hang and do just that
+my $x;
+BEGIN {$x = $SIG{__DIE__}; }
+use Test::Builder ();
+BEGIN { $SIG{__DIE__} = $x;}
+
+
+
+use File::Path ();
+
+use constant PIDFILE => 'var/jifty-server.pid';
+
+=head1 NAME
+
+Jifty::Script::Server - A standalone webserver for your Jifty application
+
+=head1 DESCRIPTION
+
+When you're getting started with Jifty, this is the server you
+want. It's lightweight and easy to work with.
+
+=head1 API
+
+=head2 options
+
+The server takes only one option, C<--port>, the port to run the
+server on.  This is overrides the port in the config file, if it is
+set there.  The default port is 8888.
+
+=cut
+
+sub options {
+    (
+     'p|port=s'   => 'port',
+     'stop'       => 'stop',
+     'sigready=s' => 'sigready',
+     'quiet'      => 'quiet',
+     'dbiprof'    => 'dbiprof',
+    )
+}
+
+=head2 run
+
+C<run> takes no arguments, but starts up a Jifty server process for
+you.
+
+=cut
+
+sub run {
+    my $self = shift;
+    Jifty->new();
+
+    if ($self->{stop}) {
+        open my $fh, '<', PIDFILE;
+        my $pid = <$fh>;
+        kill 'TERM' => $pid;
+        return;
+    }
+
+    # Purge stale mason cache data
+    my $data_dir = Jifty->config->framework('Web')->{'DataDir'};
+    my $server_class = Jifty->config->framework('Web')->{'ServerClass'} || 'Jifty::Server';
+    Jifty::Util->require($server_class);
+
+    if (-d $data_dir) {
+        File::Path::rmtree(["$data_dir/cache", "$data_dir/obj"]);
+    }
+    else {
+        File::Path::mkpath([$data_dir]);
+    }
+
+    $SIG{TERM} = sub { exit };
+    open my $fh, '>', PIDFILE or die $!;
+    print $fh $$;
+    close $fh;
+
+    Jifty->handle->dbh->{Profile} = '6/DBI::ProfileDumper'
+        if $self->{dbiprof};
+
+    $ENV{JIFTY_SERVER_SIGREADY} ||= $self->{sigready}
+        if $self->{sigready};
+    Log::Log4perl->get_logger($server_class)->less_logging(3)
+        if $self->{quiet};
+
+    $server_class->new(port => $self->{port})->run;
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Server.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Server.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,144 @@
+use warnings;
+use strict;
+
+package Jifty::Server;
+
+=head1 NAME
+
+Jifty::Server - Standalone web server for Jifty applications
+
+=head1 SYNOPSIS
+
+  use Jifty::Server;
+  my $server = Jifty::Server->new();
+  $server->run();
+
+=head1 DESCRIPTION
+
+C<Jifty::Server> is a subclass of L<HTTP::Server::Simple> which
+creates a handy standalone web server for a lightweight Jifty application.
+
+=cut
+
+
+use base qw/HTTP::Server::Simple::CGI/;
+use base qw/Jifty::Object/;
+use File::Spec;
+use Log::Log4perl;
+use HTTP::Server::Simple;
+use HTTP::Server::Simple::Recorder;
+
+=head1 METHODS
+
+=head2 new
+
+Creates a new C<Jifty::Server> object.
+
+=cut
+
+sub new {
+    my $class = shift;
+    my $self  = {};
+    bless $self, $class;
+    $self->setup_jifty(@_);
+    $self->recording_on if $ENV{'JIFTY_RECORD'};
+
+    use Hook::LexWrap;
+    wrap 'HTML::Mason::FakeApache::send_http_header', pre => sub {
+        my $r = shift;
+        my $status = $r->header_out('Status') || '200 Jifty OK';
+        print STDOUT "HTTP/1.0 $status\n";
+    };
+
+    return ($self);
+
+}
+
+=head2 setup_jifty
+
+Sets up the Jifty singleton.  This is called automatically by L</new>.
+
+=cut
+
+sub setup_jifty {
+    my $self = shift;
+    my %args = (
+                port => undef,
+                @_
+    );
+
+    Jifty->config->framework('Web')->{'Port'} = $args{port} if $args{port};
+    $self->port( Jifty->config->framework('Web')->{'Port'} || 8888 );
+}
+
+=head2 handle_request
+
+Overrives L<HTML::Server::Simple::Mason>'s handle_request method to
+make use of L<Module::Refresh> to refresh any relevant modules, as
+well as to set up the C<$JiftyWeb> global before handling the actual
+request.
+
+=cut
+
+sub handle_request {
+    my $self = shift;
+    my $cgi = shift;
+
+    Jifty->handler->handle_request( cgi  => $cgi );
+
+}
+
+
+=head2 print_banner
+
+Overrives L<HTML::Server::Simple::Mason>'s print_banner to use the
+logging framework to record the server's startup
+
+=cut
+
+sub print_banner {
+    my $self = shift;
+    $self->log->info("You can connect to your server at ", Jifty::Web->url);
+} 
+
+=head2 recorder_prefix
+
+Returns the filename prefix used if L<HTTP::Server::Simple::Recorder> support is being used.
+
+=cut
+
+sub recorder_prefix {
+    # XXX TODO FIXME get from config
+    Jifty::Util->absolute_path("log/recorded/jifty-recorded.$$")
+} 
+
+=head2 recording_on
+
+Sets this server to use L<HTTP::Server::Simple::Recorder>.
+
+(See also the C<JIFTY_RECORD> environment variable and the C<-r> switch to C<standalone_server>.)
+
+=cut
+
+sub recording_on {
+    my $class = shift;
+    our @ISA;
+    unshift @ISA, "HTTP::Server::Simple::Recorder" unless $class->isa('HTTP::Server::Simple::Recorder');
+}
+
+=head2 after_setup_listener
+
+If C<$ENV{JIFTY_SERVER_SIGREADY}> is set, send the signal to the
+parent when the server is ready for requests.
+
+=cut
+
+sub after_setup_listener {
+    my $self = shift;
+    my $sig = $ENV{JIFTY_SERVER_SIGREADY} or return;
+    kill $sig => getppid();
+}
+
+
+1;
+

Added: jifty/branches/schema-plugins/lib/Jifty/Subs.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Subs.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,172 @@
+use warnings;
+use strict;
+
+package Jifty::Subs;
+
+
+use constant new => __PACKAGE__;
+
+=head1 NAME
+
+Jifty::Subs - 
+
+=head1 SYNOPSIS
+
+ my $sid = Jifty->subs->add(
+    class       => 'Tick',
+    queries     => [{ like => '9' }],
+    mode        => 'Replace',
+    region      => "clock-time",
+    render_with => '/fragments/time',
+ );
+ Jifty->subs->cancel($sid);
+
+ my @sids = Jifty->subs->list;
+
+=head1 DESCRIPTION
+
+
+
+=cut
+
+=head2 add PARAMHASH
+
+Add a subscription for the current window or session.
+
+Takes the following parameters
+
+=over
+
+=item class
+
+What class of object shall we subscribe to notifications on
+
+=item queries
+
+An array of queries to match items of class C<class> against. The implementation of C<queries> is dependent on the type of object events are being recorded against
+
+=item mode
+
+How should the fragment sent to the client on matching events be rendered. Valid modes are C<Replace>, C<Bottom> and C<Top>
+
+=item region
+
+The moniker of the region that updates to this subscription should be rendered into
+
+=item render_with
+
+The path of the fragment used to render items matching this subscription
+
+=back
+
+=cut
+
+sub add {
+    my $class = shift;
+    my $args = {@_};
+    unless (Jifty->config->framework('PubSub')->{'Enable'}) {
+        Jifty->log->error("PubSub disabled, but $class->add called");
+        return undef;
+    }
+
+    my $id          = ($args->{window_id} || Jifty->web->session->id);
+    my $event_class = Jifty->app_class("Event", $args->{class});
+
+    my $queries = $args->{queries} || [];
+    my $channel = $event_class->encode_queries(@$queries);
+
+    # The ->modify here is calling into the callback sub{...} with
+    # the previous value of $_, that is a hashref of channels to
+    # queries associated with those channels.  The callback then
+    # massages it to add a new channel/queries mapping; the value
+    # of $_ at the end of the callback is then atomically updated
+    # into the message bus under the same key.
+    Jifty->bus->modify(
+        "$event_class-subscriptions" => sub {
+            $_->{$channel} = $queries;
+        }
+    );
+
+    # The per-window/session ($id) rendering information ("$id-render")
+    # contains a hash from subscribed channels to rendering information,
+    # including the frament, region, argument and ajax updating mode.
+    Jifty->bus->modify(
+        "$id-render" => sub {
+            $_->{$channel} = {
+                map { $_ => $args->{$_} }
+                    qw/render_with region arguments mode/
+            };
+        }
+    );
+
+    # We create/update a IPC::PubSub::Subscriber object for this $id,
+    # and have it subscribe to the channel that we're adding here.
+    Jifty->bus->modify(
+        "$id-subscriber" => sub {
+            if   ($_) { $_->subscribe($channel) }
+            else      { $_ = Jifty->bus->new_subscriber($channel) }
+        }
+    );
+
+    return "$channel!$id";
+}
+
+=head2 cancel CHANNEL_ID
+
+Cancels session or window's subscription to CHANNEL_ID
+
+=cut
+
+sub cancel {
+    my ($class, $channel_id) = @_;
+
+    unless (Jifty->config->framework('PubSub')->{'Enable'}) {
+        Jifty->log->error("PubSub disabled, but $class->add called");
+        return undef;
+    }
+
+    my ($channel, $id) = split(/!/, $channel_id, 2);
+    my ($event_class)  = split(/-/, $channel);
+
+    $id ||= Jifty->web->session->id;
+
+    Jifty->bus->modify(
+        "$event_class-subscriptions" => sub {
+            delete $_->{$channel};
+        }
+    );
+
+    Jifty->bus->modify(
+        "$id-render" => sub {
+            delete $_->{$channel};
+        }
+    );
+
+    Jifty->bus->modify(
+        "$id-subscriber" => sub {
+            if ($_) { $_->unsubscribe($channel) }
+        }
+    );
+}
+
+=head2 list [window/sessionid]
+
+Returns a lost of channel ids this session or window is subscribed to.
+
+=cut
+
+
+sub list {
+    my $self = shift;
+
+    unless (Jifty->config->framework('PubSub')->{'Enable'}) {
+        Jifty->log->error("PubSub disabled, but $self->add called");
+        return undef;
+    }
+
+    my $id   = (shift || Jifty->web->session->id);
+    my $subscribe = Jifty->bus->modify( "$id-subscriber" ) or return ();
+    return $subscribe->channels;
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Subs/Render.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Subs/Render.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,87 @@
+package Jifty::Subs::Render;
+use strict;
+use warnings;
+
+=head1 NAME
+
+Jifty::Subs::Render - 
+
+=head1 SYNOPSIS
+
+  Jifty::Subs::Render->render($id, $callback);
+
+=head1 DESCRIPTION
+
+
+
+=head2 render($id, $callback)
+
+Render all outstanding messges, and call C<$callback> with render
+mode, region name, and content.
+
+=cut
+
+sub render {
+    my ( $class, $id, $callback ) = @_;
+    my $got;
+
+    # # of fragments sent
+    my $sent = 0;
+
+    # Get the IPC::PubSub::Subscriber object and do one fetch of all new
+    # events it subscribes to, and put those into $got.
+    my $subs
+        = Jifty->bus->modify( "$id-subscriber", sub { $got = $_ ? $_->get_all : {} } );
+
+    return 0 unless %$got;
+
+    # Now we the render options for those channels (calling ->modify instead
+    # of ->fetch because we want to block if someone else is touching it;
+    # it's equivalent to ->modify("$id-render", sub { $_ }).
+    my $render = Jifty->bus->modify("$id-render");
+
+    while ( my ( $channel, $msgs ) = each(%$got) ) {
+        foreach my $rv (@$msgs) {
+
+            # XXX - We don't yet use $timestamp here.
+            my ( $timestamp, $msg ) = @$rv;
+
+
+
+            # Channel name is always App::Event::Class-MD5QUERIES
+            my $event_class = $channel;
+            $event_class =~ s/-.*//;
+
+            unless ( UNIVERSAL::can( $event_class => 'new' ) ) {
+                Jifty->log->error("Receiving unknown event $event_class from the Bus");
+                $event_class = Jifty->config->framework('ApplicationClass')."::Event";
+            }
+
+            Jifty->log->debug("Rendering $channel event $msg");
+
+            my $render_info = $render->{$channel};
+            my $region      = Jifty::Web::PageRegion->new(
+                name => $render_info->{region},
+                path => $render_info->{render_with},
+            );
+            delete Jifty->web->{'regions'}{ $region->qualified_name };
+
+            # Finally render the region.  In addition to the user-supplied arguments
+            # in $render_info, we always pass the target $region and the event object
+            # into its %ARGS.
+            my $region_content = '';
+            my $event_object   = $event_class->new($msg);
+            $region->render_as_subrequest( \$region_content,
+                {   %{ $render_info->{arguments} || {} },
+                    event => $event_object,
+                    $event_object->render_arguments,
+                }
+            );
+            $callback->( $render_info->{mode}, $region->qualified_name, $region_content);
+            $sent++;
+        }
+    }
+    return ($sent);
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Test.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Test.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,423 @@
+use warnings;
+use strict;
+
+package Jifty::Test;
+use base qw/Test::More/;
+
+use Jifty::YAML;
+use Jifty::Server;
+use Jifty::Script::Schema;
+use Email::LocalDelivery;
+use Email::Folder;
+use File::Path;
+use File::Spec;
+use File::Temp;
+
+=head1 NAME
+
+Jifty::Test - Jifty's test module
+
+=head1 SYNOPSIS
+
+    use Jifty::Test tests => 5;
+
+    ...all of Test::More's functionality...
+
+    ...any class methods defined below...
+
+=head1 DESCRIPTION
+
+Jifty::Test is a superset of Test::More.  It provides all of
+Test::More's functionality in addition to the class methods defined
+below.
+
+=head1 METHODS
+
+=head2 is_passing
+
+    my $is_passing = Jifty::Test->is_passing;
+
+Check if the test is currently in a passing state.
+
+* All tests run so far have passed
+* We have run at least one test
+* We have not run more than we planned (if we planned at all)
+
+=cut
+
+sub is_passing {
+    my $tb = Jifty::Test->builder;
+
+    my $is_failing = 0;
+    $is_failing ||= grep {not $_} $tb->summary;
+    $is_failing ||= ($tb->has_plan || '') eq 'no_plan'
+                      ? 0
+                      : $tb->expected_tests < $tb->current_test;
+
+    return !$is_failing;
+}
+
+
+=head2 is_done
+
+    my $is_done = Jifty::Test->is_done;
+
+Check if we have run all the tests we've planned.
+
+If the plan is 'no_plan' then is_done() will return true if at least
+one test has run.
+
+=cut
+
+sub is_done {
+    my $tb = Jifty::Test->builder;
+    if( ($tb->has_plan || '') eq 'no_plan' ) {
+        return $tb->current_test > 0;
+    }
+    else {
+        return $tb->expected_tests == $tb->current_test;
+    }
+}
+
+
+=begin private
+
+=head2 import_extra
+
+Called by L<Test::More>'s C<import> code when L<Jifty::Test> is first
+C<use>'d, it calls L</setup>, and asks Test::More to export its
+symbols to the namespace that C<use>'d this one.
+
+=end private
+
+=cut
+
+sub import_extra {
+    my $class = shift;
+    $class->setup;
+    Test::More->export_to_level(2);
+}
+
+=head2 setup
+
+Merges the L</test_config> into the default configuration, resets the
+database, and resets the fake "outgoing mail" folder.  This is the
+method to override if you wish to do custom setup work, such as insert
+test data into your database.
+
+=cut
+
+sub setup {
+    my $class = shift;
+
+    my $test_config = File::Temp->new( UNLINK => 0 );
+    Jifty::YAML::DumpFile($test_config, $class->test_config(Jifty::Config->new));
+    # Invoking bin/jifty and friends will now have the test config ready.
+    $ENV{'JIFTY_TEST_CONFIG'} ||= $test_config;
+    $class->builder->{test_config} = $test_config;
+    {
+        # Cache::Memcached stores things. And doesn't let them expire
+        # from the cache easily. This is fine in production, but
+        # during testing each test script needs its own namespace.  we
+        # use the pid of the current process, and save it so the keys
+        # stays the same when we fork
+      {
+          package Jifty::Record;
+          no warnings qw/redefine/;
+
+          use vars qw/$cache_key_prefix/;
+
+          $cache_key_prefix = "jifty-test-" . $$;
+        
+          sub cache_key_prefix {
+              $Jifty::Record::cache_key_prefix;
+          }
+      }
+        
+    }
+    my $root = Jifty::Util->app_root;
+
+    # Mason's disk caching sometimes causes false tests
+    rmtree([ File::Spec->canonpath("$root/var/mason") ], 0, 1);
+
+    Jifty->new( no_handle => 1 );
+
+    Log::Log4perl->get_logger("SchemaTool")->less_logging(3);
+    my $schema = Jifty::Script::Schema->new;
+    $schema->{drop_database} =
+      $schema->{create_database} =
+        $schema->{create_all_tables} = 1;
+    $schema->run;
+    Log::Log4perl->get_logger("SchemaTool")->more_logging(3);
+
+    Jifty->new();
+    $class->setup_mailbox;
+}
+
+=head2 test_config
+
+Returns a hash which overrides parts of the application's
+configuration for testing.  By default, this changes the database name
+by appending a 'test', as well as setting the port to a random port
+between 10000 and 15000.
+
+It is passed the current configuration.
+
+You can override this to provide application-specific test
+configuration, e.g:
+
+    sub test_config {
+        my $class = shift;
+        my ($config) = @_;
+        my $hash = $class->SUPER::test_config($config);
+        $hash->{framework}{LogConfig} = "etc/log-test.conf"
+    
+        return $hash;
+    }
+
+=cut
+
+sub test_config {
+    my $class = shift;
+    my ($config) = @_;
+
+    return {
+        framework => {
+            Database => {
+                Database => $config->framework('Database')->{Database} . "test",
+            },
+            Web => {
+                Port => int(rand(5000) + 10000),
+            },
+            Mailer => 'Jifty::Test',
+            MailerArgs => [],
+        }
+    };
+}
+
+=head2 make_server
+
+Creates a new L<Jifty::Server> which C<ISA> L<Jifty::TestServer> and
+returns it.
+
+=cut
+
+sub make_server {
+    my $class = shift;
+
+    # XXX: Jifty::TestServer is not a Jifty::Server, it is actually
+    # server controller that invokes bin/jifty server. kill the
+    # unshift here once we fix all the tests expecting it to be
+    # jifty::server.
+    if ($ENV{JIFTY_TESTSERVER_PROFILE} ||
+        $ENV{JIFTY_TESTSERVER_COVERAGE} ||
+        $ENV{JIFTY_TESTSERVER_DBIPROF} ||
+        $^O eq 'MSWin32') {
+        require Jifty::TestServer;
+        unshift @Jifty::Server::ISA, 'Jifty::TestServer';
+    }
+    else {
+        require Test::HTTP::Server::Simple;
+        unshift @Jifty::Server::ISA, 'Test::HTTP::Server::Simple';
+    }
+
+    Log::Log4perl->get_logger("Jifty::Server")->less_logging(3);
+    my $server = Jifty::Server->new;
+
+    return $server;
+} 
+
+
+=head2 web
+
+Like calling C<<Jifty->web>>.
+
+C<<Jifty::Test->web>> does the necessary Jifty->web initialization for
+it to be usable in a test.
+
+=cut
+
+sub web {
+    my $class = shift;
+
+    Jifty->web->request(Jifty::Request->new)   unless Jifty->web->request;
+    Jifty->web->response(Jifty::Response->new) unless Jifty->web->response;
+
+    return Jifty->web;
+}
+
+
+=head2 mailbox
+
+A mailbox used for testing mail sending.
+
+=cut
+
+sub mailbox {
+    return Jifty::Util->absolute_path("t/mailbox");
+}
+
+=head2 setup_mailbox
+
+Clears the mailbox.
+
+=cut
+
+sub setup_mailbox {
+    my $class = shift;
+
+    open my $f, ">:encoding(UTF-8)", $class->mailbox;
+    close $f;
+}
+
+=head2 teardown_mailbox
+
+Deletes the mailbox.
+
+=cut
+
+sub teardown_mailbox {
+    unlink mailbox();
+}
+
+=head2 is_available
+
+Informs L<Email::Send> that L<Jifty::Test> is always available as a mailer.
+
+=cut
+
+sub is_available { 1 }
+
+=head2 send
+
+Should not be called manually, but is
+automatically called by L<Email::Send> when using L<Jifty::Test> as a mailer.
+
+(Note that it is a class method.)
+
+=cut
+
+sub send {
+    my $class = shift;
+    my $message = shift;
+
+    Email::LocalDelivery->deliver($message->as_string, mailbox());
+}
+
+=head2 messages
+
+Returns the messages in the test mailbox, as a list of
+L<Email::Simple> objects.  You may have to use a module like
+L<Email::MIME> to parse multi-part messages stored in the mailbox.
+
+=cut
+
+sub messages {
+    return Email::Folder->new(mailbox())->messages;
+}
+
+
+=head2 test_file
+
+  my $files = Jifty::Test->test_file($file);
+
+Register $file as having been created by the test.  It will be
+cleaned up at the end of the test run I<if and only if> the test
+passes.  Otherwise it will be left alone.
+
+It returns $file so you can do this:
+
+  my $file = Jifty::Test->test_file( Jifty::Util->absolute_path("t/foo") );
+
+=cut
+
+my @Test_Files_To_Cleanup;
+sub test_file {
+    my $class = shift;
+    my $file = shift;
+
+    push @Test_Files_To_Cleanup, $file;
+
+    return $file;
+}
+
+
+=head2 test_in_isolation
+
+  my $return = Jifty::Test->test_in_isolation( sub {
+      ...your testing code...
+  });
+
+For testing testing modules so you can run testing code (which perhaps
+fail) without effecting the outer test.
+
+Saves the state of Jifty::Test's Test::Builder object and redirects
+all output to dev null before running your testing code.  It then
+restores the Test::Builder object back to its original state.
+
+    # Test that fail() returns 0
+    ok !Jifty::Test->test_in_isolation sub {
+        return fail;
+    };
+
+=cut
+
+sub test_in_isolation {
+    my $class = shift;
+    my $code  = shift;
+
+    my $tb = Jifty::Test->builder;
+
+    my $output         = $tb->output;
+    my $failure_output = $tb->failure_output;
+    my $todo_output    = $tb->todo_output;
+    my $current_test   = $tb->current_test;
+
+    $tb->output( File::Spec->devnull );
+    $tb->failure_output( File::Spec->devnull );
+    $tb->todo_output( File::Spec->devnull );
+
+    my $result = $code->();
+
+    $tb->output($output);
+    $tb->failure_output($failure_output);
+    $tb->todo_output($todo_output);
+    $tb->current_test($current_test);
+
+    return $result;
+}
+
+
+# Stick the END block in a method so we can test it.
+END { Jifty::Test->_ending }
+
+sub _ending {
+    my $Test = Jifty::Test->builder;
+    # Such a hack -- try to detect if this is a forked copy and don't
+    # do cleanup in that case.
+    return if $Test->{Original_Pid} != $$;
+
+    # If all tests passed..
+    if (Jifty::Test->is_passing && Jifty::Test->is_done) {
+        # Clean up mailbox
+        Jifty::Test->teardown_mailbox;
+
+        # Remove testing db
+        if (Jifty->handle) {
+            Jifty->handle->disconnect();
+            Log::Log4perl->get_logger("SchemaTool")->less_logging(3);
+            my $schema = Jifty::Script::Schema->new;
+            $schema->{drop_database} = 1;
+            $schema->run;
+            Log::Log4perl->get_logger("SchemaTool")->more_logging(3);
+        }
+
+        # Unlink test files
+        unlink @Test_Files_To_Cleanup;
+    }
+
+    # Unlink test file
+    unlink $Test->{test_config} if $Test->{test_config};
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Test/WWW/Mechanize.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Test/WWW/Mechanize.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,486 @@
+use strict;
+use warnings;
+
+package Jifty::Test::WWW::Mechanize;
+use base qw/Test::WWW::Mechanize/;
+
+$ENV{'http_proxy'} = ''; # Otherwise Test::WWW::Mechanize tries to go through your HTTP proxy
+
+use Jifty::YAML;
+use HTML::Lint;
+use Test::HTML::Lint qw();
+use HTTP::Cookies;
+use XML::XPath;
+use Hook::LexWrap;
+use List::Util qw(first);
+use Carp;
+
+my $Test = Test::Builder->new;
+
+# XXX TODO: We're leaving out FLUFF errors because it complains about non-standard
+# attributes such as "autocomplete" on <form> elements.  There should be a better
+# way to fix this.
+my $lint = HTML::Lint->new( only_types => [HTML::Lint::Error::STRUCTURE,
+                                           HTML::Lint::Error::HELPER] );
+
+=head1 NAME
+
+Jifty::Test::WWW::Mechanize - Subclass of L<Test::WWW::Mechanize> with
+extra Jifty features
+
+=head1 METHODS
+
+=head2 new
+
+Overrides L<Test::WWW::Mechanize>'s C<new> to automatically give the
+bot a cookie jar.
+
+=cut
+
+sub new {
+    my $class = shift;
+    my $self = $class->SUPER::new(@_);
+    $self->cookie_jar(HTTP::Cookies->new);
+    return $self;
+} 
+
+=head2 moniker_for ACTION, FIELD1 => VALUE1, FIELD2 => VALUE2
+
+Finds the moniker of the first action of type I<ACTION> whose
+"constructor" field I<FIELD1> is I<VALUE1>, and so on.
+
+   my $mon =$mech->moniker_for('BTDT::Action::UpdateTask');
+
+If there is only one action of type ACTION, be sure not to pass
+any more arguments to this method, or the method will return undef.
+
+NOTE that if you're using this in a series of different pages or forms, 
+you'll need to run it again for each new form:
+
+    $mech->fill_in_action_ok($mech->moniker_for('MyApp::Action::UpdateInfo'), 
+                             owner_id => 'someone');
+    $mech->submit_html_ok(value => 'Save');  
+
+    is($mech->action_field_value($mech->moniker_for("MyApp::Action::UpdateInfo"),
+			     'owner_id'), 
+       'someone',
+       "Owner was reassigned properly to owner 'someone'");
+
+=cut
+
+sub moniker_for {
+  my $self = shift;
+  my $action = Jifty->api->qualify(shift);
+  my %args = @_;
+
+  for my $f ($self->forms) {
+  INPUT: 
+    for my $input ($f->inputs) {
+      if ($input->type eq "hidden" and $input->name =~ /^J:A-(?:\d+-)?(.*)/ and $input->value eq $action) {
+
+        my $moniker = $1;
+
+        for my $id (keys %args) {
+          my $idfield = $f->find_input("J:A:F:F-$id-$moniker");
+          next INPUT unless $idfield and $idfield->value eq $args{$id};
+        }
+
+        return $1;
+      }
+    }
+    # if we've gotten to this point, there were no hidden fields with a moniker,
+    # possibly a form with only its continuation-marking hidden field.
+    # Fall back to a submit field with similar attributes.
+    for my $input ($f->inputs) {
+	if ($input->type eq "submit" and $input->name =~ /$action/
+	    and $input->name =~ /J:ACTIONS=([^|]+)\|/ ) {
+	  $input->name =~ /J:ACTIONS=([^|]+)\|/;
+	  my $moniker = $1;
+	  return $moniker;
+      }
+    }
+  }
+  return undef;
+}
+
+=head2 fill_in_action MONIKER, FIELD1 => VALUE1, FIELD2 => VALUE2, ...
+
+Finds the fields on the current page with the names FIELD1, FIELD2,
+etc in the MONIKER action, and fills them in.  Returns the
+L<HTML::Form> object of the form that the action is in, or undef if it
+can't find all the fields.
+
+=cut
+
+sub fill_in_action {
+    my $self = shift;
+    my $moniker = shift;
+    my %args = @_;
+
+    my $action_form = $self->action_form($moniker, keys %args);
+    return unless $action_form;
+
+    for my $arg (keys %args) {
+        my $input = $action_form->find_input("J:A:F-$arg-$moniker");
+        unless ($input) {
+            return;
+        } 
+        $input->value($args{$arg});
+    } 
+
+    return $action_form;
+}
+
+=head2 fill_in_action_ok MONIKER, FIELD1 => VALUE1, FIELD2 => VALUE2, ...
+
+Finds the fields on the current page with the names FIELD1, FIELD2,
+etc in the MONIKER action, and fills them in.  Returns the
+L<HTML::Form> object of the form that the action is in, or undef if it
+can't find all the fields.
+
+Also, passes if it finds all of the fields and fails if any of the
+fields are missing.
+
+=cut
+
+sub fill_in_action_ok {
+    my $self = shift;
+    my $moniker = shift;
+
+    my $ret = $self->fill_in_action($moniker, @_);
+    $Test->ok($ret, "Filled in action $moniker");
+} 
+
+=head2 action_form MONIKER [ARGUMENTNAMES]
+
+Returns the form (as an L<HTML::Form> object) corresponding to the
+given moniker (which also contains inputs for the given
+argumentnames), and also selects it as the current form.  Returns
+undef if it can't be found.
+
+=cut
+
+sub action_form {
+    my $self = shift;
+    my $moniker = shift;
+    my @fields = @_;
+    Carp::confess("No moniker") unless $moniker;
+
+    my $i;
+    for my $form ($self->forms) {
+        no warnings 'uninitialized';
+
+        $i++;
+        next unless first {   $_->name =~ /J:A-(?:\d+-)?$moniker/
+                           && $_->type eq "hidden" }
+                        $form->inputs;
+        next if grep {not $form->find_input("J:A:F-$_-$moniker")} @fields;
+
+        $self->form_number($i); #select it, for $mech->submit etc
+        return $form;
+    } 
+
+    # A fallback for forms that don't have any named fields except their
+    # submit button. Could stand to be refactored.
+    $i = 0;
+    for my $form ($self->forms) {
+        no warnings 'uninitialized';
+
+        $i++;
+        next unless first {   $_->name =~ /J:A-(?:\d+-)?$moniker/
+                           && $_->type eq "submit" }
+                        $form->inputs;
+        next if grep {not $form->find_input("J:A:F-$_-$moniker")} @fields;
+
+        $self->form_number($i); #select it, for $mech->submit etc
+        return $form;
+    } 
+    return;
+} 
+
+=head2 action_field_value MONIKER, FIELD
+
+Finds the fields on the current page with the names FIELD in the
+action MONIKER, and returns its value, or undef if it can't be found.
+
+=cut
+
+sub action_field_value {
+    my $self = shift;
+    my $moniker = shift;
+    my $field = shift;
+
+    my $action_form = $self->action_form($moniker, $field);
+    return unless $action_form;
+    
+    my $input = $action_form->find_input("J:A:F-$field-$moniker");
+    return unless $input;
+    return $input->value;
+}
+
+=head2 send_action CLASS ARGUMENT => VALUE, [ ... ]
+
+Sends a request to the server via the webservices API, and returns the
+L<Jifty::Result> of the action.  C<CLASS> specifies the class of the
+action, and all parameters thereafter supply argument keys and values.
+
+The URI of the page is unchanged after this; this is accomplished by
+using the "back button" after making the webservice request.
+
+=cut
+
+sub send_action {
+    my $self = shift;
+    my $class = shift;
+    my %args = @_;
+
+
+    my $uri = $self->uri->clone;
+    $uri->path("__jifty/webservices/yaml");
+
+    my $request = HTTP::Request->new(
+        POST => $uri,
+        [ 'Content-Type' => 'text/x-yaml' ],
+        Jifty::YAML::Dump(
+            {   path => $uri->path,
+                actions => {
+                    action => {
+                        moniker => 'action',
+                        class   => $class,
+                        fields  => \%args
+                    }
+                }
+            }
+        )
+    );
+    my $result = $self->request( $request );
+    my $content = eval { Jifty::YAML::Load($result->content)->{action} } || undef;
+    $self->back;
+    return $content;
+}
+
+=head2 fragment_request PATH ARGUMENT => VALUE, [ ... ]
+
+Makes a request for the fragment at PATH, using the webservices API,
+and returns the string of the result.
+
+=cut
+
+sub fragment_request {
+    my $self = shift;
+    my $path = shift;
+    my %args = @_;
+
+    my $uri = $self->uri->clone;
+    $uri->path("__jifty/webservices/xml");
+
+    my $request = HTTP::Request->new(
+        POST => $uri,
+        [ 'Content-Type' => 'text/x-yaml' ],
+        Jifty::YAML::Dump(
+            {   path => $uri->path,
+                fragments => {
+                    fragment => {
+                        name  => 'fragment',
+                        path  => $path,
+                        args  => \%args
+                    }
+                }
+            }
+        )
+    );
+    my $result = $self->request( $request );
+    use XML::Simple;
+    my $content = eval { XML::Simple::XMLin($result->content, SuppressEmpty => '')->{fragment}{content} } || '';
+    $self->back;
+    return $content;
+}
+
+
+# When it sees something like
+# http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd as a DOCTYPE, this will make
+# it open static/dtd/xhtml1-strict.dtd instead -- great for offline hacking!
+ 
+# This "require" is just to give us something to hook on to, and to prevent a
+# future require from taking effect.
+require 'XML/Parser/LWPExternEnt.pl';
+wrap 'XML::Parser::lwp_ext_ent_handler', pre => sub {
+    my $root = Jifty::Util->share_root;
+    $_[2] =~ s{ \A .+ / ([^/]+) \z }{$root/dtd/$1}xms;
+    open my $fh, '<', $_[2] or die "can't open $_[2]: $!";
+    my $content = do {local $/; <$fh>};
+    close $fh;
+    $_[-1] = $content; # override return value
+};
+wrap 'XML::Parser::lwp_ext_ent_cleanup', pre => sub {
+    $_[-1] = 1; # just return please
+};
+
+=head2 field_error_text MONIKER, FIELD
+
+Finds the error span on the current page for the name FIELD in the
+action MONIKER, and returns the text (tags stripped) from it.  (If the
+field can't be found.
+
+=cut
+
+sub field_error_text {
+    my $self = shift;
+    my $moniker = shift;
+    my $field = shift;
+
+    my $xp = XML::XPath->new( xml => $self->content );
+
+    my $id = "errors-J:A:F-$field-$moniker";
+
+    my $nodeset = $xp->findnodes(qq{//span[\@id = "$id"]});
+    return unless $nodeset->size == 1;
+    
+    # Note that $xp->getNodeText does not actually return undef for nodes that
+    # aren't found, even though it's documented to.  Thus the workaround above.
+    return $xp->getNodeText(qq{//span[\@id = "$id" ]});
+} 
+
+=head2 uri
+
+L<WWW::Mechanize> has a bug where it returns the wrong value for
+C<uri> after redirect.  This fixes that.  See
+http://rt.cpan.org/NoAuth/Bug.html?id=9059
+
+=cut
+
+sub uri { shift->response->request->uri }
+
+=head2 get_html_ok URL
+
+Calls C<get> URL, followed by testing the HTML using
+L<Test::HTML::Lint>.
+
+=cut
+
+sub get_html_ok {
+    my $self = shift;
+    $self->get(@_);
+    {
+        local $Test::Builder::Level = $Test::Builder::Level;
+        $Test::Builder::Level++;
+        Test::HTML::Lint::html_ok( $lint, $self->content, "html_ok for ".$self->uri );
+    }
+}
+
+=head2 html_ok [STRING]
+
+Tests the current C<content> using L<Test::HTML::Lint>.  If passed a string,
+tests against that instead of the current content.
+
+=cut 
+
+sub html_ok {
+    my $self    = shift;
+    my $content = shift || $self->content;
+    {
+        local $Test::Builder::Level = $Test::Builder::Level;
+        $Test::Builder::Level++;
+        Test::HTML::Lint::html_ok( $lint, $content );
+    }
+}
+
+=head2 submit_html_ok 
+
+Calls C<submit>, followed by testing the HTML using
+L<Test::HTML::Lint>.
+
+=cut
+
+sub submit_html_ok {
+    my $self = shift;
+    $self->submit(@_);
+    {
+        local $Test::Builder::Level = $Test::Builder::Level;
+        $Test::Builder::Level++;
+        Test::HTML::Lint::html_ok( $lint, $self->content );
+    }
+} 
+
+=head2 follow_link_ok 
+
+Calls C<follow_link>, followed by testing the HTML using
+L<Test::HTML::Lint>.  Warns if it cannot find the specified link (you
+should use C<ok> on C<find_link> first to check its existence).
+
+=cut
+
+sub follow_link_ok {
+    my $self = shift;
+
+    # Remove reason from end if it's there
+    pop @_ if @_ % 2;
+
+    carp("Couldn't find link") unless
+      $self->follow_link(@_);
+    {
+        local $Test::Builder::Level = $Test::Builder::Level;
+        $Test::Builder::Level++;
+        Test::HTML::Lint::html_ok( $lint, $self->content );
+    }
+} 
+
+
+=head2 session
+
+Returns the server-side L<Jifty::Web::Session> object associated with
+this Mechanize object.
+
+=cut
+
+sub session {
+    my $self = shift;
+
+    return undef unless $self->cookie_jar->as_string =~ /JIFTY_SID_\d+=([^;]+)/;
+
+    my $session = Jifty::Web::Session->new;
+    $session->load($1);
+    return $session;
+}
+
+=head2 continuation [ID]
+
+Returns the current continuation of the Mechanize object, if any.  Or,
+given an ID, returns the continuation with that ID.
+
+=cut
+
+sub continuation {
+    my $self = shift;
+
+    my $session = $self->session;
+    return undef unless $session;
+    
+    my $id = shift;
+    ($id) = $self->uri =~ /J:(?:C|CALL|RETURN)=([^&;]+)/ unless $id;
+
+    return $session->get_continuation($id);
+}
+
+=head2 current_user
+
+Returns the L<Jifty::CurrentUser> object or descendant, if any.
+
+=cut
+
+sub current_user {
+    my $self = shift;
+
+    my $session = $self->session;
+    return undef unless $session;
+
+    my $id = $session->get('user_id');
+
+    return undef unless ($id);
+
+    my $object = (Jifty->config->framework('ApplicationClass')."::CurrentUser")->new(id => $id);
+    return $object;
+}
+
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/TestServer.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/TestServer.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,88 @@
+package Jifty::TestServer;
+
+use strict;
+use warnings;
+use File::Spec;
+use Test::Builder;
+my $Tester = Test::Builder->new;
+
+my $INC = [grep { defined } map { File::Spec->rel2abs($_) } @INC ];
+my @perl = ($^X, map { "-I$_" } @$INC);
+
+=head1 NAME
+
+Jifty::TestServer - Starting and stopping jifty server for tests
+
+=head1 DESCRIPTION
+
+=head1 METHOD
+
+=head2 started_ok
+
+Like started_ok in C<Test::HTTP::Server::Simple>, start the server and
+return the URL.
+
+=cut
+
+sub started_ok {
+    my $self = shift;
+    my $text = shift;
+    $text = 'started server' unless defined $text;
+
+    if ($^O eq 'MSWin32') {
+        # dirty hack until Test::Builder->skip_rest comes true
+
+        my $why = "live test doesn't work on Win32 at the moment";
+
+        $Tester->skip($why);
+
+        unless ($Tester->{No_Plan}) {
+            for (my $ct = $Tester->{Curr_Test};
+                    $ct < $Tester->{Expected_Tests};
+                    $ct++
+            ) {
+                $Tester->skip($why); # skip rest of the test
+            }
+        }
+        exit(0);
+    }
+
+    if (my $pid = fork()) {
+        # We are expecting a USR1 from the child Jifty::Server
+        # after it's ready to listen.
+        $SIG{USR1} = sub { };
+        sleep 15;
+        $self->{started} = 1;
+        $Tester->ok(1, $text);
+        # XXX: pull from jifty::config maybe
+        return "http://localhost:".$self->port;
+    }
+
+    require POSIX;
+    if ( $^O !~ /MSWin32/ ) {
+        POSIX::setsid()
+            or die "Can't start a new session: $!";
+    }
+
+    my @extra;
+    if (my $profile_file = $ENV{JIFTY_TESTSERVER_PROFILE}) {
+        push @extra, '-d:DProf', '-MClass::Accessor::Named';
+        $ENV{"PERL_DPROF_OUT_FILE_NAME"} = $profile_file;
+    }
+    if (my $coverage = $ENV{JIFTY_TESTSERVER_COVERAGE}) {
+        push @extra, '-MDevel::Cover'.($coverage =~ m/,/ ? "=$coverage" : '');
+    }
+
+    exec(@perl, @extra, '-MJifty::Util', '-MJifty::Script',
+         '-e', 'Jifty::Script->dispatch', 'server', '--quiet',
+         '--sigready', 'USR1',
+         $ENV{JIFTY_TESTSERVER_DBIPROF} ? ('--dbiprof') : (),
+         );
+}
+
+sub DESTROY {
+    return unless $_[0]->{started};
+    exec(@perl, 'bin/jifty', 'server', '--stop');
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Upgrade.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Upgrade.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,139 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Jifty::Upgrade
+
+=head1 DESCRIPTION
+
+C<Jifty::Upgrade> is an abstract baseclass to use to customize schema
+and data upgrades that happen.
+
+=cut
+
+package Jifty::Upgrade;
+
+use base qw/Jifty::Object Exporter/;
+use vars qw/%UPGRADES @EXPORT/;
+ at EXPORT = qw/since rename/;
+
+=head2 since I<VERSION> I<SUB>
+
+C<since> is meant to be called by subclasses of C<Jifty::Upgrade>.
+Calling it signifies that I<SUB> should be run when upgrading to
+version I<VERSION>, after tables and columns are added, but before
+tables and columns are removed.  If multiple subroutines are given for
+the same version, they are run in order that they were set up.
+
+=cut
+
+sub since {
+    my ( $version, $sub ) = @_;
+    my $package = (caller)[0];
+    if ( exists $UPGRADES{$package}{$version} ) {
+        $UPGRADES{$package}{$version} =
+          sub { $UPGRADES{$package}{$version}->(); $sub->(); }
+    }
+    else {
+        $UPGRADES{$package}{$version} = $sub;
+    }
+}
+
+=head2 versions
+
+Returns the list of versions that have been registered; this is called
+by the L<Jifty::Script::Schema> tool to determine what to do while
+upgrading.
+
+=cut
+
+sub versions {
+    my $class = shift;
+    return sort keys %{ $UPGRADES{$class} || {} };
+}
+
+=head2 upgrade_to I<VERSION>
+
+Runs the subroutine that has been registered for the given version; if
+no subroutine was registered, returns a no-op subroutine.
+
+=cut
+
+sub upgrade_to {
+    my $class   = shift;
+    my $version = shift;
+    return $UPGRADES{$class}{$version} || sub { };
+}
+
+=head2 rename table => CLASS, [column => COLUMN,] to => NAME
+
+Used in upgrade subroutines, this executes the necessary SQL to rename
+the table, or column in the table, to a new name.
+
+=cut
+
+sub rename {
+    my (%args) = @_;
+
+    $args{table} ||= $args{in};
+    die "Must provide a table to rename" unless $args{table};
+
+    Jifty::Util->require( $args{table} );
+    my $table_name = $args{table}->table;
+
+    if ( $args{column} ) {
+        my $driver = Jifty->config->framework('Database')->{'Driver'};
+        if ( $driver =~ /SQLite/ ) {
+
+            # Convert columns
+            my ($schema) = Jifty->handle->fetch_result("SELECT sql FROM sqlite_master WHERE tbl_name = '$table_name' AND type = 'table'");
+
+            $schema =~ s/(.*create\s+table\s+)\S+(.*?\(\s*)//i;
+
+            my $new_table_name    = join( '_', $table_name, 'new', $$ );
+            my $new_create_clause = "$1$new_table_name$2";
+
+            my @column_info = ( split /,/, $schema );
+            my @column_names = map { /^\s*(\S+)/ ? $1 : () } @column_info;
+
+            s/^(\s*)\Q$args{column}\E/$1$args{to}/i for @column_info;
+
+            my $new_schema = $new_create_clause . join( ',', @column_info );
+            my $copy_columns = join(
+                ', ',
+                map {
+                    ( lc($_) eq lc( $args{column} ) )
+                      ? "$_ AS $args{to}"
+                      : $_
+                  } @column_names
+            );
+
+            # Convert indices
+            my $indice_sth = Jifty->handle->simple_query("SELECT sql FROM sqlite_master WHERE tbl_name = '$table_name' AND type = 'index'");
+            my @indice_sql;
+            while ( my ($index) = $indice_sth->fetchrow_array ) {
+                $index =~ s/^(.*\(.*)\b\Q$args{column}\E\b/$1$args{to}/i;
+                push @indice_sql, $index;
+            }
+            $indice_sth->finish;
+
+            # Run the conversion SQLs
+            Jifty->handle->begin_transaction;
+            Jifty->handle->simple_query($new_schema);
+            Jifty->handle->simple_query("INSERT INTO $new_table_name SELECT $copy_columns FROM $table_name");
+            Jifty->handle->simple_query("DROP TABLE $table_name");
+            Jifty->handle->simple_query("ALTER TABLE $new_table_name RENAME TO $table_name");
+            Jifty->handle->simple_query($_) for @indice_sql;
+            Jifty->handle->commit;
+        }
+        else {
+            Jifty->handle->simple_query("ALTER TABLE $table_name RENAME $args{column} TO $args{to}");
+        }
+    }
+    else {
+        Jifty->handle->simple_query("ALTER TABLE $table_name RENAME TO $args{to}");
+    }
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Upgrade/Internal.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Upgrade/Internal.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,59 @@
+use strict;
+use warnings;
+
+package Jifty::Upgrade::Internal;
+use Jifty::Upgrade;
+use base qw/Jifty::Upgrade/;
+use Jifty::Model::Metadata;
+
+# XXX TODO: there's probably a cleaner way to cope than "INITIAL_VERSION", perhaps using ->VERSIONS
+our $INITIAL_VERSION;
+
+=head2 Version 0.60427
+
+Version metadata, previously stored in _db_version, get migrated to
+_jifty_metadata, so it could be used to store more than one row
+usefully.
+
+=cut
+
+since '0.61025' => sub {
+    Jifty::Util->require('IPC::PubSub');
+    IPC::PubSub->new(
+        JiftyDBI => (
+            db_config    => Jifty->handle->{db_config},
+            table_prefix => '_jifty_pubsub_',
+            db_init      => 1,
+        )
+    );
+};
+
+since '0.60427' => sub {
+    my @v = Jifty->handle->fetch_result("SELECT major, minor, rev FROM _db_version");
+    Jifty->handle->simple_query("DROP TABLE _db_version");
+    Jifty::Model::Metadata->store( application_db_version => version->new(join'.', at v));
+    # Since we broke things before 0.60504 but fixed them shortly thereafter,
+    # Make sure the user doesn't run the 060504 upgrade unless they must
+    $INITIAL_VERSION = '0.60427';
+};
+
+since '0.60504' => sub { 
+    return if ($INITIAL_VERSION == '0.60427');
+    rename column => 'key', in => 'Jifty::Model::Session',  to => 'data_key';
+    rename column => 'key', in => 'Jifty::Model::Metadata', to => 'data_key';
+};
+
+since '0.61210' => sub { 
+    Jifty::Util->require('IPC::PubSub');
+    IPC::PubSub->new(
+        JiftyDBI => (
+            db_config    => Jifty->handle->{db_config},
+            table_prefix => '_jifty_pubsub_',
+            db_init      => 0,
+        )
+    );
+
+    rename column => 'key', in => 'IPC::PubSub::Cache::JiftyDBI::Stash::Item', to => 'data_key';
+};
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Util.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Util.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,286 @@
+use warnings;
+use strict;
+
+package Jifty::Util;
+
+=head1 NAME
+
+Jifty::Util - Things that don't fit anywhere else
+
+=head1 DESCRIPTION
+
+
+=cut
+
+use Jifty ();
+use File::Spec ();
+use Cwd ();
+
+use vars qw/%ABSOLUTE_PATH $JIFTY_ROOT $SHARE_ROOT $APP_ROOT/;
+
+
+=head2 absolute_path PATH
+
+C<absolute_path> converts PATH into an absolute path, relative to the
+application's root (as determined by L</app_root>)  This can be called
+as an object or class method.
+
+=cut
+
+sub absolute_path {
+    my $self = shift;
+    my $path = shift || '';
+
+
+    return $ABSOLUTE_PATH{$path} if (exists $ABSOLUTE_PATH{$path});
+    $path = $self->canonicalize_path($path);
+    return $ABSOLUTE_PATH{$path} = File::Spec->rel2abs($path , Jifty::Util->app_root);
+} 
+
+
+=head2 canonicalize_path PATH
+
+Takes a "path" style /foo/bar/baz and returns a canonicalized (but not necessarily absolute)
+version of the path.  Always use C</> as the separator, even on platforms which recognizes
+both C</> and C<\> as valid separators in PATH.
+
+=cut 
+
+sub canonicalize_path {
+    my $self = shift;
+    my $path = shift;
+
+    my @path = File::Spec->splitdir($path);
+
+    my @newpath;
+
+    for (@path)  {
+        # If we have an empty part and it's not the root, skip it.
+        if ( @newpath and ($_ =~ /^(?:\.|)$/)) {
+            next;
+        }
+        elsif( $_ ne '..')  {
+        push @newpath, $_ ;
+    } else {
+        pop @newpath;
+    }
+
+    }
+
+    
+    return File::Spec::Unix->catdir(@newpath);
+
+
+}
+
+
+=head2 jifty_root
+
+Returns the root directory that Jifty has been installed into.
+Uses %INC to figure out where Jifty.pm is.
+
+=cut
+
+sub jifty_root {
+    my $self = shift;
+    unless ($JIFTY_ROOT) {
+        my ($vol,$dir,$file) = File::Spec->splitpath($INC{"Jifty.pm"});
+        $JIFTY_ROOT = File::Spec->rel2abs($dir);   
+    }
+    return ($JIFTY_ROOT);
+}
+
+
+=head2 share_root
+
+Returns the 'share' directory of the installed Jifty module.  This is
+currently only used to store the common Mason components.
+
+=cut
+
+sub share_root {
+    my $self = shift;
+
+    
+    Jifty::Util->require('File::ShareDir');
+    $SHARE_ROOT ||=  eval { File::Spec->rel2abs( File::ShareDir::module_dir('Jifty') )};
+    if (not $SHARE_ROOT or not -d $SHARE_ROOT) {
+        # XXX TODO: This is a bloody hack
+        # Module::Install::ShareDir and File::ShareDir don't play nicely
+        # together
+        my @root = File::Spec->splitdir($self->jifty_root); # lib
+        pop @root; # Jifty-version
+        $SHARE_ROOT = File::Spec->catdir(@root,"share");
+    }
+    return ($SHARE_ROOT);
+}
+
+=head2 app_root
+
+Returns the application's root path.  This is done by searching upward
+from the current directory, looking for a directory which contains a
+C<bin/jifty>.  Failing that, it searches upward from wherever the
+executable was found.
+
+It C<die>s if it can only find C</usr> or C</usr/local> which fit
+these criteria.
+
+=cut
+
+sub app_root {
+    my $self = shift;
+
+
+    return $APP_ROOT if ($APP_ROOT);
+    
+    my @roots;
+
+    push( @roots, Cwd::cwd() );
+
+    eval { Jifty::Util->require('FindBin') };
+    if ( my $err = $@ ) {
+        #warn $@;
+    } else {
+        push @roots, $FindBin::Bin;
+    }
+
+    Jifty::Util->require('ExtUtils::MM') if $^O =~ /(?:MSWin32|cygwin|os2)/;
+    Jifty::Util->require('Config');
+    for (@roots) {
+        my @root = File::Spec->splitdir($_);
+        while (@root) {
+            my $try = File::Spec->catdir( @root, "bin", "jifty" );
+            if (# XXX: Just a quick hack
+                # MSWin32's 'maybe_command' sees only file extension.
+                # Maybe we should check 'jifty.bat' instead on Win32,
+                # if it is (or would be) provided.
+                # Also, /usr/bin or /usr/local/bin should be taken from
+                # %Config{bin} or %Config{scriptdir} or something like that
+                # for portablility.
+                # Note that to compare files in Win32 we have to ignore the case
+                (-e $try or (($^O =~ /(?:MSWin32|cygwin|os2)/) and MM->maybe_command($try)))
+                and lc($try) ne lc(File::Spec->catdir($Config::Config{bin}, "jifty"))
+                and lc($try) ne lc(File::Spec->catdir($Config::Config{scriptdir}, "jifty")) )
+            {
+                #warn "root: ", File::Spec->catdir(@root);
+                #warn "bin/jifty: ", File::Spec->catdir($Config::Config{bin}, "jifty");
+                #warn "scriptdir/jifty: ", File::Spec->catdir($Config::Config{scriptdir}, "jifty");
+                #warn "try: $try";
+                return $APP_ROOT = File::Spec->catdir(@root);
+            }
+            pop @root;
+        }
+    }
+    warn "Can't guess application root from current path ("
+        . Cwd::cwd()
+        . ") or bin path ($FindBin::Bin)\n";
+    return ''; # returning undef causes tons of 'uninitialized...' warnings.
+}
+
+=head2 default_app_name
+
+Returns the default name of the application.  This is the name of the
+application's root directory, as defined by L</app_root>.
+
+=cut
+
+sub default_app_name {
+    my $self = shift;
+    my @root = File::Spec->splitdir( Jifty::Util->app_root);
+    my $name =  pop @root;
+
+    # Jifty-0.10211 should become Jifty
+    $name = $1 if $name =~ /^(.*?)-(.*\..*)$/;
+
+    # But don't actually allow "Jifty" as the name
+    $name = "JiftyApp" if lc $name eq "jifty";
+
+    return $name;
+}
+
+=head2 make_path PATH
+
+When handed a directory, creates that directory, starting as far up the 
+chain as necessary. (This is what 'mkdir -p' does in your shell)
+
+=cut
+
+sub make_path {
+    my $self = shift;
+    my $whole_path = shift;
+    return 1 if (-d $whole_path);
+    Jifty::Util->require('File::Path');
+    File::Path::mkpath([$whole_path]);
+}
+
+=head2 require PATH
+
+Uses L<UNIVERSAL::require> to require the provided C<PATH>.
+Additionally, logs any failures at the C<error> log level.
+
+=cut
+
+sub require {
+    my $self = shift;
+    my $class = shift;
+
+    # Quick hack to silence warnings.
+    # Maybe some dependencies were lost.
+    unless ($class) {
+        Jifty->log->error(sprintf("no class was given at %s line %d\n", (caller)[1,2]));
+        return 0;
+    }
+
+    return 1 if $self->already_required($class);
+
+    my $retval = $class->require;
+    if ($UNIVERSAL::require::ERROR) {
+        my $error = $UNIVERSAL::require::ERROR;
+        $error =~ s/ at .*?\n$//;
+        Jifty->log->error(sprintf("$error at %s line %d\n", (caller)[1,2]));
+        return 0;
+    }
+
+    # If people forget the '1;' line in the dispatcher, don't eit them
+    if ($class =~ /::Dispatcher$/ and ref $retval eq "ARRAY") {
+        Jifty->log->error("$class did not return a true value; assuming it was a dispatcher rule");
+        Jifty::Dispatcher::_push_rule($class, $_) for @{$retval};
+    }
+
+    return 1;
+}
+
+=head2 already_required class
+
+Helper function to test whether a given class has already been require'd.
+
+=cut
+
+
+sub already_required {
+    my ($self, $class) = @_;
+    my $path =  join('/', split(/::/,$class)).".pm";
+    return ( $INC{$path} ? 1 : 0);
+}
+
+=head2 generate_uuid
+
+Generate a new UUID using B<Data::UUID>.
+
+=cut
+
+my $Data_UUID_instance;
+sub generate_uuid {
+    ($Data_UUID_instance ||= do {
+        require Data::UUID;
+        Data::UUID->new;
+    })->create_str;
+}
+
+=head1 AUTHOR
+
+Various folks at Best Practical Solutions, LLC.
+
+=cut
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/View/Mason/Handler.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/View/Mason/Handler.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,265 @@
+use strict;
+use warnings;
+
+package Jifty::View::Mason::Handler;
+
+=head1 NAME
+
+Jifty::View::Mason::Handler - Handler for Mason requests inside of Jifty
+
+=head1 SUMMARY
+
+Jifty controls all of the input and output from the Mason templating
+engine; this means that we cannot use the Mason's standard
+L<HTML::Mason::CGIHandler> interface to interact with it.
+
+=cut
+
+use HTML::Mason;
+use HTML::Mason::Utils;
+use Params::Validate qw(:all);
+use HTML::Mason::Exceptions;
+use HTML::Mason::FakeApache;
+use Encode qw();
+
+use Class::Container;
+use base qw(Class::Container);
+
+use HTML::Mason::MethodMaker
+    ( read_write => [ qw( interp ) ] );
+
+use vars qw($VERSION);
+
+__PACKAGE__->valid_params
+    (
+     interp => { isa => 'HTML::Mason::Interp' },
+    );
+
+__PACKAGE__->contained_objects
+    (
+     interp => 'HTML::Mason::Interp',
+    );
+
+
+=head2 new PARAMHASH
+
+Takes a number of key-value parameters; see L<HTML::Mason::Params>.
+Defaults the C<out_method> to L</out_method>, and the C<request_class>
+to L<HTML::Mason::Request::Jifty> (below).  Finally, adds C<h> and
+C<u> escapes, which map to L</escape_uri> and L<escape_utf8>
+respectively.
+
+=cut
+
+sub new {
+    my $package = shift;
+
+    my %p = @_;
+    my $self = $package->SUPER::new( request_class => 'HTML::Mason::Request::Jifty',
+                                     out_method => \&out_method,
+                                     %p );
+    $self->interp->compiler->add_allowed_globals('$r');
+    $self->interp->set_escape( h => \&escape_utf8 );
+    $self->interp->set_escape( u => \&escape_uri );
+
+    return $self;
+}
+
+
+=head2 out_method
+
+The default output method.  Sets the content-type to C<text/html;
+charset=utf-8> unless a content type has already been set, and then
+sends a header if need be.
+
+=cut
+
+sub out_method {
+    my $m = HTML::Mason::Request->instance;
+    my $r = Jifty->handler->apache;
+
+    $r->content_type || $r->content_type('text/html; charset=utf-8'); # Set up a default
+
+    if ($r->content_type =~ /charset=([\w-]+)$/ ) {
+        my $enc = $1;
+	if (lc($enc) =~ /utf-?8/) {
+            binmode *STDOUT, ":utf8";
+	}
+	else {
+            binmode *STDOUT, ":encoding($enc)";
+	}
+    }
+
+    unless ($r->http_header_sent or not $m->auto_send_headers) {
+        $r->send_http_header();
+    }
+
+    # We could perhaps install a new, faster out_method here that
+    # wouldn't have to keep checking whether headers have been
+    # sent and what the $r->method is.  That would require
+    # additions to the Request interface, though.
+    print STDOUT grep {defined} @_;
+}
+
+
+=head2 escape_utf8 SCALARREF
+
+Does a css-busting but minimalist escaping of whatever html you're passing in.
+
+=cut
+
+sub escape_utf8 {
+    my $ref = shift;
+    my $val = $$ref;
+    use bytes;
+    no warnings 'uninitialized';
+    $val =~ s/&/&#38;/g;
+    $val =~ s/</&lt;/g;
+    $val =~ s/>/&gt;/g;
+    $val =~ s/\(/&#40;/g;
+    $val =~ s/\)/&#41;/g;
+    $val =~ s/"/&#34;/g;
+    $val =~ s/'/&#39;/g;
+    $$ref = $val;
+    Encode::_utf8_on($$ref);
+}
+
+
+=head2 escape_uri SCALARREF
+
+Escapes URI component according to RFC2396
+
+=cut
+
+sub escape_uri {
+    my $ref = shift;
+    $$ref = Encode::encode_utf8($$ref);
+    $$ref =~ s/([^a-zA-Z0-9_.!~*'()-])/uc sprintf("%%%02X", ord($1))/eg;
+    Encode::_utf8_on($$ref);
+}
+
+
+=head2 handle_comp COMPONENT
+
+Takes a component path to render.  Deals with setting up a global
+L<HTML::Mason::FakeApache> and Request object, and calling the
+component.
+
+=cut
+
+sub handle_comp {
+    my ($self, $comp) = (shift, shift);
+
+    # Set up the global
+    my $r = Jifty->handler->apache;
+    $self->interp->set_global('$r', $r);
+
+    my %args = $self->request_args($r);
+
+    my @result;
+    if (wantarray) {
+        @result = eval { $self->interp->exec($comp, %args) };
+    } elsif ( defined wantarray ) {
+        $result[0] = eval { $self->interp->exec($comp, %args) };
+    } else {
+        eval { $self->interp->exec($comp, %args) };
+    }
+
+    if (my $err = $@) {
+        my $retval = isa_mason_exception($err, 'Abort')   ? $err->aborted_value  :
+                     isa_mason_exception($err, 'Decline') ? $err->declined_value :
+                     rethrow_exception $err;
+
+        # Unlike under mod_perl, we cannot simply return a 301 or 302
+        # status and let Apache send headers, we need to explicitly
+        # send this header ourself.
+        $r->send_http_header if $retval && grep { $retval eq $_ } ( 200, 301, 302 );
+
+        return $retval;
+    }
+
+    return wantarray ? @result : defined wantarray ? $result[0] : undef;
+}
+
+=head2 request_args
+
+The official source for request arguments is from the current
+L<Jifty::Request> object.
+
+=cut
+
+sub request_args {
+    return %{Jifty->web->request->arguments};
+}
+
+###########################################################
+package HTML::Mason::Request::Jifty;
+# Subclass for HTML::Mason::Request object $m
+
+=head1 HTML::Mason::Request::Jifty
+
+Subclass of L<HTML::Mason::Request> which is customised for Jifty's use.
+
+=cut
+
+use HTML::Mason::Exceptions;
+use HTML::Mason::Request;
+use base qw(HTML::Mason::Request);
+
+=head2 auto_send_headers
+
+Doesn't send headers if this is a subrequest (according to the current
+L<Jifty::Request>).
+
+=cut
+
+sub auto_send_headers {
+    return not Jifty->web->request->is_subrequest;
+}
+
+=head2 exec
+
+Actually runs the component; in case no headers have been sent after
+running the component, and we're supposed to send headers, sends them.
+
+=cut
+
+sub exec
+{
+    my $self = shift;
+    my $r = Jifty->handler->apache;
+    my $retval;
+
+    eval { $retval = $self->SUPER::exec(@_) };
+
+    if (my $err = $@)
+    {
+	$retval = isa_mason_exception($err, 'Abort')   ? $err->aborted_value  :
+                  isa_mason_exception($err, 'Decline') ? $err->declined_value :
+                  rethrow_exception $err;
+    }
+
+    # On a success code, send headers if they have not been sent and
+    # if we are the top-level request. Since the out_method sends
+    # headers, this will typically only apply after $m->abort.
+    if ($self->auto_send_headers
+        and not $r->http_header_sent
+        and (!$retval or $retval==200)) {
+        $r->send_http_header();
+    }
+}
+
+=head2 redirect
+
+Calls L<Jifty::Web/redirect>.
+
+=cut
+
+sub redirect {
+    my $self = shift;
+    my $url = shift;
+
+    Jifty->web->redirect($url);
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/View/Static/Handler.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/View/Static/Handler.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,244 @@
+use warnings;
+use strict;
+use File::MMagic ();
+use MIME::Types ();
+use Compress::Zlib ();
+use HTTP::Date ();
+
+
+package Jifty::View::Static::Handler;
+
+use base qw/Jifty::Object/;
+
+our ($MIME,$MAGIC);
+
+=head1 NAME
+
+Jifty::View::Static::Handler
+
+head1 DESCRIPTION
+
+This class takes care of serving out static files for a Jifty application. 
+
+When fully operational, it will use an algorithm along the lines of the following:
+
+* Static files are served out of a separate root
+* If static files go through apache:
+    * How do we merge together the two static roots?
+* If static files go through Jifty::Handler
+    * We need a flag to allow them to go through the dispatcher, too
+    * return "True" (304) for if-modified-since
+    * if the browser accepts gzipped data,
+        see if we have a cached gzipped copy
+            if so, send it
+        see if we have a marker indicating that gzip is a lose
+            if so, send uncompressed
+
+        gzip the content
+        send the gzipped content
+     * if the browser doesn't accept gzipped content
+        send the content uncompressed
+
+
+=cut
+
+
+=head2 new
+
+Create a new static file handler. Likely, only the C<Jifty::Handler> needs to do this.
+
+=cut
+sub new {
+    my $class = shift;
+    my $self = {};
+    bless $self, $class;
+}
+
+
+=head2 handle_request $path
+
+Handle a request for C<$path>. If we can't find a static file of that name, return undef.
+
+
+=cut
+
+sub handle_request {
+    my $self = shift;
+    my $path = shift;
+
+    my $local_path = $self->file_path($path) or return undef;
+
+    if ( my $since = Jifty->handler->cgi->http('If-Modified-Since') ) {
+        my @file_info = stat($local_path);
+
+        # IE appends "; length=N" to If-Modified-Since headers and we need
+        # to get rid of it so str2time doesn't choke below
+        $since =~ s/;.+$//;
+
+        return $self->send_not_modified
+            unless $file_info[9] > HTTP::Date::str2time($since);
+    }
+    my $mime_type = $self->mime_type($local_path);
+
+    if ( $self->client_accepts_gzipped_content and $mime_type =~ m!^(text/|application/x-javascript)! ) {
+        return $self->send_file($local_path, $mime_type, 'gzip');
+    } else {
+        return $self->send_file($local_path, $mime_type, 'uncompressed');
+    }
+
+}
+
+
+=head2 client_accepts_gzipped_content
+
+Returns true if it looks like the client accepts gzip encoding. Otherwise, returns false.
+
+
+=cut
+
+
+sub client_accepts_gzipped_content {
+    my $self = shift;
+    no warnings 'uninitialized';
+    return Jifty->handler->cgi->http('Accept-Encoding') =~ /\bgzip\b/;
+}
+
+
+=head2 file_path $path
+
+Returns the system path for C<$path>, searching inside the
+application's static root, loaded plugins' static roots, and finally
+Jifty's static root.  Returns undef if it can't find the file in any
+path.
+
+=cut
+
+sub file_path {
+    my $self    = shift;
+    my $file    = shift;
+    my @options = (Jifty->config->framework('Web')->{StaticRoot});
+    push @options, grep {$_} map {$_->static_root} Jifty->plugins;
+    push @options, (Jifty->config->framework('Web')->{DefaultStaticRoot});
+
+    # Chomp a leading "/static" - should this be configurable?
+    $file =~ s/^\/*?static//; 
+
+    foreach my $path (@options) {
+        my $abspath = Jifty::Util->absolute_path( File::Spec->catdir($path,$file ));
+        # If the user is trying to request something outside our static root, 
+        # decline the request
+        my $abs_base_path = Jifty::Util->absolute_path( $path );
+        unless ($abspath =~ /^\Q$abs_base_path\E/) {
+            return undef;
+        }
+        return $abspath if ( -f $abspath && -r $abspath );
+    }
+    return undef;
+
+}
+
+=head2 mime_type $path
+
+Returns the mime type of the file whose path on disk is C<$path>. Tries to use
+L<MIME::Types> to guess first. If that fails, it falls back to C<File::MMagic>.
+
+=cut
+
+sub mime_type {
+    my $self       = shift;
+    my $local_path = shift;
+
+    # The key is the file extension, the value is the MIME type to send.
+    my %type_override = (
+        # MIME::Types returns application/javascript for .js, but Opera
+        # chokes on ajax-fetched JS that has a type other than the one below
+        # JSAN.js fetches JS via Ajax when it loads JSAN modules
+        'js' => 'application/x-javascript',
+        'htc' => 'text/x-component',
+    );
+
+    return ($type_override{$1})
+        if $local_path =~ /^.*\.(.+?)$/ and defined $type_override{$1};
+
+    # Defer initialization to first use. (It's not actually cheap)
+    $MIME ||= MIME::Types->new();
+    $MAGIC ||= File::MMagic->new();
+    my $mimeobj   = $MIME->mimeTypeOf($local_path);
+    my $mime_type = ( $mimeobj ? $mimeobj->type : $MAGIC->checktype_filename($local_path));
+
+    return ($mime_type);
+}
+
+
+=head2 send_file $path $mimetype $compression
+
+Print C<$path> to STDOUT (the client), identified with a mimetype of C<$mimetype>.
+
+If C<$compression> is C<gzip>, gzip the output stream.
+
+
+=cut
+
+
+sub send_file {
+    my $self       = shift;
+    my $local_path = shift;
+    my $mime_type  = shift;
+    my $compression = shift;
+
+
+    my $fh = IO::File->new( $local_path, 'r' );
+    if ( defined $fh ) {
+        binmode $fh;
+
+        # This is designed to work under CGI or FastCGI; will need an
+        # abstraction for mod_perl
+
+        # Clear out the mason output, if any
+        Jifty->web->mason->clear_buffer if Jifty->web->mason;
+
+        my @file_info = stat($local_path);
+        my $apache = Jifty->handler->apache;
+
+        $apache->header_out( Status => 200 );
+        $apache->content_type($mime_type);
+        my $now = time();
+        $apache->header_out(Expires =>  HTTP::Date::time2str($now + 31536000));  # Expire in a year
+        $apache->header_out('Last-Modified' =>  HTTP::Date::time2str( $file_info[9]));
+        $apache->header_out('Content-Length' => $file_info[7]) unless ($compression eq 'gzip');  
+
+        $apache->header_out( "Content-Encoding" => "gzip") if ($compression eq 'gzip');
+        $apache->send_http_header();
+
+        if ($compression eq 'gzip') {
+        undef $/;
+        binmode STDOUT;
+        # XXX TODO: Cache this
+        print STDOUT Compress::Zlib::memGzip(<$fh>);
+        } else{
+            $apache->send_fd($fh);
+        }
+        close($fh);
+        return 1;
+    } else {
+        return undef;
+    }
+}
+
+
+=head2 send_not_modified
+
+Sends a "304 Not modified" response to the browser, telling it to use a cached copy.
+
+=cut
+
+sub send_not_modified {
+    my $self = shift;
+    my $apache = Jifty->handler->apache;
+    $apache->header_out( Status => 304 );
+    $apache->send_http_header();
+    return 1;
+
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Web.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Web.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,1270 @@
+use warnings;
+use strict;
+
+package Jifty::Web;
+
+=head1 NAME
+
+Jifty::Web - Web framework for a Jifty application
+
+=cut
+
+
+
+
+use CGI::Cookie;
+use XML::Writer;
+use CSS::Squish;
+use Digest::MD5 qw(md5_hex);
+use Carp qw(carp);
+use base qw/Class::Accessor::Fast Class::Data::Inheritable Jifty::Object/;
+
+use vars qw/$SERIAL @JS_INCLUDES/;
+
+__PACKAGE__->mk_accessors(
+    qw(next_page force_redirect request response session temporary_current_user _current_user _state_variables)
+);
+
+__PACKAGE__->mk_classdata($_)
+    for qw(cached_css        cached_css_digest
+           cached_javascript cached_javascript_digest javascript_libs);
+
+__PACKAGE__->javascript_libs([qw(
+    jsan/JSAN.js
+    jsan/Push.js
+    setup_jsan.js
+    jsan/Upgrade/Array/push.js
+    jsan/DOM/Events.js
+    json.js
+    prototype.js
+    cssquery/cssQuery.js
+    cssquery/cssQuery-level2.js
+    cssquery/cssQuery-level3.js
+    cssquery/cssQuery-standard.js
+    behaviour.js
+    scriptaculous/builder.js
+    scriptaculous/effects.js
+    scriptaculous/controls.js
+    formatDate.js
+    jifty.js
+    jifty_utils.js
+    jifty_subs.js
+    jifty_smoothscroll.js
+    calendar.js
+    dom-drag.js
+    halo.js
+    combobox.js
+    key_bindings.js
+    context_menu.js
+    bps_util.js
+    rico.js
+    yui/yahoo.js
+    yui/dom.js
+    yui/event.js
+    yui/calendar.js
+    yui/tabview.js
+    yui/container.js
+    app.js
+    app_behaviour.js
+    css_browser_selector.js
+)]);
+
+=head1 METHODS
+
+=head3 new
+
+Creates a new C<Jifty::Web> object
+
+=cut
+
+sub new {
+    my $class = shift;
+    my $self = bless {region_stack => []}, $class;
+    $self->session(Jifty::Web::Session->new());
+    $self->clear_state_variables;
+    return ($self);
+}
+
+=head3 mason
+
+Returns a L<HTML::Mason::Request> object
+
+=cut
+
+sub mason {
+    use HTML::Mason::Request;
+    return HTML::Mason::Request->instance;
+}
+
+
+=head3 out
+
+Send a string to the browser. The default implementation uses Mason->out;
+
+=cut
+
+sub out {
+    Carp::cluck unless defined $_[0]->mason;
+    shift->mason->out(@_);
+}
+
+
+=head3 url
+
+Returns the root url of this Jifty application.  This is pulled from
+the configuration file.  Takes an optional named path which will
+form the path part of the resulting URL.
+
+=cut
+
+sub url {
+    my $self = shift;
+    my %args = (scheme => undef,
+                path => undef,
+                @_);
+
+    if ($args{'scheme'}) {
+        $self->log->error("Jifty->web->url no longer accepts a 'scheme' argument");
+    }
+
+    my $uri;
+
+    # Try to get a host out of the environment, useful in remote testing.
+    # The code is a little hairy because there's no guarantee these
+    # environment variables have all the information.
+    if (my $http_host_env = $ENV{HTTP_HOST}) {
+        # Explicit flag needed because URI->new("noscheme") is structurally
+        # different from URI->new("http://smth"). Clunky, but works.
+        my $dirty;
+        if ($http_host_env !~ m{^https?://}) {
+            $dirty++;
+            $http_host_env = "http://" . $http_host_env;
+        }
+        $uri = URI->new($http_host_env);
+        if ($dirty && (my $req_uri_env = $ENV{REQUEST_URI})) {
+            my $req_uri = URI->new($req_uri_env);
+            $uri->scheme($req_uri->scheme) if $req_uri->can('scheme');
+            $dirty = $uri->scheme;
+        }
+        # As a last resort, peek at the BaseURL configuration setting
+        # for the scheme, which is an often-missing part.
+        if ($dirty) {
+            my $config_uri = URI->new(
+                    Jifty->config->framework("Web")->{BaseURL});
+            $uri->scheme($config_uri->scheme);
+        }
+    }
+
+    if (!$uri) {
+      my $url  = Jifty->config->framework("Web")->{BaseURL};
+      my $port = Jifty->config->framework("Web")->{Port};
+   
+      $uri = URI->new($url);
+      $uri->port($port);
+    }
+
+    if (defined $args{path}) {
+      my $path = $args{path};
+      # strip off leading '/' because ->canonical provides one
+      $path =~ s{^/}{};
+      $uri->path_query($path);
+    }
+    
+    return $uri->canonical->as_string;
+}
+
+=head3 serial 
+
+Returns a unique identifier, guaranteed to be unique within the
+runtime of a particular process (ie, within the lifetime of Jifty.pm).
+There's no sort of global uniqueness guarantee, but it should be good
+enough for generating things like moniker names.
+
+=cut
+
+sub serial {
+    my $class = shift;
+
+    # We don't use a lexical for the serial number, because then it
+    # would be reset on module refresh
+    $SERIAL ||= 0;
+    return join('', "S", ++$SERIAL, $$ );    # Start at 1.
+}
+
+=head2 SESSION MANAGEMENT
+
+=head3 setup_session
+
+Sets up the current C<session> object (a L<Jifty::Web::Session> tied
+hash).  Aborts if the session is already loaded.
+
+=cut
+
+# Create the Jifty::Web::Session object
+sub setup_session {
+    my $self = shift;
+
+    return if $self->session->loaded;
+    $self->session->load();
+}
+
+=head3 session
+
+Returns the current session's hash. In a regular user environment, it
+persists, but a request can stop that by handing it a regular hash to
+use.
+
+
+=head2 CURRENT USER
+
+=head3 current_user [USER]
+
+Getter/setter for the current user; this gets or sets the 'user' key
+in the session.  These are L<Jifty::Record> objects.
+
+If a temporary_current_user has been set, will return that instead.
+
+If the current application has no loaded current user, we get an empty
+app-specific C<CurrentUser> object. (This
+C<ApplicationClass>::CurrentUser class, a subclass of
+L<Jifty::CurrentUser>, is autogenerated if it doesn't exist).
+
+=cut
+
+sub current_user {
+    my $self = shift;
+    if (@_) {
+        my $currentuser_obj = shift;
+        $self->session->set(
+            'user_id' => $currentuser_obj ? $currentuser_obj->id : undef );
+        $self->_current_user( $currentuser_obj || undef );
+    }
+
+    if ( defined $self->temporary_current_user ) {
+        return $self->temporary_current_user;
+    } elsif ( defined $self->_current_user ) {
+        return $self->_current_user;
+
+    } elsif ( my $id = $self->session->get('user_id') ) {
+        my $object = Jifty->app_class("CurrentUser")->new( id => $id );
+        $self->_current_user($object);
+        return $object;
+    } else {
+        my $object = Jifty->app_class("CurrentUser")->new;
+        $object->is_superuser(1) if Jifty->config->framework('AdminMode');
+        $self->_current_user($object);
+        return ($object);
+    }
+}
+
+=head3 temporary_current_user [USER]
+
+Sets the current request's current_user to USER if set.
+
+This value will _not_ be persisted to the session at the end of the
+request.  To restore the original value, set temporary_current_user to
+undef.
+
+=cut
+
+=head2 REQUEST
+
+=head3 handle_request [REQUEST]
+
+This method sets up a current session, and then processes the given
+L<Jifty::Request> object.  If no request object is given, processes
+the request object in L</request>.
+
+Each action on the request is vetted in three ways -- first, it must
+be marked as C<active> by the L<Jifty::Request> (this is the default).
+Second, it must be in the set of allowed classes of actions (see
+L<Jifty::API/is_allowed>).  Finally, the action must validate.  If it
+passes all of these criteria, the action is fit to be run.
+
+Before they are run, however, the request has a chance to be
+interrupted and saved away into a continuation, to be resumed at some
+later point.  This is handled by L<Jifty::Request/save_continuation>.
+
+If the continuation isn't being saved, then C<handle_request> goes on
+to run all of the actions.  If all of the actions are successful, it
+looks to see if the request wished to call any continuations, possibly
+jumping back and re-running a request that was interrupted in the
+past.  This is handled by L<Jifty::Request/call_continuation>.
+
+For more details about continuations, see L<Jifty::Continuation>.
+
+=cut
+
+sub handle_request {
+    my $self = shift;
+    die _( "No request to handle" ) unless Jifty->web->request;
+
+    my @valid_actions;
+    for my $request_action ( $self->request->actions ) {
+        $self->log->debug("Found action ".$request_action->class . " " . $request_action->moniker);
+        next unless $request_action->active;
+        next if $request_action->has_run;
+        unless ( Jifty->api->is_allowed( $request_action->class ) ) {
+            $self->log->warn( "Attempt to call denied action '"
+                    . $request_action->class
+                    . "'" );
+            next;
+        }
+
+        # Make sure we can instantiate the action
+        my $action = $self->new_action_from_request($request_action);
+        next unless $action;
+        $request_action->modified(0);
+
+        # Try validating -- note that this is just the first pass; as
+        # actions are run, they may fill in values which alter
+        # validation of later actions
+        $self->log->debug("Validating action ".ref($action). " ".$action->moniker);
+        $self->response->result( $action->moniker => $action->result );
+        $action->validate;
+
+        push @valid_actions, $request_action;
+    }
+    $self->request->save_continuation;
+
+    unless ( $self->request->just_validating ) {
+        for my $request_action (@valid_actions) {
+
+            # Pull the action out of the request (again, since
+            # mappings may have affected parameters).  This
+            # returns the cached version unless the request has
+            # been changed by argument mapping from previous
+            # actions (Jifty::Request::Mapper)
+            my $action = $self->new_action_from_request($request_action);
+            next unless $action;
+            if ($request_action->modified) {
+                # If the request's action was changed, re-validate
+                $action->result(Jifty::Result->new);
+                $action->result->action_class(ref $action);
+                $self->response->result( $action->moniker => $action->result );
+                $self->log->debug("Re-validating action ".ref($action). " ".$action->moniker);
+                next unless $action->validate;
+            }
+
+            $self->log->debug("Running action ".ref($action). " ".$action->moniker);
+            eval { $action->run; };
+            $request_action->has_run(1);
+
+            if ( my $err = $@ ) {
+                # Poor man's exception propagation; we need to get
+                # "LAST RULE" and "ABORT" exceptions back up to the
+                # dispatcher.  This is specifically for redirects from
+                # actions
+                die $err if ( $err =~ /^(LAST RULE|ABORT)/ );
+                $self->log->fatal($err);
+                $action->result->error(
+                    Jifty->config->framework("DevelMode")
+                    ? $err
+                    : _("There was an error completing the request.  Please try again later.")
+                );
+            }
+
+            # Fill in the request with any results that that action
+            # may have yielded.
+            $self->request->do_mapping;
+        }
+    }
+
+    # If there's a continuation call, don't do the rest of this
+    return if $self->response->success and $self->request->call_continuation;
+
+    $self->redirect if $self->redirect_required;
+    $self->request->do_mapping;
+}
+
+=head3 request [VALUE]
+
+Gets or sets the current L<Jifty::Request> object.
+
+=head3 response [VALUE]
+
+Gets or sets the current L<Jifty::Response> object.
+
+=head3 form
+
+Returns the current L<Jifty::Web::Form> object, creating one if there
+isn't one already.
+
+=cut
+
+sub form {
+    my $self = shift;
+
+    $self->{form} ||= Jifty::Web::Form->new;
+    return $self->{form};
+}
+
+=head3 new_action class => CLASS, moniker => MONIKER, order => ORDER, arguments => PARAMHASH
+
+Creates a new action (an instance of a subclass of L<Jifty::Action>)
+
+C<CLASS> is L<qualified|Jifty::API/qualify>, and an instance of that
+class is created, passing the C<Jifty::Web> object, the C<MONIKER>,
+and any other arguments that C<new_action> was supplied.
+
+C<MONIKER> is a unique designator of an action on a page.  The moniker
+is content-free and non-fattening, and may be auto-generated.  It is
+used to tie together arguments that relate to the same action.
+
+C<ORDER> defines the order in which the action is run, with lower
+numerical values running first.
+
+C<ARGUMENTS> are passed to the L<Jifty::Action/new> method.  In
+addition, if the current request (C<< $self->request >>) contains an
+action with a matching moniker, any arguments that are in that
+requested action but not in the C<PARAMHASH> list are set.  This
+implements "sticky fields".
+
+As a contrast to L<Jifty::Web::Form/add_action>, this does not add the
+action to the current form -- instead, the first form field to be
+rendered will automatically register the action in the current form
+field at that time.
+
+=cut
+
+sub new_action {
+    my $self = shift;
+
+    my %args = (
+        class     => undef,
+        moniker   => undef,
+        arguments => {},
+        current_user => $self->current_user,
+        @_
+    );
+
+
+    # "Untaint" -- the implementation class is provided by the client!)
+    # Allows anything that a normal package name allows
+    my $class = delete $args{class};
+    unless ( $class =~ /^([0-9a-zA-Z_:]+)$/ ) {
+        $self->log->error( "Bad action implementation class name: ", $class );
+        return;
+    }
+    $class = $1;    # 'untaint'
+
+    # Prepend the base path (probably "App::Action") unless it's there already
+    $class = Jifty->api->qualify($class);
+
+    my $loaded = Jifty::Util->require( $class );
+    $args{moniker} ||= ($loaded ? $class : 'Jifty::Action')->_generate_moniker;
+
+    my $action_in_request = $self->request->action( $args{moniker} );
+
+    # Fields explicitly passed to new_action take precedence over those passed
+    # from the request; we read from the request to implement "sticky fields".
+    #
+    if ( $action_in_request and $action_in_request->arguments ) {
+        $args{'request_arguments'} = $action_in_request->arguments;
+    }
+
+    # The implementation class is provided by the client, so this
+    # isn't a "shouldn't happen"
+    return unless $loaded;
+
+    my $action;
+    # XXX TODO bullet proof
+    eval { $action = $class->new(%args) };
+    if ($@) {
+        my $err = $@;
+        $self->log->fatal($err);
+        return;
+    }
+
+    $self->{'actions'}{ $action->moniker } = $action;
+
+    return $action;
+}
+
+=head3 new_action_from_request REQUESTACTION
+
+Given a L<Jifty::Request::Action>, creates a new action using C<new_action>.
+
+=cut
+
+sub new_action_from_request {
+    my $self       = shift;
+    my $req_action = shift;
+    return $self->{'actions'}{ $req_action->moniker } if 
+      $self->{'actions'}{ $req_action->moniker } and not $req_action->modified;
+    $self->new_action(
+        class     => $req_action->class,
+        moniker   => $req_action->moniker,
+        order     => $req_action->order,
+        arguments => $req_action->arguments || {}
+    );
+}
+
+=head3 failed_actions
+
+Returns an array of L<Jifty::Action> objects, one for each
+L<Jifty::Request::Action> that is marked as failed in the current
+response.
+
+=cut
+
+sub failed_actions {
+    my $self = shift;
+    my @actions;
+    for my $req_action ($self->request->actions) {
+        next unless $self->response->result($req_action->moniker);
+        next unless $self->response->result($req_action->moniker)->failure;
+        push @actions, $self->new_action_from_request($req_action);
+    }
+    return @actions;
+}
+
+=head3 succeeded_actions
+
+As L</failed_actions>, but for actions that completed successfully;
+less often used.
+
+=cut
+
+sub succeeded_actions {
+    my $self = shift;
+    my @actions;
+    for my $req_action ($self->request->actions) {
+        next unless $self->response->result($req_action->moniker);
+        next unless $self->response->result($req_action->moniker)->success;
+        push @actions, $self->new_action_from_request($req_action);
+    }
+    return @actions;
+}
+
+=head2 REDIRECTS AND CONTINUATIONS
+
+=head3 next_page [VALUE]
+
+Gets or sets the next page for the framework to show.  This is
+normally set during the C<take_action> method or a L<Jifty::Action>
+
+=head3 force_redirect [VALUE]
+
+Gets or sets whether we should force a redirect to C<next_page>, even
+if it's already the current page. You might set this, e.g. to force a
+redirect after a POSTed action.
+
+=head3 redirect_required
+
+Returns true if we need to redirect, now that we've processed all the
+actions. We need a redirect if either C<next_page> is different from
+the current page, or C<force_redirect> has been set.
+
+=cut
+
+sub redirect_required {
+    my $self = shift;
+
+    return ( 1 ) if $self->force_redirect;
+
+    if (!$self->request->is_subrequest
+        and $self->next_page
+        and ( ( $self->next_page ne $self->request->path )
+              or $self->request->state_variables
+              or $self->state_variables )
+       )
+    {
+        return (1);
+
+    } else {
+        return undef;
+    }
+}
+
+=head3 redirect [TO]
+
+Redirect to the next page. If you pass this method a parameter, it
+redirects to that URL rather than B<next_page>.
+
+It creates a continuation of where you want to be, and then calls it.
+If you want to redirect to a parge with parameters, pass in a
+L<Jifty::Web::Form::Clickable> object.
+
+=cut
+
+sub redirect {
+    my $self = shift;
+    my $page = shift || $self->next_page || $self->request->path;
+    $page = Jifty::Web::Form::Clickable->new( url => $page )
+      unless ref $page and $page->isa("Jifty::Web::Form::Clickable");
+
+    carp "Don't include GET parameters in the redirect URL -- use a Jifty::Web::Form::Clickable instead.  See L<Jifty::Web/redirect>" if $page->url =~ /\?/;
+
+    my %overrides = ( @_ );
+    $page->parameter($_ => $overrides{$_}) for keys %overrides;
+
+    my @actions = Jifty->web->request->actions;
+
+    # To submit a Jifty::Action::Redirect, we don't need to serialize a continuation,
+    # unlike any other kind of actions.
+    if (  (grep { not $_->action_class->isa('Jifty::Action::Redirect') }
+                values %{ { $self->response->results } })
+        or $self->request->state_variables
+        or $self->state_variables
+        or $self->request->continuation
+        or grep { $_->active and not $_->class->isa('Jifty::Action::Redirect') } @actions )
+    {
+        my $request = Jifty::Request->new();
+        $request->add_state_variable( key => $_->key, value => $_->value )
+          for $self->request->state_variables;
+        $request->add_state_variable( key => $_, value => $self->_state_variables->{$_} )
+          for keys %{ $self->_state_variables };
+        for (@actions) {
+            my $new_action = $request->add_action(
+                moniker   => $_->moniker,
+                class     => $_->class,
+                order     => $_->order,
+                active    => $_->active && (not $_->has_run),
+                has_run   => $_->has_run,
+                arguments => $_->arguments,
+            );
+            # Clear out filehandles, which don't go thorugh continuations well
+            $new_action->arguments->{$_} = ''
+              for grep {ref $new_action->arguments->{$_} eq "Fh"}
+                keys %{$new_action->arguments || {}};
+        }
+        my %parameters = ($page->parameters);
+        $request->argument($_ => $parameters{$_}) for keys %parameters;
+        $request->path($page->url);
+
+        $request->continuation($self->request->continuation);
+        my $cont = Jifty::Continuation->new(
+            request  => $request,
+            response => $self->response,
+            parent   => $self->request->continuation,
+        );
+        $page = $page->url."?J:RETURN=" . $cont->id;
+    } else {
+        $page = $page->complete_url;
+    }
+    $self->_redirect($page);
+}
+
+sub _redirect {
+    my $self = shift;
+    my ($page) = @_;
+
+    # $page can't lead with // or it assumes it's a URI scheme.
+    $page =~ s{^/+}{/};
+
+    # This is designed to work under CGI or FastCGI; will need an
+    # abstraction for mod_perl
+
+    # Clear out the mason output, if any
+    $self->mason->clear_buffer if $self->mason;
+
+    my $apache = Jifty->handler->apache;
+
+    $self->log->debug("Redirecting to $page");
+    # Headers..
+    $apache->header_out( Location => $page );
+    $apache->header_out( Status => 302 );
+    $apache->send_http_header();
+
+    # Mason abort, or dispatcher abort out of here
+    $self->mason->abort if $self->mason;
+    Jifty::Dispatcher::_abort;
+}
+
+=head3 caller
+
+Returns the L<Jifty::Request> of our enclosing continuation, or an
+empty L<Jifty::Request> if we are not in a continuation.
+
+=cut
+
+sub caller {
+    my $self = shift;
+
+    return Jifty::Request->new unless $self->request->continuation;
+    return $self->request->continuation->request;
+}
+
+=head2 HTML GENERATION
+
+=head3 tangent PARAMHASH
+
+If called in non-void context, creates and renders a
+L<Jifty::Web::Form::Clickable> with the given I<PARAMHASH>, forcing a
+continuation save.
+
+In void context, does a redirect to the URL that the
+L<Jifty::Web::Form::Clickable> object generates.
+
+Both of these versions preserve all state variables by default.
+
+=cut
+
+sub tangent {
+    my $self = shift;
+
+    my $clickable = Jifty::Web::Form::Clickable->new(
+        returns        => { },
+        preserve_state => 1,
+        @_
+    );
+    if ( defined wantarray ) {
+        return $clickable->generate;
+    } else {
+        my $request = Jifty->web->request->clone;
+        my %clickable = $clickable->get_parameters;
+        $request->argument($_ => $clickable{$_}) for keys %clickable;
+        local Jifty->web->{request} = $request;
+        Jifty->web->request->save_continuation;
+    }
+}
+
+=head3 goto PARAMHASH
+
+Does an instant redirect to the url generated by the
+L<Jifty::Web::Form::Clickable> object generated by the I<PARAMHASH>.
+
+=cut
+
+sub goto {
+    my $self = shift;
+    Jifty->web->redirect(
+        Jifty::Web::Form::Clickable->new(@_));
+}
+
+=head3 link PARAMHASH
+
+Generates and renders a L<Jifty::Web::Form::Clickable> using the given
+I<PARAMHASH>.
+
+=cut
+
+sub link {
+    my $self = shift;
+    return Jifty::Web::Form::Clickable->new(@_)->generate;
+}
+
+=head3 return PARAMHASH
+
+If called in non-void context, creates and renders a
+L<Jifty::Web::Form::Clickable> using the given I<PARAMHASH>,
+additionally defaults to calling the current continuation.
+
+Takes an additional argument, C<to>, which can specify a default path
+to return to if there is no current continuation.
+
+In void context, does a redirect to the URL that the
+L<Jifty::Web::Form::Clickable> object generates.
+
+=cut
+
+sub return {
+    my $self = shift;
+    my %args = (@_);
+    my $continuation = Jifty->web->request->continuation;
+    if (not $continuation and $args{to}) {
+        $continuation = Jifty::Continuation->new(
+            request => Jifty::Request->new(path => $args{to})
+        );
+    }
+    delete $args{to};
+
+    my $clickable = Jifty::Web::Form::Clickable->new(
+        call => $continuation, %args
+    );
+
+    if ( defined wantarray ) {
+        return $clickable->generate;
+    }
+    else {
+        $self->redirect($clickable);
+    }
+}
+
+=head3 render_messages [MONIKER]
+
+Outputs any messages that have been added, in <div id="messages"> and
+<div id="errors"> tags.  Messages are added by calling
+L<Jifty::Result/message>.
+
+If a moniker is specified, only messages for that moniker 
+are rendered.
+
+
+=cut
+
+sub render_messages {
+    my $self = shift;
+    my $only_moniker = '';
+    $only_moniker = shift if (@_);
+
+    $self->render_error_messages($only_moniker);
+    $self->render_success_messages($only_moniker);
+
+    return '';
+}
+
+=head3 render_success_messages [MONIKER]
+
+Render success messages for the given moniker, or all of them if no
+moniker is given.
+
+=cut
+
+sub render_success_messages {
+    my $self = shift;
+    my $moniker = shift;
+
+    $self->_render_messages('message', $moniker);
+
+    return '';
+}
+
+=head3 render_error_messages [MONIKER]
+
+Render error messages for the given moniker, or all of them if no
+moniker is given.
+
+=cut
+
+sub render_error_messages {
+    my $self = shift;
+    my $moniker = shift;
+
+    $self->_render_messages('error', $moniker);
+
+    return '';
+}
+
+=head3 _render_messages TYPE [MONIKER]
+
+Output any messages of the given TYPE (either 'error' or 'message') in
+a <div id="TYPEs"> tag. If a moniker is given, only renders messages
+for that action. Internal helper for L</render_success_messages> and
+L</render_errors>.
+
+=cut
+
+sub _render_messages {
+    my $self = shift;
+    my $type = shift;
+    my $only_moniker = shift || '';
+
+    my %results = $self->response->results;
+
+    %results = ($only_moniker => $results{$only_moniker}) if $only_moniker;
+
+    return unless grep {$_->$type()} values %results;
+    
+    my $plural = $type . "s";
+    $self->out(qq{<div class="jifty results messages" id="$plural">});
+    
+    foreach my $moniker ( sort keys %results ) {
+        if ( $results{$moniker}->$type() ) {
+            $self->out( qq{<div class="$type $moniker">}
+                        . $results{$moniker}->$type()
+                        . qq{</div>} );
+        }
+    }
+    $self->out(qq{</div>});
+}
+
+=head3 query_string KEY => VALUE [, KEY => VALUE [, ...]]
+
+Returns an URL-encoded query string piece representing the arguments
+passed to it.
+
+=cut
+
+sub query_string {
+    my $self = shift;
+    my %args = @_;
+    my @params;
+    while ( ( my $key, my $value ) = each %args ) {
+        push @params,
+            $key . "=" . $self->escape_uri( $value );
+    }
+    return ( join( ';', @params ) );
+}
+
+=head3 escape STRING
+
+HTML-escapes the given string and returns it
+
+=cut
+
+sub escape {
+    no warnings 'uninitialized';
+    my $self = shift;
+    return join '', map {my $html = $_; Jifty::View::Mason::Handler::escape_utf8( \$html ); $html} @_;
+}
+
+=head3 escape_uri STRING
+
+URI-escapes the given string and returns it
+
+=cut
+
+sub escape_uri {
+    no warnings 'uninitialized';
+    my $self = shift;
+    return join '', map {my $uri = $_; Jifty::View::Mason::Handler::escape_uri( \$uri ); $uri} @_;
+}
+
+=head3 navigation
+
+Returns the L<Jifty::Web::Menu> for this web request; one is
+automatically created if it hasn't been already.
+
+=cut
+
+sub navigation {
+    my $self = shift;
+    if (!$self->{navigation}) {
+        $self->{navigation} = Jifty::Web::Menu->new();
+    }
+    return $self->{navigation};
+}
+
+=head3 page_navigation
+
+Returns the L<Jifty::Web::Menu> for this web request; one is
+automatically created if it hasn't been already.  This is useful
+for separating page-level navigation from app-level navigation.
+
+=cut
+
+sub page_navigation {
+    my $self = shift;
+    if (!$self->{page_navigation}) {
+        $self->{page_navigation} = Jifty::Web::Menu->new();
+    }
+    return $self->{page_navigation};
+}
+
+=head3 include_css
+
+Returns a C<< <link> >> tag for the compressed CSS
+
+=cut
+
+sub include_css {
+    my $self = shift;
+    
+    if ( Jifty->config->framework('DevelMode') ) {
+        $self->out(
+            '<link rel="stylesheet" type="text/css" '
+            . 'href="/static/css/main.css" />'
+        );
+    }
+    else {
+        $self->generate_css;
+    
+        $self->out(
+            '<link rel="stylesheet" type="text/css" href="/__jifty/css/'
+            . __PACKAGE__->cached_css_digest . '.css" />'
+        );
+    }
+    
+    return '';
+}
+
+=head3 generate_css
+
+Checks if the compressed CSS is generated, and if it isn't, generates
+and caches it.
+
+=cut
+
+sub generate_css {
+    my $self = shift;
+    
+    if (not defined __PACKAGE__->cached_css_digest
+            or Jifty->config->framework('DevelMode'))
+    {
+        Jifty->log->debug("Generating CSS...");
+        
+        my $app   = File::Spec->catdir(
+                        Jifty->config->framework('Web')->{'StaticRoot'},
+                        'css'
+                    );
+
+        my $jifty = File::Spec->catdir(
+                        Jifty->config->framework('Web')->{'DefaultStaticRoot'},
+                        'css'
+                    );
+
+        my $file = Jifty::Util->absolute_path(
+                        File::Spec->catpath( '', $app, 'main.css' )
+                   );
+
+        if ( not -e $file ) {
+            $file = Jifty::Util->absolute_path(
+                         File::Spec->catpath( '', $jifty, 'main.css' )
+                    );
+        }
+
+        CSS::Squish->roots( Jifty::Util->absolute_path( $app ), $jifty );
+        
+        my $css = CSS::Squish->concatenate( $file );
+
+        __PACKAGE__->cached_css( $css );
+        __PACKAGE__->cached_css_digest( md5_hex( $css ) );
+    }
+}
+
+=head3 include_javascript
+
+Returns a C<< <script> >> tag for the compressed Javascript.
+
+Your application specific javascript goes in
+F<share/web/static/js/app.js>.  This will be automagically included if
+it exists.
+
+If you want to add javascript behaviour to your page using CSS
+selectors then put your behaviour rules in
+F<share/web/static/js/app_behaviour.js> which will also be
+automagically included if it exists.  The C<behaviour.js> library is
+included by Jifty.  For more information on C<behaviour.js> see
+L<http://bennolan.com/behaviour/>.
+
+However if you want to include other javascript libraries you need to
+add them to the javascript_libs array of your application.  Do this in
+the C<start> sub of your main application class.  For example if your application is Foo then in L<lib/Foo.pm>
+
+ sub start {
+   Jifty->web->javascript_libs([
+ 			       @{ Jifty->web->javascript_libs },
+ 			       "yourJavascriptLib.js",
+ 			      ]);
+ }
+
+Jifty will look for javascript libraries under share/web/static/js/ by
+default.
+
+=cut
+
+sub include_javascript {
+    my $self  = shift;
+    
+    if ( Jifty->config->framework('DevelMode') ) {
+        for my $file ( @{ __PACKAGE__->javascript_libs } ) {
+            $self->out(
+                qq[<script type="text/javascript" src="/static/js/$file"></script>\n]
+            );
+        }
+    }
+    else {
+        $self->generate_javascript;
+    
+        $self->out(
+            qq[<script type="text/javascript" src="/__jifty/js/]
+            . __PACKAGE__->cached_javascript_digest . qq[.js"></script>]
+        );
+    }
+    
+    return '';
+}
+
+=head3 generate_javascript
+
+Checks if the compressed JS is generated, and if it isn't, generates
+and caches it.
+
+=cut
+
+sub generate_javascript {
+    my $self = shift;
+    
+    if (not defined __PACKAGE__->cached_javascript_digest
+            or Jifty->config->framework('DevelMode'))
+    {
+        Jifty->log->debug("Generating JS...");
+        
+        my @roots = (
+            Jifty::Util->absolute_path(
+                File::Spec->catdir(
+                    Jifty->config->framework('Web')->{'StaticRoot'},
+                    'js'
+                )
+            ),
+
+            Jifty::Util->absolute_path(
+                File::Spec->catdir(
+                    Jifty->config->framework('Web')->{'DefaultStaticRoot'},
+                    'js'
+                )
+            ),
+        );
+        
+        my $js = "";
+
+        for my $file ( @{ __PACKAGE__->javascript_libs } ) {
+            my $include;
+        
+            for my $root (@roots) {
+                my @spec = File::Spec->splitpath( $root, 1 );
+                my $path = File::Spec->catpath( @spec[0,1], $file );
+                
+                if ( -e $path ) {
+                    $include = $path;
+                    last;
+                }
+            }
+
+            if ( defined $include ) {
+                my $fh;
+
+                if ( open $fh, '<', $include ) {
+                    $js .= "/* Including '$file' */\n\n";
+                    $js .= $_ while <$fh>;
+                    $js .= "\n/* End of '$file' */\n\n";
+                }
+                else {
+                    $js .= "\n/* Unable to open '$file': $! */\n";
+                }
+            }
+            else {
+                $js .= "\n/* Unable to find '$file' */\n";
+            }
+        }
+
+        __PACKAGE__->cached_javascript( $js );
+        __PACKAGE__->cached_javascript_digest( md5_hex( $js ) );
+    }
+}
+
+=head2 STATE VARIABLES
+
+=head3 get_variable NAME
+
+Gets a page specific variable from the request object.
+
+=cut
+
+sub get_variable {
+    my $self = shift;
+    my $name = shift;
+    my $var  = $self->request->state_variable($name);
+    return undef unless ($var);
+    return $var->value();
+
+}
+
+=head3 set_variable NAME VALUE
+
+Takes a key-value pair for variables to serialize and hand off to the next page.
+
+Behind the scenes, these variables get serialized into every link or
+form that is marked as 'state preserving'.  See
+L<Jifty::Web::Form::Clickable>.
+
+Passing C<undef> as a value will remove the variable
+
+=cut
+
+sub set_variable {
+    my $self  = shift;
+    my $name  = shift;
+    my $value = shift;
+
+    if (!defined($value)) {
+        delete $self->_state_variables->{$name};
+    } else {
+        $self->_state_variables->{$name} = $value;
+    }
+
+}
+
+=head3 state_variables
+
+Returns all of the state variables that have been set for the next
+request, as a hash;
+
+N.B. These are B<not> prefixed with C<J:V->, as they were in earlier
+versions of Jifty
+
+=cut
+
+sub state_variables {
+    my $self = shift;
+    return %{ $self->_state_variables };
+}
+
+=head3 clear_state_variables
+
+Remove all the state variables to be serialized for the next request.
+
+=cut
+
+sub clear_state_variables {
+    my $self = shift;
+
+    $self->_state_variables({});
+}
+
+=head2 REGIONS
+
+=head3 get_region [QUALIFIED NAME]
+
+Given a fully C<QUALIFIED NAME> of a region, returns the
+L<Jifty::Web::PageRegion> with that name, or undef if no such region
+exists.
+
+=cut
+
+sub get_region {
+    my $self = shift;
+    my ($name) = @_;
+    return $self->{'regions'}{$name};
+}
+
+=head3 region PARAMHASH
+
+The provided PARAMHASH is used to create and render a
+L<Jifty::Web::PageRegion>; the C<PARAMHASH> is passed directly to its
+L<Jifty::Web::PageRegion/new> method, and then
+L<Jifty::Web::PageRegion/render> is called.
+
+=cut
+
+sub region {
+    my $self = shift;
+
+    # Create a region
+    my $region = Jifty::Web::PageRegion->new(@_) or return; 
+
+    # Render it
+    $region->render;
+}
+
+=head3 current_region
+
+Returns the name of the current L<Jifty::Web::PageRegion>, or undef if
+there is none.
+
+=cut
+
+sub current_region {
+    my $self = shift;
+    return $self->{'region_stack'}
+        ? $self->{'region_stack'}[-1]
+        : undef;
+}
+
+=head3 qualified_region [REGION]
+
+Returns the fully qualified name of the current
+L<Jifty::Web::PageRegion>, or the empty string if there is none.  If
+C<REGION> is supplied, gives the qualified name of C<REGION> were it
+placed in the current region.
+
+=cut
+
+sub qualified_region {
+    my $self = shift;
+    return join( "-", map { $_->name } @{ $self->{'region_stack'} || [] }, @_ );
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Web/Form.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Web/Form.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,329 @@
+use warnings;
+use strict;
+
+package Jifty::Web::Form;
+
+use base qw/Jifty::Object Class::Accessor::Fast/;
+
+__PACKAGE__->mk_accessors(qw(actions printed_actions name call is_open disable_autocomplete));
+
+=head2 new ARGS
+
+Creates a new L<Jifty::Web::Form>.  Arguments:
+
+=over
+
+=item name
+
+The name given to the form.  This is mostly for naming specific forms
+for testing.
+
+=item call
+
+All buttons in this form will act as continuation calls for the given
+continuation id.
+
+=item disable_autocomplete
+
+Disable B<browser> autocomplete for this form.  Jifty autocomplete will still
+work.
+
+=back
+
+=cut
+
+sub new {
+    my $class = shift;
+    my $self = bless {}, ref $class ? ref $class : $class;
+
+    my %args = (
+        name => undef,
+        call => undef,
+        disable_autocomplete => undef,
+        @_,
+    );
+
+    $self->_init(%args);
+    return $self;
+}
+
+=head2 PRIVATE _init
+
+Reinitialize this form.
+
+=over
+
+=item name
+
+The form name
+
+=item call
+
+The continuation id to call
+
+=back
+
+=cut
+
+
+sub _init {
+    my $self = shift;
+    my %args = (name => undef,
+                call => undef,
+                disable_autocomplete => undef,
+                @_);
+
+    $self->actions( {} ) ;
+    $self->printed_actions( {} ) ;
+    $self->name($args{name});
+    $self->call($args{call});
+    $self->disable_autocomplete($args{disable_autocomplete});
+}
+
+
+=head2 actions
+
+Returns a reference to a hash of L<Jifty::Action> objects in this form keyed by moniker.
+
+If you want to add actions to this form, use L</add_action>
+
+=cut
+
+=head2 name [VALUE]
+
+Gets or sets the HTML name given to the form element.
+
+=cut
+
+=head2 call [CONTID]
+
+Gets or sets the continuation ID that will be called for this form.
+
+=cut
+
+=head2 is_open [BOOL]
+
+This accessor returns true if Jifty is currently in the middle of rendering a form
+(if it's printed a <form> but not yet printed a </form> tag.) Use this in your
+components to decide whether to open a form or not if you might be called from a
+template that opened the form for you.
+
+=cut
+
+=head2 add_action PARAMHASH
+
+Calls L<Jifty::Web/new_action> with the paramhash given, and adds it to
+the form.
+
+=cut
+
+sub add_action {
+    my $self = shift;
+    $self->register_action(Jifty->web->new_action(@_));
+}
+
+
+
+=head2 register_action ACTION
+
+Adds C<ACTION> as an action for this form. Called so that actions'
+form fields can register the action against the form they're being
+used in.
+
+=cut
+
+
+sub register_action {
+    my $self = shift;
+    my $action = shift;
+    $self->actions->{ $action->moniker } =  $action;
+    return $action;
+}
+
+
+=head2 has_action MONIKER
+
+If this form has an action whose monkier is C<MONIKER>, returns
+it. Otherwise returns undef.
+
+=cut
+
+sub has_action {
+    my $self    = shift;
+    my $moniker = shift;
+    if ( exists $self->actions->{$moniker} ) {
+        return $self->actions->{$moniker};
+    }
+    else { return undef }
+
+}
+
+
+
+=head2 start
+
+Renders the opening form tag.
+
+=cut
+
+sub start {
+    my $self = shift;
+
+    my %args = (@_);
+
+    if ($self->is_open) {
+        $self->log->warn("Trying to open a form when we already have one open");
+    }
+
+    for (keys %args) {
+        if ( $self->can($_) ) {
+            $self->$_($args{$_});
+        } else {
+			my (undef, $template, $line) = caller;
+            $self->log->warn("Unknown parameter to Jifty->web->form->start: $_ in $template line $line");
+        }
+    }
+
+    my $form_start = qq!<form method="post" action="!  . Jifty->web->escape( $ENV{PATH_INFO} ) . qq!"!;
+    $form_start .= qq! name="@{[ $self->name ]}"! if defined $self->name;
+    $form_start .= qq! autocomplete="off"!  if defined $self->disable_autocomplete;
+    $form_start .= qq! enctype="multipart/form-data" >\n!;
+    Jifty->web->out($form_start);
+
+    # Write out state variables early, so that if a form gets
+    # submitted before the page finishes loading, the state vars don't
+    # get lost
+    $self->_preserve_state_variables();
+
+    $self->is_open(1);
+    '';
+}
+
+=head2 submit MESSAGE, [PARAMETERS]
+
+Renders a submit button with the text MESSAGE on it (which will be
+HTML escaped).  Returns the empty string (for ease of use in
+interpolation).  Any extra PARAMETERS are passed to
+L<Jifty::Web::Form::Field::Button>'s constructor.
+
+=cut
+
+sub submit {
+    my $self = shift;
+
+    my $button = Jifty::Web::Form::Clickable->new(submit => undef, @_)->generate;
+    Jifty->web->out(qq{<div class="submit_button">});
+    $button->render_widget;
+    Jifty->web->out(qq{</div>});
+
+    return '';
+}
+
+=head2 end
+
+Renders the closing form tag (including rendering errors for and
+registering all of the actions)  After doing this, it resets its
+internal state such that L</start> may be called again.
+
+=cut
+
+sub end {
+    my $self = shift;
+
+    unless ($self->is_open) {
+        $self->log->warn("Trying to close a form when we don't have one open");
+    }
+    Jifty->web->out( qq!<div class="hidden">\n! );
+
+    $self->_print_registered_actions();
+    $self->_preserve_continuations();
+
+    Jifty->web->out( qq!</div>\n! );
+
+    Jifty->web->out( qq!</form>\n! );
+    $self->is_open(0);
+    # Clear out all the registered actions and the name
+    $self->_init();
+
+    '';
+}
+
+
+=head2 print_action_registration MONIKER
+
+Print out the action registration goo for this action _right now_, unless we've already done so.
+
+=cut
+
+
+sub print_action_registration {
+    my $self = shift;
+    my $moniker = shift;
+
+
+    my $action = $self->has_action($moniker);
+    return unless ($action);
+    return if exists $self->printed_actions->{$moniker};
+    $self->printed_actions->{$moniker} = 1;
+
+    $action->register();
+
+}
+
+
+# At the point this is called, it should only include actions we're
+# registering that have no form fields and haven't been explicitly
+# registered.
+sub _print_registered_actions {
+    my $self = shift;
+    for my $a ( keys %{ $self->actions } ) {
+        $self->print_action_registration($a);
+    }
+}
+
+sub _preserve_state_variables {
+    my $self = shift;
+
+    my %vars = Jifty->web->state_variables;
+    for (keys %vars) {
+        Jifty->web->out( qq{<input type="hidden" name="}
+                . 'J:V-' . $_
+                . qq{" value="}
+                . $vars{$_}
+                . qq{" />\n} );
+    }
+}
+
+sub _preserve_continuations {
+    my $self = shift;
+
+    if ($self->call) {
+        Jifty->web->out( qq{<input type="hidden" name="J:CALL" value="}
+                                . (ref $self->call ? $self->call->id : $self->call)
+                                . qq{" />});
+    } elsif (Jifty->web->request->continuation) {
+        Jifty->web->out( qq{<input type="hidden" name="J:C" value="}
+                                . Jifty->web->request->continuation->id
+                                . qq{" />});
+    }
+
+}
+
+=head2 next_page PARAMHASH
+
+Set the page this form should go to on success.  This simply creates a
+L<Jifty::Action::Redirect> action; any parameters in the C<PARAMHASH>
+are passed as arguments to the L<Jifty::Action::Redirect> action.
+
+Returns an empty string so it can be included in forms
+
+=cut
+
+sub next_page {
+    my $self = shift;
+
+    $self->add_action(class => "Jifty::Action::Redirect", moniker => "next_page", arguments => {@_});
+    return '';
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Web/Form/Clickable.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Web/Form/Clickable.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,546 @@
+use warnings;
+use strict;
+
+package Jifty::Web::Form::Clickable;
+
+=head1 NAME
+
+Jifty::Web::Form::Clickable - Some item that can be clicked on --
+either a button or a link.
+
+=head1 DESCRIPTION
+
+=cut
+
+use base 'Jifty::Web::Form::Element';
+
+=head2 accessors
+
+Clickable adds C<url>, C<escape_label>, C<continuation>, C<call>,
+C<returns>, C<submit>, and C<preserve_state> to the list of accessors
+and mutators, in addition to those offered by
+L<Jifty::Web::Form::Element/accessors>.
+
+=cut
+
+sub accessors {
+    shift->SUPER::accessors,
+        qw(url escape_label tooltip continuation call returns submit target preserve_state render_as_button render_as_link);
+}
+__PACKAGE__->mk_accessors(
+    qw(url escape_label tooltip continuation call returns submit target preserve_state render_as_button render_as_link)
+);
+
+=head2 new PARAMHASH
+
+Creates a new L<Jifty::Web::Form::Clickable> object.  Depending on the
+requirements, it may render as a link or as a button.  Possible
+parameters in the I<PARAMHASH> are:
+
+=over 4
+
+=item url
+
+Sets the page that the user will end up on after they click the
+button.  Defaults to the current page.
+
+=item label
+
+The text on the clickable object.
+
+=item tooltip
+
+Additional information about the link target.
+
+=item escape_label
+
+If set to true, HTML escapes the content of the label and tooltip before
+displaying them.  This is only relevant for objects that are rendered as
+HTML links.  The default is true.
+
+=item continuation
+
+The current continuation for the link.  Defaults to the current
+continuation now, if there is one.  This may be either a
+L<Jifty::Continuation> object, or the C<id> of such.
+
+=item call
+
+The continuation to call when the link is clicked.  This will happen
+after actions have run, if any.  Like C<continuation>, this may be a
+L<Jifty::Continuation> object or the C<id> of such.
+
+=item returns
+
+Passing this parameter implies the creation of a continuation when the
+link is clicked.  It takes an anonymous hash of return location to
+where the return value is pulled from -- that is, the same structure
+the C<parameters> method takes.
+
+See L<Jifty::Request::Mapper/query_parameters> for details.
+
+=item submit
+
+A list of actions to run when the object is clicked.  This may be an
+array refrence or a single element; each element may either be a
+moniker or a L<Jifty::Action>.  An undefined value submits B<all>
+actions in the form, an empty list reference (the default) submits
+none.
+
+=item preserve_state
+
+A boolean; whether state variables are preserved across the link.
+Defaults to true if there are any AJAX actions on the link, false
+otherwise.
+
+=item parameters
+
+A hash reference of query parameters that go on the link or button.
+These will end up being submitted exactly like normal query
+parameters.
+
+=item as_button
+
+By default, Jifty will attempt to make the clickable into a link
+rather than a button, if there are no actions to run on submit.
+Providing a true value for C<as_button> forces L<generate> to produce
+a L<Jifty::Web::Form::Clickable::InlineButton> instead of a
+L<Jifty::Web::Form::Link>.
+
+=item as_link
+
+Attempt to rework a button into displaying as a link -- note that this
+only works in javascript browsers.  Supplying B<both> C<as_button> and
+C<as_link> will work, and not as perverse as it might sound at first
+-- it allows you to make any simple GET request into a POST request,
+while still appearing as a link (a GET request).
+
+=item target
+
+For things that start off as links, give them an html C<target> attribute.
+
+=cut
+
+=item Anything from L<Jifty::Web::Form::Element>
+
+Note that this includes the C<onclick> parameter, which allows
+you to attach javascript to your Clickable object, but be careful
+that your Javascript looks like C<return someFunction();>, or you may
+get an unexpected error from your browser.
+
+=back
+
+=cut
+
+sub new {
+    my $class = shift;
+    my ($root) = $ENV{'REQUEST_URI'} =~ /([^\?]*)/;
+
+    my %args = (
+        parameters     => {},
+        as_button      => 0,
+        as_link        => 0,
+        @_,
+    );
+
+    $args{render_as_button} = delete $args{as_button};
+    $args{render_as_link}   = delete $args{as_link};
+
+    my $self = $class->SUPER::new({
+        class          => '',
+        label          => 'Click me!',
+        url            => $root,
+        escape_label   => 1,
+        tooltip        => '',
+        continuation   => Jifty->web->request->continuation,
+        submit         => [],
+        preserve_state => 0,
+        parameters     => {},
+    }, \%args);
+
+    for (qw/continuation call/) {
+        $self->{$_} = $self->{$_}->id if $self->{$_} and ref $self->{$_};
+    }
+
+    if ( $self->{submit} ) {
+        $self->{submit} = [ $self->{submit} ] unless ref $self->{submit} eq "ARRAY";
+        $self->{submit}
+            = [ map { ref $_ ? $_->moniker : $_ } @{ $self->{submit} } ];
+    }
+
+    # Anything doing fragment replacement needs to preserve the
+    # current state as well
+    if ( grep { $self->$_ } $self->handlers or $self->preserve_state ) {
+        my %state_vars = Jifty->web->state_variables;
+        while ( my ($key,  $val) = each %state_vars ) {
+            if ( $key =~ /^region-(.*?)\.(.*)$/ ) {
+                $self->region_argument( $1, $2 => $val );
+            } elsif ( $key =~ /^region-(.*)$/ ) {
+                $self->region_fragment( $1, $val );
+            } else {
+                $self->state_variable( $key => $val );
+            }
+        }
+    }
+
+    $self->parameter( $_ => $args{parameters}{$_} )
+        for keys %{ $args{parameters} };
+
+    return $self;
+}
+
+=head2 url
+
+Sets the page that the user will end up on after they click the
+button.  Defaults to the current page.
+
+=head2 label
+
+The text on the clickable object.
+
+=head2 escape_label
+
+If set to true, HTML escapes the content of the label before
+displaying it.  This is only relevant for objects that are rendered as
+HTML links.  The default is true.
+
+=head2 continuation
+
+The current continuation for the link.  Defaults to the current
+continuation now, if there is one.  This may be either a
+L<Jifty::Continuation> object, or the C<id> of such.
+
+=head2 call
+
+The continuation to call when the link is clicked.  This will happen
+after actions have run, if any.  Like C<continuation>, this may be a
+L<Jifty::Continuation> object or the C<id> of such.
+
+=head2 returns
+
+Passing this parameter implies the creation of a continuation when the
+link is clicked.  It takes an anonymous hash of return location to
+where the return value is pulled from.  See L<Jifty::Request::Mapper>
+for details.
+
+=head2 submit
+
+A list of actions to run when the object is clicked.  This may be an
+array refrence or a single element; each element may either be a
+moniker or a L<Jifty::Action>.  An undefined value submits B<all>
+actions in the form, an empty list reference (the default) submits
+none.
+
+=head2 preserve_state
+
+A boolean; whether state variables are preserved across the link.
+Defaults to true if there are any AJAX actions on the link, false
+otherwise.
+
+=head2 parameter KEY VALUE
+
+Sets the given HTTP paramter named C<KEY> to the given C<VALUE>.
+
+=cut
+
+sub parameter {
+    my $self = shift;
+    my ( $key, $value ) = @_;
+    $self->{parameters}{$key} = $value;
+}
+
+=head2 state_variable KEY VALUE
+
+Sets the state variable named C<KEY> to C<VALUE>.
+
+=cut
+
+sub state_variable {
+    my $self = shift;
+    my ( $key, $value, $fallback ) = @_;
+    if ( defined $value and length $value ) {
+        $self->{state_variable}{"J:V-$key"} = $value;
+    } else {
+        delete $self->{state_variable}{"J:V-$key"};
+        $self->{fallback}{"J:V-$key"} = $fallback;
+    }
+}
+
+=head2 region_fragment NAME PATH
+
+Sets the path of the fragment named C<NAME> to be C<PATH>.
+
+=cut
+
+sub region_fragment {
+    my $self = shift;
+    my ( $region, $fragment ) = @_;
+
+    my $name = ref $region ? $region->qualified_name : $region;
+    my $defaults = Jifty->web->get_region($name);
+
+    if ( $defaults and $fragment eq $defaults->default_path ) {
+        $self->state_variable( "region-$name" => undef, $fragment );
+    } else {
+        $self->state_variable( "region-$name" => $fragment );
+    }
+}
+
+=head2 region_argument NAME ARG VALUE
+
+Sets the value of the C<ARG> argument on the fragment named C<NAME> to
+C<VALUE>.
+
+=cut
+
+sub region_argument {
+    my $self = shift;
+    my ( $region, $argument, $value ) = @_;
+
+    my $name = ref $region ? $region->qualified_name : $region;
+    my $defaults = Jifty->web->get_region($name);
+    my $default = $defaults ? $defaults->default_argument($argument) : undef;
+
+    if (   ( not defined $default and not defined $value )
+        or ( defined $default and defined $value and $default eq $value ) )
+    {
+        $self->state_variable( "region-$name.$argument" => undef, $value );
+    } else {
+        $self->state_variable( "region-$name.$argument" => $value );
+    }
+
+}
+
+# Query-map any complex structures
+sub _map {
+    my %old_args = @_;
+    my %new_args;
+
+    while (my ($key, $val) = each %old_args) {
+        my ($new_key, $new_val) = Jifty::Request::Mapper->query_parameters($key => $val);
+        $new_args{$new_key} = $new_val;
+    }
+
+    return %new_args;
+}
+
+=head2 parameters
+
+Returns the generic list of parameters attached to the link as a hash.
+Use of this is discouraged in favor or L</post_parameters> and
+L</get_parameters>.
+
+=cut
+
+sub parameters {
+    my $self = shift;
+
+    my %parameters;
+
+    if ( $self->returns ) {
+        %parameters = Jifty::Request::Mapper->query_parameters( %{ $self->returns } );
+        $parameters{"J:CREATE"} = 1;
+        $parameters{"J:PATH"} = Jifty::Web::Form::Clickable->new( url => $self->url,
+                                                                  parameters => $self->{parameters},
+                                                                  continuation => undef,
+                                                                )->complete_url;
+    } else {
+        %parameters = %{ $self->{parameters} };        
+    }
+
+    %parameters = _map( %{$self->{state_variable} || {}}, %parameters );
+
+    $parameters{"J:CALL"} = $self->call
+        if $self->call;
+
+    $parameters{"J:C"} = $self->continuation
+        if $self->continuation
+        and not $self->call;
+
+    return %parameters;
+}
+
+=head2 post_parameters
+
+The hash of parameters as they would be needed on a POST request.
+
+=cut
+
+sub post_parameters {
+    my $self = shift;
+
+    my %parameters = ( _map( %{ $self->{fallback} || {} } ), $self->parameters );
+
+    my ($root) = $ENV{'REQUEST_URI'} =~ /([^\?]*)/;
+
+    # Submit actions should only show up once
+    my %uniq;
+    $self->submit([grep {not $uniq{$_}++} @{$self->submit}]) if $self->submit;
+
+    # Add a redirect, if this isn't to the right page
+    if ( $self->url ne $root and not $self->returns ) {
+        require Jifty::Action::Redirect;
+        my $redirect = Jifty::Action::Redirect->new(
+            arguments => { url => $self->url } );
+        $parameters{ $redirect->register_name } = ref $redirect;
+        $parameters{ $redirect->form_field_name('url') } = $self->url;
+        $parameters{"J:ACTIONS"} = join( '!', @{ $self->submit }, $redirect->moniker )
+          if $self->submit;
+    } else {
+        $parameters{"J:ACTIONS"} = join( '!', @{ $self->submit } )
+          if $self->submit;
+    }
+
+    return %parameters;
+}
+
+=head2 get_parameters
+
+The hash of parameters as they would be needed on a GET request.
+
+=cut
+
+sub get_parameters {
+    my $self = shift;
+
+    my %parameters = $self->parameters;
+
+    return %parameters;
+}
+
+=head2 complete_url
+
+Returns the complete GET URL, as it would appear on a link.
+
+=cut
+
+sub complete_url {
+    my $self = shift;
+
+    my %parameters = $self->get_parameters;
+
+    my ($root) = $ENV{'REQUEST_URI'} =~ /([^\?]*)/;
+    my $url = $self->returns ? $root : $self->url;
+    if (%parameters) {
+        $url .= ( $url =~ /\?/ ) ? ";" : "?";
+        $url .= Jifty->web->query_string(%parameters);
+    }
+
+    return $url;
+}
+
+sub _defined_accessor_values {
+    my $self = shift;
+    return { map { my $val = $self->$_; defined $val ? ($_ => $val) : () } 
+        $self->SUPER::accessors };
+}
+
+=head2 as_link
+
+Returns the clickable as a L<Jifty::Web::Form::Link>, if possible.
+Use of this method is discouraged in favor of L</generate>, which can
+better determine if a link or a button is more appropriate.
+
+=cut
+
+sub as_link {
+    my $self = shift;
+
+    my $args = $self->_defined_accessor_values;
+    my $link = Jifty::Web::Form::Link->new(
+        { %$args,
+          escape_label => $self->escape_label,
+          url          => $self->complete_url,
+          target       => $self->target,
+          @_ }
+    );
+    return $link;
+}
+
+=head2 as_button
+
+Returns the clickable as a L<Jifty::Web::Form::Field::InlineButton>,
+if possible.  Use of this method is discouraged in favor of
+L</generate>, which can better determine if a link or a button is more
+appropriate.
+
+=cut
+
+sub as_button {
+    my $self = shift;
+
+    my $args = $self->_defined_accessor_values;
+    my $field = Jifty::Web::Form::Field->new(
+        { %$args,
+          type => 'InlineButton',
+          @_ }
+    );
+    my %parameters = $self->post_parameters;
+
+    $field->input_name(
+        join "|",
+        map      { $_ . "=" . $parameters{$_} }
+            grep { defined $parameters{$_} } keys %parameters
+    );
+    $field->name( join '|', keys %{ $args->{parameters} } );
+    $field->button_as_link($self->render_as_link);
+
+    return $field;
+}
+
+=head2 generate
+
+Returns a L<Jifty::Web::Form::Field::InlineButton> or
+I<Jifty::Web::Form::Link>, whichever is more appropriate given the
+parameters.
+
+=cut
+
+## XXX TODO: This code somewhat duplicates hook-handling logic in
+## Element.pm, in terms of handling shortcuts like
+## 'refresh_self'. Some of the logic should probably be unified.
+
+sub generate {
+    my $self = shift;
+    for my $trigger ( $self->handlers ) {
+        my $value = $self->$trigger;
+        next unless $value;
+        my @hooks = @{$value};
+        for my $hook (@hooks) {
+            next unless ref $hook eq "HASH";
+            $hook->{region} ||= $hook->{refresh} || Jifty->web->qualified_region;
+
+            my $region = ref $hook->{region} ? $hook->{region} : Jifty->web->get_region( $hook->{region} );
+
+            if ($hook->{replace_with}) {
+                my $currently_shown = '';
+                if ($region) {
+
+                my $state_var = Jifty->web->request->state_variable("region-".$region->qualified_name);
+                $currently_shown = $state_var->value if ($state_var);
+                } 
+                # Toggle region if the toggle flag is set, and clicking wouldn't change path
+                if ($hook->{toggle} and $hook->{replace_with} eq $currently_shown) {
+                    $self->region_fragment( $hook->{region}, "/__jifty/empty" );
+#                    Jifty->web->request->remove_state_variable('region-'.$region->qualified_name);
+                } else {
+                    $self->region_fragment( $hook->{region}, $hook->{replace_with} )
+                }
+                
+            }
+            $self->region_argument( $hook->{region}, $_ => $hook->{args}{$_} )
+                for keys %{ $hook->{args} };
+            if ( $hook->{submit} ) {
+                $self->{submit} ||= [];
+                $hook->{submit} = [ $hook->{submit} ] unless ref $hook->{submit} eq "ARRAY";
+                push @{ $self->{submit} }, @{ $hook->{submit} };
+            }
+        }
+    }
+
+    return ( ( not( $self->submit ) || @{ $self->submit } || $self->render_as_button )
+        ? $self->as_button(@_)
+        : $self->as_link(@_) );
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Web/Form/Element.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Web/Form/Element.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,397 @@
+use warnings;
+use strict;
+
+package Jifty::Web::Form::Element;
+
+=head1 NAME
+
+Jifty::Web::Form::Element - Some item that can be rendered in a form
+
+=head1 DESCRIPTION
+
+Describes any HTML element that might live in a form, and thus might
+have javascript on it.
+
+Handlers are placed on L<Jifty::Web::Form::Element> objects by calling
+the name of the javascript event handler, such as C<onclick>, with a
+set of arguments.
+
+The format of the arguments passed to C<onclick> (or any similar
+method) is a hash reference or string.  Strings are inserted verbatim.
+
+Hash references can take a number of possible keys.  The most
+important is the mode of the fragment replacement, if any; it is
+specified by providing at most one of the following keys:
+
+=over
+
+=item append => PATH
+
+Add the given C<PATH> as a new fragment, just before the close of the
+CSS selector given by L</element>, which defaults to the end of the
+current region.
+
+=item prepend => PATH
+
+Add the given C<PATH> as a new fragment, just after the start of the
+CSS selector given by L</element>, which defaults to the start of the
+current region.
+
+=item replace_with => PATH
+
+Replaces the region specified by the C<region> parameter (which
+defaults to the current region) with the fragment located at the given
+C<PATH>.  If C<undef> is passed as the C<PATH>, acts like a L</delete>.
+
+=item refresh => REGION
+
+Refreshes the given C<REGION>, which should be a
+L<Jifty::Web::PageRegion> object, or the fully qualified name of such.
+
+=item refresh_self => 1
+
+Refreshes the current region; this is the default action, if a
+non-empty C<args> is supplied, but no other mode is given.
+
+=item delete => REGION
+
+Removes the given C<REGION> from the page, permanently.
+
+=back
+
+The following options are also supported:
+
+=over
+
+=item toggle => BOOLEAN
+
+If set to true, then the link will possibly toggle the region to
+empty, if the region's current path is the same as the path the region
+is trying to be set to.
+
+=item region => REGION
+
+The region that should be updated.  This defaults to the current
+region.
+
+=item element => CSS SELECTOR
+
+A css selector specifying where the new region should be placed; used
+with L</append> and L</prepend>, above.  The
+L<Jifty::Web::PageRegion/get_element> method may be useful in
+specifying elements of parent page regions.
+
+=item submit => MONIKER
+
+An action, moniker of an action, or array reference to such; these
+actions are submitted when the event is fired.
+
+=item disable => BOOLEAN
+
+If true, disable all form fields associated with the actions in
+C<submit> when this Element is clicked. This serves to give immediate
+visual indication that the request is being processed, as well as to
+prevent double-submits.
+
+Defaults to true.
+
+=item args => HASHREF
+
+Arguments to the region.  These will override the arguments to the
+region that the region was given when it was last rendered.
+
+=item effect => STRING
+
+The Scriptaculous visual effect to use when updating or creating the
+fragment.
+
+=item effect_args => HASHREF
+
+A hashref of arguments to pass to the effect when it is created.  These
+can be used to change the duration of the effect, for instance.
+
+=item confirm => STRING
+
+Prompt the user with a Javascript confirm dialog with the given text
+before carrying out the rest of the handlers. If the user cancels, do
+nothing, otherwise proceed as normal.
+
+TODO: This does not have a non-Javascript fallback method yet.
+
+=back
+
+=cut
+
+use base qw/Jifty::Object Class::Accessor::Fast/;
+use Jifty::JSON;
+
+=head2 handlers
+
+Currently, the only supported event handlers are C<onclick>.
+WARNING: if you use the onclick handler, make sure that your javascript
+is "return (function name);", or you may well get a very strange-looking
+error from your browser.
+
+=cut
+
+sub handlers { qw(onclick); }
+
+=head2 accessors
+
+Any descendant of L<Jifty::Web::Form::Element> should be able to
+accept any of the event handlers (above) as one of the keys to its
+C<new> parameter hash.
+
+=cut
+
+sub accessors { shift->handlers, qw(class key_binding key_binding_label id label tooltip) }
+__PACKAGE__->mk_accessors(qw(_onclick class key_binding key_binding_label id label tooltip));
+
+=head2 new PARAMHASH OVERRIDE
+
+Create a new C<Jifty::Web::Form::Element> object blessed with
+PARAMHASH, and set with accessors for the hash values in OVERRIDE.
+
+=cut
+
+sub new {
+    my ($class, $args, $override) = @_;
+    # force using accessor for onclick init
+    if (my $onclick = delete $args->{onclick}) {
+        $override->{onclick} = $onclick;
+    }
+
+    my $self = $class->SUPER::new($args);
+
+    if ($override) {
+        for my $field ( $self->accessors() ) {
+            # XXX: warn about unexpected ones
+            $self->$field( $override->{$field} ) if exists $override->{$field};
+        }
+    }
+
+    return $self;
+}
+
+=head2 onclick
+
+=cut
+
+sub onclick {
+    my $self = shift;
+    return $self->_onclick unless @_;
+
+    my ($arg) = @_;
+
+    $arg = [$arg] unless ref $arg eq 'ARRAY';
+
+    #Normalize arguments as much as possible here, to simplify later
+    #processing of them here and in Clickable.
+    for my $hook ( @$arg ) {
+        next unless ref $hook eq 'HASH';
+
+        # Normalize actions to monikers to prevent circular references,
+        # since Jifty::Action caches instances of Jifty::Web::Form::Clickable.
+        if ( $hook->{submit} ) {
+            $hook->{submit} = [ $hook->{submit} ] unless ref $hook->{submit} eq "ARRAY";
+            $hook->{submit} = [ map { ref $_ ? $_->moniker : $_ } @{ $hook->{submit} } ];
+        }
+
+        if ( $hook->{args} ) {
+            # We're going to pass complex query mapping structures
+            # as-is to the server, but we need to make sure we're not
+            # trying to pass around Actions, merely their monikers.
+            for my $key ( keys %{ $hook->{args} } ) {
+                next unless ref $hook->{args}{$key} eq "HASH";
+                $hook->{args}{$key}{$_} = $hook->{args}{$key}{$_}->moniker
+                  for grep { ref $hook->{args}{$key}{$_} }
+                  keys %{ $hook->{args}{$key} };
+            }
+        } else {
+            $hook->{args} = {};
+        }
+
+    }
+
+    $self->_onclick($arg);
+}
+
+=head2 javascript
+
+Returns the javascript necessary to make the events happen.
+
+=cut
+
+sub javascript {
+    my $self = shift;
+
+    my $response = "";
+    for my $trigger ( $self->handlers ) {
+        my $value = $self->$trigger;
+        next unless $value;
+
+        my @fragments;
+        my %actions;    # Maps actions => disable?
+        my $confirm;
+
+        for my $hook (grep {ref $_ eq "HASH"} (@{$value})) {
+
+            my %args;
+
+            # Submit action
+            if ( $hook->{submit} ) {
+                my $disable = exists $hook->{disable} ? $hook->{disable} : 1;
+                # Normalize to 1/0 to pass to JS
+                $disable = $disable ? 1 : 0;
+                $actions{$_} = $disable for (@{ $hook->{submit} }); 
+            }
+
+            $hook->{region} ||= Jifty->web->qualified_region;
+
+            # Should we show a javascript confirm message?
+            if ($hook->{confirm}) {
+                $confirm = $hook->{confirm};
+            }
+
+            # Placement
+            if (exists $hook->{append}) {
+                @args{qw/mode path/} = ('Bottom', $hook->{append});
+                $hook->{element} ||= "#region-".$hook->{region};
+            } elsif (exists $hook->{prepend}) {
+                @args{qw/mode path/} = ('Top', $hook->{prepend});
+                $hook->{element} ||= "#region-".$hook->{region};
+            } elsif (exists $hook->{replace_with}) {
+                if (defined $hook->{replace_with}) {
+                    @args{qw/mode path region/} = ('Replace', $hook->{replace_with}, $hook->{region});
+                } else {
+                    @args{qw/mode region/} = ('Delete', $hook->{region});
+                }
+            } elsif (exists $hook->{refresh}) {
+                my $region = ref $hook->{refresh} ? $hook->{refresh} : Jifty->web->get_region($hook->{refresh});
+                if ($region) {
+                    @args{qw/mode path region/} = ('Replace', $region->path, $region->qualified_name);
+                } else {
+                    $self->log->debug("Can't find region ".$hook->{refresh});
+                    @args{qw/mode path region/} = ('Replace', undef, $hook->{refresh});
+                }
+            } elsif ((exists $hook->{refresh_self} and Jifty->web->current_region) or (Jifty->web->current_region and $hook->{args} and %{$hook->{args}})) {
+                # If we just pass arguments, treat as a refresh_self
+                @args{qw/mode path region/} = ('Replace', Jifty->web->current_region->path, Jifty->web->current_region);
+            } elsif (exists $hook->{delete}) {
+                @args{qw/mode region/} = ('Delete', $hook->{delete});
+            } else {
+                # If we're not doing any of the above, skip this one
+                next;
+            }
+
+            # Qualified name if we have a ref
+            $args{region} = $args{region}->qualified_name if ref $args{region};
+
+            # What element we're replacing.
+            if ($hook->{element}) {
+                $args{element} = ref $hook->{element} ? "#region-".$hook->{element}->qualified_name : $hook->{element};
+                $args{region}  = $args{element} =~ /^#region-(\S+)/ ? "$1-".Jifty->web->serial : Jifty->web->serial;
+            }
+
+            # Arguments
+            $args{args} = $hook->{args};
+
+            # Toggle functionality
+            $args{toggle} = 1 if $hook->{toggle};
+
+            # Effects
+            $args{$_} = $hook->{$_} for grep {exists $hook->{$_}} qw/effect effect_args/;
+
+            push @fragments, \%args;
+        }
+
+        my $string = join ";", (grep {not ref $_} (ref $value eq "ARRAY" ? @{$value} : ($value)));
+        if (@fragments or %actions) {
+
+            my $update = "update( ". Jifty::JSON::objToJson( {actions => \%actions, fragments => \@fragments }, {singlequote => 1}) .", this );";
+            $string .= $self->javascript_preempt ? "return $update" : "$update; return true;";
+        }
+        if ($confirm) {
+            $string = "if(!confirm(" . Jifty::JSON::objToJson($confirm, {singlequote => 1}) . ")) return false;" . $string;
+        }
+        $response .= qq| $trigger="$string"|;
+    }
+    return $response;
+}
+
+=head2 javascript_preempt
+
+Returns true if the the javascript's handlers should prevent the web
+browser's standard effects from happening; that is, for C<onclick>, it
+prevents buttons from submitting and the like.  The default is to
+return true, but this can be overridden.
+
+=cut
+
+sub javascript_preempt { return 1 };
+
+=head2 class
+
+Sets the CSS class that the element will display as
+
+=head2 key_binding
+
+Sets the key binding associated with this element
+
+=head2 key_binding_label
+
+Sets the key binding label associated with this element (if none is specified,
+the normal label is used instead)
+
+=head2 id
+
+Subclasses must override this to provide each element with a unique id.
+
+=head2 label
+
+Sets the label of the element.  This will be used for the key binding
+legend if key_binding_label is not set.
+
+=head2 key_binding_javascript
+
+Returns the javascript fragment to add key binding for this input, if
+one exists.
+
+=cut
+
+sub key_binding_javascript {
+    my $self  = shift;
+    my $key   = $self->key_binding;
+    my $label = defined $self->key_binding_label
+                    ? $self->key_binding_label
+                    : $self->label;
+    if ($key) {
+        return "Jifty.KeyBindings.add("
+                . Jifty::JSON::objToJson( uc($key), { singlequote => 1 } ).","
+                . "'click', "
+                . Jifty::JSON::objToJson( $self->id, { singlequote => 1 } ).","
+                . Jifty::JSON::objToJson( $label, { singlequote => 0 } )
+                . ");";
+    }
+}
+
+=head2 render_key_binding
+
+Renders the javascript from L</key_binding_javscript> in a <script>
+tag, if needed.
+
+=cut
+
+sub render_key_binding {
+    my $self = shift;
+    return unless $self->key_binding;
+    Jifty->web->out(
+        '<script type="text/javascript"><!--' .
+        "\n" .
+        $self->key_binding_javascript .
+        "\n" .
+        "--></script>");
+    return '';
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,731 @@
+=begin properties
+
+constructor
+canonicalizer
+available_values
+ajax_validates
+autocompleter
+
+default_value
+valid_values
+validator
+render_as
+label
+hints
+placeholder
+max_length
+mandatory
+
+=end properties
+
+=cut
+
+use warnings;
+use strict;
+ 
+package Jifty::Web::Form::Field;
+
+=head1 NAME
+
+Jifty::Web::Form::Field - Web input of some sort
+
+=head1 DESCRIPTION
+
+Describes a form input in a L<Jifty::Action>.
+C<Jifty::Web::Form::Field>s know what action they are on, and aquire
+properties from the L<Jifty::Action> which they are part of.  Each key
+in the L<Jifty::Action/arguments> hash becomes a
+C<Jifty::Web::Form::Field> whose L</name> is that key.
+
+C<Jifty::Web::Form::Field>s stringify using the L</render> method, to
+aid in placing them in L<HTML::Mason> components.
+
+=cut
+
+use base 'Jifty::Web::Form::Element';
+
+use Scalar::Util;
+use HTML::Entities;
+
+# We need the anonymous sub because otherwise the method of the base class is
+# always called, instead of the appropriate overridden method in a child class.
+use overload '""' => sub { shift->render }, bool => sub { 1 };
+
+=head2 new
+
+Creates a new L<Jifty::Web::Form::Field> (possibly magically blessing into a subclass).
+Should only be called from C<< $action->arguments >>.
+
+=cut
+
+sub new {
+    my $class = shift;
+    my $self = $class->SUPER::new(
+      { type          => 'text',
+        class         => '',
+        input_name    => '',
+        default_value => '',
+        sticky_value  => '',
+        render_mode   => 'update' });
+    my $args = ref($_[0]) ? $_[0] : {@_};
+
+    my $subclass = ucfirst($args->{render_as} || $args->{type} || 'text');
+    $subclass = 'Jifty::Web::Form::Field::' . $subclass unless $subclass =~ /::/;
+    bless $self, $subclass if Jifty::Util->require($subclass);
+
+    for my $field ( $self->accessors() ) {
+        $self->$field( $args->{$field} ) if exists $args->{$field};
+    }
+
+    # If they key and/or value imply that this argument is going to be
+    # a mapped argument, then do the mapping and mark the field as hidden.
+    my ($key, $value) = Jifty::Request::Mapper->query_parameters($self->input_name, $self->current_value);
+    if ($key ne $self->input_name) {
+        require Jifty::Web::Form::Field::Hidden;
+        bless $self, "Jifty::Web::Form::Field::Hidden";
+        $self->input_name($key);
+        $self->default_value($value);
+        $self->sticky_value(undef);
+    }
+
+    # now that the form field has been instantiated, register the action with the form.
+    if ($self->action and not (Jifty->web->form->has_action($self->action))) {
+        Jifty->web->form->register_action( $self->action);
+        Jifty->web->form->print_action_registration($self->action->moniker);
+    }
+    return $self;
+}
+
+
+=head2 accessors
+
+Lists the accessors that are able to be called from within a call to
+C<new>.  Subclasses should extend this list.
+
+=cut
+
+sub accessors { shift->SUPER::accessors(), qw(name label input_name type sticky sticky_value default_value action mandatory ajax_validates ajax_canonicalizes autocompleter preamble hints placeholder focus render_mode max_length _element_id disable_autocomplete); }
+__PACKAGE__->mk_accessors(qw(name _label _input_name type sticky sticky_value default_value _action mandatory ajax_validates ajax_canonicalizes autocompleter preamble hints placeholder focus render_mode max_length _element_id disable_autocomplete));
+
+=head2 name [VALUE]
+
+Gets or sets the name of the field.  This is seperate from the name of
+the label (see L</label>) and the form input name (see
+L</input_name>), though both default to this name.  This name should
+match to a key in the L<Jifty::Action/arguments> hash.  If this
+C<Jifty::Web::Form::Field> was created via L<Jifty::Action/form_field>,
+this is automatically set for you.
+
+=head2 class [VALUE]
+
+Gets or sets the CSS display class applied to the label and widget.
+
+=head2 type [VALUE]
+
+Gets or sets the type of the HTML <input> field -- that is, 'text' or
+'password'.  Defauts to 'text'.
+
+=head2 key_binding VALUE
+
+Sets this form field's "submit" key binding to VALUE. 
+
+=head2 key_binding_label VALUE
+
+Sets this form field's key binding label to VALUE.  If none is specified
+the normal label is used.
+
+=head2 default_value [VALUE]
+
+Gets or sets the default value for the form.
+
+=head2 sticky_value [VALUE]
+
+Gets or sets the value for the form field that was submitted in the last action.
+
+=head2 mandatory [VALUE]
+
+A boolean indicating that the argument B<must> be present when the
+user submits the form.
+
+=head2 focus [VALUE]
+
+If true, put focus on this form field when the page loads.
+
+=head2 ajax_validates [VALUE]
+
+A boolean value indicating if user input into an HTML form field for
+this argument should be L<validated|Jifty::Manual::Glossary/validate>
+via L<AJAX|Jifty::Manual::Glossary/AJAX> as the user fills out the
+form, instead of waiting until submit. Arguments will B<always> be
+validated before the action is run, whether or not they also
+C<ajax_validate>.
+
+=head2 ajax_canonicalizes [VALUE]
+
+A boolean value indicating if user input into an HTML form field for
+this argument should be L<canonicalized|Jifty::Manual::Glossary/canonicalize>
+via L<AJAX|Jifty::Manual::Glassary/AJAX> as the user fills out the
+form, instead of waiting until submit.  Arguments will B<always> be
+canonicalized before the action is run, whether or not they also
+C<ajax_canonicalize>
+
+=head2 disable_autocomplete [VALUE]
+
+Gets or sets whether to disable _browser_ autocomplete for this field.
+
+=head2 preamble [VALUE]
+
+Gets or sets the preamble located in front of the field.
+
+=head2 id 
+
+For the purposes of L<Jifty::Web::Form::Element>, the unique id of
+this field is its input name.
+
+=cut
+
+sub id {
+    my $self = shift;
+    return $self->input_name;
+}
+
+=head2 input_name [VALUE]
+
+Gets or sets the form field input name, as it is rendered in the HTML.
+If we've been explicitly named, return that, otherwise return a name
+based on the moniker of the action and the name of the form.
+
+=cut
+
+sub input_name {
+    my $self = shift;
+
+# If we've been explicitly handed a name, we should run with it.
+# Otherwise, we should ask our action, how to turn our "name"
+# into a form input name.
+
+    my $ret = $self->_input_name(@_);
+    return $ret if $ret;
+
+    my $action = $self->action;
+    return $action ? $self->action->form_field_name( $self->name )
+                   : '';
+}
+
+
+=head2 fallback_name
+
+Return the form field's fallback name. This should be used to create a
+hidden input with a value of 0 to accompany checkboxes or to let 
+comboboxes fall back to the text input if, and only if no value is
+selected from the SELECT.  (We use this order, so that we can stick the
+label and not the value in the INPUT field. To make that work, we also need
+to clear the SELECT after the user types in the INPUT.
+
+=cut
+
+sub fallback_name {
+    my $self = shift;
+
+    if ($self->action) {
+    return $self->action->fallback_form_field_name( $self->name );
+    }
+    else {
+        # XXX TODO, we should have a more graceful way to compose these in the absence of an action
+        my $name = $self->input_name;
+        $name =~ s/^J:A:F/J:A:F:F/;
+        return($name)
+    }
+}
+
+
+=head2 label [VALUE]
+
+Gets or sets the label on the field.  This defaults to the name of the
+object.
+
+=cut
+
+sub label {
+    my $self = shift;
+    my $val = $self->_label(@_);
+    defined $val ? $val :  $self->name;
+
+}
+
+=head2 hints [VALUE]
+
+Hints for the user to explain this field
+
+=cut
+
+sub hints {
+    my $self = shift;
+    return $self->_hints_accessor unless @_;
+
+    my $hint = shift;
+    # people sometimes say hints are "foo" rather than hints is "foo"
+    if (ref $hint eq 'ARRAY') {
+        $hint = shift @$hint;
+    }
+    return $self->_hints_accessor($hint);
+}
+
+
+=head2 element_id 
+
+Returns a unique C<id> attribute for this field based on the field name. This is
+consistent for the life of the L<Jifty::Web::Form::Field> object but isn't predictable;
+
+=cut
+
+
+sub element_id {
+    my $self = shift;
+    return $self->_element_id || $self->_element_id( $self->input_name ."-".Jifty->web->serial); 
+}
+
+=head2 action [VALUE]
+
+Gets or sets the L<Jifty::Action> object that this
+C<Jifty::Web::Form::Field> is associated with.  This is called
+automatically if this C<Jifty::Action> was created via
+L<Jifty::Web::Form::Field/form_field>.
+
+=cut
+
+sub action {
+    my $self   = shift;
+    my $action = $self->_action(@_);
+
+    # If we're setting the action, we need to weaken
+    # the reference to not get caught in a loop
+    Scalar::Util::weaken( $self->{_action} ) if @_;
+    return $action;
+}
+
+=head2 current_value
+
+Gets the current value we should be using for this form field.
+
+If the argument is marked as "sticky" (default) and there is a value for this 
+field from a previous action submit AND that action did not have a "success" 
+response, returns that submit's value. Otherwise, returns the action's argument's 
+default_value for this field.
+
+=cut
+
+sub current_value {
+    my $self = shift;
+
+    if ($self->sticky_value and $self->sticky) {
+        return $self->sticky_value;
+    } else {
+        return $self->default_value;
+    }
+}
+
+=head2 render
+
+Outputs this form element in a span with class C<form_field>.  This
+outputs the label, the widget itself, any hints, any errors, and any
+warnings using L</render_label>, L</render_widget>, L</render_hints>,
+L</render_errors>, and L</render_warnings>, respectively.  Returns an
+empty string.
+
+This is also what C<Jifty::Web::Form::Field>s do when stringified.
+
+=cut
+
+sub render {
+    my $self = shift;
+    $self->render_wrapper_start();
+    $self->render_preamble();
+
+
+    $self->render_label();
+    if ($self->render_mode eq 'update') { 
+        $self->render_widget();
+        $self->render_autocomplete_div();
+        $self->render_inline_javascript();
+        $self->render_hints();
+        $self->render_errors();
+        $self->render_warnings();
+        $self->render_canonicalization_notes();
+    } elsif ($self->render_mode eq 'read'){ 
+        $self->render_value();
+    }
+    $self->render_wrapper_end();
+    return ('');
+}
+
+=head2 render_inline_javascript
+
+Render a <script> tag (if neceesary) containing any inline javascript
+that should follow this form field. This is used to add an
+autocompleter, placeholder, or keybinding to form fields where needed.
+
+=cut
+
+sub render_inline_javascript {
+    my $self = shift;
+
+    my $javascript;
+
+    $javascript = join(
+        "\n",
+        grep {$_} (
+            $self->autocomplete_javascript(),
+            $self->placeholder_javascript(),
+            $self->key_binding_javascript(),
+            $self->focus_javascript()
+        )
+    );
+    
+    if($javascript =~ /\S/) {
+        Jifty->web->out(qq{<script type="text/javascript"><!--
+    $javascript
+--></script>
+});
+    }
+}
+
+=head2 classes
+
+Renders a default CSS class for each part of our widget.
+
+=cut
+
+
+sub classes {
+    my $self = shift;
+    my $name = $self->name;
+    return join(' ', ($self->class||''), ($name ? "argument-".$name : ''));
+}
+
+
+=head2 render_wrapper_start
+
+Output the start of div that wraps the form field
+
+=cut
+
+sub render_wrapper_start {
+    my $self = shift;
+    my @classes = qw(form_field);
+    if ($self->mandatory) { push @classes, 'mandatory' }
+    if ($self->name)      { push @classes, 'argument-'.$self->name }
+    Jifty->web->out('<div class="'.join(' ', @classes).'">' ."\n");
+}
+
+=head2 render_wrapper_end
+
+Output the div that wraps the form field
+
+=cut
+
+sub render_wrapper_end {
+    my $self = shift;
+    Jifty->web->out("</div>"."\n");
+}
+
+=head2 render_preamble
+
+Outputs the preamble of this form field, using a <span> HTML element
+with CSS class C<preamble> and whatever L</class> specifies.  Returns an
+empty string.
+
+Use this for sticking instructions right in front of a widget
+
+=cut
+
+
+sub render_preamble {
+    my $self = shift;
+    Jifty->web->out(
+qq!<span class="preamble @{[$self->classes]}">@{[_($self->preamble) || '' ]}</span>\n!
+    );
+
+    return '';
+}
+
+=head2 render_label
+
+Outputs the label of this form field, using a <label> HTML element
+with the CSS class C<label> and whatever L</class> specifies.  Returns
+an empty string.
+
+=cut
+
+sub render_label {
+    my $self = shift;
+    if ( $self->render_mode eq 'update' ) {
+        Jifty->web->out(
+qq!<label class="label @{[$self->classes]}" for="@{[$self->element_id ]}">@{[_($self->label) ]}</label>\n!
+        );
+    } else {
+        Jifty->web->out(
+            qq!<span class="label @{[$self->classes]}">@{[_($self->label) ]}</span>\n!
+        );
+    }
+
+    return '';
+}
+
+
+=head2 render_widget
+
+Outputs the actual entry widget for this form element.  This defaults
+to an <input> element, though subclasses commonly override this.
+Returns an empty string.
+
+=cut
+
+sub render_widget {
+    my $self  = shift;
+    my $field = qq!  <input !;
+    $field .= qq! type="@{[ $self->type ]}"!;
+    $field .= qq! name="@{[ $self->input_name ]}"! if ($self->input_name);
+    $field .= qq! id="@{[ $self->element_id ]}"!;
+    $field .= qq! value="@{[Jifty->web->escape($self->current_value)]}"! if defined $self->current_value;
+    $field .= $self->_widget_class; 
+    $field .= qq! size="@{[ $self->max_length() ]}" maxlength="@{[ $self->max_length() ]}"! if ($self->max_length());
+    $field .= qq! autocomplete="off"! if defined $self->disable_autocomplete;
+    $field .= " " .$self->other_widget_properties;
+    $field .= qq!  />\n!;
+    Jifty->web->out($field);
+    return '';
+}
+
+=head2 other_widget_properties
+
+If your widget subclass has other properties it wants to insert into the html
+of the main widget and you haven't subclassed render_widget then you can just
+subclass this.
+
+If you have subclassed render_widget then just stick them in your local sub
+render_widget.
+
+We use this for marking password fields as not-autocomplete so the browser does
+not try to use its form autocompletion on them.
+
+
+=cut
+
+sub other_widget_properties {''}
+
+=head2 _widget_class
+
+Returns the "class=" line for our widget. Optionally takes extra classes to append to our list.
+
+=cut
+
+sub _widget_class {
+    my $self = shift;
+    my @classes = ( 'widget',
+                    $self->classes,
+                    ( $self->ajax_validates     ? ' ajaxvalidation' : '' ),
+                    ( $self->ajax_canonicalizes ? ' ajaxcanonicalization' : '' ),
+                    ( $self->autocompleter      ? ' ajaxautocompletes' : '' ),
+                    ( $self->focus              ? ' focus' : ''),
+                    @_ );
+
+    return qq! class="!. join(' ', at classes).  qq!"!
+
+}
+
+=head2 render_value
+
+Renders a "view" version of the widget for field. Usually, this is just plain text.
+
+=cut
+
+
+sub render_value {
+    my $self  = shift;
+    my $field = '<span';
+    $field .= qq! class="@{[ $self->classes ]}"> !;
+    # XXX: force stringify the value because maketext is buggy with overloaded objects.
+    $field .= Jifty->web->escape(_("@{[$self->current_value]}")) if defined $self->current_value;
+    $field .= qq!</span>\n!;
+    Jifty->web->out($field);
+    return '';
+}
+
+
+
+=head2 render_autocomplete_div
+
+Renders an empty div that /__jifty/autocomplete.xml can fill
+in. Returns an empty string.
+
+=cut
+
+sub render_autocomplete_div { 
+    my $self = shift;
+    return unless($self->autocompleter);
+    Jifty->web->out(
+qq!<div class="autocomplete" id="@{[$self->element_id]}-autocomplete" style="display: none;"></div>!);
+
+    return '';
+}
+
+=head2 render_autocomplete
+
+Renders the div tag and javascript necessary to do autocompletion for
+this form field. Deprecated internally in favor of
+L</render_autocomplete_div> and L</autocomplete_javascript>, but kept
+for backwards compatability since there exists external code that uses
+it.
+
+=cut
+
+sub render_autocomplete {
+    my $self = shift;
+    return unless $self->autocompleter;
+    $self->render_autocomplete_div;
+    Jifty->web->out(qq{<script type="text/javascript"><!--
+    @{[$self->autocomplete_javascript]}
+--></script>});
+    return '';
+}
+
+
+
+=head2 autocomplete_javascript
+
+Returns renders the tiny snippet of javascript to make an autocomplete
+call, if necessary.
+
+=cut
+
+sub autocomplete_javascript {
+    my $self = shift;
+    return unless($self->autocompleter);
+    return qq{new Jifty.Autocompleter('@{[$self->element_id]}','@{[$self->element_id]}-autocomplete')};
+}
+
+=head2 placeholder_javascript
+
+Returns the javascript necessary to insert a placeholder into this
+form field (greyed-out text that is written in using javascript, and
+vanishes when the user focuses the field). 
+
+=cut
+
+sub placeholder_javascript {
+    my $self = shift;
+    return unless $self->placeholder;
+    my $placeholder = $self->placeholder;
+    $placeholder =~ s{(['\\])}{\\$1}g;
+    $placeholder =~ s{\n}{\\n}g;
+    $placeholder =~ s{\r}{\\r}g;
+    return qq{new Jifty.Placeholder('@{[$self->element_id]}', '$placeholder');};
+}
+
+=head2 focus_javascript
+
+Returns the javascript necessary to focus this form field on page
+load, if necessary.
+
+=cut
+
+sub focus_javascript {
+    my $self = shift;
+    return undef;
+    if($self->focus) {
+        return qq{document.getElementById("@{[$self->element_id]}").focus()};
+        return qq{DOM.Events.addListener( window, "load", function(){document.getElementById("@{[$self->element_id]}").focus()})};
+    }
+}
+
+=head2 render_hints
+
+Renders any hints for using this input.  Defaults to nothing, though
+subclasses commonly override this.  Returns an empty string.
+
+=cut
+
+sub render_hints { 
+    my $self = shift;
+    Jifty->web->out(
+qq!<span class="hints @{[$self->classes]}">@{[_($self->hints) || '']}</span>\n!
+    );
+
+    return '';
+
+}
+
+
+=head2 render_errors
+
+Outputs a <div> with any errors for this action, even if there are
+none -- AJAX could fill it in.
+
+=cut
+
+sub render_errors {
+    my $self = shift;
+
+    return unless $self->action;
+
+    Jifty->web->out(
+qq!<span class="error @{[$self->classes]}" id="@{[$self->action->error_div_id($self->name)]}">@{[  $self->action->result->field_error( $self->name ) || '']}</span>\n!
+    );
+    return '';
+}
+
+=head2 render_warnings
+
+Outputs a <div> with any warnings for this action, even if there are
+none -- AJAX could fill it in.
+
+=cut
+
+sub render_warnings {
+    my $self = shift;
+
+    return unless $self->action;
+
+    Jifty->web->out(
+qq!<span class="warning @{[$self->classes]}" id="@{[$self->action->warning_div_id($self->name)]}">@{[  $self->action->result->field_warning( $self->name ) || '']}</span>\n!
+    );
+    return '';
+}
+
+=head2 render_canonicalization_notes
+
+Outputs a <div> with any canonicalization notes for this action, even if there are
+none -- AJAX could fill it in.
+
+=cut
+
+sub render_canonicalization_notes {
+    my $self = shift;
+
+    return unless $self->action;
+
+    Jifty->web->out(
+qq!<span class="canonicalization_note @{[$self->classes]}" id="@{[$self->action->canonicalization_note_div_id($self->name)]}">@{[$self->action->result->field_canonicalization_note( $self->name ) || '']}</span>\n!
+    );
+    return '';
+}
+
+
+=for private
+
+=head2 length
+
+# Deprecated API
+
+
+=cut
+
+sub length {
+    my $self = shift;
+    Carp::carp("->length is deprecated; use ->max_length instead");
+    $self->max_length(@_);
+}
+
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/Button.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/Button.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,57 @@
+use warnings;
+use strict;
+ 
+package Jifty::Web::Form::Field::Button;
+
+use base qw/Jifty::Web::Form::Field/;
+__PACKAGE__->mk_accessors(qw/button_as_link/);
+
+
+=head1 NAME
+
+Jifty::Web::Form::Field::Button
+
+=cut
+
+=head1 SUMMARY
+
+L<Jifty::Web::Form::Field::Button/> is a L<Jifty::Web::Form::Field>
+
+=head1 METHODS
+
+=head2 accessors
+
+Provide the C<button_as_link> accessor (in addition to
+L<Jifty::Web::Form::Field>'s default accessors), which controls if the
+button is reworked in javascript to appear as a link.
+
+=cut
+
+sub accessors { shift->SUPER::accessors(), 'button_as_link' }
+
+=head2 render_widget
+
+Renders the button widget.
+
+=cut
+
+sub render_widget {
+    my $self  = shift;
+    my $field = join(
+        ' ',
+        '<input',
+        'type="submit"',
+        'name="' . $self->input_name . '" ',
+        'value="' . _($self->label ). '"',
+        'id="'. Jifty->web->serial . '"',
+        ($self->key_binding ? qq( accesskey="@{[$self->key_binding]}") : ''),
+        $self->_widget_class('button', ($self->button_as_link ? ("button_as_link") : ())),
+        $self->javascript,
+        ' />',
+        "\n"
+    );
+    Jifty->web->out($field);
+    return '';
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/Checkbox.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/Checkbox.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,74 @@
+use warnings;
+use strict;
+ 
+package Jifty::Web::Form::Field::Checkbox;
+
+use base qw/Jifty::Web::Form::Field/;
+__PACKAGE__->mk_accessors(qw/checked value/);
+
+=head2 accessors
+
+Provide C<checked> and C<value> accessors, in addition to
+L<Jifty::Web::Form::Field>'s default accessors.  C<value> defaults to
+"1".
+
+=cut
+
+sub accessors { shift->SUPER::accessors(), 'checked' , 'value' }
+
+=head2 render_widget
+
+Renders the checkbox widget.
+
+=cut
+
+sub render_widget {
+    my $self  = shift;
+    my $field =  qq!<input type="hidden" name="@{[ $self->fallback_name ]}" value="0" />\n!;
+
+    $field .= qq!<input!;
+    $field .= qq! type="checkbox"!;
+    $field .= qq! name="@{[ $self->input_name ]}"!;
+    $field .= qq! id="@{[ $self->element_id ]}"!;
+    $field .= qq! value="@{[$self->value ||1]}"!;
+    $field .= $self->_widget_class;
+    $field .= qq! checked="checked"! if ($self->checked or $self->current_value);
+
+    $field .= $self->javascript;
+    
+    $field .= qq! />\n!;
+    Jifty->web->out($field);
+    '';
+}
+
+=head2 render_value
+
+Renders value as a checkbox widget.
+
+=cut
+
+sub render_value {
+    my $self  = shift;
+    my $field .= qq!<input type="checkbox"!;
+    $field .= qq! name="@{[ $self->input_name ]}"!;
+    $field .= qq! id="@{[ $self->element_id ]}"!;
+    $field .= qq! value="@{[$self->value ||1]}"!;
+    $field .= $self->_widget_class;
+    $field .= qq! checked="checked"! if ($self->checked or $self->current_value);
+    $field .= qq! disabled="disabled" readonly="readonly"!;
+    $field .= qq! />\n!;
+
+    Jifty->web->out($field);
+    return '';
+}
+
+=head2 javascript_preempt
+
+By default, javascript (such as onclick handlers) should not actually
+prevent browsers from placing the checkmark in the checkbox.
+
+=cut
+
+sub javascript_preempt { return 0; }
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/Combobox.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/Combobox.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,72 @@
+use warnings;
+use strict;
+ 
+package Jifty::Web::Form::Field::Combobox;
+
+use base qw/Jifty::Web::Form::Field/;
+
+=head2 render_widget
+
+Renders the select widget.
+
+=cut
+
+sub render_widget {
+    my $self  = shift;
+
+my $field = <<"EOF";
+<nobr>
+<span id="@{[ $self->element_id ]}_Container" class="combobox">
+<input name="@{[ $self->fallback_name ]}" 
+       id="@{[ $self->element_id ]}" 
+       @{[ $self->_widget_class('combo-text')]}
+       value="@{[ $self->current_value ]}" 
+       type="text" 
+       size="30"
+       autocomplete="off" /><span id="@{[ $self->element_id ]}_Button" 
+       @{[ $self->_widget_class('combo-button')]}
+        ></span></span><span style="display: none"></span><select 
+        name="@{[ $self->input_name ]}" 
+        id="@{[ $self->_element_id ]}_List" 
+        @{[ $self->_widget_class('combo-list')]}
+        onchange="ComboBox_SimpleAttach(this, this.form['@{[ $self->element_id ]}']); " 
+        >
+<option style="display: none" value=""></option>
+EOF
+
+
+    for my $opt (@{ $self->action->available_values($self->name) }) {
+        my $display = $opt->{'display'};
+        my $value   = $opt->{'value'} ||'' ;
+        # TODO XXX FIXME worry about escape value, display?
+        $field .= qq!<option value="$value"!;
+        $field .= qq! selected="selected"!
+            if defined $self->current_value and $self->current_value eq $value;
+        $field .= qq!>$display</option>\n!;
+    } 
+    
+
+
+$field .= <<"EOF";
+</select>
+<script language="javascript"><!--
+ComboBox_InitWith('@{[ $self->element_id ]}');
+//--></script>
+</nobr>
+EOF
+
+
+
+        Jifty->web->out($field);
+    '';
+}
+
+=head2 render_autocomplete
+
+Never render anything for autocomplete.
+
+=cut
+
+sub render_autocomplete {''}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/Date.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/Date.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,19 @@
+use warnings;
+use strict;
+ 
+package Jifty::Web::Form::Field::Date;
+
+use base qw/Jifty::Web::Form::Field/;
+
+=head2 classes
+
+Output date fields with the class 'date'
+
+=cut
+
+sub classes {
+    my $self = shift;
+    return join(' ', 'date', ($self->SUPER::classes));
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/Hidden.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/Hidden.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,27 @@
+use warnings;
+use strict;
+ 
+package Jifty::Web::Form::Field::Hidden;
+
+use base qw/Jifty::Web::Form::Field/;
+
+=head2 type
+
+The HTML input type is C<hidden>.
+
+=cut
+
+sub type { 'hidden' }
+
+=head2 render
+
+Renders a hidden form field. Doesn't render anything except the widget itself.
+
+=cut
+
+sub render {
+    my $self  = shift;
+    $self->render_widget();
+    return '';
+}
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/InlineButton.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/InlineButton.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,42 @@
+use warnings;
+use strict;
+ 
+package Jifty::Web::Form::Field::InlineButton;
+
+use base qw/Jifty::Web::Form::Field::Button/;
+
+
+=head2 render
+
+Overrides the default render sub to print a span, not a div and not print labels and preambles and hints
+
+XXX TODO the superclass should be more general, so we can just override those things to not print
+
+=cut
+
+=head2 render_wrapper_start
+
+Don't show a wrapper for an inline button
+
+=head2 render_wrapper_end
+
+Don't show a wrapper for an inline button
+
+=head2 render_label
+
+Don't show a label for an inline button
+
+=head2 render_hints
+
+Don't show hints for an inline button
+
+=cut
+
+
+
+sub render_wrapper_start { }
+sub render_wrapper_end {}
+sub render_label {}
+sub render_hints {}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/Password.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/Password.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,59 @@
+use warnings;
+use strict;
+ 
+package Jifty::Web::Form::Field::Password;
+
+use base qw/Jifty::Web::Form::Field/;
+
+=head2 type
+
+The HTML input type is C<password>.
+
+=cut
+
+sub type { 'password' }
+
+=head2 current_value
+
+The default value of a password field should B<always> be empty.
+
+=cut
+
+sub current_value {''}
+
+=head2 other_widget_properties
+
+No browser-based form auto-completion in password fields ;)
+
+Note: This has nothing to do with Jifty's Autocomplete mechanism.
+
+=cut
+
+sub other_widget_properties {
+    return q{autocomplete="off"};
+}
+
+=head2 render_value 
+
+Never render a value for a password
+
+=cut
+
+
+sub render_value {
+    Jifty->web->out('-');
+    return '';
+}
+
+=head2 classes
+
+Output password fields with the class 'password'
+
+=cut
+
+sub classes {
+    my $self = shift;
+    return join(' ', 'password', ($self->SUPER::classes));
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/Radio.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/Radio.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,70 @@
+use warnings;
+use strict;
+ 
+package Jifty::Web::Form::Field::Radio;
+
+use base qw/Jifty::Web::Form::Field/;
+
+=head2 render_widget
+
+Renders the whole radio button collection.
+
+=cut
+
+sub render_widget {
+    my $self  = shift;
+
+    for my $opt (@{ $self->action->available_values($self->name) }) {
+        $self->render_option($opt);
+    }
+}
+
+=head2 render_label
+
+We need to output the label as a span instead since the labels are associated 
+with the individual options.
+
+=cut
+
+sub render_label {
+    my $self = shift;
+    Jifty->web->out(
+        qq!<span class="label @{[$self->classes]}">@{[_($self->label) ]}</span>\n!
+    );
+
+    return '';
+}
+
+=head2 render_option option
+
+Renders a radio widget
+
+=cut
+
+sub render_option {
+    my $self = shift;
+    my $opt = shift;
+    my $display = $opt->{'display'};
+    my $value   = defined $opt->{'value'} ? $opt->{'value'} : '';
+
+    
+    my $id = $self->element_id . "-" . $value;
+    $id =~ s/\s+/_/;
+    my $field = qq! <input type="radio" !;
+    $field .= qq! name="@{[ $self->input_name ]}"!;
+    $field .= qq! id="@{[ $id ]}"!;
+    $field .= qq! value="@{[ $value ]}"!;
+    $field .= $self->_widget_class;
+
+    $field .= qq! checked="checked" !
+      if defined $self->current_value and $self->current_value eq $value;
+    $field .= qq! /><label for="@{[ $id ]}"!;
+    $field .= $self->_widget_class;
+    $field .= qq!>$display</label>\n!;
+    $field = qq{<span class="radiooption">$field</span>};
+
+    Jifty->web->out($field);
+    '';
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/ResetButton.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/ResetButton.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,33 @@
+use warnings;
+use strict;
+ 
+package Jifty::Web::Form::Field::ResetButton;
+
+use base qw/Jifty::Web::Form::Field::InlineButton/;
+
+
+=head2 render_widget
+
+Renders the reset button widget
+
+=cut
+
+sub render_widget {
+    my $self  = shift;
+    my $field = join(
+        ' ',
+        '<input',
+        'type="reset"',
+        'name="' . $self->input_name . '" ',
+        'value="' . _($self->label ). '"',
+        $self->_widget_class('button', 'reset'),
+        $self->javascript,
+        ' />',
+        "\n"
+    );
+    Jifty->web->out($field);
+    return '';
+}
+
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/Select.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/Select.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,62 @@
+use warnings;
+use strict;
+ 
+package Jifty::Web::Form::Field::Select;
+
+use base qw/Jifty::Web::Form::Field/;
+
+=head2 render_widget
+
+Renders the select widget.
+
+=cut
+
+sub render_widget {
+    my $self  = shift;
+    my $field = qq! <select !;
+    $field .= qq! name="@{[ $self->input_name ]}"!;
+    $field .= qq! id="@{[ $self->element_id ]}"!;
+    $field .= $self->_widget_class;
+    $field .= qq!      >\n!;
+    for my $opt (@{ $self->action->available_values($self->name) }) {
+        my $display = $opt->{'display'};
+        my $value   = $opt->{'value'};
+        $value = "" unless defined $value;
+        $field .= qq!<option value="$value"!;
+        $field .= qq! selected="selected"!
+            if defined $self->current_value and $self->current_value eq $value;
+        $field .= qq!>!;
+        $field .= Jifty->web->escape(_($display)) if defined $display;
+        $field .= qq!</option>\n!;
+    } 
+    $field .= qq!</select>\n!;
+    Jifty->web->out($field);
+    '';
+}
+
+
+=head2 render_value
+
+Rather than rendering the "key" side of our value for this field, render
+what would actually display to the user in the select-box
+
+=cut
+
+
+sub render_value {
+    my $self  = shift;
+    my $field = '<span';
+    $field .= qq! class="@{[ $self->classes ]}"> !;
+    my $value = $self->current_value;
+    if(defined $value) {
+        my @value = grep { $_->{value} eq $value }
+                        @{ $self->action->available_values($self->name) };
+        $value = $value[0]->{display} if scalar @value;
+    }
+    $field .= Jifty->web->escape(_($value)) if defined $value;
+    $field .= qq!</span>\n!;
+    Jifty->web->out($field);
+    return '';
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/Text.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/Text.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,26 @@
+use warnings;
+use strict;
+
+package Jifty::Web::Form::Field::Text;
+use base qw/Jifty::Web::Form::Field/;
+
+=head1 NAME
+
+Jifty::Web::Form::Field::Text - Renders as a small text field
+
+=cut
+
+our $VERSION = 1;
+
+=head2 classes
+
+Output text fields with the class 'text'
+
+=cut
+
+sub classes {
+    my $self = shift;
+    return join(' ', 'text', ($self->SUPER::classes));
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/Textarea.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/Textarea.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,41 @@
+use warnings;
+use strict;
+ 
+package Jifty::Web::Form::Field::Textarea;
+
+use base qw/Jifty::Web::Form::Field/;
+
+__PACKAGE__->mk_accessors(qw(rows cols));
+
+=head2 accessors
+
+Provide C<rows> and C<cols> accessors, in addition to
+L<Jifty::Web::Form::Field>'s default accessors.
+
+=cut
+
+sub accessors { shift->SUPER::accessors(), 'rows', 'cols' }
+
+=head2 render_widget
+
+Renders the textarea widget.
+
+=cut
+
+sub render_widget {
+    my $self  = shift;
+    my $field;
+    $field .= qq!<textarea!;
+    $field .= qq! name="@{[ $self->input_name ]}"!;
+    $field .= qq! id="@{[ $self->element_id ]}"!;
+    $field .= qq! rows="@{[$self->rows || 5 ]}"!;
+    $field .= qq! cols="@{[$self->cols || 60]}"!;
+    $field .= $self->_widget_class;
+    $field .= qq! >!;
+    $field .= Jifty->web->escape($self->current_value) if $self->current_value;
+    $field .= qq!</textarea>\n!;
+    Jifty->web->out($field);
+    '';
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/Unrendered.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/Unrendered.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,21 @@
+use warnings;
+use strict;
+ 
+package Jifty::Web::Form::Field::Unrendered;
+
+use base qw/Jifty::Web::Form::Field/;
+
+=head2 render
+
+Don't render anything. For one reason or another, this form field should never, ever be rendered, perhaps
+because it's internally generated and handled. 
+
+=cut
+
+sub render {
+    my $self  = shift;
+    # XXX TODO this shouldn't be in the superclass like that
+    '';
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/Upload.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Web/Form/Field/Upload.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,47 @@
+use warnings;
+use strict;
+ 
+package Jifty::Web::Form::Field::Upload;
+
+=head1 NAME
+
+Jifty::Web::Form::Field::Upload - File upload field
+
+=head1 DESCRIPTION
+
+An input field that renders using C<< <input type="file" /> >>.  The
+argument value that the action recieves from this field via
+L<Jifty::Action/argument_value> will be a filehandle, which can be
+read in the usual ways.
+
+=cut
+
+use base qw/Jifty::Web::Form::Field/;
+
+=head2 render_widget
+
+Renders the file upload widget.
+
+=cut
+
+sub render_widget {
+    my $self  = shift;
+    my $field = qq!<input type="file" name="@{[ $self->input_name ]}" !;
+    $field .= $self->_widget_class();
+        $field .= qq!/>!;
+    Jifty->web->out($field);
+    '';
+}
+
+=head2 render_value
+
+The 'value', rendered, is empty so that BLOBs and the like don't get
+streamed to the browser.
+
+=cut
+
+sub render_value {
+    '';
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Web/Form/Link.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Web/Form/Link.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,120 @@
+use warnings;
+use strict;
+
+package Jifty::Web::Form::Link;
+
+=head1 NAME
+
+Jifty::Web::Form::Link - Creates a state-preserving HTML link
+
+=head1 DESCRIPTION
+
+Describes an HTML link that may be AJAX-enabled.  Most of the
+computation of this comes from L<Jifty::Web::Form::Clickable>, which
+generates L<Jifty::Web::Form::Link>s.
+
+=cut
+
+use base 'Jifty::Web::Form::Element';
+
+# Since we don't inherit from Form::Field, we don't otherwise stringify.
+# We need the anonymous sub because otherwise the method of the base class is
+# always called, instead of the appropriate overridden method in a possible
+# child class.
+use overload '""' => sub { shift->render }, bool => sub { 1 };
+
+=head2 accessors
+
+Link adds C<url> and C<escape_label> to the list of possible accessors
+and mutators, in addition to those offered by
+L<Jifty::Web::Form::Element/accessors>.
+
+=cut
+
+sub accessors { shift->SUPER::accessors(), qw(url escape_label tooltip target); }
+__PACKAGE__->mk_accessors(qw(url escape_label tooltip target));
+
+=head2 new PARAMHASH
+
+Creates a new L<Jifty::Web::Form::Link> object.  Possible arguments to
+the C<PARAMHASH> are:
+
+=over
+
+=item url (optional)
+
+The URL of the link; defaults to the current URL.
+
+=item tooltip
+
+Additional information about the link.
+
+=item target
+
+Target of the link.  Mostly useful when specified as "_blank" to open
+a new window or as the name of a already existing window.
+
+=item escape_label
+
+HTML escape the label and tooltip? Defaults to true
+
+=item anything from L<Jifty::Web::Form::Element>
+
+Any parameter which L<Jifty::Web::Form::Element/new> can take.
+
+=back
+
+=cut
+
+sub new {
+    my $class = shift;
+    my $args = ref($_[0]) ? $_[0] : {@_};
+    my $self  = $class->SUPER::new(
+      { url          => $ENV{PATH_INFO},
+        label        => "Click me!",
+        tooltip      => undef,
+        escape_label => 1,
+        class        => '',
+        target       => '' }, $args );
+
+    return $self;
+}
+
+=head2 url [URL]
+
+Gets or sets the URL that the link links to.
+
+=cut
+
+=head2 render
+
+Render the string of the link, including any necessary javascript.
+
+=cut
+
+sub render {
+    my $self = shift;
+
+    my $label = $self->label;
+    $label = Jifty->web->escape( $label )
+        if ( $self->escape_label );
+
+    my $tooltip = $self->tooltip;
+    $tooltip = Jifty->web->escape( $tooltip )
+        if ( $tooltip and $self->escape_label );
+
+    Jifty->web->out(qq(<a));
+    Jifty->web->out(qq( id="@{[$self->id]}"))         if $self->id;
+    Jifty->web->out(qq( class="@{[$self->class]}"))   if $self->class;
+    Jifty->web->out(qq( title="@{[$self->tooltip]}")) if $tooltip;
+    Jifty->web->out(qq( target="@{[$self->target]}")) if $self->target;
+    Jifty->web->out(qq( accesskey="@{[$self->key_binding]}")) if $self->key_binding;
+    Jifty->web->out(qq( href="@{[Jifty->web->escape($self->url)]}"));
+    Jifty->web->out( $self->javascript() );
+    Jifty->web->out(qq(>$label</a>));
+    $self->render_key_binding();
+
+    return ('');
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Web/Menu.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Web/Menu.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,284 @@
+package Jifty::Web::Menu;
+
+use base qw/Class::Accessor::Fast/;
+use URI;
+use Scalar::Util ();
+
+__PACKAGE__->mk_accessors(qw(label parent sort_order link target escape_label class));
+
+=head2 new PARAMHASH
+
+Creates a new L<Jifty::Web::Menu> object.  Possible keys in the
+I<PARAMHASH> are C<label>, C<parent>, C<sort_order>, C<url>, and
+C<active>.  See the subroutines with the respective name below for
+each option's use.
+
+=cut
+
+sub new {
+    my $package = shift;
+    # Class::Accessor only wants a hashref;
+    $package->SUPER::new( ref($_[0]) eq 'HASH' ? @_ : {@_} );
+
+}
+
+
+=head2 label [STRING]
+
+Sets or returns the string that the menu item will be displayed as.
+
+=head2 parent [MENU]
+
+Gets or sets the parent L<Jifty::Web::Menu> of this item; this defaults
+to null.
+
+=head2 sort_order [NUMBER]
+
+Gets or sets the sort order of the item, as it will be displayed under
+the parent.  This defaults to adding onto the end.
+
+
+=head2 link
+
+Gets or set a Jifty::Web::Link object that represents this menu item. If
+you're looking to do complex ajaxy things with menus, this is likely
+the option you want.
+
+=head2 target [STRING]
+
+Get or set the frame or pseudo-target for this link. something like L<_blank>
+
+=cut
+
+=head2 class [STRING]
+
+Gets or sets the CSS class the link should have in addition to the default
+classes.  This is only used if C<link> isn't specified.
+
+=head2 url
+
+Gets or sets the URL that the menu's link goes to.  If the link
+provided is not absolute (does not start with a "/"), then is is
+treated as relative to it's parent's url, and made absolute.
+
+=cut
+
+sub url {
+    my $self = shift;
+    $self->{url} = shift if @_;
+
+    $self->{url} = URI->new_abs($self->{url}, $self->parent->url . "/")->as_string
+      if $self->parent and $self->parent->url;
+
+    return $self->{url};
+}
+
+=head2 active [BOOLEAN]
+
+Gets or sets if the menu item is marked as active.  Setting this
+cascades to all of the parents of the menu item.
+
+=cut
+
+sub active {
+    my $self = shift;
+    if (@_) {
+        $self->{active} = shift;
+        $self->parent->active($self->{active}) if defined $self->parent;
+    }
+    return $self->{active};
+}
+
+=head2 child KEY [, PARAMHASH]
+
+If only a I<KEY> is provided, returns the child with that I<KEY>.
+
+Otherwise, creates or overwrites the child with that key, passing the
+I<PARAMHASH> to L<Jifty::Web::Menu/new>.  Additionally, the paramhash's
+C<label> defaults to the I<KEY>, and the C<sort_order> defaults to the
+pre-existing child's sort order (if a C<KEY> is being over-written) or
+the end of the list, if it is a new C<KEY>.
+
+=cut
+
+sub child {
+    my $self = shift;
+    my $key = shift;
+    if (@_) {
+        $self->{children}{$key} = Jifty::Web::Menu->new({parent => $self,
+                                                        sort_order => ($self->{children}{$key}{sort_order}
+                                                                       || scalar values %{$self->{children}}),
+                                                        label => $key,
+                                                        escape_label => 1,
+                                                        @_
+                                                       });
+        Scalar::Util::weaken($self->{children}{$key}{parent});
+        # Activate it
+        if (my $url = $self->{children}{$key}->url and Jifty->web->request) {
+            # XXX TODO cleanup for mod_perl
+            my $base_path = Jifty->web->request->path;
+            chomp($base_path);
+        
+            $base_path =~ s/index\.html$//g;
+            $base_path =~ s/\/+$//g;
+            $url =~ s/\/+$//i;
+    
+            if ($url eq $base_path) {
+                $self->{children}{$key}->active(1); 
+            }
+        }
+    }
+
+    return $self->{children}{$key}
+}
+
+=head2 active_child
+
+Returns the first active child node, or C<undef> is there is none.
+
+=cut
+
+sub active_child {
+    my $self = shift;
+    foreach my $kid ($self->children) {
+        return $kid if $kid->active;
+    }
+    return undef;
+}
+
+
+=head2 delete KEY
+
+Removes the child with the provided I<KEY>.
+
+=cut
+
+sub delete {
+    my $self = shift;
+    my $key = shift;
+    delete $self->{children}{$key};
+}
+
+=head2 children
+
+Returns the children of this menu item in sorted order; as an array in
+array context, or as an array reference in scalar context.
+
+=cut
+
+sub children {
+    my $self = shift;
+    my @kids = values %{$self->{children} || {}};
+    @kids = sort {$a->sort_order <=> $b->sort_order} @kids;
+    return wantarray ? @kids : \@kids;
+}
+
+
+=head2 render_as_menu
+
+Render this menu with HTML markup as multiple dropdowns, suitable for
+an application's menu
+
+=cut
+
+sub render_as_menu {
+    my $self = shift;
+    my @kids = $self->children;
+    Jifty->web->out(qq{<ul class="menu">});
+
+    for (@kids) {
+	$_->render_as_hierarchical_menu_item();
+    }
+    Jifty->web->out(qq{</ul>});
+    '';
+}
+
+
+=head2 render_as_context_menu
+
+Render this menu with html markup as an inline dropdown menu.
+
+
+=cut
+
+
+sub render_as_context_menu {
+	my $self = shift;
+    	Jifty->web->out( qq{<ul class="context_menu">});
+	$self->render_as_hierarchical_menu_item();
+	Jifty->web->out(qq{</ul>});
+	'';
+}
+
+=head2 render_as_hierarchical_menu_item
+
+Render an <li> for this item. suitable for use in a regular or contextual
+menu. Currently renders one level of submenu, if it exists.
+
+=cut
+
+sub render_as_hierarchical_menu_item {
+    my $self = shift;
+    my %args = (
+        class => '',
+        @_
+    );
+    my @kids = $self->children;
+    my $id   = Jifty->web->serial;
+    Jifty->web->out( qq{<li class="toplevel }
+            . ( $self->active ? 'open active' : 'closed' ) . qq{">}
+            . qq{<span class="title">} );
+    Jifty->web->out( $self->as_link );
+    Jifty->web->out(qq{</span>});
+    if (@kids) {
+        Jifty->web->out(
+            qq{<span class="expand"><a href="#" onclick="Jifty.ContextMenu.hideshow('}
+                . $id
+                . qq{'); return false;">&nbsp;</a></span>}
+                . qq{<ul id="}
+                . $id
+                . qq{">} );
+        for (@kids) {
+            Jifty->web->out(qq{<li class="submenu }.($_->active ? 'active' : '' ).qq{">});
+
+            # We should be able to get this as a string.
+            # Either stringify the link object or output the label
+            # This is really icky. XXX TODO
+            Jifty->web->out( $_->as_link );
+            Jifty->web->out("</li>");
+        }
+        Jifty->web->out(qq{</ul>});
+    }
+    Jifty->web->out(qq{</li>});
+    '';
+
+}
+
+=head2 as_link
+
+Return this menu item as a C<Jifty::Web::Link>, either the one we were
+initialized with or a new one made from the C</label> and C</url>
+
+If there's no C</url> and no C</link>, renders just the label.
+
+=cut
+
+sub as_link {
+    my $self = shift;
+    # Stringifying $self->link may return '' and output something, so
+    # we need to be careful to not stringify it more than once, and to
+    # check it for defined-ness, not truth.
+    if ( defined (my $str = $self->link) ) {
+        return $str;
+    } elsif ( $self->url ) {
+        return Jifty->web->link( label => _( $self->label ),
+                                 url   => $self->url,
+                                 escape_label => $self->escape_label,
+                                 target => $self->target,
+                                 class => $self->class );
+    } else {
+        return _( $self->label );
+    }
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Web/PageRegion.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Web/PageRegion.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,339 @@
+use warnings;
+use strict;
+ 
+package Jifty::Web::PageRegion;
+
+=head1 NAME
+
+Jifty::Web::PageRegion - Defines a page region
+
+=head1 DESCRIPTION
+
+Describes a region of the page which contains a Mason fragment which
+can be updated via AJAX or via query parameters.
+
+=cut
+
+use base qw/Jifty::Object Class::Accessor::Fast/;
+__PACKAGE__->mk_accessors(qw(name default_path default_arguments qualified_name parent region_wrapper));
+use Jifty::JSON;
+
+=head2 new PARAMHASH
+
+Creates a new page region.  The possible arguments in the C<PARAMHASH>
+are:
+
+=over
+
+=item name
+
+The (unqualified) name of the region.  This is used to generate a
+unique id -- it should consist of only letters and numbers.
+
+=item path
+
+The path to the fragment that this page region contains.  Defaults to
+C</__jifty/empty>, which, as its name implies, is empty.
+
+=item defaults (optional)
+
+Specifies an optional set of parameter defaults.  These should all be
+simple scalars, as they might be passed across HTTP if AJAX is used.
+
+=item parent (optional)
+
+The parent L<Jifty::Web::PageRegion> that this region is enclosed in.
+
+=item region_wrapper (optional)
+
+A boolean; whether or not the region, when rendered, will include the
+HTML region preamble that makes Javascript aware of its presence.
+Defaults to true.
+
+=back
+
+=cut
+
+sub new {
+    my $class = shift;
+    my $self = bless {}, $class;
+
+    my %args = (
+                name => undef,
+                path => "/__jifty/empty",
+                defaults => {},
+                parent => undef,
+                region_wrapper => 1,
+                @_
+               );
+
+    # Name is required
+    if (not defined $args{name}) {
+        warn "Name is required for page regions.";
+        return;
+    }
+
+    # References don't go over HTTP very well
+    if (grep {ref $_} values %{$args{defaults}}) {
+        warn "Reference '$args{defaults}{$_}' passed as default for '$_' to region '$args{name}'"
+          for grep {ref $args{defaults}{$_}} keys %{$args{defaults}};
+        return;
+    }
+
+    $self->name($args{name});
+    $self->qualified_name(Jifty->web->qualified_region($self));
+    $self->default_path($args{path});
+    $self->default_arguments($args{defaults});
+    $self->arguments({});
+    $self->parent($args{parent} || Jifty->web->current_region);
+    $self->region_wrapper($args{region_wrapper});
+
+    # Keep track of the fully qualified name (which should be unique)
+    $self->log->warn("Repeated region: " . $self->qualified_name)
+        if Jifty->web->get_region( $self->qualified_name );
+    Jifty->web->{'regions'}{ $self->qualified_name } = $self;
+
+    return $self;
+}
+
+=head2 name [NAME]
+
+Gets or sets the name of the page region.
+
+=cut
+
+=head2 qualified_name [NAME]
+
+Gets or sets the fully qualified name of the page region.  This should
+be unique on a page.  This is usually set by L</enter>, based on the
+page regions that this region is inside.  See
+L<Jifty::Web/qualified_region>.
+
+=cut
+
+=head2 default_path [PATH]
+
+Gets or sets the default path of the fragment.  This is overridden by
+L</path>.
+
+=cut
+
+=head2 path [PATH]
+
+Gets or sets the path that the fragment actually contains.  This
+overrides L</default_path>.
+
+=cut
+
+sub path {
+    my $self = shift;
+    $self->{path} = shift if @_;
+    return $self->{path} || $self->default_path;
+}
+
+=head2 default_argument NAME [VALUE]
+
+Gets or sets the default value of the C<NAME> argument.  This is used
+as a fallback, and also to allow generated links to minimize the
+amount of state they must transmit.
+
+=cut
+
+sub default_argument {
+    my $self = shift;
+    my $name = shift;
+    $self->{default_arguments}{$name} = shift if @_;
+    return $self->{default_arguments}{$name} || '';
+}
+
+=head2 argument NAME [VALUE]
+
+Gets or sets the actual run-time value of the page region.  This
+usually comes from HTTP parameters.  It overrides the
+L</default_argument> of the same C<NAME>.
+
+=cut
+
+sub argument {
+    my $self = shift;
+    my $name = shift;
+    $self->{arguments}{$name} = shift if @_;
+    return $self->{arguments}{$name} || $self->default_argument($name);
+}
+
+=head2 arguments [HASHREF]
+
+Sets all arguments at once, or returns all arguments.  The latter will
+also include all default arguments.
+
+=cut
+
+sub arguments {
+    my $self = shift;
+    $self->{arguments} = shift if @_;
+    return { %{$self->{default_arguments}}, %{$self->{arguments}}};
+}
+
+=head2 enter
+
+Enters the region; this sets the qualified name based on
+L<Jifty::Web/qualified_region>, and uses that to pull runtime values
+for the L</path> and L</argument>s from the
+L<Jifty::Request/state_variables>.
+
+=cut
+
+sub enter {
+    my $self = shift;
+
+    # Add ourselves to the region stack
+    push @{Jifty->web->{'region_stack'}}, $self;
+
+    # Merge in the settings passed in via state variables
+    for my $var (Jifty->web->request->state_variables) {
+        my $key = $var->key;
+        my $value = $var->value || '';
+
+        if ($key =~ /^region-(.*?)\.(.*)/ and $1 eq $self->qualified_name and $value ne $self->default_argument($2)) {
+            $self->argument($2 => $value);
+        }
+        if ($key =~ /^region-(.*)$/ and $1 eq $self->qualified_name and $value ne $self->default_path) {
+            $self->path($value);
+        }
+
+        # We should always inherit the state variables from the uplevel request.
+        Jifty->web->set_variable($key => $value);
+    }
+}
+
+=head2 exit 
+
+Exits the page region, if it is the most recent one.  Normally, you
+won't need to call this by hand; however, if you are calling L</enter>
+by hand, you will need to call the corresponding C<exit>.
+
+=cut
+
+sub exit {
+    my $self = shift;
+
+    if (Jifty->web->current_region != $self) {
+        $self->log->warn("Attempted to exit page region ".$self->qualified_name." when it wasn't the most recent");
+    } else {
+        pop @{Jifty->web->{'region_stack'}};
+    }
+}
+
+=head2 as_string
+
+Deals with the bulk of the effort to show a page region.  Returns a
+string of the fragment and associated javascript (if any).
+
+=cut
+
+sub as_string {
+    my $self = shift;
+
+    if (Jifty->web->current_region ne $self) {
+        # XXX TODO: Possibly we should just call ->enter
+        warn "Attempt to call as_string on a region which is not the current region";
+        return "";
+    }
+    
+    my %arguments = %{ $self->arguments };
+
+    # undef arguments cause warnings. We hatesses the warnings, we do.
+    defined $arguments{$_} or delete $arguments{$_} for keys %arguments;
+    my $result = "";
+
+    # We need to tell the browser this is a region and what its
+    # default arguments are as well as the path of the "fragment".  We
+    # do this by passing in a snippet of javascript which encodes this
+    # information.  We only render this region wrapper if we're asked
+    # to (which is true by default)
+    if ( $self->region_wrapper ) {
+        $result .= qq|<script type="text/javascript">\n|
+            . qq|new Region('| . $self->qualified_name . qq|',|
+            . Jifty::JSON::objToJson( \%arguments, { singlequote => 1 } ) . qq|,| 
+            . qq|'| . $self->path . qq|',|
+            . ( $self->parent ? qq|'| . $self->parent->qualified_name . qq|'| : q|null|)
+            . qq|);\n|
+            . qq|</script>|
+            . qq|<div id="region-| . $self->qualified_name . qq|">|;
+    }
+
+    $self->render_as_subrequest(\$result, \%arguments);
+    $result .= qq|</div>| if ( $self->region_wrapper );
+
+    return $result;
+}
+
+=head2 render_as_subrequest
+
+=cut
+
+sub render_as_subrequest {
+    my ($self, $out_method, $arguments, $enable_actions) = @_;
+
+    my $orig_out = Jifty->handler->mason->interp->out_method || \&Jifty::View::Mason::Handler::out_method;
+
+    Jifty->handler->mason->interp->out_method($out_method);
+
+    # Make a fake request and throw it at the dispatcher
+    my $subrequest = Jifty->web->request->clone;
+    $subrequest->argument( region => $self );
+    # XXX: use ->arguments?
+    $subrequest->argument( $_ => $arguments->{$_}) for keys %$arguments;
+    $subrequest->path( $self->path );
+    $subrequest->top_request( Jifty->web->request->top_request );
+
+    # Remove all of the actions
+    unless ($enable_actions) {
+	$_->active(0) for ($subrequest->actions);
+    }
+    # $subrequest->clear_actions;
+    local Jifty->web->{request} = $subrequest;
+
+    # While we're inside this region, have Mason to tack its response
+    # onto a variable and not send headers when it does so
+    #XXX TODO: There's gotta be a better way to localize it
+    my $region_content = '';
+
+    # Call into the dispatcher
+    Jifty->handler->dispatcher->handle_request;
+
+    Jifty->handler->mason->interp->out_method($orig_out);
+
+    return;
+}
+
+=head2 render
+
+Calls L</enter>, outputs the results of L</as_string>, and then calls
+L</exit>.  Returns an empty string.
+
+=cut
+
+sub render {
+    my $self = shift;
+
+    $self->enter;
+    Jifty->web->out($self->as_string);
+    $self->exit;
+    "";
+}
+
+=head2 get_element [RULES]
+
+Returns a CSS2 selector which selects only elements under this region
+which fit the C<RULES>.  This method is used by AJAX code to specify
+where to add new regions.
+
+=cut
+
+sub get_element {
+    my $self = shift;
+    return "#region-" . $self->qualified_name . ' ' . join(' ', @_);
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Web/Session.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Web/Session.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,322 @@
+use warnings;
+use strict;
+
+package Jifty::Web::Session;
+use base qw/Jifty::Object/;
+use CGI::Cookie ();
+use DateTime ();
+ 
+=head1 NAME
+
+Jifty::Web::Session - A Jifty session handler
+
+=cut
+
+=head2 new
+
+Returns a new, empty session.
+
+=cut
+
+sub new {
+    my $class = shift;
+
+    my $session_class = Jifty->config->framework('Web')->{'SessionClass'};
+    if ($session_class and $class ne $session_class) {
+        Jifty::Util->require( $session_class );
+        return $session_class->new(@_);
+    }
+    else {
+        return bless {}, $class;
+    }
+}
+
+=head2 id
+
+Returns the session's id if it has been loaded, or C<undef> otherwise.
+
+=cut
+
+sub id {
+    my $self = shift;
+    return $self->loaded ? $self->_session->session_id : undef;
+}
+
+=head2 load [ID]
+
+Load up the current session from the given C<ID>, or the appropriate
+cookie (see L</cookie_name>) otherwise.  If both of those fail,
+creates a session in the database.
+
+=cut
+
+sub load {
+    my $self       = shift;
+    my $session_id = shift;
+
+   $session_id ||= $self->_get_session_id_from_client();
+
+    my $session = Jifty::Model::Session->new;
+    $session->load_by_cols(
+        session_id => $session_id,
+        key_type   => "session"
+        )
+        if $session_id;
+
+    $session->create( key_type => "session" ) unless $session->id;
+    $self->_session($session);
+    $self->{cache} = undef;
+}
+
+sub _get_session_id_from_client {
+        my $self = shift;
+        my %cookies    = CGI::Cookie->fetch();
+        my $cookie_name = $self->cookie_name;
+        my $session_id
+            = $cookies{$cookie_name} ? $cookies{$cookie_name}->value() : undef;
+}
+
+=head2 unload
+
+Flush the session, and leaves the session object blank.
+
+=cut
+
+sub unload {
+    my $self = shift;
+
+    return unless $self->loaded;
+    $self->_session(undef);
+}
+
+=head2 loaded
+
+Returns true if the session has already been loaded.
+
+=cut
+
+sub loaded {
+    my $self = shift;
+    return $self->_session;
+}
+
+sub _session {
+    my $self = shift;
+    $self->{'_session'} = shift if (@_);
+    return ( $self->{'_session'} );
+}
+
+=head2 get KEY [TYPE]
+
+Returns the value for C<KEY> for the current user's session.  C<TYPE>,
+which defaults to "key", allows accessing of other namespaces in the
+session, including "metadata" and "continuation".
+
+=cut
+
+sub get {
+    my $self     = shift;
+    my $key      = shift;
+    my $key_type = shift || "key";
+
+    return undef unless $self->loaded;
+
+    if ($key_type eq "continuation" or $key_type eq "session") {
+        my $setting = Jifty::Model::Session->new;
+        $setting->load_by_cols(
+            session_id => $self->id,
+            key_type   => $key_type,
+            data_key   => $key
+        );
+        return $setting->value;
+    } else {
+        unless ($self->{cache}) {
+            my $settings = Jifty::Model::SessionCollection->new;
+            $settings->limit( column => 'session_id', value => $self->id, case_sensitive => '1' );
+            $settings->limit( column => 'key_type',   value => 'continuation', operator => '!=', entry_aggregator => 'and', case_sensitive => '1' );
+            $settings->limit( column => 'key_type',   value => 'session', operator => '!=', entry_aggregator => 'and', case_sensitive => '1' );
+            while (my $row = $settings->next) {
+                $self->{cache}{$row->key_type}{$row->data_key} = $row->value;
+            }
+        }
+
+        return $self->{cache}{$key_type}{$key};
+    }
+
+}
+
+=head2 set KEY => VALUE, [TYPE]
+
+Sets the value C<VALUE> for C<KEY> for the session.  C<TYPE>, which
+defaults to "key", allows values to be set in other namespaces,
+including "metadata" and "continuation". C<VALUE> can be an arbitrary
+perl data structue -- C<Jifty::Web::Session> will serialize it for
+you.
+
+=cut
+
+sub set {
+    my $self     = shift;
+    my $key      = shift;
+    my $value    = shift;
+    my $key_type = shift || "key";
+
+    return undef unless $self->loaded;
+    $self->_session->set_updated( DateTime->now );
+
+    my $setting = Jifty::Model::Session->new;
+    $setting->load_by_cols(
+        session_id => $self->id,
+        key_type   => $key_type,
+        data_key   => $key
+    );
+    if ( $setting->id ) {
+        $setting->set_value($value);
+    } else {
+        $setting->create(
+            session_id => $self->id,
+            key_type   => $key_type,
+            data_key   => $key,
+            value      => $value
+        );
+    }
+
+    $self->{cache}{$key_type}{$key} = $value
+      if $self->{cache};
+
+}
+
+=head2 remove KEY, [TYPE]
+
+Remove key C<KEY> from the cache.  C<TYPE> defaults to "key".
+
+=cut
+
+sub remove {
+    my $self     = shift;
+    my $key      = shift;
+    my $key_type = shift || "key";
+
+    return undef unless $self->loaded;
+    $self->_session->set_updated( DateTime->now );
+
+    my $setting = Jifty::Model::Session->new;
+    $setting->load_by_cols(
+        session_id => $self->id,
+        key_type   => $key_type,
+        data_key   => $key
+    );
+    $setting->delete if $setting->id;
+}
+
+=head2 set_continuation ID CONT
+
+Stores a continuation in the session.
+
+=cut
+
+sub set_continuation {
+    my $self = shift;
+    $self->set( @_, "continuation" );
+}
+
+=head2 get_continuation ID
+
+Pulls a continuation from the current session. Expects a continuation
+C<ID>.
+
+=cut
+
+sub get_continuation {
+    my $self = shift;
+    $self->get( @_, "continuation" );
+
+}
+
+=head2 remove_continuation ID
+
+Removes a continuation with id C<ID> from the store.
+
+=cut
+
+sub remove_continuation {
+    my $self = shift;
+    $self->remove( @_, "continuation" );
+}
+
+=head2 continuations
+
+Return a hash of all the continuations in this session, keyed by the
+continuations' C<id>.
+
+=cut
+
+sub continuations {
+    my $self = shift;
+
+    return () unless $self->loaded;
+
+    my $conts = Jifty::Model::SessionCollection->new;
+    $conts->limit( column => "key_type",   value => "continuation", case_sensitive => '1' );
+    $conts->limit( column => "session_id", value => $self->id, case_sensitive=> '1' );
+
+    my %continuations;
+    $continuations{ $_->key } = $_->value while $_ = $conts->next;
+    return %continuations;
+}
+
+=head2 set_cookie
+
+Sets the session cookie.
+
+=cut
+
+sub set_cookie {
+    my $self = shift;
+
+    my $cookie_name = $self->cookie_name;
+    my %cookies     = CGI::Cookie->fetch();
+    my $session_id
+        = $cookies{$cookie_name} ? $cookies{$cookie_name}->value() : undef;
+    my $cookie = new CGI::Cookie(
+        -name    => $cookie_name,
+        -value   => $self->id,
+        -expires => $self->expires,
+    );
+
+    # XXX TODO might need to change under mod_perl
+    if ( not $cookies{$cookie_name}
+        or ( $cookies{$cookie_name} ne $cookie->as_string ) )
+    {
+        Jifty->web->response->add_header(
+            'Set-Cookie' => $cookie->as_string );
+    }
+}
+
+=head2 cookie_name
+
+Returns the current session's cookie_name -- it is the same for all
+users, but varies according to the port the server is running on.
+
+=cut
+
+sub cookie_name {
+    my $self = shift;
+    my $cookie_name = "JIFTY_SID_" . ( $ENV{'SERVER_PORT'} || 'NOPORT' );
+    return ($cookie_name);
+}
+
+=head2 expires [VALUE]
+
+Get or set the session's expiration date, in a format expected by
+Cache::Cache.
+
+=cut
+
+sub expires {
+    my $self = shift;
+    $self->set( 'expires' => shift, "metadata" ) if @_;
+    return ( $self->get( 'expires', "metadata" ) );
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/Web/Session/ClientSide.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/Web/Session/ClientSide.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,239 @@
+package Jifty::Web::Session::ClientSide;
+
+=head1 NAME
+
+Jifty::Web::Session::ClientSide - Session handler for client-side sessions
+
+=head1 SYNOPSIS
+
+In your F<etc/config.yml>:
+
+  framework:
+    Web:
+      SessionClass: Jifty::Web::Session::ClientSide
+      SessionSecret: secret_passphrase
+
+=cut
+
+use strict;
+use warnings;
+use base 'Jifty::Web::Session';
+use Jifty::Model::Session();
+use Jifty::YAML ();
+use Compress::Zlib ();
+use Crypt::CBC ();
+use Crypt::Rijndael ();
+use CGI::Cookie::Splitter ();
+
+my $session_key;
+my $splitter = CGI::Cookie::Splitter->new;
+
+=head2 new
+
+Returns a new, empty session handler, subclassing L<Jifty::Web::Session>.
+
+=cut
+
+sub new {
+    my $class = shift;
+    my $session_key = Jifty->config->framework('Web')->{'SessionSecret'}
+        or die "Please set SessionSecret in your framework/Web settings";
+    my $cipher = Crypt::CBC->new(
+        -key    => $session_key,
+        -cipher => 'Rijndael',
+    );
+    bless { _cipher => $cipher, _session => undef }, $class;
+}
+
+=head2 _cipher
+
+Accessor to the underlying L<Crypt::CBC> object that encapsulates the
+server-side secret.
+
+=cut
+
+sub _cipher {
+    my $self = shift;
+    $self->{'_cipher'} = shift if (@_);
+    return ( $self->{'_cipher'} );
+}
+
+=head2 id
+
+Returns the session's id if it has been loaded, or C<undef> otherwise.
+
+=cut
+
+sub id {
+    my $self = shift;
+    return $self->loaded ? $self->_session->{session_id} : undef;
+}
+
+=head2 load [ID]
+
+Load up the current session from the given C<ID>, or the appropriate
+cookie (see L<Jifty::Web::Session/cookie_name>) otherwise.
+
+If both of those fail, creates a session in memory.
+
+=cut
+
+sub load {
+    my $self       = shift;
+    my $session_id = shift;
+    my %cookies    = CGI::Cookie->fetch();
+
+    unless ($session_id) {
+        my $cookie_name = $self->cookie_name;
+        $session_id = $cookies{$cookie_name}
+            ? $cookies{$cookie_name}->value()
+            : Jifty::Model::Session->new_session_id,
+    }
+
+    my $data;
+
+    {
+        local $@;
+        eval {
+            ($data) = grep {
+                $_->name eq "JIFTY_DAT_$session_id"
+            } $splitter->join(values %cookies);
+        };
+
+        if ($@) {
+            # Reassembly of cookie failed -- start a new session
+            $session_id = Jifty::Model::Session->new_session_id;
+            warn $@;
+        }
+    }
+
+    if ($data) {
+        local $@;
+        eval {
+            $self->_session(
+                Jifty::YAML::Load(
+                    Compress::Zlib::uncompress(
+                        $self->_cipher->decrypt(
+                            $data->value
+                        )
+                    )
+                )
+            );
+            die "Session id mismatch"
+                unless $self->_session->{session_id} eq $session_id;
+            1;
+        } and return;
+        warn $@ if $@;
+    }
+
+    $self->_session({
+        session_id   => $session_id,
+        continuation => {},
+        metadata     => {},
+        key          => {},
+    });
+}
+
+=head2 get KEY [TYPE]
+
+See L<Jifty::Web::Session/get>.
+
+=cut
+
+sub get {
+    my $self     = shift;
+    my $key      = shift;
+    my $key_type = shift || "key";
+
+    return undef unless $self->loaded;
+    return $self->_session->{$key_type}{$key};
+}
+
+=head2 set KEY => VALUE, [TYPE]
+
+See L<Jifty::Web::Session/set>.
+
+=cut
+
+sub set {
+    my $self     = shift;
+    my $key      = shift;
+    my $value    = shift;
+    my $key_type = shift || "key";
+
+    return undef unless $self->loaded;
+    $self->_session->{$key_type}{$key} = $value;
+
+    # XXX - delay until the very last moment?
+    $self->flush;
+}
+
+=head2 remove KEY, [TYPE]
+
+See L<Jifty::Web::Session/remove>.
+
+=cut
+
+sub remove {
+    my $self     = shift;
+    my $key      = shift;
+    my $key_type = shift || "key";
+
+    return undef unless $self->loaded;
+    delete $self->_session->{$key_type}{$key};
+}
+
+=head2 continuations
+
+See L<Jifty::Web::Session/continuations>.
+
+=cut
+
+sub continuations {
+    my $self     = shift;
+    return () unless $self->loaded;
+    return %{ $self->_session->{continuation} };
+}
+
+=head2 unload
+
+See L<Jifty::Web::Session/unload>.
+
+=cut
+
+sub unload {
+    my $self = shift;
+    $self->flush;
+    $self->_session(undef);
+}
+
+=head2 flush
+
+Outputs the client-side session as one or more cookies.
+
+=cut
+
+sub flush {
+    my $self = shift;
+    my $session_id = $self->id or return;
+
+    my $data_cookie = CGI::Cookie->new(
+        -name    => "JIFTY_DAT_$session_id",
+        -expires => $self->expires,
+        -value   => $self->_cipher->encrypt(
+            Compress::Zlib::compress(
+                Jifty::YAML::Dump(
+                    $self->_session
+                )
+            )
+        )
+    );
+
+    foreach my $cookie ($splitter->split( $data_cookie )) {
+        Jifty->web->response->add_header(
+            'Set-Cookie' => $cookie->as_string
+        );
+    }
+}
+
+1;

Added: jifty/branches/schema-plugins/lib/Jifty/YAML.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/lib/Jifty/YAML.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,44 @@
+use warnings;
+use strict;
+
+package Jifty::YAML;
+
+=head1 NAME
+
+Jifty::YAML -- Wrapper around L<YAML>
+
+=head1 DESCRIPTION
+
+Provides a wrapper around the L<YAML> library.  If the faster L<YAML::Syck>
+is available, then it's used instead.
+
+=cut
+
+BEGIN {
+    local $@;
+    no strict 'refs';
+    no warnings 'once';
+
+    if ( eval { require YAML::Syck; YAML::Syck->VERSION(0.71) } ) {
+        *Load     = *YAML::Syck::Load;
+
+
+        require YAML;
+        # Use YAML::Dump for the moment since YAML.pm segfaults on
+        #  reading stupidly long (~20K characters) double-quoted
+        #  strings, and we need to produce YAML.pm-readable output.
+        *Dump     = *YAML::Dump;
+        #*Dump     = *YAML::Syck::Dump;
+
+        *LoadFile = *YAML::Syck::LoadFile;
+        *DumpFile = *YAML::Syck::DumpFile;
+    } else {
+        require YAML;
+        *Load     = *YAML::Load;
+        *Dump     = *YAML::Dump;
+        *LoadFile = *YAML::LoadFile;
+        *DumpFile = *YAML::DumpFile;
+    }
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/AuthCASLogin/MANIFEST
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthCASLogin/MANIFEST	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,8 @@
+MANIFEST
+lib/Jifty/Plugin/AuthCASLogin.pm
+lib/Jifty/Plugin/AuthCASLogin/Action/CASLogin.pm
+lib/Jifty/Plugin/AuthCASLogin/Action/CASLogout.pm
+lib/Jifty/Plugin/AuthCASLogin/Dispatcher.pm
+Makefile.PL
+share/web/templates/caslogin
+share/web/templates/caslogout

Added: jifty/branches/schema-plugins/plugins/AuthCASLogin/Makefile.PL
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthCASLogin/Makefile.PL	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,11 @@
+use inc::Module::Install;
+name('Jifty-Plugin-AuthCASLogin');
+license('Perl');
+version('0.01');
+requires('Jifty' => '0.60912');
+requires('Jifty::Plugin::Login');
+requires('AuthCAS');
+
+install_share;
+
+WriteAll;

Added: jifty/branches/schema-plugins/plugins/AuthCASLogin/debian/changelog
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthCASLogin/debian/changelog	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,12 @@
+libjifty-plugin-authcaslogin-perl (0-2) unstable; urgency=low
+
+  *  update
+
+ -- AGOSTINI Yves <agostini at univ-metz.fr>  Fri, 22 Dec 2006 17:39:23 +0100
+
+libjifty-plugin-authcaslogin-perl (0-1) unstable; urgency=low
+
+  * Initial Release.
+
+ -- AGOSTINI Yves <agostini at univ-metz.fr>  Fri, 17 Nov 2006 14:29:45 +0100
+

Added: jifty/branches/schema-plugins/plugins/AuthCASLogin/debian/compat
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthCASLogin/debian/compat	Mon Jan 29 21:05:02 2007
@@ -0,0 +1 @@
+4

Added: jifty/branches/schema-plugins/plugins/AuthCASLogin/debian/control
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthCASLogin/debian/control	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,18 @@
+Source: libjifty-plugin-authcaslogin-perl
+Section: perl
+Priority: optional
+Build-Depends: debhelper (>= 4.0.2)
+Build-Depends-Indep: perl (>= 5.8.0-7)
+Maintainer: AGOSTINI Yves <agostini at univ-metz.fr>
+Standards-Version: 3.6.1
+
+Package: libjifty-plugin-authcaslogin-perl
+Architecture: all
+Depends: ${perl:Depends}, ${misc:Depends}, libauthcas-perl
+Description:  Jifty::Plugin::AuthCASLogin
+ MUST BE USED WITH Login PLUGIN
+ .
+ Add cas users in Jifty::Plugin::Login::Model::User. 
+ Distinct id for cas users is email field with login at CAS.user
+ .
+ This description was automagically extracted from the module by dh-make-perl.

Added: jifty/branches/schema-plugins/plugins/AuthCASLogin/debian/copyright
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthCASLogin/debian/copyright	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,7 @@
+This is the debian package for the  module.
+It was created by AGOSTINI Yves <agostini at univ-metz.fr> using dh-make-perl.
+
+This copyright info was automatically extracted from the perl module.
+It may not be accurate, so you better check the module sources
+if don't want to get into legal troubles.
+

Added: jifty/branches/schema-plugins/plugins/AuthCASLogin/debian/files
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthCASLogin/debian/files	Mon Jan 29 21:05:02 2007
@@ -0,0 +1 @@
+libjifty-plugin-authcaslogin-perl_0-2_all.deb perl optional

Added: jifty/branches/schema-plugins/plugins/AuthCASLogin/debian/rules
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthCASLogin/debian/rules	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,83 @@
+#!/usr/bin/make -f
+# This debian/rules file is provided as a template for normal perl
+# packages. It was created by Marc Brockschmidt <marc at dch-faq.de> for
+# the Debian Perl Group (http://pkg-perl.alioth.debian.org/) but may
+# be used freely wherever it is useful.
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+# If set to a true value then MakeMaker's prompt function will
+# always return the default without waiting for user input.
+export PERL_MM_USE_DEFAULT=1
+
+PACKAGE=$(shell dh_listpackages)
+
+ifndef PERL
+PERL = /usr/bin/perl
+endif
+
+TMP     =$(CURDIR)/debian/$(PACKAGE)
+
+build: build-stamp
+build-stamp:
+	dh_testdir
+
+	# Add commands to compile the package here
+	$(PERL) Makefile.PL INSTALLDIRS=vendor
+	$(MAKE) OPTIMIZE="-Wall -O2 -g"
+
+	touch build-stamp
+
+clean:
+	dh_testdir
+	dh_testroot
+
+	# Add commands to clean up after the build process here
+	-$(MAKE) distclean
+
+	dh_clean build-stamp install-stamp
+
+install: build install-stamp
+install-stamp:
+	dh_testdir
+	dh_testroot
+	dh_clean -k
+
+	# Add commands to install the package into debian/$PACKAGE_NAME here
+	$(MAKE) test
+	$(MAKE) install DESTDIR=$(TMP) PREFIX=/usr
+
+	# As this is a architecture independent package, we are not
+	# supposed to install stuff to /usr/lib. MakeMaker creates
+	# the dirs, we delete them from the deb:
+	rmdir --ignore-fail-on-non-empty --parents $(TMP)/usr/lib/perl5
+
+	touch install-stamp
+
+binary-arch:
+# We have nothing to do by default.
+
+binary-indep: build install
+	dh_testdir
+	dh_testroot
+#	dh_installcron
+#	dh_installmenu
+#	dh_installexamples
+	dh_installdocs 
+	dh_installchangelogs 
+	dh_perl
+	dh_link
+	dh_strip
+	dh_compress
+	dh_fixperms
+	dh_installdeb
+	dh_gencontrol
+	dh_md5sums
+	dh_builddeb
+
+source diff:                                                                  
+	@echo >&2 'source and diff are obsolete - use dpkg-source -b'; false
+
+binary: binary-indep binary-arch
+.PHONY: build clean binary-indep binary-arch binary

Added: jifty/branches/schema-plugins/plugins/AuthCASLogin/lib/Jifty/Plugin/AuthCASLogin.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthCASLogin/lib/Jifty/Plugin/AuthCASLogin.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,54 @@
+use strict;
+use warnings;
+
+=head1 NAME
+
+Jifty::Plugin::AuthCASOnly
+
+=head1 DESCRIPTION
+
+B<MUST BE USED WITH Login PLUGIN>
+
+Add cas users in L<Jifty::Plugin::Login::Model::User>. 
+Distinct id for cas users is C<email> field with C<login at CAS.user>
+
+=head1 CONFIG
+
+ in etc/config.yml
+  Plugins: 
+    - Login: {}
+    - AuthCASLogin: 
+       CASserver: https://auth.univ-metz.fr
+       CAFile: /home/agostini/univ.crt
+
+=head1 SEE ALSO
+
+L<AuthCAS>
+
+=cut
+
+package Jifty::Plugin::AuthCASLogin;
+use base qw/Jifty::Plugin/;
+use AuthCAS;
+
+# Your plugin goes here.  If takes any configuration or arguments, you
+# probably want to override L<Jifty::Plugin/init>.
+
+{
+    my $CAS;
+
+    sub init {
+        my $self = shift;
+        my %args = @_;
+
+    	my $cafile = $args{CAFile};
+    	my $casserver = $args{CASserver};
+    	$CAS = new AuthCAS(casUrl => $casserver, CAFile => $cafile);
+    }
+
+    sub CAS {
+	   return $CAS;
+    }
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/AuthCASLogin/lib/Jifty/Plugin/AuthCASLogin/Action/CASLogin.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthCASLogin/lib/Jifty/Plugin/AuthCASLogin/Action/CASLogin.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,102 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Jifty::Plugin::AuthCASLogin::Action::CASLogin
+
+=cut
+
+package Jifty::Plugin::AuthCASLogin::Action::CASLogin;
+use base qw/Jifty::Action Jifty::Plugin::Login Jifty::Plugin::AuthCASLogin/;
+#use AuthCAS;
+
+
+=head2 arguments
+
+Return the ticket form field
+
+=cut
+
+sub arguments {
+    return (
+        {
+            ticket => {
+                label          => 'cas ticket',
+           #     mandatory      => 1,
+                ajax_validates => 1,
+            },
+
+        }
+    );
+
+}
+
+=head2 validate_ticket ST
+
+for ajax_validates
+Makes sure that the ticket submitted is legal.
+
+
+=cut
+
+sub validate_ticket {
+    my $self  = shift;
+    my $ticket = shift;
+
+    unless ( $ticket && $ticket !~ /^[A-Za-z0-9-]+$/ ) {
+        return $self->validation_error(
+            ticket => _("That doesn't look like a valid ticket.") );
+    }
+
+
+    return $self->validation_ok('ticket');
+}
+
+
+=head2 take_action
+
+Actually check the user's password. If it's right, log them in.
+Otherwise, throw an error.
+
+
+=cut
+
+sub take_action {
+    my $self = shift;
+    my $ticket = $self->argument_value('ticket');
+
+    my $service_url = ($ENV{SERVER_PORT} == 443)?'https://':'http://'.
+    	$ENV{HTTP_HOST}.'/caslogin';
+
+    if (! $ticket) {
+        my $login_url = $self->CAS->getServerLoginURL($service_url);
+        Jifty->web->_redirect($login_url);
+        return 1;
+      }
+      
+    my $username = $self->CAS->validateST($service_url,$ticket);
+    my $error = &AuthCAS::get_errors();
+    if ($error) {
+      Jifty->log->info("CAS error: $ticket $username : $error");
+      return;
+    }
+      
+    my $LoginUser = $self->LoginUserClass();
+    my $CurrentUser = $self->CurrentUserClass();
+    my $u = $LoginUser->new( current_user => $CurrentUser->superuser );
+
+    $u->load_by_cols( email => $username.'@CAS.user');
+    my $id = $u->id;
+    if (!$id) { 
+   	($id) = $u->create(name => $username, email => $username.'@CAS.user'); 
+	}
+    Jifty->log->debug("Login user id: $id"); 
+
+    # Actually do the signin thing.
+     Jifty->web->current_user( $CurrentUser->new( id => $u->id ) );
+
+    return 1;
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/AuthCASLogin/lib/Jifty/Plugin/AuthCASLogin/Action/CASLogout.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthCASLogin/lib/Jifty/Plugin/AuthCASLogin/Action/CASLogout.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,35 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Jifty::Plugin::AuthCASLogin::Action::CASLogout
+
+=cut
+
+package Jifty::Plugin::AuthCASLogin::Action::CASLogout;
+use base qw/Jifty::Action/;
+
+=head2 arguments
+
+Return the email and password form fields
+
+=cut
+
+sub arguments {
+    return ( {} );
+}
+
+=head2 take_action
+
+Nuke the current user object
+
+=cut
+
+sub take_action {
+    my $self = shift;
+    Jifty->web->current_user(undef);
+    return 1;
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/AuthCASLogin/lib/Jifty/Plugin/AuthCASLogin/Dispatcher.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthCASLogin/lib/Jifty/Plugin/AuthCASLogin/Dispatcher.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,45 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::AuthCASLogin::Dispatcher;
+use Jifty::Dispatcher -base;
+
+# Put any plugin-specific dispatcher rules here.
+
+before 'caslogin' => run {
+ if (get('ticket')) {
+    set 'action' =>
+        Jifty->web->new_action(
+        class => 'CASLogin',
+        moniker => 'casloginbox',
+	arguments => { ticket => get('ticket') },
+    );
+#    set 'next' => Jifty->web->request->continuation
+#        || Jifty::Continuation->new(
+#        request => Jifty::Request->new( path => "/" ) );
+  };
+};
+
+# Log out
+before 'caslogout' => run {
+    Jifty->web->request->add_action(
+        class   => 'CASLogout',
+        moniker => 'caslogout',
+    );
+};
+
+
+# Login
+#on 'caslogin' => run {
+#    set 'action' =>
+#        Jifty->web->new_action(
+#        class => 'CASLogin',
+#        moniker => 'casloginbox'
+#    );
+#    set 'next' => Jifty->web->request->continuation
+#        || Jifty::Continuation->new(
+#        request => Jifty::Request->new( path => "/" ) );
+#};
+
+
+1;

Added: jifty/branches/schema-plugins/plugins/AuthCASLogin/share/web/templates/caslogin
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthCASLogin/share/web/templates/caslogin	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,11 @@
+<%args>
+$ticket => ""
+</%args>
+<%init>
+Jifty->web->new_action(
+    moniker => 'casloginbox',
+    class   => 'CASLogin',
+    arguments => { ticket => $ticket }
+)->run;
+Jifty->web->redirect("/");
+</%init>

Added: jifty/branches/schema-plugins/plugins/AuthCASLogin/share/web/templates/caslogout
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthCASLogin/share/web/templates/caslogout	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,3 @@
+<&| /_elements/wrapper, title => "Logged out" &>
+<p><% _("Ok, you're now logged out. Have a good day.") %></p>
+</&>

Added: jifty/branches/schema-plugins/plugins/AuthCASLogin/t/00-load.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthCASLogin/t/00-load.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,5 @@
+#!/usr/bin/env perl -w
+use strict;
+use Test::More tests => 1;
+
+use_ok('Jifty::Plugin::AuthCASLogin');

Added: jifty/branches/schema-plugins/plugins/AuthCASOnly/MANIFEST
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthCASOnly/MANIFEST	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,18 @@
+debian/changelog
+debian/compat
+debian/control
+debian/copyright
+debian/files
+debian/rules
+lib/Jifty/Plugin/AuthCASOnly.pm
+lib/Jifty/Plugin/AuthCASOnly/Action/CASLogin.pm
+lib/Jifty/Plugin/AuthCASOnly/Action/CASLogout.pm
+lib/Jifty/Plugin/AuthCASOnly/CurrentUser.pm
+lib/Jifty/Plugin/AuthCASOnly/Dispatcher.pm
+lib/Jifty/Plugin/AuthCASOnly/Model/CASUser.pm
+Makefile.PL
+MANIFEST			This list of files
+META.yml
+share/web/templates/caslogin
+share/web/templates/caslogout
+t/00-load.t

Added: jifty/branches/schema-plugins/plugins/AuthCASOnly/MANIFEST.bak
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthCASOnly/MANIFEST.bak	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,57 @@
+debian/changelog
+debian/compat
+debian/control
+debian/copyright
+debian/files
+debian/libjifty-plugin-authcaslogin-perl.substvars
+debian/libjifty-plugin-authcaslogin-perl/DEBIAN/control
+debian/libjifty-plugin-authcaslogin-perl/DEBIAN/md5sums
+debian/libjifty-plugin-authcaslogin-perl/usr/share/doc/libjifty-plugin-authcaslogin-perl/changelog.Debian.gz
+debian/libjifty-plugin-authcaslogin-perl/usr/share/doc/libjifty-plugin-authcaslogin-perl/copyright
+debian/libjifty-plugin-authcaslogin-perl/usr/share/man/man3/Jifty::Plugin::AuthCASLogin.3pm.gz
+debian/libjifty-plugin-authcaslogin-perl/usr/share/man/man3/Jifty::Plugin::AuthCASLogin::Action::CASLogin.3pm.gz
+debian/libjifty-plugin-authcaslogin-perl/usr/share/man/man3/Jifty::Plugin::AuthCASLogin::Action::CASLogout.3pm.gz
+debian/libjifty-plugin-authcaslogin-perl/usr/share/perl5/auto/Jifty/Plugin/AuthCASLogin/web/templates/caslogin
+debian/libjifty-plugin-authcaslogin-perl/usr/share/perl5/auto/Jifty/Plugin/AuthCASLogin/web/templates/caslogout
+debian/libjifty-plugin-authcaslogin-perl/usr/share/perl5/Jifty/Plugin/AuthCASLogin.pm
+debian/libjifty-plugin-authcaslogin-perl/usr/share/perl5/Jifty/Plugin/AuthCASLogin/Action/CASLogin.pm
+debian/libjifty-plugin-authcaslogin-perl/usr/share/perl5/Jifty/Plugin/AuthCASLogin/Action/CASLogout.pm
+debian/libjifty-plugin-authcaslogin-perl/usr/share/perl5/Jifty/Plugin/AuthCASLogin/Dispatcher.pm
+debian/libjifty-plugin-authcasonly-perl.substvars
+debian/libjifty-plugin-authcasonly-perl/DEBIAN/control
+debian/libjifty-plugin-authcasonly-perl/DEBIAN/md5sums
+debian/libjifty-plugin-authcasonly-perl/usr/share/doc/libjifty-plugin-authcasonly-perl/changelog.Debian.gz
+debian/libjifty-plugin-authcasonly-perl/usr/share/doc/libjifty-plugin-authcasonly-perl/copyright
+debian/libjifty-plugin-authcasonly-perl/usr/share/man/man3/Jifty::Plugin::AuthCASOnly.3pm.gz
+debian/libjifty-plugin-authcasonly-perl/usr/share/man/man3/Jifty::Plugin::AuthCASOnly::Action::CASLogin.3pm.gz
+debian/libjifty-plugin-authcasonly-perl/usr/share/man/man3/Jifty::Plugin::AuthCASOnly::Action::CASLogout.3pm.gz
+debian/libjifty-plugin-authcasonly-perl/usr/share/perl5/auto/Jifty/Plugin/AuthCASOnly/web/templates/caslogin
+debian/libjifty-plugin-authcasonly-perl/usr/share/perl5/auto/Jifty/Plugin/AuthCASOnly/web/templates/caslogout
+debian/libjifty-plugin-authcasonly-perl/usr/share/perl5/Jifty/Plugin/AuthCASOnly.pm
+debian/libjifty-plugin-authcasonly-perl/usr/share/perl5/Jifty/Plugin/AuthCASOnly/Action/CASLogin.pm
+debian/libjifty-plugin-authcasonly-perl/usr/share/perl5/Jifty/Plugin/AuthCASOnly/Action/CASLogout.pm
+debian/libjifty-plugin-authcasonly-perl/usr/share/perl5/Jifty/Plugin/AuthCASOnly/CurrentUser.pm
+debian/libjifty-plugin-authcasonly-perl/usr/share/perl5/Jifty/Plugin/AuthCASOnly/Dispatcher.pm
+debian/libjifty-plugin-authcasonly-perl/usr/share/perl5/Jifty/Plugin/AuthCASOnly/Model/CASUser.pm
+debian/rules
+inc/Module/Install.pm
+inc/Module/Install/Base.pm
+inc/Module/Install/Can.pm
+inc/Module/Install/Fetch.pm
+inc/Module/Install/Makefile.pm
+inc/Module/Install/Metadata.pm
+inc/Module/Install/Share.pm
+inc/Module/Install/Win32.pm
+inc/Module/Install/WriteAll.pm
+lib/Jifty/Plugin/AuthCASOnly.pm
+lib/Jifty/Plugin/AuthCASOnly/Action/CASLogin.pm
+lib/Jifty/Plugin/AuthCASOnly/Action/CASLogout.pm
+lib/Jifty/Plugin/AuthCASOnly/CurrentUser.pm
+lib/Jifty/Plugin/AuthCASOnly/Dispatcher.pm
+lib/Jifty/Plugin/AuthCASOnly/Model/CASUser.pm
+Makefile.PL
+MANIFEST			This list of files
+META.yml
+share/web/templates/caslogin
+share/web/templates/caslogout
+t/00-load.t

Added: jifty/branches/schema-plugins/plugins/AuthCASOnly/META.yml
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthCASOnly/META.yml	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,14 @@
+build_requires: 
+  ExtUtils::MakeMaker: 6.11
+distribution_type: module
+generated_by: Module::Install version 0.64
+license: Perl
+name: Jifty-Plugin-AuthCASOnly
+no_index: 
+  directory: 
+    - inc
+    - t
+requires: 
+  AuthCAS: 0
+  Jifty: 0.60912
+version: 0.01

Added: jifty/branches/schema-plugins/plugins/AuthCASOnly/Makefile
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthCASOnly/Makefile	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,816 @@
+# This Makefile is for the Jifty::Plugin::AuthCASOnly extension to perl.
+#
+# It was generated automatically by MakeMaker version
+# 6.30_01 (Revision: Revision: 4535 ) from the contents of
+# Makefile.PL. Don't edit this file, edit Makefile.PL instead.
+#
+#       ANY CHANGES MADE HERE WILL BE LOST!
+#
+#   MakeMaker ARGV: ()
+#
+#   MakeMaker Parameters:
+
+#     DIR => []
+#     DISTNAME => q[Jifty-Plugin-AuthCASOnly]
+#     NAME => q[Jifty::Plugin::AuthCASOnly]
+#     NO_META => q[1]
+#     PL_FILES => {  }
+#     PREREQ_PM => { Jifty=>q[0.60912], ExtUtils::MakeMaker=>q[6.11], AuthCAS=>q[0] }
+#     VERSION => q[0.01]
+#     dist => { PREOP=>q[$(PERL) -I. -MModule::Install::Admin -e "dist_preop(q($(DISTVNAME)))"] }
+
+# --- MakeMaker post_initialize section:
+
+
+# --- MakeMaker const_config section:
+
+# These definitions are from config.sh (via /usr/lib/perl/5.8/Config.pm)
+
+# They may have been overridden via Makefile.PL or on the command line
+AR = ar
+CC = cc
+CCCDLFLAGS = -fPIC
+CCDLFLAGS = -Wl,-E
+DLEXT = so
+DLSRC = dl_dlopen.xs
+LD = cc
+LDDLFLAGS = -shared -L/usr/local/lib
+LDFLAGS =  -L/usr/local/lib
+LIBC = /lib/libc-2.3.6.so
+LIB_EXT = .a
+OBJ_EXT = .o
+OSNAME = linux
+OSVERS = 2.6.18.3
+RANLIB = :
+SITELIBEXP = /usr/local/share/perl/5.8.8
+SITEARCHEXP = /usr/local/lib/perl/5.8.8
+SO = so
+EXE_EXT = 
+FULL_AR = /usr/bin/ar
+VENDORARCHEXP = /usr/lib/perl5
+VENDORLIBEXP = /usr/share/perl5
+
+
+# --- MakeMaker constants section:
+AR_STATIC_ARGS = cr
+DIRFILESEP = /
+DFSEP = $(DIRFILESEP)
+NAME = Jifty::Plugin::AuthCASOnly
+NAME_SYM = Jifty_Plugin_AuthCASOnly
+VERSION = 0.01
+VERSION_MACRO = VERSION
+VERSION_SYM = 0_01
+DEFINE_VERSION = -D$(VERSION_MACRO)=\"$(VERSION)\"
+XS_VERSION = 0.01
+XS_VERSION_MACRO = XS_VERSION
+XS_DEFINE_VERSION = -D$(XS_VERSION_MACRO)=\"$(XS_VERSION)\"
+INST_ARCHLIB = blib/arch
+INST_SCRIPT = blib/script
+INST_BIN = blib/bin
+INST_LIB = blib/lib
+INST_MAN1DIR = blib/man1
+INST_MAN3DIR = blib/man3
+MAN1EXT = 1p
+MAN3EXT = 3pm
+INSTALLDIRS = site
+DESTDIR = 
+PREFIX = /usr
+PERLPREFIX = $(PREFIX)
+SITEPREFIX = $(PREFIX)/local
+VENDORPREFIX = $(PREFIX)
+INSTALLPRIVLIB = $(PERLPREFIX)/share/perl/5.8
+DESTINSTALLPRIVLIB = $(DESTDIR)$(INSTALLPRIVLIB)
+INSTALLSITELIB = $(SITEPREFIX)/share/perl/5.8.8
+DESTINSTALLSITELIB = $(DESTDIR)$(INSTALLSITELIB)
+INSTALLVENDORLIB = $(VENDORPREFIX)/share/perl5
+DESTINSTALLVENDORLIB = $(DESTDIR)$(INSTALLVENDORLIB)
+INSTALLARCHLIB = $(PERLPREFIX)/lib/perl/5.8
+DESTINSTALLARCHLIB = $(DESTDIR)$(INSTALLARCHLIB)
+INSTALLSITEARCH = $(SITEPREFIX)/lib/perl/5.8.8
+DESTINSTALLSITEARCH = $(DESTDIR)$(INSTALLSITEARCH)
+INSTALLVENDORARCH = $(VENDORPREFIX)/lib/perl5
+DESTINSTALLVENDORARCH = $(DESTDIR)$(INSTALLVENDORARCH)
+INSTALLBIN = $(PERLPREFIX)/bin
+DESTINSTALLBIN = $(DESTDIR)$(INSTALLBIN)
+INSTALLSITEBIN = $(SITEPREFIX)/bin
+DESTINSTALLSITEBIN = $(DESTDIR)$(INSTALLSITEBIN)
+INSTALLVENDORBIN = $(VENDORPREFIX)/bin
+DESTINSTALLVENDORBIN = $(DESTDIR)$(INSTALLVENDORBIN)
+INSTALLSCRIPT = $(PERLPREFIX)/bin
+DESTINSTALLSCRIPT = $(DESTDIR)$(INSTALLSCRIPT)
+INSTALLSITESCRIPT = $(SITEPREFIX)/bin
+DESTINSTALLSITESCRIPT = $(DESTDIR)$(INSTALLSITESCRIPT)
+INSTALLVENDORSCRIPT = $(VENDORPREFIX)/bin
+DESTINSTALLVENDORSCRIPT = $(DESTDIR)$(INSTALLVENDORSCRIPT)
+INSTALLMAN1DIR = $(PERLPREFIX)/share/man/man1
+DESTINSTALLMAN1DIR = $(DESTDIR)$(INSTALLMAN1DIR)
+INSTALLSITEMAN1DIR = $(SITEPREFIX)/man/man1
+DESTINSTALLSITEMAN1DIR = $(DESTDIR)$(INSTALLSITEMAN1DIR)
+INSTALLVENDORMAN1DIR = $(VENDORPREFIX)/share/man/man1
+DESTINSTALLVENDORMAN1DIR = $(DESTDIR)$(INSTALLVENDORMAN1DIR)
+INSTALLMAN3DIR = $(PERLPREFIX)/share/man/man3
+DESTINSTALLMAN3DIR = $(DESTDIR)$(INSTALLMAN3DIR)
+INSTALLSITEMAN3DIR = $(SITEPREFIX)/man/man3
+DESTINSTALLSITEMAN3DIR = $(DESTDIR)$(INSTALLSITEMAN3DIR)
+INSTALLVENDORMAN3DIR = $(VENDORPREFIX)/share/man/man3
+DESTINSTALLVENDORMAN3DIR = $(DESTDIR)$(INSTALLVENDORMAN3DIR)
+PERL_LIB =
+PERL_ARCHLIB = /usr/lib/perl/5.8
+LIBPERL_A = libperl.a
+FIRST_MAKEFILE = Makefile
+MAKEFILE_OLD = Makefile.old
+MAKE_APERL_FILE = Makefile.aperl
+PERLMAINCC = $(CC)
+PERL_INC = /usr/lib/perl/5.8/CORE
+PERL = /usr/bin/perl "-Iinc"
+FULLPERL = /usr/bin/perl "-Iinc"
+ABSPERL = $(PERL)
+PERLRUN = $(PERL)
+FULLPERLRUN = $(FULLPERL)
+ABSPERLRUN = $(ABSPERL)
+PERLRUNINST = $(PERLRUN) "-I$(INST_ARCHLIB)" "-Iinc" "-I$(INST_LIB)"
+FULLPERLRUNINST = $(FULLPERLRUN) "-I$(INST_ARCHLIB)" "-Iinc" "-I$(INST_LIB)"
+ABSPERLRUNINST = $(ABSPERLRUN) "-I$(INST_ARCHLIB)" "-Iinc" "-I$(INST_LIB)"
+PERL_CORE = 0
+PERM_RW = 644
+PERM_RWX = 755
+
+MAKEMAKER   = /usr/share/perl/5.8/ExtUtils/MakeMaker.pm
+MM_VERSION  = 6.30_01
+MM_REVISION = Revision: 4535 
+
+# FULLEXT = Pathname for extension directory (eg Foo/Bar/Oracle).
+# BASEEXT = Basename part of FULLEXT. May be just equal FULLEXT. (eg Oracle)
+# PARENT_NAME = NAME without BASEEXT and no trailing :: (eg Foo::Bar)
+# DLBASE  = Basename part of dynamic library. May be just equal BASEEXT.
+FULLEXT = Jifty/Plugin/AuthCASOnly
+BASEEXT = AuthCASOnly
+PARENT_NAME = Jifty::Plugin
+DLBASE = $(BASEEXT)
+VERSION_FROM = 
+OBJECT = 
+LDFROM = $(OBJECT)
+LINKTYPE = dynamic
+BOOTDEP = 
+
+# Handy lists of source code files:
+XS_FILES = 
+C_FILES  = 
+O_FILES  = 
+H_FILES  = 
+MAN1PODS = 
+MAN3PODS = lib/Jifty/Plugin/AuthCASOnly.pm \
+	lib/Jifty/Plugin/AuthCASOnly/Action/CASLogin.pm \
+	lib/Jifty/Plugin/AuthCASOnly/Action/CASLogout.pm
+
+# Where is the Config information that we are using/depend on
+CONFIGDEP = $(PERL_ARCHLIB)$(DFSEP)Config.pm $(PERL_INC)$(DFSEP)config.h
+
+# Where to build things
+INST_LIBDIR      = $(INST_LIB)/Jifty/Plugin
+INST_ARCHLIBDIR  = $(INST_ARCHLIB)/Jifty/Plugin
+
+INST_AUTODIR     = $(INST_LIB)/auto/$(FULLEXT)
+INST_ARCHAUTODIR = $(INST_ARCHLIB)/auto/$(FULLEXT)
+
+INST_STATIC      = 
+INST_DYNAMIC     = 
+INST_BOOT        = 
+
+# Extra linker info
+EXPORT_LIST        = 
+PERL_ARCHIVE       = 
+PERL_ARCHIVE_AFTER = 
+
+
+TO_INST_PM = lib/Jifty/Plugin/AuthCASOnly.pm \
+	lib/Jifty/Plugin/AuthCASOnly/Action/CASLogin.pm \
+	lib/Jifty/Plugin/AuthCASOnly/Action/CASLogout.pm \
+	lib/Jifty/Plugin/AuthCASOnly/CurrentUser.pm \
+	lib/Jifty/Plugin/AuthCASOnly/Dispatcher.pm \
+	lib/Jifty/Plugin/AuthCASOnly/Model/CASUser.pm
+
+PM_TO_BLIB = lib/Jifty/Plugin/AuthCASOnly/CurrentUser.pm \
+	blib/lib/Jifty/Plugin/AuthCASOnly/CurrentUser.pm \
+	lib/Jifty/Plugin/AuthCASOnly/Model/CASUser.pm \
+	blib/lib/Jifty/Plugin/AuthCASOnly/Model/CASUser.pm \
+	lib/Jifty/Plugin/AuthCASOnly/Action/CASLogout.pm \
+	blib/lib/Jifty/Plugin/AuthCASOnly/Action/CASLogout.pm \
+	lib/Jifty/Plugin/AuthCASOnly/Dispatcher.pm \
+	blib/lib/Jifty/Plugin/AuthCASOnly/Dispatcher.pm \
+	lib/Jifty/Plugin/AuthCASOnly/Action/CASLogin.pm \
+	blib/lib/Jifty/Plugin/AuthCASOnly/Action/CASLogin.pm \
+	lib/Jifty/Plugin/AuthCASOnly.pm \
+	blib/lib/Jifty/Plugin/AuthCASOnly.pm
+
+
+# --- MakeMaker platform_constants section:
+MM_Unix_VERSION = 1.50_01
+PERL_MALLOC_DEF = -DPERL_EXTMALLOC_DEF -Dmalloc=Perl_malloc -Dfree=Perl_mfree -Drealloc=Perl_realloc -Dcalloc=Perl_calloc
+
+
+# --- MakeMaker tool_autosplit section:
+# Usage: $(AUTOSPLITFILE) FileToSplit AutoDirToSplitInto
+AUTOSPLITFILE = $(ABSPERLRUN)  -e 'use AutoSplit;  autosplit($$ARGV[0], $$ARGV[1], 0, 1, 1)'
+
+
+
+# --- MakeMaker tool_xsubpp section:
+
+
+# --- MakeMaker tools_other section:
+SHELL = /bin/sh
+CHMOD = chmod
+CP = cp
+MV = mv
+NOOP = $(SHELL) -c true
+NOECHO = @
+RM_F = rm -f
+RM_RF = rm -rf
+TEST_F = test -f
+TOUCH = touch
+UMASK_NULL = umask 0
+DEV_NULL = > /dev/null 2>&1
+MKPATH = $(ABSPERLRUN) "-MExtUtils::Command" -e mkpath
+EQUALIZE_TIMESTAMP = $(ABSPERLRUN) "-MExtUtils::Command" -e eqtime
+ECHO = echo
+ECHO_N = echo -n
+UNINST = 0
+VERBINST = 0
+MOD_INSTALL = $(ABSPERLRUN) -MExtUtils::Install -e 'install({@ARGV}, '\''$(VERBINST)'\'', 0, '\''$(UNINST)'\'');'
+DOC_INSTALL = $(ABSPERLRUN) "-MExtUtils::Command::MM" -e perllocal_install
+UNINSTALL = $(ABSPERLRUN) "-MExtUtils::Command::MM" -e uninstall
+WARN_IF_OLD_PACKLIST = $(ABSPERLRUN) "-MExtUtils::Command::MM" -e warn_if_old_packlist
+MACROSTART = 
+MACROEND = 
+USEMAKEFILE = -f
+FIXIN = $(PERLRUN) "-MExtUtils::MY" -e "MY->fixin(shift)"
+
+
+# --- MakeMaker makemakerdflt section:
+makemakerdflt: all
+	$(NOECHO) $(NOOP)
+
+
+# --- MakeMaker dist section:
+TAR = tar
+TARFLAGS = cvf
+ZIP = zip
+ZIPFLAGS = -r
+COMPRESS = gzip --best
+SUFFIX = .gz
+SHAR = shar
+PREOP = $(PERL) -I. -MModule::Install::Admin -e "dist_preop(q($(DISTVNAME)))"
+POSTOP = $(NOECHO) $(NOOP)
+TO_UNIX = $(NOECHO) $(NOOP)
+CI = ci -u
+RCS_LABEL = rcs -Nv$(VERSION_SYM): -q
+DIST_CP = best
+DIST_DEFAULT = tardist
+DISTNAME = Jifty-Plugin-AuthCASOnly
+DISTVNAME = Jifty-Plugin-AuthCASOnly-0.01
+
+
+# --- MakeMaker macro section:
+
+
+# --- MakeMaker depend section:
+
+
+# --- MakeMaker cflags section:
+
+
+# --- MakeMaker const_loadlibs section:
+
+
+# --- MakeMaker const_cccmd section:
+
+
+# --- MakeMaker post_constants section:
+
+
+# --- MakeMaker pasthru section:
+
+PASTHRU = LIBPERL_A="$(LIBPERL_A)"\
+	LINKTYPE="$(LINKTYPE)"\
+	PREFIX="$(PREFIX)"
+
+
+# --- MakeMaker special_targets section:
+.SUFFIXES : .xs .c .C .cpp .i .s .cxx .cc $(OBJ_EXT)
+
+.PHONY: all config static dynamic test linkext manifest blibdirs clean realclean disttest distdir
+
+
+
+# --- MakeMaker c_o section:
+
+
+# --- MakeMaker xs_c section:
+
+
+# --- MakeMaker xs_o section:
+
+
+# --- MakeMaker top_targets section:
+all :: pure_all manifypods
+	$(NOECHO) $(NOOP)
+
+
+pure_all :: config pm_to_blib subdirs linkext
+	$(NOECHO) $(NOOP)
+
+subdirs :: $(MYEXTLIB)
+	$(NOECHO) $(NOOP)
+
+config :: $(FIRST_MAKEFILE) blibdirs
+	$(NOECHO) $(NOOP)
+
+help :
+	perldoc ExtUtils::MakeMaker
+
+
+# --- MakeMaker blibdirs section:
+blibdirs : $(INST_LIBDIR)$(DFSEP).exists $(INST_ARCHLIB)$(DFSEP).exists $(INST_AUTODIR)$(DFSEP).exists $(INST_ARCHAUTODIR)$(DFSEP).exists $(INST_BIN)$(DFSEP).exists $(INST_SCRIPT)$(DFSEP).exists $(INST_MAN1DIR)$(DFSEP).exists $(INST_MAN3DIR)$(DFSEP).exists
+	$(NOECHO) $(NOOP)
+
+# Backwards compat with 6.18 through 6.25
+blibdirs.ts : blibdirs
+	$(NOECHO) $(NOOP)
+
+$(INST_LIBDIR)$(DFSEP).exists :: Makefile.PL
+	$(NOECHO) $(MKPATH) $(INST_LIBDIR)
+	$(NOECHO) $(CHMOD) 755 $(INST_LIBDIR)
+	$(NOECHO) $(TOUCH) $(INST_LIBDIR)$(DFSEP).exists
+
+$(INST_ARCHLIB)$(DFSEP).exists :: Makefile.PL
+	$(NOECHO) $(MKPATH) $(INST_ARCHLIB)
+	$(NOECHO) $(CHMOD) 755 $(INST_ARCHLIB)
+	$(NOECHO) $(TOUCH) $(INST_ARCHLIB)$(DFSEP).exists
+
+$(INST_AUTODIR)$(DFSEP).exists :: Makefile.PL
+	$(NOECHO) $(MKPATH) $(INST_AUTODIR)
+	$(NOECHO) $(CHMOD) 755 $(INST_AUTODIR)
+	$(NOECHO) $(TOUCH) $(INST_AUTODIR)$(DFSEP).exists
+
+$(INST_ARCHAUTODIR)$(DFSEP).exists :: Makefile.PL
+	$(NOECHO) $(MKPATH) $(INST_ARCHAUTODIR)
+	$(NOECHO) $(CHMOD) 755 $(INST_ARCHAUTODIR)
+	$(NOECHO) $(TOUCH) $(INST_ARCHAUTODIR)$(DFSEP).exists
+
+$(INST_BIN)$(DFSEP).exists :: Makefile.PL
+	$(NOECHO) $(MKPATH) $(INST_BIN)
+	$(NOECHO) $(CHMOD) 755 $(INST_BIN)
+	$(NOECHO) $(TOUCH) $(INST_BIN)$(DFSEP).exists
+
+$(INST_SCRIPT)$(DFSEP).exists :: Makefile.PL
+	$(NOECHO) $(MKPATH) $(INST_SCRIPT)
+	$(NOECHO) $(CHMOD) 755 $(INST_SCRIPT)
+	$(NOECHO) $(TOUCH) $(INST_SCRIPT)$(DFSEP).exists
+
+$(INST_MAN1DIR)$(DFSEP).exists :: Makefile.PL
+	$(NOECHO) $(MKPATH) $(INST_MAN1DIR)
+	$(NOECHO) $(CHMOD) 755 $(INST_MAN1DIR)
+	$(NOECHO) $(TOUCH) $(INST_MAN1DIR)$(DFSEP).exists
+
+$(INST_MAN3DIR)$(DFSEP).exists :: Makefile.PL
+	$(NOECHO) $(MKPATH) $(INST_MAN3DIR)
+	$(NOECHO) $(CHMOD) 755 $(INST_MAN3DIR)
+	$(NOECHO) $(TOUCH) $(INST_MAN3DIR)$(DFSEP).exists
+
+
+
+# --- MakeMaker linkext section:
+
+linkext :: $(LINKTYPE)
+	$(NOECHO) $(NOOP)
+
+
+# --- MakeMaker dlsyms section:
+
+
+# --- MakeMaker dynamic section:
+
+dynamic :: $(FIRST_MAKEFILE) $(INST_DYNAMIC) $(INST_BOOT)
+	$(NOECHO) $(NOOP)
+
+
+# --- MakeMaker dynamic_bs section:
+
+BOOTSTRAP =
+
+
+# --- MakeMaker dynamic_lib section:
+
+
+# --- MakeMaker static section:
+
+## $(INST_PM) has been moved to the all: target.
+## It remains here for awhile to allow for old usage: "make static"
+static :: $(FIRST_MAKEFILE) $(INST_STATIC)
+	$(NOECHO) $(NOOP)
+
+
+# --- MakeMaker static_lib section:
+
+
+# --- MakeMaker manifypods section:
+
+POD2MAN_EXE = $(PERLRUN) "-MExtUtils::Command::MM" -e pod2man "--"
+POD2MAN = $(POD2MAN_EXE)
+
+
+manifypods : pure_all  \
+	lib/Jifty/Plugin/AuthCASOnly/Action/CASLogout.pm \
+	lib/Jifty/Plugin/AuthCASOnly/Action/CASLogin.pm \
+	lib/Jifty/Plugin/AuthCASOnly.pm \
+	lib/Jifty/Plugin/AuthCASOnly/Action/CASLogout.pm \
+	lib/Jifty/Plugin/AuthCASOnly/Action/CASLogin.pm \
+	lib/Jifty/Plugin/AuthCASOnly.pm
+	$(NOECHO) $(POD2MAN) --section=$(MAN3EXT) --perm_rw=$(PERM_RW) \
+	  lib/Jifty/Plugin/AuthCASOnly/Action/CASLogout.pm $(INST_MAN3DIR)/Jifty::Plugin::AuthCASOnly::Action::CASLogout.$(MAN3EXT) \
+	  lib/Jifty/Plugin/AuthCASOnly/Action/CASLogin.pm $(INST_MAN3DIR)/Jifty::Plugin::AuthCASOnly::Action::CASLogin.$(MAN3EXT) \
+	  lib/Jifty/Plugin/AuthCASOnly.pm $(INST_MAN3DIR)/Jifty::Plugin::AuthCASOnly.$(MAN3EXT) 
+
+
+
+
+# --- MakeMaker processPL section:
+
+
+# --- MakeMaker installbin section:
+
+
+# --- MakeMaker subdirs section:
+
+# none
+
+# --- MakeMaker clean_subdirs section:
+clean_subdirs :
+	$(NOECHO) $(NOOP)
+
+
+# --- MakeMaker clean section:
+
+# Delete temporary files but do not touch installed files. We don't delete
+# the Makefile here so a later make realclean still has a makefile to use.
+
+clean :: clean_subdirs
+	- $(RM_F) \
+	  *$(LIB_EXT) core \
+	  core.[0-9] $(INST_ARCHAUTODIR)/extralibs.all \
+	  core.[0-9][0-9] $(BASEEXT).bso \
+	  pm_to_blib.ts core.[0-9][0-9][0-9][0-9] \
+	  $(BASEEXT).x $(BOOTSTRAP) \
+	  perl$(EXE_EXT) tmon.out \
+	  *$(OBJ_EXT) pm_to_blib \
+	  $(INST_ARCHAUTODIR)/extralibs.ld blibdirs.ts \
+	  core.[0-9][0-9][0-9][0-9][0-9] *perl.core \
+	  core.*perl.*.? $(MAKE_APERL_FILE) \
+	  perl $(BASEEXT).def \
+	  core.[0-9][0-9][0-9] mon.out \
+	  lib$(BASEEXT).def perlmain.c \
+	  perl.exe so_locations \
+	  $(BASEEXT).exp 
+	- $(RM_RF) \
+	  blib 
+	- $(MV) $(FIRST_MAKEFILE) $(MAKEFILE_OLD) $(DEV_NULL)
+
+
+# --- MakeMaker realclean_subdirs section:
+realclean_subdirs :
+	$(NOECHO) $(NOOP)
+
+
+# --- MakeMaker realclean section:
+# Delete temporary files (via clean) and also delete dist files
+realclean purge ::  clean realclean_subdirs
+	- $(RM_F) \
+	  $(MAKEFILE_OLD) $(FIRST_MAKEFILE) 
+	- $(RM_RF) \
+	  $(DISTVNAME) 
+
+
+# --- MakeMaker metafile section:
+metafile:
+	$(NOECHO) $(NOOP)
+
+
+# --- MakeMaker signature section:
+signature :
+	cpansign -s
+
+
+# --- MakeMaker dist_basics section:
+distclean :: realclean distcheck
+	$(NOECHO) $(NOOP)
+
+distcheck :
+	$(PERLRUN) "-MExtUtils::Manifest=fullcheck" -e fullcheck
+
+skipcheck :
+	$(PERLRUN) "-MExtUtils::Manifest=skipcheck" -e skipcheck
+
+manifest :
+	$(PERLRUN) "-MExtUtils::Manifest=mkmanifest" -e mkmanifest
+
+veryclean : realclean
+	$(RM_F) *~ *.orig */*~ */*.orig
+
+
+
+# --- MakeMaker dist_core section:
+
+dist : $(DIST_DEFAULT) $(FIRST_MAKEFILE)
+	$(NOECHO) $(ABSPERLRUN) -l -e 'print '\''Warning: Makefile possibly out of date with $(VERSION_FROM)'\''' \
+	  -e '    if -e '\''$(VERSION_FROM)'\'' and -M '\''$(VERSION_FROM)'\'' < -M '\''$(FIRST_MAKEFILE)'\'';'
+
+tardist : $(DISTVNAME).tar$(SUFFIX)
+	$(NOECHO) $(NOOP)
+
+uutardist : $(DISTVNAME).tar$(SUFFIX)
+	uuencode $(DISTVNAME).tar$(SUFFIX) $(DISTVNAME).tar$(SUFFIX) > $(DISTVNAME).tar$(SUFFIX)_uu
+
+$(DISTVNAME).tar$(SUFFIX) : distdir
+	$(PREOP)
+	$(TO_UNIX)
+	$(TAR) $(TARFLAGS) $(DISTVNAME).tar $(DISTVNAME)
+	$(RM_RF) $(DISTVNAME)
+	$(COMPRESS) $(DISTVNAME).tar
+	$(POSTOP)
+
+zipdist : $(DISTVNAME).zip
+	$(NOECHO) $(NOOP)
+
+$(DISTVNAME).zip : distdir
+	$(PREOP)
+	$(ZIP) $(ZIPFLAGS) $(DISTVNAME).zip $(DISTVNAME)
+	$(RM_RF) $(DISTVNAME)
+	$(POSTOP)
+
+shdist : distdir
+	$(PREOP)
+	$(SHAR) $(DISTVNAME) > $(DISTVNAME).shar
+	$(RM_RF) $(DISTVNAME)
+	$(POSTOP)
+
+
+# --- MakeMaker distdir section:
+create_distdir :
+	$(RM_RF) $(DISTVNAME)
+	$(PERLRUN) "-MExtUtils::Manifest=manicopy,maniread" \
+		-e "manicopy(maniread(),'$(DISTVNAME)', '$(DIST_CP)');"
+
+distdir : create_distdir  
+	$(NOECHO) $(NOOP)
+
+
+
+# --- MakeMaker dist_test section:
+disttest : distdir
+	cd $(DISTVNAME) && $(ABSPERLRUN) Makefile.PL 
+	cd $(DISTVNAME) && $(MAKE) $(PASTHRU)
+	cd $(DISTVNAME) && $(MAKE) test $(PASTHRU)
+
+
+
+# --- MakeMaker dist_ci section:
+
+ci :
+	$(PERLRUN) "-MExtUtils::Manifest=maniread" \
+	  -e "@all = keys %{ maniread() };" \
+	  -e "print(qq{Executing $(CI) @all\n}); system(qq{$(CI) @all});" \
+	  -e "print(qq{Executing $(RCS_LABEL) ...\n}); system(qq{$(RCS_LABEL) @all});"
+
+
+# --- MakeMaker distmeta section:
+distmeta : create_distdir metafile
+	$(NOECHO) cd $(DISTVNAME) && $(ABSPERLRUN) -MExtUtils::Manifest=maniadd -e 'eval { maniadd({q{META.yml} => q{Module meta-data (added by MakeMaker)}}) } ' \
+	  -e '    or print "Could not add META.yml to MANIFEST: $${'\''@'\''}\n"'
+
+
+
+# --- MakeMaker distsignature section:
+distsignature : create_distdir
+	$(NOECHO) cd $(DISTVNAME) && $(ABSPERLRUN) -MExtUtils::Manifest=maniadd -e 'eval { maniadd({q{SIGNATURE} => q{Public-key signature (added by MakeMaker)}}) } ' \
+	  -e '    or print "Could not add SIGNATURE to MANIFEST: $${'\''@'\''}\n"'
+	$(NOECHO) cd $(DISTVNAME) && $(TOUCH) SIGNATURE
+	cd $(DISTVNAME) && cpansign -s
+
+
+
+# --- MakeMaker install section:
+
+install :: all pure_install doc_install
+	$(NOECHO) $(NOOP)
+
+install_perl :: all pure_perl_install doc_perl_install
+	$(NOECHO) $(NOOP)
+
+install_site :: all pure_site_install doc_site_install
+	$(NOECHO) $(NOOP)
+
+install_vendor :: all pure_vendor_install doc_vendor_install
+	$(NOECHO) $(NOOP)
+
+pure_install :: pure_$(INSTALLDIRS)_install
+	$(NOECHO) $(NOOP)
+
+doc_install :: doc_$(INSTALLDIRS)_install
+	$(NOECHO) $(NOOP)
+
+pure__install : pure_site_install
+	$(NOECHO) $(ECHO) INSTALLDIRS not defined, defaulting to INSTALLDIRS=site
+
+doc__install : doc_site_install
+	$(NOECHO) $(ECHO) INSTALLDIRS not defined, defaulting to INSTALLDIRS=site
+
+pure_perl_install ::
+	$(NOECHO) umask 022; $(MOD_INSTALL) \
+		$(INST_LIB) $(DESTINSTALLPRIVLIB) \
+		$(INST_ARCHLIB) $(DESTINSTALLARCHLIB) \
+		$(INST_BIN) $(DESTINSTALLBIN) \
+		$(INST_SCRIPT) $(DESTINSTALLSCRIPT) \
+		$(INST_MAN1DIR) $(DESTINSTALLMAN1DIR) \
+		$(INST_MAN3DIR) $(DESTINSTALLMAN3DIR)
+	$(NOECHO) $(WARN_IF_OLD_PACKLIST) \
+		$(SITEARCHEXP)/auto/$(FULLEXT)
+
+
+pure_site_install ::
+	$(NOECHO) umask 02; $(MOD_INSTALL) \
+		read $(SITEARCHEXP)/auto/$(FULLEXT)/.packlist \
+		write $(DESTINSTALLSITEARCH)/auto/$(FULLEXT)/.packlist \
+		$(INST_LIB) $(DESTINSTALLSITELIB) \
+		$(INST_ARCHLIB) $(DESTINSTALLSITEARCH) \
+		$(INST_BIN) $(DESTINSTALLSITEBIN) \
+		$(INST_SCRIPT) $(DESTINSTALLSITESCRIPT) \
+		$(INST_MAN1DIR) $(DESTINSTALLSITEMAN1DIR) \
+		$(INST_MAN3DIR) $(DESTINSTALLSITEMAN3DIR)
+	$(NOECHO) $(WARN_IF_OLD_PACKLIST) \
+		$(PERL_ARCHLIB)/auto/$(FULLEXT)
+
+pure_vendor_install ::
+	$(NOECHO) umask 022; $(MOD_INSTALL) \
+		$(INST_LIB) $(DESTINSTALLVENDORLIB) \
+		$(INST_ARCHLIB) $(DESTINSTALLVENDORARCH) \
+		$(INST_BIN) $(DESTINSTALLVENDORBIN) \
+		$(INST_SCRIPT) $(DESTINSTALLVENDORSCRIPT) \
+		$(INST_MAN1DIR) $(DESTINSTALLVENDORMAN1DIR) \
+		$(INST_MAN3DIR) $(DESTINSTALLVENDORMAN3DIR)
+
+doc_perl_install ::
+
+doc_site_install ::
+	$(NOECHO) $(ECHO) Appending installation info to $(DESTINSTALLSITEARCH)/perllocal.pod
+	-$(NOECHO) umask 02; $(MKPATH) $(DESTINSTALLSITEARCH)
+	-$(NOECHO) umask 02; $(DOC_INSTALL) \
+		"Module" "$(NAME)" \
+		"installed into" "$(INSTALLSITELIB)" \
+		LINKTYPE "$(LINKTYPE)" \
+		VERSION "$(VERSION)" \
+		EXE_FILES "$(EXE_FILES)" \
+		>> $(DESTINSTALLSITEARCH)/perllocal.pod
+
+doc_vendor_install ::
+
+
+uninstall :: uninstall_from_$(INSTALLDIRS)dirs
+	$(NOECHO) $(NOOP)
+
+uninstall_from_perldirs ::
+
+uninstall_from_sitedirs ::
+	$(NOECHO) $(UNINSTALL) $(SITEARCHEXP)/auto/$(FULLEXT)/.packlist
+
+uninstall_from_vendordirs ::
+
+
+
+# --- MakeMaker force section:
+# Phony target to force checking subdirectories.
+FORCE:
+	$(NOECHO) $(NOOP)
+
+
+# --- MakeMaker perldepend section:
+
+
+# --- MakeMaker makefile section:
+# We take a very conservative approach here, but it's worth it.
+# We move Makefile to Makefile.old here to avoid gnu make looping.
+$(FIRST_MAKEFILE) : Makefile.PL $(CONFIGDEP)
+	$(NOECHO) $(ECHO) "Makefile out-of-date with respect to $?"
+	$(NOECHO) $(ECHO) "Cleaning current config before rebuilding Makefile..."
+	-$(NOECHO) $(RM_F) $(MAKEFILE_OLD)
+	-$(NOECHO) $(MV)   $(FIRST_MAKEFILE) $(MAKEFILE_OLD)
+	- $(MAKE) $(USEMAKEFILE) $(MAKEFILE_OLD) clean $(DEV_NULL)
+	$(PERLRUN) Makefile.PL 
+	$(NOECHO) $(ECHO) "==> Your Makefile has been rebuilt. <=="
+	$(NOECHO) $(ECHO) "==> Please rerun the $(MAKE) command.  <=="
+	false
+
+
+
+# --- MakeMaker staticmake section:
+
+# --- MakeMaker makeaperl section ---
+MAP_TARGET    = perl
+FULLPERL      = /usr/bin/perl
+
+$(MAP_TARGET) :: static $(MAKE_APERL_FILE)
+	$(MAKE) $(USEMAKEFILE) $(MAKE_APERL_FILE) $@
+
+$(MAKE_APERL_FILE) : $(FIRST_MAKEFILE) pm_to_blib
+	$(NOECHO) $(ECHO) Writing \"$(MAKE_APERL_FILE)\" for this $(MAP_TARGET)
+	$(NOECHO) $(PERLRUNINST) \
+		Makefile.PL DIR= \
+		MAKEFILE=$(MAKE_APERL_FILE) LINKTYPE=static \
+		MAKEAPERL=1 NORECURS=1 CCCDLFLAGS=
+
+
+# --- MakeMaker test section:
+
+TEST_VERBOSE=0
+TEST_TYPE=test_$(LINKTYPE)
+TEST_FILE = test.pl
+TEST_FILES = t/*.t
+TESTDB_SW = -d
+
+testdb :: testdb_$(LINKTYPE)
+
+test :: $(TEST_TYPE)
+
+test_dynamic :: pure_all
+	PERL_DL_NONLAZY=1 $(FULLPERLRUN) "-MExtUtils::Command::MM" "-e" "test_harness($(TEST_VERBOSE), 'inc', '$(INST_LIB)', '$(INST_ARCHLIB)')" $(TEST_FILES)
+
+testdb_dynamic :: pure_all
+	PERL_DL_NONLAZY=1 $(FULLPERLRUN) $(TESTDB_SW) "-Iinc" "-I$(INST_LIB)" "-I$(INST_ARCHLIB)" $(TEST_FILE)
+
+test_ : test_dynamic
+
+test_static :: test_dynamic
+testdb_static :: testdb_dynamic
+
+
+# --- MakeMaker ppd section:
+# Creates a PPD (Perl Package Description) for a binary distribution.
+ppd:
+	$(NOECHO) $(ECHO) '<SOFTPKG NAME="$(DISTNAME)" VERSION="0,01,0,0">' > $(DISTNAME).ppd
+	$(NOECHO) $(ECHO) '    <TITLE>$(DISTNAME)</TITLE>' >> $(DISTNAME).ppd
+	$(NOECHO) $(ECHO) '    <ABSTRACT></ABSTRACT>' >> $(DISTNAME).ppd
+	$(NOECHO) $(ECHO) '    <AUTHOR></AUTHOR>' >> $(DISTNAME).ppd
+	$(NOECHO) $(ECHO) '    <IMPLEMENTATION>' >> $(DISTNAME).ppd
+	$(NOECHO) $(ECHO) '        <DEPENDENCY NAME="AuthCAS" VERSION="0,0,0,0" />' >> $(DISTNAME).ppd
+	$(NOECHO) $(ECHO) '        <DEPENDENCY NAME="ExtUtils-MakeMaker" VERSION="6,11,0,0" />' >> $(DISTNAME).ppd
+	$(NOECHO) $(ECHO) '        <DEPENDENCY NAME="Jifty" VERSION="0,60912,0,0" />' >> $(DISTNAME).ppd
+	$(NOECHO) $(ECHO) '        <OS NAME="$(OSNAME)" />' >> $(DISTNAME).ppd
+	$(NOECHO) $(ECHO) '        <ARCHITECTURE NAME="i486-linux-gnu-thread-multi" />' >> $(DISTNAME).ppd
+	$(NOECHO) $(ECHO) '        <CODEBASE HREF="" />' >> $(DISTNAME).ppd
+	$(NOECHO) $(ECHO) '    </IMPLEMENTATION>' >> $(DISTNAME).ppd
+	$(NOECHO) $(ECHO) '</SOFTPKG>' >> $(DISTNAME).ppd
+
+
+# --- MakeMaker pm_to_blib section:
+
+pm_to_blib : $(TO_INST_PM)
+	$(NOECHO) $(ABSPERLRUN) -MExtUtils::Install -e 'pm_to_blib({@ARGV}, '\''$(INST_LIB)/auto'\'', '\''$(PM_FILTER)'\'')' \
+	  lib/Jifty/Plugin/AuthCASOnly/CurrentUser.pm blib/lib/Jifty/Plugin/AuthCASOnly/CurrentUser.pm \
+	  lib/Jifty/Plugin/AuthCASOnly/Model/CASUser.pm blib/lib/Jifty/Plugin/AuthCASOnly/Model/CASUser.pm \
+	  lib/Jifty/Plugin/AuthCASOnly/Action/CASLogout.pm blib/lib/Jifty/Plugin/AuthCASOnly/Action/CASLogout.pm \
+	  lib/Jifty/Plugin/AuthCASOnly/Dispatcher.pm blib/lib/Jifty/Plugin/AuthCASOnly/Dispatcher.pm \
+	  lib/Jifty/Plugin/AuthCASOnly/Action/CASLogin.pm blib/lib/Jifty/Plugin/AuthCASOnly/Action/CASLogin.pm \
+	  lib/Jifty/Plugin/AuthCASOnly.pm blib/lib/Jifty/Plugin/AuthCASOnly.pm 
+	$(NOECHO) $(TOUCH) pm_to_blib
+
+
+# --- MakeMaker selfdocument section:
+
+
+# --- MakeMaker postamble section:
+
+
+# End.
+# Postamble by Module::Install 0.640
+# --- Module::Install::Admin::Makefile section:
+
+realclean purge ::
+	$(RM_F) $(DISTVNAME).tar$(SUFFIX)
+	$(RM_RF) inc MANIFEST.bak _build
+	$(PERL) -I. -MModule::Install::Admin -e "remove_meta()"
+
+reset :: purge
+
+upload :: test dist
+	cpan-upload -verbose $(DISTVNAME).tar$(SUFFIX)
+
+grok ::
+	perldoc Module::Install
+
+distsign ::
+	cpansign -s
+
+config ::
+	$(NOECHO) $(MOD_INSTALL) \
+		"share" $(INST_AUTODIR)
+

Added: jifty/branches/schema-plugins/plugins/AuthCASOnly/Makefile.PL
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthCASOnly/Makefile.PL	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,10 @@
+use inc::Module::Install;
+name('Jifty-Plugin-AuthCASOnly');
+license('Perl');
+version('0.01');
+requires('Jifty' => '0.60912');
+requires('AuthCAS');
+
+install_share;
+
+WriteAll;

Added: jifty/branches/schema-plugins/plugins/AuthCASOnly/debian/changelog
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthCASOnly/debian/changelog	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,6 @@
+libjifty-plugin-authcasonly-perl (0-1) unstable; urgency=low
+
+  * Initial Release.
+
+ -- AGOSTINI Yves <agostini at univ-metz.fr>  Fri, 17 Nov 2006 14:29:45 +0100
+

Added: jifty/branches/schema-plugins/plugins/AuthCASOnly/debian/compat
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthCASOnly/debian/compat	Mon Jan 29 21:05:02 2007
@@ -0,0 +1 @@
+4

Added: jifty/branches/schema-plugins/plugins/AuthCASOnly/debian/control
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthCASOnly/debian/control	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,16 @@
+Source: libjifty-plugin-authcasonly-perl
+Section: perl
+Priority: optional
+Build-Depends: debhelper (>= 4.0.2)
+Build-Depends-Indep: perl (>= 5.8.0-7)
+Maintainer: AGOSTINI Yves <agostini at univ-metz.fr>
+Standards-Version: 3.6.1
+
+Package: libjifty-plugin-authcasonly-perl
+Architecture: all
+Depends: ${perl:Depends}, ${misc:Depends}, libauthcas-perl
+Description:  Jifty::Plugin::AuthCASOnly
+ MUST BE USED WITHOUT Login PLUGIN
+ .
+ .
+ This description was automagically extracted from the module by dh-make-perl.

Added: jifty/branches/schema-plugins/plugins/AuthCASOnly/debian/copyright
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthCASOnly/debian/copyright	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,7 @@
+This is the debian package for the  module.
+It was created by AGOSTINI Yves <agostini at univ-metz.fr> using dh-make-perl.
+
+This copyright info was automatically extracted from the perl module.
+It may not be accurate, so you better check the module sources
+if don't want to get into legal troubles.
+

Added: jifty/branches/schema-plugins/plugins/AuthCASOnly/debian/files
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthCASOnly/debian/files	Mon Jan 29 21:05:02 2007
@@ -0,0 +1 @@
+libjifty-plugin-authcasonly-perl_0-1_all.deb perl optional

Added: jifty/branches/schema-plugins/plugins/AuthCASOnly/debian/rules
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthCASOnly/debian/rules	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,83 @@
+#!/usr/bin/make -f
+# This debian/rules file is provided as a template for normal perl
+# packages. It was created by Marc Brockschmidt <marc at dch-faq.de> for
+# the Debian Perl Group (http://pkg-perl.alioth.debian.org/) but may
+# be used freely wherever it is useful.
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+# If set to a true value then MakeMaker's prompt function will
+# always return the default without waiting for user input.
+export PERL_MM_USE_DEFAULT=1
+
+PACKAGE=$(shell dh_listpackages)
+
+ifndef PERL
+PERL = /usr/bin/perl
+endif
+
+TMP     =$(CURDIR)/debian/$(PACKAGE)
+
+build: build-stamp
+build-stamp:
+	dh_testdir
+
+	# Add commands to compile the package here
+	$(PERL) Makefile.PL INSTALLDIRS=vendor
+	$(MAKE) OPTIMIZE="-Wall -O2 -g"
+
+	touch build-stamp
+
+clean:
+	dh_testdir
+	dh_testroot
+
+	# Add commands to clean up after the build process here
+	-$(MAKE) distclean
+
+	dh_clean build-stamp install-stamp
+
+install: build install-stamp
+install-stamp:
+	dh_testdir
+	dh_testroot
+	dh_clean -k
+
+	# Add commands to install the package into debian/$PACKAGE_NAME here
+	$(MAKE) test
+	$(MAKE) install DESTDIR=$(TMP) PREFIX=/usr
+
+	# As this is a architecture independent package, we are not
+	# supposed to install stuff to /usr/lib. MakeMaker creates
+	# the dirs, we delete them from the deb:
+	rmdir --ignore-fail-on-non-empty --parents $(TMP)/usr/lib/perl5
+
+	touch install-stamp
+
+binary-arch:
+# We have nothing to do by default.
+
+binary-indep: build install
+	dh_testdir
+	dh_testroot
+#	dh_installcron
+#	dh_installmenu
+#	dh_installexamples
+	dh_installdocs 
+	dh_installchangelogs 
+	dh_perl
+	dh_link
+	dh_strip
+	dh_compress
+	dh_fixperms
+	dh_installdeb
+	dh_gencontrol
+	dh_md5sums
+	dh_builddeb
+
+source diff:                                                                  
+	@echo >&2 'source and diff are obsolete - use dpkg-source -b'; false
+
+binary: binary-indep binary-arch
+.PHONY: build clean binary-indep binary-arch binary

Added: jifty/branches/schema-plugins/plugins/AuthCASOnly/lib/Jifty/Plugin/AuthCASOnly.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthCASOnly/lib/Jifty/Plugin/AuthCASOnly.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,65 @@
+use strict;
+use warnings;
+
+=head1 NAME
+
+ Jifty::Plugin::AuthCASOnly
+
+=head1 DESCRIPTION
+
+ MUST NOT BE USED WITH LOGIN PLUGIN
+
+=head1 CONFIG
+
+ in etc/config.yml
+  Plugins: 
+    - AuthCASOnly: 
+       CASserver: https://auth.univ-metz.fr
+       CAFile: /home/agostini/univ.crt
+                    
+
+=cut
+
+package Jifty::Plugin::AuthCASOnly;
+use base qw/Jifty::Plugin/;
+use AuthCAS;
+
+# Your plugin goes here.  If takes any configuration or arguments, you
+# probably want to override L<Jifty::Plugin/init>.
+
+{
+    my ($CurrentCASUserClass, $AuthCASUserClass, $CAS);
+
+    sub init {
+        my $self = shift;
+        my %args = @_;
+        my $appname = Jifty->config->framework('ApplicationName');
+        $CurrentCASUserClass = $args{CurrentUserClass}
+            || "${appname}::CurrentUser";
+        $AuthCASUserClass = $args{AuthCASUserClass}
+            || "${appname}::Model::CASUser";
+
+	my ($conf, $cafile, $casserver);
+    	foreach (@{Jifty->config->framework('Plugins')}) {
+        	$conf = $_ if (defined $_->{'AuthCASOnly'});
+    	}
+    	$cafile = $conf->{'AuthCASOnly'}->{'CAFile'};
+    	$casserver = $conf->{'AuthCASOnly'}->{'CASserver'};
+    	$CAS = new AuthCAS(casUrl => $casserver,
+                  CAFile => $cafile);
+    }
+
+    sub CurrentCASUserClass {
+        return $CurrentCASUserClass;
+    }
+
+    sub AuthCASUserClass {
+        return $AuthCASUserClass;
+    }
+
+    sub CAS {
+	return $CAS;
+    }
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/AuthCASOnly/lib/Jifty/Plugin/AuthCASOnly/Action/CASLogin.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthCASOnly/lib/Jifty/Plugin/AuthCASOnly/Action/CASLogin.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,102 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Jifty::Plugin::AuthCASOnly::Action::CASLogin
+
+=cut
+
+package Jifty::Plugin::AuthCASOnly::Action::CASLogin;
+use base qw/Jifty::Action Jifty::Plugin::AuthCASOnly/;
+#use AuthCAS;
+
+
+=head2 arguments
+
+Return the ticket form field
+
+=cut
+
+sub arguments {
+    return (
+        {
+            ticket => {
+                label          => 'cas ticket',
+           #     mandatory      => 1,
+                ajax_validates => 1,
+            },
+
+        }
+    );
+
+}
+
+=head2 validate_ticket ST
+
+for ajax_validates
+Makes sure that the ticket submitted is legal.
+
+
+=cut
+
+sub validate_ticket {
+    my $self  = shift;
+    my $ticket = shift;
+
+    unless ( $ticket && $ticket !~ /^[A-Za-z0-9-]+$/ ) {
+        return $self->validation_error(
+            ticket => _("That doesn't look like a valid ticket.") );
+    }
+
+
+    return $self->validation_ok('ticket');
+}
+
+
+=head2 take_action
+
+Actually check the user's password. If it's right, log them in.
+Otherwise, throw an error.
+
+
+=cut
+
+sub take_action {
+    my $self = shift;
+    my $ticket = $self->argument_value('ticket');
+
+    my $service_url = ($ENV{SERVER_PORT} == 443)?'https://':'http://'.
+    	$ENV{HTTP_HOST}.'/caslogin';
+
+    if (! $ticket) {
+        my $login_url = $self->CAS->getServerLoginURL($service_url);
+        Jifty->web->_redirect($login_url);
+        return 1;
+      }
+      
+    my $username = $self->CAS->validateST($service_url,$ticket);
+    my $error = &AuthCAS::get_errors();
+    if ($error) {
+      Jifty->log->info("CAS error: $ticket $username : $error");
+      return;
+    }
+      
+    my $CASUser = $self->AuthCASUserClass();
+    my $CurrentUser = $self->CurrentCASUserClass();
+    my $u = $CASUser->new( current_user => $CurrentUser->superuser );
+
+    $u->load_by_cols( name => $username);
+    my $id = $u->id;
+    if (!$id) { 
+   	($id) = $u->create(name => $username, created_on => DateTime->now); 
+	}
+    Jifty->log->debug("Login user id: $id"); 
+
+    # Actually do the signin thing.
+     Jifty->web->current_user( $CurrentUser->new( id => $u->id ) );
+
+    return 1;
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/AuthCASOnly/lib/Jifty/Plugin/AuthCASOnly/Action/CASLogout.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthCASOnly/lib/Jifty/Plugin/AuthCASOnly/Action/CASLogout.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,35 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Jifty::Plugin::AuthCASOnly::Action::CASLogout
+
+=cut
+
+package Jifty::Plugin::AuthCASOnly::Action::CASLogout;
+use base qw/Jifty::Action/;
+
+=head2 arguments
+
+Return the email and password form fields
+
+=cut
+
+sub arguments {
+    return ( {} );
+}
+
+=head2 take_action
+
+Nuke the current user object
+
+=cut
+
+sub take_action {
+    my $self = shift;
+    Jifty->web->current_user(undef);
+    return 1;
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/AuthCASOnly/lib/Jifty/Plugin/AuthCASOnly/CurrentUser.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthCASOnly/lib/Jifty/Plugin/AuthCASOnly/CurrentUser.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,39 @@
+use warnings;
+use strict;
+
+
+package Jifty::Plugin::AuthCASOnly::CurrentUser;
+
+use base qw/Jifty::CurrentUser Jifty::Plugin::AuthCASOnly/;
+
+=head2 new PARAMHASH
+
+Instantiate a new current user object, loading the user by paramhash:
+
+   my $item = Jifty::Plugin::AuthCASOnly::Model::Item->new( Jifty::Plugin::AuthCASOnly::CurrentUser->new(name => 'user'));
+
+if you give the param 
+    _bootstrap => 1
+
+your object will be marked as a bootstrap user. You can use that to do an endrun around acls.
+
+=cut
+
+
+
+sub _init {
+    my $self = shift;
+    my %args = (@_);
+    my $AuthCASUserClass = $self->AuthCASUserClass;
+
+    if (delete $args{'_bootstrap'} ) {
+        $self->is_bootstrap_user(1);
+    } elsif (keys %args) {
+        $self->user_object($AuthCASUserClass->new(current_user => $self));
+        $self->user_object->load_by_cols(%args);
+    }
+    $self->SUPER::_init(%args);
+}
+
+
+1;

Added: jifty/branches/schema-plugins/plugins/AuthCASOnly/lib/Jifty/Plugin/AuthCASOnly/Dispatcher.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthCASOnly/lib/Jifty/Plugin/AuthCASOnly/Dispatcher.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,46 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::AuthCASOnly::Dispatcher;
+use Jifty::Dispatcher -base;
+
+# Put any plugin-specific dispatcher rules here.
+
+before 'caslogin' => run {
+ if (get('ticket')) {
+    set 'action' =>
+        Jifty->web->new_action(
+        class => 'CASLogin',
+        moniker => 'casloginbox',
+	arguments => { ticket => get('ticket') },
+	arguments => { redirect => get('redirect') },
+    );
+#    set 'next' => Jifty->web->request->continuation
+#        || Jifty::Continuation->new(
+#        request => Jifty::Request->new( path => "/" ) );
+  };
+};
+
+# Log out
+before 'caslogout' => run {
+    Jifty->web->request->add_action(
+        class   => 'CASLogout',
+        moniker => 'caslogout',
+    );
+};
+
+
+# Login
+#on 'caslogin' => run {
+#    set 'action' =>
+#        Jifty->web->new_action(
+#        class => 'CASLogin',
+#        moniker => 'casloginbox'
+#    );
+#    set 'next' => Jifty->web->request->continuation
+#        || Jifty::Continuation->new(
+#        request => Jifty::Request->new( path => "/" ) );
+#};
+
+
+1;

Added: jifty/branches/schema-plugins/plugins/AuthCASOnly/lib/Jifty/Plugin/AuthCASOnly/Model/CASUser.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthCASOnly/lib/Jifty/Plugin/AuthCASOnly/Model/CASUser.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,71 @@
+package Jifty::Plugin::AuthCASOnly::Model::CASUser;
+use base qw/Jifty::Record/;
+use Jifty::DBI::Schema;
+use Scalar::Defer;
+
+use Jifty::Record schema {
+column
+  name => type is 'text',
+  label is 'Name',
+  is mandatory,
+  is distinct;
+
+column 'created_on' =>
+  type is 'datetime',
+  is immutable,
+  default is defer { DateTime->now },
+  filters are 'Jifty::DBI::Filter::DateTime';
+};
+
+
+sub create {
+    my $self  = shift;
+    my %args  = (@_);
+    my (@ret) = $self->SUPER::create(%args);
+
+#    if ( $self->id and not $self->email_confirmed ) {
+#        Jifty::Plugin::Login::Notification::ConfirmAddress->new( to => $self )
+#          ->send;
+#    }
+    return (@ret);
+}
+
+=head2 current_user_can
+
+Allows the current user to see all their own attributes and
+everyone else to see their username.
+
+Allows the current user to update any of their own attributes
+except whether or not their email has been confirmed.
+
+Passes everything else off to the superclass.
+
+=cut
+
+sub current_user_can {
+    my $self  = shift;
+    my $right = shift;
+    my %args  = (@_);
+    # This line breaks admin mode. I like admin mode.
+    #    Carp::confess if ( $right eq 'read' and not $args{'column'} );
+    if (    $right eq 'read'
+        and $self->id == $self->current_user->id )
+    {
+        return 1;
+    }
+    elsif ( $right eq 'read' and $args{'column'} eq 'name' ) {
+        return (1);
+
+    }
+    elsif ( $right eq 'update'
+        and $self->id == $self->current_user->id
+#        and $args{'column'} ne 'email_confirmed' 
+        )
+    {
+        return (1);
+    }
+
+    return $self->SUPER::current_user_can( $right, %args );
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/AuthCASOnly/share/web/templates/caslogin
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthCASOnly/share/web/templates/caslogin	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,11 @@
+<%args>
+$ticket => ""
+</%args>
+<%init>
+Jifty->web->new_action(
+    moniker => 'casloginbox',
+    class   => 'CASLogin',
+    arguments => { ticket => $ticket }
+)->run;
+Jifty->web->redirect("/");
+</%init>

Added: jifty/branches/schema-plugins/plugins/AuthCASOnly/share/web/templates/caslogout
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthCASOnly/share/web/templates/caslogout	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,3 @@
+<&| /_elements/wrapper, title => "Logged out" &>
+<p><% _("Ok, you're now logged out. Have a good day.") %></p>
+</&>

Added: jifty/branches/schema-plugins/plugins/AuthCASOnly/t/00-load.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthCASOnly/t/00-load.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,5 @@
+#!/usr/bin/env perl -w
+use strict;
+use Test::More tests => 1;
+
+use_ok('Jifty::Plugin::AuthCASOnly');

Added: jifty/branches/schema-plugins/plugins/AuthLDAPLogin/MANIFEST
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthLDAPLogin/MANIFEST	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,16 @@
+debian/changelog
+debian/compat
+debian/control
+debian/copyright
+debian/rules
+lib/Jifty/Plugin/AuthLDAPLogin.pm
+lib/Jifty/Plugin/AuthLDAPLogin/Action/LDAPLogin.pm
+lib/Jifty/Plugin/AuthLDAPLogin/Action/LDAPLogout.pm
+lib/Jifty/Plugin/AuthLDAPLogin/Dispatcher.pm
+Makefile.PL
+MANIFEST
+share/po/en.po
+share/po/fr.po
+share/web/templates/ldaplogin
+share/web/templates/ldaplogout
+t/00-load.t

Added: jifty/branches/schema-plugins/plugins/AuthLDAPLogin/Makefile.PL
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthLDAPLogin/Makefile.PL	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,11 @@
+use inc::Module::Install;
+name('Jifty-Plugin-AuthLDAPLogin');
+license('Perl');
+version('0.01');
+requires('Jifty' => '0.60912');
+requires('Jifty::Plugin::Login');
+requires('Net::LDAP');
+
+install_share;
+
+WriteAll;

Added: jifty/branches/schema-plugins/plugins/AuthLDAPLogin/debian/changelog
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthLDAPLogin/debian/changelog	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,6 @@
+libjifty-plugin-authldaplogin-perl (0-1) unstable; urgency=low
+
+  * Initial Release.
+
+ -- AGOSTINI Yves <agostini at univ-metz.fr>  Fri, 17 Nov 2006 09:52:24 +0100
+

Added: jifty/branches/schema-plugins/plugins/AuthLDAPLogin/debian/compat
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthLDAPLogin/debian/compat	Mon Jan 29 21:05:02 2007
@@ -0,0 +1 @@
+4

Added: jifty/branches/schema-plugins/plugins/AuthLDAPLogin/debian/control
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthLDAPLogin/debian/control	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,19 @@
+Source: libjifty-plugin-authldaplogin-perl
+Section: perl
+Priority: optional
+Build-Depends: debhelper (>= 4.0.2)
+Build-Depends-Indep: perl (>= 5.8.0-7)
+Maintainer: AGOSTINI Yves <agostini at univ-metz.fr>
+Standards-Version: 3.6.1
+
+Package: libjifty-plugin-authldaplogin-perl
+Architecture: all
+Depends: ${perl:Depends}, ${misc:Depends}, libjifty-plugin-login-perl, 
+ libnet-ldap
+Description:  Jifty::Plugin::AuthLDAPLogin
+ MUST BE USED WITH Login PLUGIN.
+ .
+ Add ldap users in Jifty::Plugin::Login::Model::User. 
+ Distinct id for ldap users is email field with login at LDAP.user
+ .
+ This description was automagically extracted from the module by dh-make-perl.

Added: jifty/branches/schema-plugins/plugins/AuthLDAPLogin/debian/copyright
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthLDAPLogin/debian/copyright	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,7 @@
+This is the debian package for the  module.
+It was created by AGOSTINI Yves <agostini at univ-metz.fr> using dh-make-perl.
+
+This copyright info was automatically extracted from the perl module.
+It may not be accurate, so you better check the module sources
+if don't want to get into legal troubles.
+

Added: jifty/branches/schema-plugins/plugins/AuthLDAPLogin/debian/rules
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthLDAPLogin/debian/rules	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,83 @@
+#!/usr/bin/make -f
+# This debian/rules file is provided as a template for normal perl
+# packages. It was created by Marc Brockschmidt <marc at dch-faq.de> for
+# the Debian Perl Group (http://pkg-perl.alioth.debian.org/) but may
+# be used freely wherever it is useful.
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+# If set to a true value then MakeMaker's prompt function will
+# always return the default without waiting for user input.
+export PERL_MM_USE_DEFAULT=1
+
+PACKAGE=$(shell dh_listpackages)
+
+ifndef PERL
+PERL = /usr/bin/perl
+endif
+
+TMP     =$(CURDIR)/debian/$(PACKAGE)
+
+build: build-stamp
+build-stamp:
+	dh_testdir
+
+	# Add commands to compile the package here
+	$(PERL) Makefile.PL INSTALLDIRS=vendor
+	$(MAKE) OPTIMIZE="-Wall -O2 -g"
+
+	touch build-stamp
+
+clean:
+	dh_testdir
+	dh_testroot
+
+	# Add commands to clean up after the build process here
+	-$(MAKE) distclean
+
+	dh_clean build-stamp install-stamp
+
+install: build install-stamp
+install-stamp:
+	dh_testdir
+	dh_testroot
+	dh_clean -k
+
+	# Add commands to install the package into debian/$PACKAGE_NAME here
+	$(MAKE) test
+	$(MAKE) install DESTDIR=$(TMP) PREFIX=/usr
+
+	# As this is a architecture independent package, we are not
+	# supposed to install stuff to /usr/lib. MakeMaker creates
+	# the dirs, we delete them from the deb:
+	rmdir --ignore-fail-on-non-empty --parents $(TMP)/usr/lib/perl5
+
+	touch install-stamp
+
+binary-arch:
+# We have nothing to do by default.
+
+binary-indep: build install
+	dh_testdir
+	dh_testroot
+#	dh_installcron
+#	dh_installmenu
+#	dh_installexamples
+	dh_installdocs 
+	dh_installchangelogs 
+	dh_perl
+	dh_link
+	dh_strip
+	dh_compress
+	dh_fixperms
+	dh_installdeb
+	dh_gencontrol
+	dh_md5sums
+	dh_builddeb
+
+source diff:                                                                  
+	@echo >&2 'source and diff are obsolete - use dpkg-source -b'; false
+
+binary: binary-indep binary-arch
+.PHONY: build clean binary-indep binary-arch binary

Added: jifty/branches/schema-plugins/plugins/AuthLDAPLogin/lib/Jifty/Plugin/AuthLDAPLogin.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthLDAPLogin/lib/Jifty/Plugin/AuthLDAPLogin.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,69 @@
+use strict;
+use warnings;
+
+=head1 NAME
+
+Jifty::Plugin::AuthLDAPLogin
+
+=head1 DESCRIPTION
+
+B<MUST BE USED WITH Login PLUGIN.>
+
+Add ldap users in L<Jifty::Plugin::Login::Model::User>. 
+Distinct id for ldap users is C<email> field with C<login at LDAP.user>
+
+add /ldaplogin
+    /ldpalogout
+
+=head1 CONFIG
+
+in etc/config.yml
+
+  Plugins: 
+    - Login: {}
+    - AuthLDAPLogin: 
+       LDAPhost: ldap.univ.fr           # ldap server
+       LDAPbase: ou=people,dc=.....     # base ldap
+       LDAPuid: uid                     # optional
+
+=head1 SEE ALSO
+
+L<Net::LDAP>
+
+=cut
+
+package Jifty::Plugin::AuthLDAPLogin;
+use base qw/Jifty::Plugin/;
+use Net::LDAP;
+
+# Your plugin goes here.  If takes any configuration or arguments, you
+# probably want to override L<Jifty::Plugin/init>.
+
+{
+    my ($LDAP, %params);
+
+    sub init {
+        my $self = shift;
+        my %args = @_;
+
+    	$params{'Hostname'} = $args{LDAPhost};
+    	$params{'base'} = $args{LDAPbase};
+    	$params{'uid'} = $args{LDAPuid} || "uid";
+    	$LDAP = Net::LDAP->new($params{Hostname},async=>1,onerror => 'undef', debug => 0);
+    }
+
+    sub LDAP {
+	    return $LDAP;
+    }
+
+    sub base {
+        return $params{'base'};
+    }
+
+    sub uid {
+        return $params{'uid'};
+    }
+
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/AuthLDAPLogin/lib/Jifty/Plugin/AuthLDAPLogin/Action/LDAPLogin.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthLDAPLogin/lib/Jifty/Plugin/AuthLDAPLogin/Action/LDAPLogin.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,106 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Jifty::Plugin::AuthLDAPLogin::Action::LDAPLogin
+
+=cut
+
+package Jifty::Plugin::AuthLDAPLogin::Action::LDAPLogin;
+use base qw/Jifty::Action Jifty::Plugin::Login Jifty::Plugin::AuthLDAPLogin/;
+
+
+=head2 arguments
+
+Return the login form field
+
+=cut
+
+sub arguments {
+    return (
+        {
+            name => {
+                label          => _('Login'),
+                mandatory      => 1,
+                ajax_validates => 1,
+            },
+
+            password => {
+                type      => 'password',
+                label     => _('Password'),
+                mandatory => 1
+            },
+
+        }
+    );
+
+}
+
+=head2 validate_name NAME
+
+For ajax_validates.
+Makes sure that the name submitted is a legal login.
+
+
+=cut
+
+sub validate_name {
+    my $self  = shift;
+    my $name = shift;
+
+    unless ( $name =~ /^[A-Za-z0-9-]+$/ ) {
+        return $self->validation_error(
+            name => _("That doesn't look like a valid login.") );
+    }
+
+
+    return $self->validation_ok('name');
+}
+
+
+=head2 take_action
+
+Bind on ldap to check the user's password. If it's right, log them in.
+Otherwise, throw an error.
+
+
+=cut
+
+sub take_action {
+    my $self = shift;
+    my $username = $self->argument_value('name');
+    my $dn = $self->uid().'='.$username.','.
+        $self->base();
+
+    # Bind on ldap
+    my $msg = $self->LDAP()->bind($dn ,'password' =>$self->argument_value('password'));
+    
+    unless (not $msg->code) {
+        $self->result->error(
+     _('You may have mistyped your login or password. Give it another shot?')
+        );
+        return;
+    }
+
+    my $LDAPUser = $self->LoginUserClass();
+    my $CurrentUser = $self->CurrentUserClass();
+    my $u = $LDAPUser->new( current_user => $CurrentUser->superuser );
+
+    # Distinct id is login at LDAP.user
+    # Add user to User Login model
+    $u->load_by_cols( email => $username.'@LDAP.user');
+    my $id = $u->id;
+    if (!$id) {
+        ($id) = $u->create(name => $username, email => $username.'@LDAP.user');
+    }
+
+    Jifty->log->debug("Login user id: $id"); 
+
+    # Actually do the signin thing.
+     Jifty->web->current_user( $CurrentUser->new( id => $u->id ) );
+
+    return 1;
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/AuthLDAPLogin/lib/Jifty/Plugin/AuthLDAPLogin/Action/LDAPLogout.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthLDAPLogin/lib/Jifty/Plugin/AuthLDAPLogin/Action/LDAPLogout.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,35 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Jifty::Plugin::AuthLDAPLogin::Action::LDAPLogout
+
+=cut
+
+package Jifty::Plugin::AuthLDAPLogin::Action::LDAPLogout;
+use base qw/Jifty::Action/;
+
+=head2 arguments
+
+Return the email and password form fields
+
+=cut
+
+sub arguments {
+    return ( {} );
+}
+
+=head2 take_action
+
+Nuke the current user object
+
+=cut
+
+sub take_action {
+    my $self = shift;
+    Jifty->web->current_user(undef);
+    return 1;
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/AuthLDAPLogin/lib/Jifty/Plugin/AuthLDAPLogin/Dispatcher.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthLDAPLogin/lib/Jifty/Plugin/AuthLDAPLogin/Dispatcher.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,31 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::AuthLDAPLogin::Dispatcher;
+use Jifty::Dispatcher -base;
+
+# Put any plugin-specific dispatcher rules here.
+
+# Log out
+before 'ldaplogout' => run {
+    Jifty->web->request->add_action(
+        class   => 'LDAPLogout',
+        moniker => 'ldaplogout',
+    );
+};
+
+
+# Login
+on 'ldaplogin' => run {
+    set 'action' =>
+        Jifty->web->new_action(
+        class => 'LDAPLogin',
+        moniker => 'ldaploginbox'
+    );
+    set 'next' => Jifty->web->request->continuation
+        || Jifty::Continuation->new(
+        request => Jifty::Request->new( path => "/" ) );
+};
+
+
+1;

Added: jifty/branches/schema-plugins/plugins/AuthLDAPLogin/share/po/en.po
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthLDAPLogin/share/po/en.po	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,25 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
+"Language-Team: LANGUAGE <LL at li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: lib/Jifty/Plugin/AuthLDAPLogin/Action/LDAPLogin.pm:54
+msgid "That doesn't look like a valid login."
+msgstr ""
+
+#: lib/Jifty/Plugin/AuthLDAPLogin/Action/LDAPLogin.pm:81
+msgid "You may have mistyped your login or password. Give it another shot?"
+msgstr ""
+

Added: jifty/branches/schema-plugins/plugins/AuthLDAPLogin/share/po/fr.po
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthLDAPLogin/share/po/fr.po	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,25 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"POT-Creation-Date: 2006-11-17 10:23+ZONE\n"
+"PO-Revision-Date: 2006-11-17 10:23+ZONE\n"
+"Last-Translator: Yves Agostini <agostini at univ-metz.fr>\n"
+"Language-Team: LANGUAGE <LL at li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=ISO8859-15\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: lib/Jifty/Plugin/AuthLDAPLogin/Action/LDAPLogin.pm:54
+msgid "That doesn't look like a valid login."
+msgstr "Cet identifiant ne semble pas valide."
+
+#: lib/Jifty/Plugin/AuthLDAPLogin/Action/LDAPLogin.pm:81
+msgid "You may have mistyped your login or password. Give it another shot?"
+msgstr "Erreur dans votre identifiant ou votre mot de passe. Ré-essayez."
+

Added: jifty/branches/schema-plugins/plugins/AuthLDAPLogin/share/web/templates/ldaplogin
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthLDAPLogin/share/web/templates/ldaplogin	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,19 @@
+<%args>
+$action => undef
+$next => undef
+</%args>
+<&|/_elements/wrapper, title => 'Login' &>
+
+% if (not Jifty->web->current_user->id) {
+<h2><% _('Login') %></h2>
+<% Jifty->web->form->start(call => $next, name => "ldaploginbox") %>
+<% $action->form_field('name') %>
+<% $action->form_field('password') %>
+%#<% $action->form_field('remember') %>
+<% Jifty->web->form->submit(label => _('Login'), submit => $action) %>
+<% Jifty->web->form->end %>
+% }
+% else {
+<% _("You're already logged in.") %>
+% }
+</&>

Added: jifty/branches/schema-plugins/plugins/AuthLDAPLogin/share/web/templates/ldaplogout
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthLDAPLogin/share/web/templates/ldaplogout	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,3 @@
+<&| /_elements/wrapper, title => "Logged out" &>
+<p><% _("Ok, you're now logged out. Have a good day.") %></p>
+</&>

Added: jifty/branches/schema-plugins/plugins/AuthLDAPLogin/t/00-load.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthLDAPLogin/t/00-load.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,5 @@
+#!/usr/bin/env perl -w
+use strict;
+use Test::More tests => 1;
+
+use_ok('Jifty::Plugin::AuthLDAPLogin');

Added: jifty/branches/schema-plugins/plugins/AuthLDAPOnly/MANIFEST
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthLDAPOnly/MANIFEST	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,13 @@
+lib/Jifty/Plugin/AuthLDAPOnly.pm
+lib/Jifty/Plugin/AuthLDAPOnly/Action/LDAPLogin.pm
+lib/Jifty/Plugin/AuthLDAPOnly/Action/LDAPLogout.pm
+lib/Jifty/Plugin/AuthLDAPOnly/CurrentUser.pm
+lib/Jifty/Plugin/AuthLDAPOnly/Dispatcher.pm
+lib/Jifty/Plugin/AuthLDAPOnly/Model/LDAPUser.pm
+Makefile.PL
+MANIFEST
+share/po/en.po
+share/po/fr.po
+share/web/templates/ldaplogin
+share/web/templates/ldaplogout
+t/00-load.t

Added: jifty/branches/schema-plugins/plugins/AuthLDAPOnly/Makefile.PL
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthLDAPOnly/Makefile.PL	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,11 @@
+use inc::Module::Install;
+name('Jifty-Plugin-AuthLDAPOnly');
+license('Perl');
+version('0.01');
+requires('Jifty' => '0.60912');
+requires('Scalar::Defer');
+requires('Net::LDAP');
+
+install_share;
+
+WriteAll;

Added: jifty/branches/schema-plugins/plugins/AuthLDAPOnly/debian/changelog
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthLDAPOnly/debian/changelog	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,6 @@
+libjifty-plugin-authldaponly-perl (0-1) unstable; urgency=low
+
+  * Initial Release.
+
+ -- AGOSTINI Yves <agostini at univ-metz.fr>  Fri, 17 Nov 2006 11:10:53 +0100
+

Added: jifty/branches/schema-plugins/plugins/AuthLDAPOnly/debian/compat
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthLDAPOnly/debian/compat	Mon Jan 29 21:05:02 2007
@@ -0,0 +1 @@
+4

Added: jifty/branches/schema-plugins/plugins/AuthLDAPOnly/debian/control
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthLDAPOnly/debian/control	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,19 @@
+Source: libjifty-plugin-authldaponly-perl
+Section: perl
+Priority: optional
+Build-Depends: debhelper (>= 4.0.2)
+Build-Depends-Indep: perl (>= 5.8.0-7)
+Maintainer: AGOSTINI Yves <agostini at univ-metz.fr>
+Standards-Version: 3.6.1
+
+Package: libjifty-plugin-authldaponly-perl
+Architecture: all
+Depends: ${perl:Depends}, ${misc:Depends}, libjifty-perl, libnet-ldap-perl 
+Description:  Jifty::Plugin::AuthLDAPOnly
+ MUST NOT BE USED WITH Login PLUGIN
+ .
+ Provide authentication: only for users in your ldap.
+ .
+ If you need external users see Jifty::Plugin::AuthLDAPLogin
+ .
+ This description was automagically extracted from the module by dh-make-perl.

Added: jifty/branches/schema-plugins/plugins/AuthLDAPOnly/debian/copyright
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthLDAPOnly/debian/copyright	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,7 @@
+This is the debian package for the  module.
+It was created by AGOSTINI Yves <agostini at univ-metz.fr> using dh-make-perl.
+
+This copyright info was automatically extracted from the perl module.
+It may not be accurate, so you better check the module sources
+if don't want to get into legal troubles.
+

Added: jifty/branches/schema-plugins/plugins/AuthLDAPOnly/debian/rules
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthLDAPOnly/debian/rules	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,83 @@
+#!/usr/bin/make -f
+# This debian/rules file is provided as a template for normal perl
+# packages. It was created by Marc Brockschmidt <marc at dch-faq.de> for
+# the Debian Perl Group (http://pkg-perl.alioth.debian.org/) but may
+# be used freely wherever it is useful.
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+# If set to a true value then MakeMaker's prompt function will
+# always return the default without waiting for user input.
+export PERL_MM_USE_DEFAULT=1
+
+PACKAGE=$(shell dh_listpackages)
+
+ifndef PERL
+PERL = /usr/bin/perl
+endif
+
+TMP     =$(CURDIR)/debian/$(PACKAGE)
+
+build: build-stamp
+build-stamp:
+	dh_testdir
+
+	# Add commands to compile the package here
+	$(PERL) Makefile.PL INSTALLDIRS=vendor
+	$(MAKE) OPTIMIZE="-Wall -O2 -g"
+
+	touch build-stamp
+
+clean:
+	dh_testdir
+	dh_testroot
+
+	# Add commands to clean up after the build process here
+	-$(MAKE) distclean
+
+	dh_clean build-stamp install-stamp
+
+install: build install-stamp
+install-stamp:
+	dh_testdir
+	dh_testroot
+	dh_clean -k
+
+	# Add commands to install the package into debian/$PACKAGE_NAME here
+	#$(MAKE) test
+	$(MAKE) install DESTDIR=$(TMP) PREFIX=/usr
+
+	# As this is a architecture independent package, we are not
+	# supposed to install stuff to /usr/lib. MakeMaker creates
+	# the dirs, we delete them from the deb:
+	rmdir --ignore-fail-on-non-empty --parents $(TMP)/usr/lib/perl5
+
+	touch install-stamp
+
+binary-arch:
+# We have nothing to do by default.
+
+binary-indep: build install
+	dh_testdir
+	dh_testroot
+#	dh_installcron
+#	dh_installmenu
+#	dh_installexamples
+	dh_installdocs 
+	dh_installchangelogs 
+	dh_perl
+	dh_link
+	dh_strip
+	dh_compress
+	dh_fixperms
+	dh_installdeb
+	dh_gencontrol
+	dh_md5sums
+	dh_builddeb
+
+source diff:                                                                  
+	@echo >&2 'source and diff are obsolete - use dpkg-source -b'; false
+
+binary: binary-indep binary-arch
+.PHONY: build clean binary-indep binary-arch binary

Added: jifty/branches/schema-plugins/plugins/AuthLDAPOnly/lib/Jifty/Plugin/AuthLDAPOnly.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthLDAPOnly/lib/Jifty/Plugin/AuthLDAPOnly.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,83 @@
+use strict;
+use warnings;
+
+=head1 NAME
+
+Jifty::Plugin::AuthLDAPOnly
+
+=head1 DESCRIPTION
+
+B<MUST NOT BE USED WITH Login PLUGIN>
+
+Provide authentication: only for users in your ldap.
+
+If you need external users see C<Jifty::Plugin::AuthLDAPLogin>
+
+=head1 CONFIG
+
+in etc/config.yml
+  Plugins: 
+    - AuthLDAPOnly: 
+       LDAPhost: ldap1.univ-metz.fr     # ldap host
+       LDAPbase: ou=people, ou=...      # ldap base
+       LDAPuid: uid                     # optional
+
+in your user model
+  use base qw/Jifty::Plugin::AuthLDAPOnly::Model::LDAPUser/;
+
+in your application use /ldaplogin and /ldaplogout
+
+=head1 SEE ALSO
+
+L<Net::LDAP>
+
+=cut
+
+package Jifty::Plugin::AuthLDAPOnly;
+use base qw/Jifty::Plugin/;
+use Net::LDAP;
+
+# Your plugin goes here.  If takes any configuration or arguments, you
+# probably want to override L<Jifty::Plugin/init>.
+
+{
+    my ($CurrentLDAPUserClass, $AuthLDAPUserClass, $LDAP, %params);
+
+    sub init {
+        my $self = shift;
+        my %args = @_;
+        my $appname = Jifty->config->framework('ApplicationName');
+        $CurrentLDAPUserClass = $args{CurrentUserClass}
+            || "${appname}::CurrentUser";
+        $AuthLDAPUserClass = $args{AuthLDAPUserClass}
+            || "${appname}::Model::LDAPUser";
+
+    	$params{'Hostname'} = $args{LDAPhost};
+    	$params{'base'} = $args{LDAPbase};
+    	$params{'uid'} = $args{LDAPuid} || "uid";
+    	$LDAP = Net::LDAP->new($params{Hostname},async=>1,onerror => 'undef', debug => 0);
+    }
+
+    sub CurrentLDAPUserClass {
+        return $CurrentLDAPUserClass;
+    }
+
+    sub AuthLDAPUserClass {
+        return $AuthLDAPUserClass;
+    }
+
+    sub LDAP {
+	    return $LDAP;
+    }
+
+    sub base {
+        return $params{'base'};
+    }
+
+    sub uid {
+        return $params{'uid'};
+    }
+
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/AuthLDAPOnly/lib/Jifty/Plugin/AuthLDAPOnly/Action/LDAPLogin.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthLDAPOnly/lib/Jifty/Plugin/AuthLDAPOnly/Action/LDAPLogin.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,103 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Jifty::Plugin::AuthLDAPOnly::Action::LDAPLogin
+
+=cut
+
+package Jifty::Plugin::AuthLDAPOnly::Action::LDAPLogin;
+use base qw/Jifty::Action Jifty::Plugin::AuthLDAPOnly/;
+
+
+=head2 arguments
+
+Return the ticket form field
+
+=cut
+
+sub arguments {
+    return (
+        {
+            name => {
+                label          => _('Login'),
+                mandatory      => 1,
+                ajax_validates => 1,
+            },
+
+            password => {
+                type      => 'password',
+                label     => _('Password'),
+                mandatory => 1
+            },
+
+        }
+    );
+
+}
+
+=head2 validate_name NAME
+
+For ajax_validates.
+Makes sure that the name submitted is a legal login.
+
+
+=cut
+
+sub validate_name {
+    my $self  = shift;
+    my $name = shift;
+
+    unless ( $name =~ /^[A-Za-z0-9-]+$/ ) {
+        return $self->validation_error(
+            name => _("That doesn't look like a valid login.") );
+    }
+
+
+    return $self->validation_ok('name');
+}
+
+
+=head2 take_action
+
+Bind on ldap to check the user's password. If it's right, log them in.
+Otherwise, throw an error.
+
+
+=cut
+
+sub take_action {
+    my $self = shift;
+    my $dn = $self->uid().'='.$self->argument_value('name').','.
+        $self->base();
+
+    # Bind on ldap
+    my $msg = $self->LDAP()->bind($dn ,'password' =>$self->argument_value('password'));
+    
+    unless (not $msg->code) {
+        $self->result->error(
+     _('You may have mistyped your login or password. Give it another shot?')
+        );
+        return;
+    }
+
+    my $LDAPUser = $self->AuthLDAPUserClass();
+    my $CurrentUser = $self->CurrentLDAPUserClass();
+    my $u = $LDAPUser->new( current_user => $CurrentUser->superuser );
+
+    # Add user to LDAPUser model
+    $u->load_by_cols( name => $self->argument_value('name'));
+    my $id = $u->id;
+    if (!$id) { 
+   	($id) = $u->create(name => $self->argument_value('name'), created_on => DateTime->now); 
+	}
+    Jifty->log->debug("Login user id: $id"); 
+
+    # Actually do the signin thing.
+     Jifty->web->current_user( $CurrentUser->new( id => $u->id ) );
+
+    return 1;
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/AuthLDAPOnly/lib/Jifty/Plugin/AuthLDAPOnly/Action/LDAPLogout.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthLDAPOnly/lib/Jifty/Plugin/AuthLDAPOnly/Action/LDAPLogout.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,35 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Jifty::Plugin::AuthLDAPOnly::Action::LDAPLogout
+
+=cut
+
+package Jifty::Plugin::AuthLDAPOnly::Action::LDAPLogout;
+use base qw/Jifty::Action/;
+
+=head2 arguments
+
+Return the email and password form fields
+
+=cut
+
+sub arguments {
+    return ( {} );
+}
+
+=head2 take_action
+
+Nuke the current user object
+
+=cut
+
+sub take_action {
+    my $self = shift;
+    Jifty->web->current_user(undef);
+    return 1;
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/AuthLDAPOnly/lib/Jifty/Plugin/AuthLDAPOnly/CurrentUser.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthLDAPOnly/lib/Jifty/Plugin/AuthLDAPOnly/CurrentUser.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,39 @@
+use warnings;
+use strict;
+
+
+package Jifty::Plugin::AuthLDAPOnly::CurrentUser;
+
+use base qw/Jifty::CurrentUser Jifty::Plugin::AuthLDAPOnly/;
+
+=head2 new PARAMHASH
+
+Instantiate a new current user object, loading the user by paramhash:
+
+   my $item = Jifty::Plugin::AuthLDAPOnly::Model::Item->new( Jifty::Plugin::AuthCASOnly::CurrentUser->new(email => 'user at LDAP.user'));
+
+if you give the param 
+    _bootstrap => 1
+
+your object will be marked as a bootstrap user. You can use that to do an endrun around acls.
+
+=cut
+
+
+
+sub _init {
+    my $self = shift;
+    my %args = (@_);
+    my $AuthLDAPUserClass = $self->AuthLDAPUserClass;
+
+    if (delete $args{'_bootstrap'} ) {
+        $self->is_bootstrap_user(1);
+    } elsif (keys %args) {
+        $self->user_object($AuthLDAPUserClass->new(current_user => $self));
+        $self->user_object->load_by_cols(%args);
+    }
+    $self->SUPER::_init(%args);
+}
+
+
+1;

Added: jifty/branches/schema-plugins/plugins/AuthLDAPOnly/lib/Jifty/Plugin/AuthLDAPOnly/Dispatcher.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthLDAPOnly/lib/Jifty/Plugin/AuthLDAPOnly/Dispatcher.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,31 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::AuthLDAPOnly::Dispatcher;
+use Jifty::Dispatcher -base;
+
+# Put any plugin-specific dispatcher rules here.
+
+# Log out
+before 'ldaplogout' => run {
+    Jifty->web->request->add_action(
+        class   => 'LDAPLogout',
+        moniker => 'ldaplogout',
+    );
+};
+
+
+# Login
+on 'ldaplogin' => run {
+    set 'action' =>
+        Jifty->web->new_action(
+        class => 'LDAPLogin',
+        moniker => 'ldaploginbox'
+    );
+    set 'next' => Jifty->web->request->continuation
+        || Jifty::Continuation->new(
+        request => Jifty::Request->new( path => "/" ) );
+};
+
+
+1;

Added: jifty/branches/schema-plugins/plugins/AuthLDAPOnly/lib/Jifty/Plugin/AuthLDAPOnly/Model/LDAPUser.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthLDAPOnly/lib/Jifty/Plugin/AuthLDAPOnly/Model/LDAPUser.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,72 @@
+package Jifty::Plugin::AuthLDAPOnly::Model::LDAPUser;
+use base qw/Jifty::Record/;
+use Jifty::DBI::Schema;
+use Scalar::Defer;
+
+use Jifty::Record schema {
+
+column
+  name => type is 'text',
+  label is 'Name',
+  is mandatory,
+  is distinct;
+
+column 'created_on' =>
+  type is 'datetime',
+  is immutable,
+  default is defer { DateTime->now },
+  filters are 'Jifty::DBI::Filter::DateTime';
+
+};
+
+
+sub create {
+    my $self  = shift;
+    my %args  = (@_);
+    my (@ret) = $self->SUPER::create(%args);
+
+#    if ( $self->id and not $self->email_confirmed ) {
+#        Jifty::Plugin::Login::Notification::ConfirmAddress->new( to => $self )
+#          ->send;
+#    }
+    return (@ret);
+}
+
+=head2 current_user_can
+
+Allows the current user to see all their own attributes and
+everyone else to see their username.
+
+Allows the current user to update any of their own attributes
+except whether or not their email has been confirmed.
+
+Passes everything else off to the superclass.
+
+=cut
+
+sub current_user_can {
+    my $self  = shift;
+    my $right = shift;
+    my %args  = (@_);
+    # This line breaks admin mode. I like admin mode.
+    #    Carp::confess if ( $right eq 'read' and not $args{'column'} );
+    if (    $right eq 'read'
+        and $self->id == $self->current_user->id )
+    {
+        return 1;
+    }
+    elsif ( $right eq 'read' and $args{'column'} eq 'name' ) {
+        return (1);
+
+    }
+    elsif ( $right eq 'update'
+        and $self->id == $self->current_user->id
+        )
+    {
+        return (1);
+    }
+
+    return $self->SUPER::current_user_can( $right, %args );
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/AuthLDAPOnly/share/po/en.po
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthLDAPOnly/share/po/en.po	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,40 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
+"Language-Team: LANGUAGE <LL at li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: lib/Jifty/Plugin/AuthLDAPOnly/Action/LDAPLogin.pm:24 share/web/templates/ldaplogin:13 share/web/templates/ldaplogin:8
+msgid "Login"
+msgstr ""
+
+#: share/web/templates/ldaplogout:2
+msgid "Ok, you're now logged out. Have a good day."
+msgstr ""
+
+#: lib/Jifty/Plugin/AuthLDAPOnly/Action/LDAPLogin.pm:31
+msgid "Password"
+msgstr ""
+
+#: lib/Jifty/Plugin/AuthLDAPOnly/Action/LDAPLogin.pm:54
+msgid "That doesn't look like a valid login."
+msgstr ""
+
+#: lib/Jifty/Plugin/AuthLDAPOnly/Action/LDAPLogin.pm:80
+msgid "You may have mistyped your login or password. Give it another shot?"
+msgstr ""
+
+#: share/web/templates/ldaplogin:17
+msgid "You're already logged in."
+msgstr ""

Added: jifty/branches/schema-plugins/plugins/AuthLDAPOnly/share/po/fr.po
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthLDAPOnly/share/po/fr.po	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,25 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
+"Language-Team: LANGUAGE <LL at li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=ISO8859-15\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: lib/Jifty/Plugin/AuthLDAPOnly/Action/LDAPLogin.pm:54
+msgid "That doesn't look like a valid login."
+msgstr "Cet identifiant ne semble pas valide."
+
+#: lib/Jifty/Plugin/AuthLDAPOnly/Action/LDAPLogin.pm:80
+msgid "You may have mistyped your login or password. Give it another shot?"
+msgstr "Erreur dans votre identifiant ou votre mot de passe. Ré-essayez."
+

Added: jifty/branches/schema-plugins/plugins/AuthLDAPOnly/share/web/templates/ldaplogin
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthLDAPOnly/share/web/templates/ldaplogin	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,19 @@
+<%args>
+$action => undef
+$next => undef
+</%args>
+<&|/_elements/wrapper, title => 'Login' &>
+
+% if (not Jifty->web->current_user->id) {
+<h2><% _('Login') %></h2>
+<% Jifty->web->form->start(call => $next, name => "ldaploginbox") %>
+<% $action->form_field('name') %>
+<% $action->form_field('password') %>
+%#<% $action->form_field('remember') %>
+<% Jifty->web->form->submit(label => _('Login'), submit => $action) %>
+<% Jifty->web->form->end %>
+% }
+% else {
+<% _("You're already logged in.") %>
+% }
+</&>

Added: jifty/branches/schema-plugins/plugins/AuthLDAPOnly/share/web/templates/ldaplogout
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthLDAPOnly/share/web/templates/ldaplogout	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,3 @@
+<&| /_elements/wrapper, title => "Logged out" &>
+<p><% _("Ok, you're now logged out. Have a good day.") %></p>
+</&>

Added: jifty/branches/schema-plugins/plugins/AuthLDAPOnly/t/00-load.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthLDAPOnly/t/00-load.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,5 @@
+#!/usr/bin/env perl -w
+use strict;
+use Test::More tests => 1;
+
+use_ok('Jifty::Plugin::AuthLDAPOnly');

Added: jifty/branches/schema-plugins/plugins/AuthzLDAP/MANIFEST
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthzLDAP/MANIFEST	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,9 @@
+lib/Jifty/Plugin/AuthzLDAP.pm
+lib/Jifty/Plugin/AuthzLDAP/Action/LDAPValidate.pm
+lib/Jifty/Plugin/AuthzLDAP/Model/LDAPFilter.pm
+Makefile.PL
+MANIFEST
+share/po/en.po
+share/po/fr.po
+share/web/templates/error/AccessDenied
+t/00-load.t

Added: jifty/branches/schema-plugins/plugins/AuthzLDAP/Makefile.PL
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthzLDAP/Makefile.PL	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,10 @@
+use inc::Module::Install;
+name('Jifty-Plugin-AuthzLDAP');
+license('Perl');
+version('0.01');
+requires('Jifty' => '0.60912');
+requires('Net::LDAP');
+
+install_share;
+
+WriteAll;

Added: jifty/branches/schema-plugins/plugins/AuthzLDAP/lib/Jifty/Plugin/AuthzLDAP.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthzLDAP/lib/Jifty/Plugin/AuthzLDAP.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,187 @@
+use strict;
+use warnings;
+
+=head1 NAME
+
+Jifty::Plugin::AuthzLDAP
+
+=head1 DESCRIPTION
+
+Jifty plugin.
+Provide ldap authorization with filters table and cache.
+
+NOW FOR TESTING AND COMMENTS
+
+
+=head1 CONFIGURATION NOTES
+
+in etc/config.yml
+  Plugins: 
+    - AuthzLDAP: 
+       LDAPbind: cn=testldap,ou=admins,dc=myorg,dc=org #
+       LDAPpass: test                   # password
+       LDAPhost: ldap.myorg.org         # ldap host
+       LDAPbase: ou=people,dc=myorg..   # ldap base
+       LDAPuid: uid                     # optional
+       CacheTimout: 20                  # minutes, optional, default 20 minutes
+
+in application create a LDAPFilter model
+        use base qw/Jifty::Plugin::AuthzLDAP::Model::LDAPFilter/;
+
+in LDAPFilter model create your filters, something like
+ name    |filter                         |is_group
+ is_admin|(!eduPersonAffiliation=STUDENT)|0
+ in_admin|cn=admin,ou=groups,dc=my.org   |1
+
+to protect access to /admin
+in "TestApp" application create a lib/TestApp/Dispatcher.pm 
+
+    use strict;
+    use warnings;
+
+    package TestApp::Dispatcher;
+    use Jifty::Dispatcher -base;
+
+    before '/admin/*' => run {
+       # Authentication
+       Jifty->web->tangent(url => '/login')
+            if (! Jifty->web->current_user->id);
+       # Authorization
+       my $user = Jifty->web->current_user->user_object->name;
+       Jifty->web->tangent(url => '/error/AccessDenied')
+            if (! Jifty::Plugin::AuthzLDAP->ldapvalidate($user,'is_admin') );
+    };
+
+    1
+
+=head1 SEE ALSO
+
+L<Net::LDAP>
+
+=cut
+
+
+package Jifty::Plugin::AuthzLDAP;
+use base qw/Jifty::Plugin/;
+use Net::LDAP;
+use Cache::MemoryCache;
+
+{
+    my ($LDAPFilterClass, $LDAP, $cache, %params);
+
+    sub init {
+        my $self = shift;
+        my %args = @_;
+
+        my $appname = Jifty->config->framework('ApplicationName');
+        $LDAPFilterClass = "${appname}::Model::LDAPFilter";
+
+        $params{'Hostname'} = $args{LDAPhost};
+        $params{'base'} = $args{LDAPbase};
+        $params{'uid'} = $args{LDAPuid} || "uid";
+        $params{'dn'} = $args{LDAPbind};
+        $params{'pass'} = $args{LDAPpass};
+        $params{'timeout'} = $args{CacheTimout} || "20 minutes";
+
+        $LDAP = Net::LDAP->new($params{Hostname},async=>1,onerror => 'undef', debug => 0);
+
+        $cache = new Cache::MemoryCache( { 'namespace' => $appname.'AuthzLDAP',
+                                            'default_expires_in' => $params{'timeout'} } );
+    }
+
+    sub LDAPFilterClass {
+        return $LDAPFilterClass;
+    }
+
+    sub LDAP {
+        return $LDAP;
+    }
+
+    sub DN {
+        return $params{'dn'};
+    }
+
+    sub PASS {
+        return $params{'pass'};
+    }
+
+    sub UID {
+        return $params{'uid'};
+    }
+
+    sub BASE {
+        return $params{'base'};
+    }
+    
+    sub CACHE {
+        return $cache;
+    }
+
+}
+
+=head2 bind
+
+Bind to ldap
+
+=cut
+
+sub bind {
+    my $self = shift;
+    my $msg = $self->LDAP()->bind($self->DN() ,'password' =>$self->PASS());
+    unless (not $msg->code) {
+        Jifty->log->error("Bind to ldap server failed"); 
+        return;
+    }
+}
+
+=head2 validate NAME FILTERNAME
+
+return 1 if NAME validate FILTER or NAME-FILTERNAME in cache
+else return 0
+
+If FILTERNAME is flagged as is_group, search if user is uniquemember of this group
+as supported by the Netscape Directory Server
+
+=cut
+
+sub ldapvalidate {
+    my ($self, $user, $filtername) = @_;
+    my $response  = 'nok';
+    
+    my $cachekey = $user.'-'.$filtername;
+    my $cache = $self->CACHE->get($cachekey);
+    return ($cache eq 'ok')?1:0 if (defined $cache);
+   
+    my $record = $self->LDAPFilterClass()->new();
+    $record->load_by_cols( name => $filtername);
+
+    # (?) allow use of writing filter in filtername
+    # TODO: filtername must be cleanned
+    # my $filter = ($record->filter)?$record->filter:$filtername;
+    my $filter = $record->filter;
+
+    $user = $self->UID().'='.$user.','.$self->BASE();
+    
+    # (??) how to catch AuthLDAP bind if it's used?
+    $self->bind();
+
+    my $msg;
+    # manage group as supported by the Netscape Directory Server 
+    if ($record->is_group) {
+        $msg = $self->LDAP()->compare( $filter, attr=>"uniquemember", value=>$user );
+        Jifty->log->debug("search grp: ".$msg->code); 
+        $response = 'ok' if ( $msg->code == 6 );
+    } else {
+            $filter = '('. $filter .')' if ( $filter !~ /^\(/ );
+            $msg = $self->LDAP()->search( base => $user, filter => $filter );
+            Jifty->log->debug("search: ".$msg->count); 
+            $response = 'ok' if (! $msg->code &&  $msg->count );
+    }
+
+    $self->CACHE->set($cachekey,$response);
+
+    return ( $response eq 'ok' )?1:0; 
+}
+
+
+1;

Added: jifty/branches/schema-plugins/plugins/AuthzLDAP/lib/Jifty/Plugin/AuthzLDAP/Action/LDAPValidate.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthzLDAP/lib/Jifty/Plugin/AuthzLDAP/Action/LDAPValidate.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,68 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Jifty::Plugin::AuthzLDAP::Action::LDAPValidate
+
+=cut
+
+package Jifty::Plugin::AuthzLDAP::Action::LDAPValidate;
+use base qw/Jifty::Action Jifty::Plugin::AuthzLDAP/;
+
+
+=head2 arguments
+
+Return the ticket form field
+
+=cut
+
+sub arguments {
+    return (
+        {
+            name => {
+                mandatory      => 1
+            },
+
+            filter => {
+                mandatory => 1
+            },
+
+        }
+    );
+
+}
+
+=head2 take_action
+
+Bind on ldap to check the user's password. If it's right, log them in.
+Otherwise, throw an error.
+
+
+=cut
+
+sub take_action {
+    my $self = shift;
+    my $user = $self->argument_value('name');
+    my $filter = $self->argument_value('filter');
+
+    Jifty->log->debug("action: $user $filter");
+
+    # Bind on ldap
+    #my $msg = $self->bind();
+    
+    my $msg = $self->ldapvalidate($user,$filter);
+
+    Jifty->log->debug("validate: $msg");
+
+    if (not $msg) {
+        $self->result->error(
+            _('Access denied.') );
+        return;}
+
+    return 1;
+}
+
+1;
+
+

Added: jifty/branches/schema-plugins/plugins/AuthzLDAP/lib/Jifty/Plugin/AuthzLDAP/Model/LDAPFilter.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthzLDAP/lib/Jifty/Plugin/AuthzLDAP/Model/LDAPFilter.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,63 @@
+package Jifty::Plugin::AuthzLDAP::Model::LDAPFilter::Schema;
+use Jifty::DBI::Schema;
+use Scalar::Defer;
+
+column
+  name => type is 'text',
+  label is 'Name',
+  is mandatory,
+  is distinct;
+
+column
+  filter => type is 'text',
+  label is 'Filter',
+  is mandatory;
+
+column
+   is_group => type is 'boolean',
+   label is 'Group';
+
+column 'created_on' =>
+  type is 'datetime',
+  is immutable,
+  default is defer { DateTime->now },
+  filters are 'Jifty::DBI::Filter::DateTime';
+
+
+package Jifty::Plugin::AuthzLDAP::Model::LDAPFilter;
+use base qw/Jifty::Record/;
+
+sub create {
+    my $self  = shift;
+    my %args  = (@_);
+    my (@ret) = $self->SUPER::create(%args);
+
+    return (@ret);
+}
+
+
+=head2 current_user_can ACTION
+
+Only superuser can create or edit filters.
+Logged-in users can read. 
+
+=cut
+
+sub current_user_can {
+    my $self = shift;
+    my $type = shift;
+
+    if ($type eq 'create' || $type eq 'update') {
+        return 0 if
+           !$self->current_user->is_superuser;
+        return 1;
+    } elsif($type eq 'read') {
+        return 1 if 
+            $self->current_user->id || $self->current_user->is_superuser;
+        return 0;
+    }
+
+    return $self->SUPER::current_user_can($type, @_);
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/AuthzLDAP/share/po/en.po
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthzLDAP/share/po/en.po	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,20 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
+"Language-Team: LANGUAGE <LL at li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: lib/Jifty/Plugin/AuthzLDAP/Action/LDAPValidate.pm:60 share/web/templates/error/AccessDenied:1 share/web/templates/error/AccessDenied:2
+msgid "Access denied."
+msgstr ""

Added: jifty/branches/schema-plugins/plugins/AuthzLDAP/share/po/fr.po
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthzLDAP/share/po/fr.po	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,20 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
+"Language-Team: LANGUAGE <LL at li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=ISO8859-15\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: lib/Jifty/Plugin/AuthzLDAP/Action/LDAPValidate.pm:60 share/web/templates/error/AccessDenied:1 share/web/templates/error/AccessDenied:2
+msgid "Access denied."
+msgstr "Accès non autorisé."

Added: jifty/branches/schema-plugins/plugins/AuthzLDAP/share/web/templates/error/AccessDenied
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthzLDAP/share/web/templates/error/AccessDenied	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,3 @@
+<&| /_elements/wrapper, title => _("Access denied.") &>
+<p><% _("Access denied.") %></p>
+</&>

Added: jifty/branches/schema-plugins/plugins/AuthzLDAP/t/00-load.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/AuthzLDAP/t/00-load.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,5 @@
+#!/usr/bin/env perl -w
+use strict;
+use Test::More tests => 1;
+
+use_ok('Jifty::Plugin::AuthzLDAP');

Added: jifty/branches/schema-plugins/plugins/EditInPlace/Makefile.PL
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/EditInPlace/Makefile.PL	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,9 @@
+use inc::Module::Install 0.46;
+name('Jifty-Plugin-EditInPlace');
+requires('Jifty' => '0.60510' );
+&auto_install();
+
+install_share;
+
+WriteAll;
+

Added: jifty/branches/schema-plugins/plugins/EditInPlace/debian/changelog
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/EditInPlace/debian/changelog	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,6 @@
+libjifty-plugin-editinplace-perl (0-1) unstable; urgency=low
+
+  * Initial Release.
+
+ -- AGOSTINI Yves <agostini at univ-metz.fr>  Mon, 23 Oct 2006 11:26:37 +0200
+

Added: jifty/branches/schema-plugins/plugins/EditInPlace/debian/compat
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/EditInPlace/debian/compat	Mon Jan 29 21:05:02 2007
@@ -0,0 +1 @@
+4

Added: jifty/branches/schema-plugins/plugins/EditInPlace/debian/control
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/EditInPlace/debian/control	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,15 @@
+Source: libjifty-plugin-editinplace-perl
+Section: perl
+Priority: optional
+Build-Depends: debhelper (>= 4.0.2)
+Build-Depends-Indep: perl (>= 5.8.0-7)
+Maintainer: AGOSTINI Yves <agostini at univ-metz.fr>
+Standards-Version: 3.6.1
+
+Package: libjifty-plugin-editinplace-perl
+Architecture: all
+Depends: ${perl:Depends}, ${misc:Depends}, 
+Description:  Jifty::Plugin::EditInPlace
+ .
+ Jifty Plugin, file editor for devel mode.
+ .

Added: jifty/branches/schema-plugins/plugins/EditInPlace/debian/rules
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/EditInPlace/debian/rules	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,83 @@
+#!/usr/bin/make -f
+# This debian/rules file is provided as a template for normal perl
+# packages. It was created by Marc Brockschmidt <marc at dch-faq.de> for
+# the Debian Perl Group (http://pkg-perl.alioth.debian.org/) but may
+# be used freely wherever it is useful.
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+# If set to a true value then MakeMaker's prompt function will
+# always return the default without waiting for user input.
+export PERL_MM_USE_DEFAULT=1
+
+PACKAGE=$(shell dh_listpackages)
+
+ifndef PERL
+PERL = /usr/bin/perl
+endif
+
+TMP     =$(CURDIR)/debian/$(PACKAGE)
+
+build: build-stamp
+build-stamp:
+	dh_testdir
+
+	# Add commands to compile the package here
+	$(PERL) Makefile.PL INSTALLDIRS=vendor
+	$(MAKE) OPTIMIZE="-Wall -O2 -g"
+
+	touch build-stamp
+
+clean:
+	dh_testdir
+	dh_testroot
+
+	# Add commands to clean up after the build process here
+	-$(MAKE) distclean
+
+	dh_clean build-stamp install-stamp
+
+install: build install-stamp
+install-stamp:
+	dh_testdir
+	dh_testroot
+	dh_clean -k
+
+	# Add commands to install the package into debian/$PACKAGE_NAME here
+	$(MAKE) test
+	$(MAKE) install DESTDIR=$(TMP) PREFIX=/usr
+
+	# As this is a architecture independent package, we are not
+	# supposed to install stuff to /usr/lib. MakeMaker creates
+	# the dirs, we delete them from the deb:
+	rmdir --ignore-fail-on-non-empty --parents $(TMP)/usr/lib/perl5
+
+	touch install-stamp
+
+binary-arch:
+# We have nothing to do by default.
+
+binary-indep: build install
+	dh_testdir
+	dh_testroot
+#	dh_installcron
+#	dh_installmenu
+#	dh_installexamples
+	dh_installdocs 
+	dh_installchangelogs 
+	dh_perl
+	dh_link
+	dh_strip
+	dh_compress
+	dh_fixperms
+	dh_installdeb
+	dh_gencontrol
+	dh_md5sums
+	dh_builddeb
+
+source diff:                                                                  
+	@echo >&2 'source and diff are obsolete - use dpkg-source -b'; false
+
+binary: binary-indep binary-arch
+.PHONY: build clean binary-indep binary-arch binary

Added: jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/AutoInstall.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/AutoInstall.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,768 @@
+#line 1
+package Module::AutoInstall;
+
+use strict;
+use Cwd                 ();
+use ExtUtils::MakeMaker ();
+
+use vars qw{$VERSION};
+BEGIN {
+	$VERSION = '1.03';
+}
+
+# special map on pre-defined feature sets
+my %FeatureMap = (
+    ''      => 'Core Features',    # XXX: deprecated
+    '-core' => 'Core Features',
+);
+
+# various lexical flags
+my ( @Missing, @Existing,  %DisabledTests, $UnderCPAN,     $HasCPANPLUS );
+my ( $Config,  $CheckOnly, $SkipInstall,   $AcceptDefault, $TestOnly );
+my ( $PostambleActions, $PostambleUsed );
+
+# See if it's a testing or non-interactive session
+_accept_default( $ENV{AUTOMATED_TESTING} or ! -t STDIN ); 
+_init();
+
+sub _accept_default {
+    $AcceptDefault = shift;
+}
+
+sub missing_modules {
+    return @Missing;
+}
+
+sub do_install {
+    __PACKAGE__->install(
+        [
+            $Config
+            ? ( UNIVERSAL::isa( $Config, 'HASH' ) ? %{$Config} : @{$Config} )
+            : ()
+        ],
+        @Missing,
+    );
+}
+
+# initialize various flags, and/or perform install
+sub _init {
+    foreach my $arg (
+        @ARGV,
+        split(
+            /[\s\t]+/,
+            $ENV{PERL_AUTOINSTALL} || $ENV{PERL_EXTUTILS_AUTOINSTALL} || ''
+        )
+      )
+    {
+        if ( $arg =~ /^--config=(.*)$/ ) {
+            $Config = [ split( ',', $1 ) ];
+        }
+        elsif ( $arg =~ /^--installdeps=(.*)$/ ) {
+            __PACKAGE__->install( $Config, @Missing = split( /,/, $1 ) );
+            exit 0;
+        }
+        elsif ( $arg =~ /^--default(?:deps)?$/ ) {
+            $AcceptDefault = 1;
+        }
+        elsif ( $arg =~ /^--check(?:deps)?$/ ) {
+            $CheckOnly = 1;
+        }
+        elsif ( $arg =~ /^--skip(?:deps)?$/ ) {
+            $SkipInstall = 1;
+        }
+        elsif ( $arg =~ /^--test(?:only)?$/ ) {
+            $TestOnly = 1;
+        }
+    }
+}
+
+# overrides MakeMaker's prompt() to automatically accept the default choice
+sub _prompt {
+    goto &ExtUtils::MakeMaker::prompt unless $AcceptDefault;
+
+    my ( $prompt, $default ) = @_;
+    my $y = ( $default =~ /^[Yy]/ );
+
+    print $prompt, ' [', ( $y ? 'Y' : 'y' ), '/', ( $y ? 'n' : 'N' ), '] ';
+    print "$default\n";
+    return $default;
+}
+
+# the workhorse
+sub import {
+    my $class = shift;
+    my @args  = @_ or return;
+    my $core_all;
+
+    print "*** $class version " . $class->VERSION . "\n";
+    print "*** Checking for Perl dependencies...\n";
+
+    my $cwd = Cwd::cwd();
+
+    $Config = [];
+
+    my $maxlen = length(
+        (
+            sort   { length($b) <=> length($a) }
+              grep { /^[^\-]/ }
+              map  {
+                ref($_)
+                  ? ( ( ref($_) eq 'HASH' ) ? keys(%$_) : @{$_} )
+                  : ''
+              }
+              map { +{@args}->{$_} }
+              grep { /^[^\-]/ or /^-core$/i } keys %{ +{@args} }
+        )[0]
+    );
+
+    while ( my ( $feature, $modules ) = splice( @args, 0, 2 ) ) {
+        my ( @required, @tests, @skiptests );
+        my $default  = 1;
+        my $conflict = 0;
+
+        if ( $feature =~ m/^-(\w+)$/ ) {
+            my $option = lc($1);
+
+            # check for a newer version of myself
+            _update_to( $modules, @_ ) and return if $option eq 'version';
+
+            # sets CPAN configuration options
+            $Config = $modules if $option eq 'config';
+
+            # promote every features to core status
+            $core_all = ( $modules =~ /^all$/i ) and next
+              if $option eq 'core';
+
+            next unless $option eq 'core';
+        }
+
+        print "[" . ( $FeatureMap{ lc($feature) } || $feature ) . "]\n";
+
+        $modules = [ %{$modules} ] if UNIVERSAL::isa( $modules, 'HASH' );
+
+        unshift @$modules, -default => &{ shift(@$modules) }
+          if ( ref( $modules->[0] ) eq 'CODE' );    # XXX: bugward combatability
+
+        while ( my ( $mod, $arg ) = splice( @$modules, 0, 2 ) ) {
+            if ( $mod =~ m/^-(\w+)$/ ) {
+                my $option = lc($1);
+
+                $default   = $arg    if ( $option eq 'default' );
+                $conflict  = $arg    if ( $option eq 'conflict' );
+                @tests     = @{$arg} if ( $option eq 'tests' );
+                @skiptests = @{$arg} if ( $option eq 'skiptests' );
+
+                next;
+            }
+
+            printf( "- %-${maxlen}s ...", $mod );
+
+            if ( $arg and $arg =~ /^\D/ ) {
+                unshift @$modules, $arg;
+                $arg = 0;
+            }
+
+            # XXX: check for conflicts and uninstalls(!) them.
+            if (
+                defined( my $cur = _version_check( _load($mod), $arg ||= 0 ) ) )
+            {
+                print "loaded. ($cur" . ( $arg ? " >= $arg" : '' ) . ")\n";
+                push @Existing, $mod => $arg;
+                $DisabledTests{$_} = 1 for map { glob($_) } @skiptests;
+            }
+            else {
+                print "missing." . ( $arg ? " (would need $arg)" : '' ) . "\n";
+                push @required, $mod => $arg;
+            }
+        }
+
+        next unless @required;
+
+        my $mandatory = ( $feature eq '-core' or $core_all );
+
+        if (
+            !$SkipInstall
+            and (
+                $CheckOnly
+                or _prompt(
+                    qq{==> Auto-install the }
+                      . ( @required / 2 )
+                      . ( $mandatory ? ' mandatory' : ' optional' )
+                      . qq{ module(s) from CPAN?},
+                    $default ? 'y' : 'n',
+                ) =~ /^[Yy]/
+            )
+          )
+        {
+            push( @Missing, @required );
+            $DisabledTests{$_} = 1 for map { glob($_) } @skiptests;
+        }
+
+        elsif ( !$SkipInstall
+            and $default
+            and $mandatory
+            and
+            _prompt( qq{==> The module(s) are mandatory! Really skip?}, 'n', )
+            =~ /^[Nn]/ )
+        {
+            push( @Missing, @required );
+            $DisabledTests{$_} = 1 for map { glob($_) } @skiptests;
+        }
+
+        else {
+            $DisabledTests{$_} = 1 for map { glob($_) } @tests;
+        }
+    }
+
+    $UnderCPAN = _check_lock();    # check for $UnderCPAN
+
+    if ( @Missing and not( $CheckOnly or $UnderCPAN ) ) {
+        require Config;
+        print
+"*** Dependencies will be installed the next time you type '$Config::Config{make}'.\n";
+
+        # make an educated guess of whether we'll need root permission.
+        print "    (You may need to do that as the 'root' user.)\n"
+          if eval '$>';
+    }
+    print "*** $class configuration finished.\n";
+
+    chdir $cwd;
+
+    # import to main::
+    no strict 'refs';
+    *{'main::WriteMakefile'} = \&Write if caller(0) eq 'main';
+}
+
+# Check to see if we are currently running under CPAN.pm and/or CPANPLUS;
+# if we are, then we simply let it taking care of our dependencies
+sub _check_lock {
+    return unless @Missing;
+
+    if ($ENV{PERL5_CPANPLUS_IS_RUNNING}) {
+        print <<'END_MESSAGE';
+
+*** Since we're running under CPANPLUS, I'll just let it take care
+    of the dependency's installation later.
+END_MESSAGE
+        return 1;
+    }
+
+    _load_cpan();
+
+    # Find the CPAN lock-file
+    my $lock = MM->catfile( $CPAN::Config->{cpan_home}, ".lock" );
+    return unless -f $lock;
+
+    # Check the lock
+    local *LOCK;
+    return unless open(LOCK, $lock);
+
+    if (
+            ( $^O eq 'MSWin32' ? _under_cpan() : <LOCK> == getppid() )
+        and ( $CPAN::Config->{prerequisites_policy} || '' ) ne 'ignore'
+    ) {
+        print <<'END_MESSAGE';
+
+*** Since we're running under CPAN, I'll just let it take care
+    of the dependency's installation later.
+END_MESSAGE
+        return 1;
+    }
+
+    close LOCK;
+    return;
+}
+
+sub install {
+    my $class = shift;
+
+    my $i;    # used below to strip leading '-' from config keys
+    my @config = ( map { s/^-// if ++$i; $_ } @{ +shift } );
+
+    my ( @modules, @installed );
+    while ( my ( $pkg, $ver ) = splice( @_, 0, 2 ) ) {
+
+        # grep out those already installed
+        if ( defined( _version_check( _load($pkg), $ver ) ) ) {
+            push @installed, $pkg;
+        }
+        else {
+            push @modules, $pkg, $ver;
+        }
+    }
+
+    return @installed unless @modules;  # nothing to do
+    return @installed if _check_lock(); # defer to the CPAN shell
+
+    print "*** Installing dependencies...\n";
+
+    return unless _connected_to('cpan.org');
+
+    my %args = @config;
+    my %failed;
+    local *FAILED;
+    if ( $args{do_once} and open( FAILED, '.#autoinstall.failed' ) ) {
+        while (<FAILED>) { chomp; $failed{$_}++ }
+        close FAILED;
+
+        my @newmod;
+        while ( my ( $k, $v ) = splice( @modules, 0, 2 ) ) {
+            push @newmod, ( $k => $v ) unless $failed{$k};
+        }
+        @modules = @newmod;
+    }
+
+    if ( _has_cpanplus() ) {
+        _install_cpanplus( \@modules, \@config );
+    } else {
+        _install_cpan( \@modules, \@config );
+    }
+
+    print "*** $class installation finished.\n";
+
+    # see if we have successfully installed them
+    while ( my ( $pkg, $ver ) = splice( @modules, 0, 2 ) ) {
+        if ( defined( _version_check( _load($pkg), $ver ) ) ) {
+            push @installed, $pkg;
+        }
+        elsif ( $args{do_once} and open( FAILED, '>> .#autoinstall.failed' ) ) {
+            print FAILED "$pkg\n";
+        }
+    }
+
+    close FAILED if $args{do_once};
+
+    return @installed;
+}
+
+sub _install_cpanplus {
+    my @modules   = @{ +shift };
+    my @config    = _cpanplus_config( @{ +shift } );
+    my $installed = 0;
+
+    require CPANPLUS::Backend;
+    my $cp   = CPANPLUS::Backend->new;
+    my $conf = $cp->configure_object;
+
+    return unless $conf->can('conf') # 0.05x+ with "sudo" support
+               or _can_write($conf->_get_build('base'));  # 0.04x
+
+    # if we're root, set UNINST=1 to avoid trouble unless user asked for it.
+    my $makeflags = $conf->get_conf('makeflags') || '';
+    if ( UNIVERSAL::isa( $makeflags, 'HASH' ) ) {
+        # 0.03+ uses a hashref here
+        $makeflags->{UNINST} = 1 unless exists $makeflags->{UNINST};
+
+    } else {
+        # 0.02 and below uses a scalar
+        $makeflags = join( ' ', split( ' ', $makeflags ), 'UNINST=1' )
+          if ( $makeflags !~ /\bUNINST\b/ and eval qq{ $> eq '0' } );
+
+    }
+    $conf->set_conf( makeflags => $makeflags );
+    $conf->set_conf( prereqs   => 1 );
+
+    
+
+    while ( my ( $key, $val ) = splice( @config, 0, 2 ) ) {
+        $conf->set_conf( $key, $val );
+    }
+
+    my $modtree = $cp->module_tree;
+    while ( my ( $pkg, $ver ) = splice( @modules, 0, 2 ) ) {
+        print "*** Installing $pkg...\n";
+
+        MY::preinstall( $pkg, $ver ) or next if defined &MY::preinstall;
+
+        my $success;
+        my $obj = $modtree->{$pkg};
+
+        if ( $obj and defined( _version_check( $obj->{version}, $ver ) ) ) {
+            my $pathname = $pkg;
+            $pathname =~ s/::/\\W/;
+
+            foreach my $inc ( grep { m/$pathname.pm/i } keys(%INC) ) {
+                delete $INC{$inc};
+            }
+
+            my $rv = $cp->install( modules => [ $obj->{module} ] );
+
+            if ( $rv and ( $rv->{ $obj->{module} } or $rv->{ok} ) ) {
+                print "*** $pkg successfully installed.\n";
+                $success = 1;
+            } else {
+                print "*** $pkg installation cancelled.\n";
+                $success = 0;
+            }
+
+            $installed += $success;
+        } else {
+            print << ".";
+*** Could not find a version $ver or above for $pkg; skipping.
+.
+        }
+
+        MY::postinstall( $pkg, $ver, $success ) if defined &MY::postinstall;
+    }
+
+    return $installed;
+}
+
+sub _cpanplus_config {
+	my @config = ();
+	while ( @_ ) {
+		my ($key, $value) = (shift(), shift());
+		if ( $key eq 'prerequisites_policy' ) {
+			if ( $value eq 'follow' ) {
+				$value = CPANPLUS::Internals::Constants::PREREQ_INSTALL();
+			} elsif ( $value eq 'ask' ) {
+				$value = CPANPLUS::Internals::Constants::PREREQ_ASK();
+			} elsif ( $value eq 'ignore' ) {
+				$value = CPANPLUS::Internals::Constants::PREREQ_IGNORE();
+			} else {
+				die "*** Cannot convert option $key = '$value' to CPANPLUS version.\n";
+			}
+		} else {
+			die "*** Cannot convert option $key to CPANPLUS version.\n";
+		}
+	}
+	return @config;
+}
+
+sub _install_cpan {
+    my @modules   = @{ +shift };
+    my @config    = @{ +shift };
+    my $installed = 0;
+    my %args;
+
+    _load_cpan();
+    require Config;
+
+    if (CPAN->VERSION < 1.80) {
+        # no "sudo" support, probe for writableness
+        return unless _can_write( MM->catfile( $CPAN::Config->{cpan_home}, 'sources' ) )
+                  and _can_write( $Config::Config{sitelib} );
+    }
+
+    # if we're root, set UNINST=1 to avoid trouble unless user asked for it.
+    my $makeflags = $CPAN::Config->{make_install_arg} || '';
+    $CPAN::Config->{make_install_arg} =
+      join( ' ', split( ' ', $makeflags ), 'UNINST=1' )
+      if ( $makeflags !~ /\bUNINST\b/ and eval qq{ $> eq '0' } );
+
+    # don't show start-up info
+    $CPAN::Config->{inhibit_startup_message} = 1;
+
+    # set additional options
+    while ( my ( $opt, $arg ) = splice( @config, 0, 2 ) ) {
+        ( $args{$opt} = $arg, next )
+          if $opt =~ /^force$/;    # pseudo-option
+        $CPAN::Config->{$opt} = $arg;
+    }
+
+    local $CPAN::Config->{prerequisites_policy} = 'follow';
+
+    while ( my ( $pkg, $ver ) = splice( @modules, 0, 2 ) ) {
+        MY::preinstall( $pkg, $ver ) or next if defined &MY::preinstall;
+
+        print "*** Installing $pkg...\n";
+
+        my $obj     = CPAN::Shell->expand( Module => $pkg );
+        my $success = 0;
+
+        if ( $obj and defined( _version_check( $obj->cpan_version, $ver ) ) ) {
+            my $pathname = $pkg;
+            $pathname =~ s/::/\\W/;
+
+            foreach my $inc ( grep { m/$pathname.pm/i } keys(%INC) ) {
+                delete $INC{$inc};
+            }
+
+            my $rv = $args{force} ? CPAN::Shell->force( install => $pkg )
+                                  : CPAN::Shell->install($pkg);
+            $rv ||= eval {
+                $CPAN::META->instance( 'CPAN::Distribution', $obj->cpan_file, )
+                  ->{install}
+                  if $CPAN::META;
+            };
+
+            if ( $rv eq 'YES' ) {
+                print "*** $pkg successfully installed.\n";
+                $success = 1;
+            }
+            else {
+                print "*** $pkg installation failed.\n";
+                $success = 0;
+            }
+
+            $installed += $success;
+        }
+        else {
+            print << ".";
+*** Could not find a version $ver or above for $pkg; skipping.
+.
+        }
+
+        MY::postinstall( $pkg, $ver, $success ) if defined &MY::postinstall;
+    }
+
+    return $installed;
+}
+
+sub _has_cpanplus {
+    return (
+        $HasCPANPLUS = (
+            $INC{'CPANPLUS/Config.pm'}
+              or _load('CPANPLUS::Shell::Default')
+        )
+    );
+}
+
+# make guesses on whether we're under the CPAN installation directory
+sub _under_cpan {
+    require Cwd;
+    require File::Spec;
+
+    my $cwd  = File::Spec->canonpath( Cwd::cwd() );
+    my $cpan = File::Spec->canonpath( $CPAN::Config->{cpan_home} );
+
+    return ( index( $cwd, $cpan ) > -1 );
+}
+
+sub _update_to {
+    my $class = __PACKAGE__;
+    my $ver   = shift;
+
+    return
+      if defined( _version_check( _load($class), $ver ) );  # no need to upgrade
+
+    if (
+        _prompt( "==> A newer version of $class ($ver) is required. Install?",
+            'y' ) =~ /^[Nn]/
+      )
+    {
+        die "*** Please install $class $ver manually.\n";
+    }
+
+    print << ".";
+*** Trying to fetch it from CPAN...
+.
+
+    # install ourselves
+    _load($class) and return $class->import(@_)
+      if $class->install( [], $class, $ver );
+
+    print << '.'; exit 1;
+
+*** Cannot bootstrap myself. :-( Installation terminated.
+.
+}
+
+# check if we're connected to some host, using inet_aton
+sub _connected_to {
+    my $site = shift;
+
+    return (
+        ( _load('Socket') and Socket::inet_aton($site) ) or _prompt(
+            qq(
+*** Your host cannot resolve the domain name '$site', which
+    probably means the Internet connections are unavailable.
+==> Should we try to install the required module(s) anyway?), 'n'
+          ) =~ /^[Yy]/
+    );
+}
+
+# check if a directory is writable; may create it on demand
+sub _can_write {
+    my $path = shift;
+    mkdir( $path, 0755 ) unless -e $path;
+
+    return 1 if -w $path;
+
+    print << ".";
+*** You are not allowed to write to the directory '$path';
+    the installation may fail due to insufficient permissions.
+.
+
+    if (
+        eval '$>' and lc(`sudo -V`) =~ /version/ and _prompt(
+            qq(
+==> Should we try to re-execute the autoinstall process with 'sudo'?),
+            ((-t STDIN) ? 'y' : 'n')
+        ) =~ /^[Yy]/
+      )
+    {
+
+        # try to bootstrap ourselves from sudo
+        print << ".";
+*** Trying to re-execute the autoinstall process with 'sudo'...
+.
+        my $missing = join( ',', @Missing );
+        my $config = join( ',',
+            UNIVERSAL::isa( $Config, 'HASH' ) ? %{$Config} : @{$Config} )
+          if $Config;
+
+        return
+          unless system( 'sudo', $^X, $0, "--config=$config",
+            "--installdeps=$missing" );
+
+        print << ".";
+*** The 'sudo' command exited with error!  Resuming...
+.
+    }
+
+    return _prompt(
+        qq(
+==> Should we try to install the required module(s) anyway?), 'n'
+    ) =~ /^[Yy]/;
+}
+
+# load a module and return the version it reports
+sub _load {
+    my $mod  = pop;    # class/instance doesn't matter
+    my $file = $mod;
+
+    $file =~ s|::|/|g;
+    $file .= '.pm';
+
+    local $@;
+    return eval { require $file; $mod->VERSION } || ( $@ ? undef: 0 );
+}
+
+# Load CPAN.pm and it's configuration
+sub _load_cpan {
+    return if $CPAN::VERSION;
+    require CPAN;
+    if ( $CPAN::HandleConfig::VERSION ) {
+        # Newer versions of CPAN have a HandleConfig module
+        CPAN::HandleConfig->load;
+    } else {
+    	# Older versions had the load method in Config directly
+        CPAN::Config->load;
+    }
+}
+
+# compare two versions, either use Sort::Versions or plain comparison
+sub _version_check {
+    my ( $cur, $min ) = @_;
+    return unless defined $cur;
+
+    $cur =~ s/\s+$//;
+
+    # check for version numbers that are not in decimal format
+    if ( ref($cur) or ref($min) or $cur =~ /v|\..*\./ or $min =~ /v|\..*\./ ) {
+        if ( ( $version::VERSION or defined( _load('version') )) and
+             version->can('new') 
+            ) {
+
+            # use version.pm if it is installed.
+            return (
+                ( version->new($cur) >= version->new($min) ) ? $cur : undef );
+        }
+        elsif ( $Sort::Versions::VERSION or defined( _load('Sort::Versions') ) )
+        {
+
+            # use Sort::Versions as the sorting algorithm for a.b.c versions
+            return ( ( Sort::Versions::versioncmp( $cur, $min ) != -1 )
+                ? $cur
+                : undef );
+        }
+
+        warn "Cannot reliably compare non-decimal formatted versions.\n"
+          . "Please install version.pm or Sort::Versions.\n";
+    }
+
+    # plain comparison
+    local $^W = 0;    # shuts off 'not numeric' bugs
+    return ( $cur >= $min ? $cur : undef );
+}
+
+# nothing; this usage is deprecated.
+sub main::PREREQ_PM { return {}; }
+
+sub _make_args {
+    my %args = @_;
+
+    $args{PREREQ_PM} = { %{ $args{PREREQ_PM} || {} }, @Existing, @Missing }
+      if $UnderCPAN or $TestOnly;
+
+    if ( $args{EXE_FILES} and -e 'MANIFEST' ) {
+        require ExtUtils::Manifest;
+        my $manifest = ExtUtils::Manifest::maniread('MANIFEST');
+
+        $args{EXE_FILES} =
+          [ grep { exists $manifest->{$_} } @{ $args{EXE_FILES} } ];
+    }
+
+    $args{test}{TESTS} ||= 't/*.t';
+    $args{test}{TESTS} = join( ' ',
+        grep { !exists( $DisabledTests{$_} ) }
+          map { glob($_) } split( /\s+/, $args{test}{TESTS} ) );
+
+    my $missing = join( ',', @Missing );
+    my $config =
+      join( ',', UNIVERSAL::isa( $Config, 'HASH' ) ? %{$Config} : @{$Config} )
+      if $Config;
+
+    $PostambleActions = (
+        $missing
+        ? "\$(PERL) $0 --config=$config --installdeps=$missing"
+        : "\$(NOECHO) \$(NOOP)"
+    );
+
+    return %args;
+}
+
+# a wrapper to ExtUtils::MakeMaker::WriteMakefile
+sub Write {
+    require Carp;
+    Carp::croak "WriteMakefile: Need even number of args" if @_ % 2;
+
+    if ($CheckOnly) {
+        print << ".";
+*** Makefile not written in check-only mode.
+.
+        return;
+    }
+
+    my %args = _make_args(@_);
+
+    no strict 'refs';
+
+    $PostambleUsed = 0;
+    local *MY::postamble = \&postamble unless defined &MY::postamble;
+    ExtUtils::MakeMaker::WriteMakefile(%args);
+
+    print << "." unless $PostambleUsed;
+*** WARNING: Makefile written with customized MY::postamble() without
+    including contents from Module::AutoInstall::postamble() --
+    auto installation features disabled.  Please contact the author.
+.
+
+    return 1;
+}
+
+sub postamble {
+    $PostambleUsed = 1;
+
+    return << ".";
+
+config :: installdeps
+\t\$(NOECHO) \$(NOOP)
+
+checkdeps ::
+\t\$(PERL) $0 --checkdeps
+
+installdeps ::
+\t$PostambleActions
+
+.
+
+}
+
+1;
+
+__END__
+
+#line 1003

Added: jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/Install.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/Install.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,281 @@
+#line 1
+package Module::Install;
+
+# For any maintainers:
+# The load order for Module::Install is a bit magic.
+# It goes something like this...
+#
+# IF ( host has Module::Install installed, creating author mode ) {
+#     1. Makefile.PL calls "use inc::Module::Install"
+#     2. $INC{inc/Module/Install.pm} set to installed version of inc::Module::Install
+#     3. The installed version of inc::Module::Install loads
+#     4. inc::Module::Install calls "require Module::Install"
+#     5. The ./inc/ version of Module::Install loads
+# } ELSE {
+#     1. Makefile.PL calls "use inc::Module::Install"
+#     2. $INC{inc/Module/Install.pm} set to ./inc/ version of Module::Install
+#     3. The ./inc/ version of Module::Install loads
+# }
+
+use 5.004;
+use strict 'vars';
+
+use vars qw{$VERSION};
+BEGIN {
+    # All Module::Install core packages now require synchronised versions.
+    # This will be used to ensure we don't accidentally load old or
+    # different versions of modules.
+    # This is not enforced yet, but will be some time in the next few
+    # releases once we can make sure it won't clash with custom
+    # Module::Install extensions.
+    $VERSION = '0.64';
+}
+
+# Whether or not inc::Module::Install is actually loaded, the
+# $INC{inc/Module/Install.pm} is what will still get set as long as
+# the caller loaded module this in the documented manner.
+# If not set, the caller may NOT have loaded the bundled version, and thus
+# they may not have a MI version that works with the Makefile.PL. This would
+# result in false errors or unexpected behaviour. And we don't want that.
+my $file = join( '/', 'inc', split /::/, __PACKAGE__ ) . '.pm';
+unless ( $INC{$file} ) {
+    die <<"END_DIE";
+Please invoke ${\__PACKAGE__} with:
+
+    use inc::${\__PACKAGE__};
+
+not:
+
+    use ${\__PACKAGE__};
+
+END_DIE
+}
+
+# If the script that is loading Module::Install is from the future,
+# then make will detect this and cause it to re-run over and over
+# again. This is bad. Rather than taking action to touch it (which
+# is unreliable on some platforms and requires write permissions)
+# for now we should catch this and refuse to run.
+if ( -f $0 and (stat($0))[9] > time ) {
+	die << "END_DIE";
+Your installer $0 has a modification time in the future.
+
+This is known to create infinite loops in make.
+
+Please correct this, then run $0 again.
+
+END_DIE
+}
+
+use Cwd        ();
+use File::Find ();
+use File::Path ();
+use FindBin;
+
+*inc::Module::Install::VERSION = *VERSION;
+ at inc::Module::Install::ISA     = __PACKAGE__;
+
+sub autoload {
+    my $self = shift;
+    my $who  = $self->_caller;
+    my $cwd  = Cwd::cwd();
+    my $sym  = "${who}::AUTOLOAD";
+    $sym->{$cwd} = sub {
+        my $pwd = Cwd::cwd();
+        if ( my $code = $sym->{$pwd} ) {
+            # delegate back to parent dirs
+            goto &$code unless $cwd eq $pwd;
+        }
+        $$sym =~ /([^:]+)$/ or die "Cannot autoload $who - $sym";
+        unshift @_, ($self, $1);
+        goto &{$self->can('call')} unless uc($1) eq $1;
+    };
+}
+
+sub import {
+    my $class = shift;
+    my $self  = $class->new(@_);
+    my $who   = $self->_caller;
+
+    unless ( -f $self->{file} ) {
+        require "$self->{path}/$self->{dispatch}.pm";
+        File::Path::mkpath("$self->{prefix}/$self->{author}");
+        $self->{admin} = "$self->{name}::$self->{dispatch}"->new( _top => $self );
+        $self->{admin}->init;
+        @_ = ($class, _self => $self);
+        goto &{"$self->{name}::import"};
+    }
+
+    *{"${who}::AUTOLOAD"} = $self->autoload;
+    $self->preload;
+
+    # Unregister loader and worker packages so subdirs can use them again
+    delete $INC{"$self->{file}"};
+    delete $INC{"$self->{path}.pm"};
+}
+
+sub preload {
+    my ($self) = @_;
+
+    unless ( $self->{extensions} ) {
+        $self->load_extensions(
+            "$self->{prefix}/$self->{path}", $self
+        );
+    }
+
+    my @exts = @{$self->{extensions}};
+    unless ( @exts ) {
+        my $admin = $self->{admin};
+        @exts = $admin->load_all_extensions;
+    }
+
+    my %seen;
+    foreach my $obj ( @exts ) {
+        while (my ($method, $glob) = each %{ref($obj) . '::'}) {
+            next unless $obj->can($method);
+            next if $method =~ /^_/;
+            next if $method eq uc($method);
+            $seen{$method}++;
+        }
+    }
+
+    my $who = $self->_caller;
+    foreach my $name ( sort keys %seen ) {
+        *{"${who}::$name"} = sub {
+            ${"${who}::AUTOLOAD"} = "${who}::$name";
+            goto &{"${who}::AUTOLOAD"};
+        };
+    }
+}
+
+sub new {
+    my ($class, %args) = @_;
+
+    # ignore the prefix on extension modules built from top level.
+    my $base_path = Cwd::abs_path($FindBin::Bin);
+    unless ( Cwd::abs_path(Cwd::cwd()) eq $base_path ) {
+        delete $args{prefix};
+    }
+
+    return $args{_self} if $args{_self};
+
+    $args{dispatch} ||= 'Admin';
+    $args{prefix}   ||= 'inc';
+    $args{author}   ||= ($^O eq 'VMS' ? '_author' : '.author');
+    $args{bundle}   ||= 'inc/BUNDLES';
+    $args{base}     ||= $base_path;
+    $class =~ s/^\Q$args{prefix}\E:://;
+    $args{name}     ||= $class;
+    $args{version}  ||= $class->VERSION;
+    unless ( $args{path} ) {
+        $args{path}  = $args{name};
+        $args{path}  =~ s!::!/!g;
+    }
+    $args{file}     ||= "$args{base}/$args{prefix}/$args{path}.pm";
+
+    bless( \%args, $class );
+}
+
+sub call {
+	my ($self, $method) = @_;
+	my $obj = $self->load($method) or return;
+        splice(@_, 0, 2, $obj);
+	goto &{$obj->can($method)};
+}
+
+sub load {
+    my ($self, $method) = @_;
+
+    $self->load_extensions(
+        "$self->{prefix}/$self->{path}", $self
+    ) unless $self->{extensions};
+
+    foreach my $obj (@{$self->{extensions}}) {
+        return $obj if $obj->can($method);
+    }
+
+    my $admin = $self->{admin} or die <<"END_DIE";
+The '$method' method does not exist in the '$self->{prefix}' path!
+Please remove the '$self->{prefix}' directory and run $0 again to load it.
+END_DIE
+
+    my $obj = $admin->load($method, 1);
+    push @{$self->{extensions}}, $obj;
+
+    $obj;
+}
+
+sub load_extensions {
+    my ($self, $path, $top) = @_;
+
+    unless ( grep { lc $_ eq lc $self->{prefix} } @INC ) {
+        unshift @INC, $self->{prefix};
+    }
+
+    foreach my $rv ( $self->find_extensions($path) ) {
+        my ($file, $pkg) = @{$rv};
+        next if $self->{pathnames}{$pkg};
+
+        local $@;
+        my $new = eval { require $file; $pkg->can('new') };
+        unless ( $new ) {
+            warn $@ if $@;
+            next;
+        }
+        $self->{pathnames}{$pkg} = delete $INC{$file};
+        push @{$self->{extensions}}, &{$new}($pkg, _top => $top );
+    }
+
+    $self->{extensions} ||= [];
+}
+
+sub find_extensions {
+    my ($self, $path) = @_;
+
+    my @found;
+    File::Find::find( sub {
+        my $file = $File::Find::name;
+        return unless $file =~ m!^\Q$path\E/(.+)\.pm\Z!is;
+        my $subpath = $1;
+        return if lc($subpath) eq lc($self->{dispatch});
+
+        $file = "$self->{path}/$subpath.pm";
+        my $pkg = "$self->{name}::$subpath";
+        $pkg =~ s!/!::!g;
+
+        # If we have a mixed-case package name, assume case has been preserved
+        # correctly.  Otherwise, root through the file to locate the case-preserved
+        # version of the package name.
+        if ( $subpath eq lc($subpath) || $subpath eq uc($subpath) ) {
+            open PKGFILE, "<$subpath.pm" or die "find_extensions: Can't open $subpath.pm: $!";
+            my $in_pod = 0;
+            while ( <PKGFILE> ) {
+                $in_pod = 1 if /^=\w/;
+                $in_pod = 0 if /^=cut/;
+                next if ($in_pod || /^=cut/);  # skip pod text
+                next if /^\s*#/;               # and comments
+                if ( m/^\s*package\s+($pkg)\s*;/i ) {
+                    $pkg = $1;
+                    last;
+                }
+            }
+            close PKGFILE;
+        }
+
+        push @found, [ $file, $pkg ];
+    }, $path ) if -d $path;
+
+    @found;
+}
+
+sub _caller {
+    my $depth = 0;
+    my $call  = caller($depth);
+    while ( $call eq __PACKAGE__ ) {
+        $depth++;
+        $call = caller($depth);
+    }
+    return $call;
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/Install/AutoInstall.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/Install/AutoInstall.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,61 @@
+#line 1
+package Module::Install::AutoInstall;
+
+use strict;
+use Module::Install::Base;
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+	$VERSION = '0.64';
+	$ISCORE  = 1;
+	@ISA     = qw{Module::Install::Base};
+}
+
+sub AutoInstall { $_[0] }
+
+sub run {
+    my $self = shift;
+    $self->auto_install_now(@_);
+}
+
+sub write {
+    my $self = shift;
+    $self->auto_install(@_);
+}
+
+sub auto_install {
+    my $self = shift;
+    return if $self->{done}++;
+
+    # Flatten array of arrays into a single array
+    my @core = map @$_, map @$_, grep ref,
+               $self->build_requires, $self->requires;
+
+    my @config = @_;
+
+    # We'll need Module::AutoInstall
+    $self->include('Module::AutoInstall');
+    require Module::AutoInstall;
+
+    Module::AutoInstall->import(
+        (@config ? (-config => \@config) : ()),
+        (@core   ? (-core   => \@core)   : ()),
+        $self->features,
+    );
+
+    $self->makemaker_args( Module::AutoInstall::_make_args() );
+
+    my $class = ref($self);
+    $self->postamble(
+        "# --- $class section:\n" .
+        Module::AutoInstall::postamble()
+    );
+}
+
+sub auto_install_now {
+    my $self = shift;
+    $self->auto_install(@_);
+    Module::AutoInstall::do_install();
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/Install/Base.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/Install/Base.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,70 @@
+#line 1
+package Module::Install::Base;
+
+$VERSION = '0.64';
+
+# Suspend handler for "redefined" warnings
+BEGIN {
+	my $w = $SIG{__WARN__};
+	$SIG{__WARN__} = sub { $w };
+}
+
+### This is the ONLY module that shouldn't have strict on
+# use strict;
+
+#line 41
+
+sub new {
+    my ($class, %args) = @_;
+
+    foreach my $method ( qw(call load) ) {
+        *{"$class\::$method"} = sub {
+            shift()->_top->$method(@_);
+        } unless defined &{"$class\::$method"};
+    }
+
+    bless( \%args, $class );
+}
+
+#line 61
+
+sub AUTOLOAD {
+    my $self = shift;
+    local $@;
+    my $autoload = eval { $self->_top->autoload } or return;
+    goto &$autoload;
+}
+
+#line 76
+
+sub _top { $_[0]->{_top} }
+
+#line 89
+
+sub admin {
+    $_[0]->_top->{admin} or Module::Install::Base::FakeAdmin->new;
+}
+
+sub is_admin {
+    $_[0]->admin->VERSION;
+}
+
+sub DESTROY {}
+
+package Module::Install::Base::FakeAdmin;
+
+my $Fake;
+sub new { $Fake ||= bless(\@_, $_[0]) }
+
+sub AUTOLOAD {}
+
+sub DESTROY {}
+
+# Restore warning handler
+BEGIN {
+	$SIG{__WARN__} = $SIG{__WARN__}->();
+}
+
+1;
+
+#line 138

Added: jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/Install/Can.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/Install/Can.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,82 @@
+#line 1
+package Module::Install::Can;
+
+use strict;
+use Module::Install::Base;
+use Config ();
+### This adds a 5.005 Perl version dependency.
+### This is a bug and will be fixed.
+use File::Spec ();
+use ExtUtils::MakeMaker ();
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+	$VERSION = '0.64';
+	$ISCORE  = 1;
+	@ISA     = qw{Module::Install::Base};
+}
+
+# check if we can load some module
+### Upgrade this to not have to load the module if possible
+sub can_use {
+	my ($self, $mod, $ver) = @_;
+	$mod =~ s{::|\\}{/}g;
+	$mod .= '.pm' unless $mod =~ /\.pm$/i;
+
+	my $pkg = $mod;
+	$pkg =~ s{/}{::}g;
+	$pkg =~ s{\.pm$}{}i;
+
+	local $@;
+	eval { require $mod; $pkg->VERSION($ver || 0); 1 };
+}
+
+# check if we can run some command
+sub can_run {
+	my ($self, $cmd) = @_;
+
+	my $_cmd = $cmd;
+	return $_cmd if (-x $_cmd or $_cmd = MM->maybe_command($_cmd));
+
+	for my $dir ((split /$Config::Config{path_sep}/, $ENV{PATH}), '.') {
+		my $abs = File::Spec->catfile($dir, $_[1]);
+		return $abs if (-x $abs or $abs = MM->maybe_command($abs));
+	}
+
+	return;
+}
+
+# can we locate a (the) C compiler
+sub can_cc {
+	my $self   = shift;
+	my @chunks = split(/ /, $Config::Config{cc}) or return;
+
+	# $Config{cc} may contain args; try to find out the program part
+	while (@chunks) {
+		return $self->can_run("@chunks") || (pop(@chunks), next);
+	}
+
+	return;
+}
+
+# Fix Cygwin bug on maybe_command();
+if ( $^O eq 'cygwin' ) {
+	require ExtUtils::MM_Cygwin;
+	require ExtUtils::MM_Win32;
+	if ( ! defined(&ExtUtils::MM_Cygwin::maybe_command) ) {
+		*ExtUtils::MM_Cygwin::maybe_command = sub {
+			my ($self, $file) = @_;
+			if ($file =~ m{^/cygdrive/}i and ExtUtils::MM_Win32->can('maybe_command')) {
+				ExtUtils::MM_Win32->maybe_command($file);
+			} else {
+				ExtUtils::MM_Unix->maybe_command($file);
+			}
+		}
+	}
+}
+
+1;
+
+__END__
+
+#line 157

Added: jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/Install/Fetch.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/Install/Fetch.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,93 @@
+#line 1
+package Module::Install::Fetch;
+
+use strict;
+use Module::Install::Base;
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+	$VERSION = '0.64';
+	$ISCORE  = 1;
+	@ISA     = qw{Module::Install::Base};
+}
+
+sub get_file {
+    my ($self, %args) = @_;
+    my ($scheme, $host, $path, $file) = 
+        $args{url} =~ m|^(\w+)://([^/]+)(.+)/(.+)| or return;
+
+    if ( $scheme eq 'http' and ! eval { require LWP::Simple; 1 } ) {
+        $args{url} = $args{ftp_url}
+            or (warn("LWP support unavailable!\n"), return);
+        ($scheme, $host, $path, $file) = 
+            $args{url} =~ m|^(\w+)://([^/]+)(.+)/(.+)| or return;
+    }
+
+    $|++;
+    print "Fetching '$file' from $host... ";
+
+    unless (eval { require Socket; Socket::inet_aton($host) }) {
+        warn "'$host' resolve failed!\n";
+        return;
+    }
+
+    return unless $scheme eq 'ftp' or $scheme eq 'http';
+
+    require Cwd;
+    my $dir = Cwd::getcwd();
+    chdir $args{local_dir} or return if exists $args{local_dir};
+
+    if (eval { require LWP::Simple; 1 }) {
+        LWP::Simple::mirror($args{url}, $file);
+    }
+    elsif (eval { require Net::FTP; 1 }) { eval {
+        # use Net::FTP to get past firewall
+        my $ftp = Net::FTP->new($host, Passive => 1, Timeout => 600);
+        $ftp->login("anonymous", 'anonymous at example.com');
+        $ftp->cwd($path);
+        $ftp->binary;
+        $ftp->get($file) or (warn("$!\n"), return);
+        $ftp->quit;
+    } }
+    elsif (my $ftp = $self->can_run('ftp')) { eval {
+        # no Net::FTP, fallback to ftp.exe
+        require FileHandle;
+        my $fh = FileHandle->new;
+
+        local $SIG{CHLD} = 'IGNORE';
+        unless ($fh->open("|$ftp -n")) {
+            warn "Couldn't open ftp: $!\n";
+            chdir $dir; return;
+        }
+
+        my @dialog = split(/\n/, <<"END_FTP");
+open $host
+user anonymous anonymous\@example.com
+cd $path
+binary
+get $file $file
+quit
+END_FTP
+        foreach (@dialog) { $fh->print("$_\n") }
+        $fh->close;
+    } }
+    else {
+        warn "No working 'ftp' program available!\n";
+        chdir $dir; return;
+    }
+
+    unless (-f $file) {
+        warn "Fetching failed: $@\n";
+        chdir $dir; return;
+    }
+
+    return if exists $args{size} and -s $file != $args{size};
+    system($args{run}) if exists $args{run};
+    unlink($file) if $args{remove};
+
+    print(((!exists $args{check_for} or -e $args{check_for})
+        ? "done!" : "failed! ($!)"), "\n");
+    chdir $dir; return !$?;
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/Install/Include.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/Install/Include.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,34 @@
+#line 1
+package Module::Install::Include;
+
+use strict;
+use Module::Install::Base;
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+	$VERSION = '0.64';
+	$ISCORE  = 1;
+	@ISA     = qw{Module::Install::Base};
+}
+
+sub include {
+	shift()->admin->include(@_);
+}
+
+sub include_deps {
+	shift()->admin->include_deps(@_);
+}
+
+sub auto_include {
+	shift()->admin->auto_include(@_);
+}
+
+sub auto_include_deps {
+	shift()->admin->auto_include_deps(@_);
+}
+
+sub auto_include_dependent_dists {
+	shift()->admin->auto_include_dependent_dists(@_);
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/Install/Makefile.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/Install/Makefile.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,208 @@
+#line 1
+package Module::Install::Makefile;
+
+use strict 'vars';
+use Module::Install::Base;
+use ExtUtils::MakeMaker ();
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+	$VERSION = '0.64';
+	$ISCORE  = 1;
+	@ISA     = qw{Module::Install::Base};
+}
+
+sub Makefile { $_[0] }
+
+my %seen = ();
+
+sub prompt {
+    shift;
+
+    # Infinite loop protection
+    my @c = caller();
+    if ( ++$seen{"$c[1]|$c[2]|$_[0]"} > 3 ) {
+        die "Caught an potential prompt infinite loop ($c[1]|$c[2]|$_[0])";
+    }
+
+    # In automated testing, always use defaults
+    if ( $ENV{AUTOMATED_TESTING} and ! $ENV{PERL_MM_USE_DEFAULT} ) {
+        local $ENV{PERL_MM_USE_DEFAULT} = 1;
+        goto &ExtUtils::MakeMaker::prompt;
+    } else {
+        goto &ExtUtils::MakeMaker::prompt;
+    }
+}
+
+sub makemaker_args {
+    my $self = shift;
+    my $args = ($self->{makemaker_args} ||= {});
+    %$args = ( %$args, @_ ) if @_;
+    $args;
+}
+
+# For mm args that take multiple space-seperated args,
+# append an argument to the current list.
+sub makemaker_append {
+    my $self = shift;
+    my $name = shift;
+    my $args = $self->makemaker_args;
+    $args->{name} = defined $args->{$name}
+    	? join( ' ', $args->{name}, @_ )
+    	: join( ' ', @_ );
+}
+
+sub build_subdirs {
+    my $self    = shift;
+    my $subdirs = $self->makemaker_args->{DIR} ||= [];
+    for my $subdir (@_) {
+        push @$subdirs, $subdir;
+    }
+}
+
+sub clean_files {
+    my $self  = shift;
+    my $clean = $self->makemaker_args->{clean} ||= {};
+    %$clean = (
+        %$clean, 
+        FILES => join(' ', grep length, $clean->{FILES}, @_),
+    );
+}
+
+sub realclean_files {
+    my $self  = shift;
+    my $realclean = $self->makemaker_args->{realclean} ||= {};
+    %$realclean = (
+        %$realclean, 
+        FILES => join(' ', grep length, $realclean->{FILES}, @_),
+    );
+}
+
+sub libs {
+    my $self = shift;
+    my $libs = ref $_[0] ? shift : [ shift ];
+    $self->makemaker_args( LIBS => $libs );
+}
+
+sub inc {
+    my $self = shift;
+    $self->makemaker_args( INC => shift );
+}
+
+sub write {
+    my $self = shift;
+    die "&Makefile->write() takes no arguments\n" if @_;
+
+    my $args = $self->makemaker_args;
+    $args->{DISTNAME} = $self->name;
+    $args->{NAME}     = $self->module_name || $self->name || $self->determine_NAME($args);
+    $args->{VERSION}  = $self->version || $self->determine_VERSION($args);
+    $args->{NAME}     =~ s/-/::/g;
+    if ( $self->tests ) {
+        $args->{test} = { TESTS => $self->tests };
+    }
+    if ($] >= 5.005) {
+        $args->{ABSTRACT} = $self->abstract;
+        $args->{AUTHOR}   = $self->author;
+    }
+    if ( eval($ExtUtils::MakeMaker::VERSION) >= 6.10 ) {
+        $args->{NO_META} = 1;
+    }
+    if ( eval($ExtUtils::MakeMaker::VERSION) > 6.17 and $self->sign ) {
+        $args->{SIGN} = 1;
+    }
+    unless ( $self->is_admin ) {
+        delete $args->{SIGN};
+    }
+
+    # merge both kinds of requires into prereq_pm
+    my $prereq = ($args->{PREREQ_PM} ||= {});
+    %$prereq = ( %$prereq, map { @$_ } map { @$_ } grep $_,
+                 ($self->build_requires, $self->requires) );
+
+    # merge both kinds of requires into prereq_pm
+    my $subdirs = ($args->{DIR} ||= []);
+    if ($self->bundles) {
+        foreach my $bundle (@{ $self->bundles }) {
+            my ($file, $dir) = @$bundle;
+            push @$subdirs, $dir if -d $dir;
+            delete $prereq->{$file};
+        }
+    }
+
+    if ( my $perl_version = $self->perl_version ) {
+        eval "use $perl_version; 1"
+            or die "ERROR: perl: Version $] is installed, "
+                . "but we need version >= $perl_version";
+    }
+
+    my %args = map { ( $_ => $args->{$_} ) } grep {defined($args->{$_})} keys %$args;
+    if ($self->admin->preop) {
+        $args{dist} = $self->admin->preop;
+    }
+
+    my $mm = ExtUtils::MakeMaker::WriteMakefile(%args);
+    $self->fix_up_makefile($mm->{FIRST_MAKEFILE} || 'Makefile');
+}
+
+sub fix_up_makefile {
+    my $self          = shift;
+    my $makefile_name = shift;
+    my $top_class     = ref($self->_top) || '';
+    my $top_version   = $self->_top->VERSION || '';
+
+    my $preamble = $self->preamble 
+        ? "# Preamble by $top_class $top_version\n"
+            . $self->preamble
+        : '';
+    my $postamble = "# Postamble by $top_class $top_version\n"
+        . ($self->postamble || '');
+
+    local *MAKEFILE;
+    open MAKEFILE, "< $makefile_name" or die "fix_up_makefile: Couldn't open $makefile_name: $!";
+    my $makefile = do { local $/; <MAKEFILE> };
+    close MAKEFILE or die $!;
+
+    $makefile =~ s/\b(test_harness\(\$\(TEST_VERBOSE\), )/$1'inc', /;
+    $makefile =~ s/( -I\$\(INST_ARCHLIB\))/ -Iinc$1/g;
+    $makefile =~ s/( "-I\$\(INST_LIB\)")/ "-Iinc"$1/g;
+    $makefile =~ s/^(FULLPERL = .*)/$1 "-Iinc"/m;
+    $makefile =~ s/^(PERL = .*)/$1 "-Iinc"/m;
+
+    # Module::Install will never be used to build the Core Perl
+    # Sometimes PERL_LIB and PERL_ARCHLIB get written anyway, which breaks
+    # PREFIX/PERL5LIB, and thus, install_share. Blank them if they exist
+    $makefile =~ s/^PERL_LIB = .+/PERL_LIB =/m;
+    #$makefile =~ s/^PERL_ARCHLIB = .+/PERL_ARCHLIB =/m;
+
+    # Perl 5.005 mentions PERL_LIB explicitly, so we have to remove that as well.
+    $makefile =~ s/("?)-I\$\(PERL_LIB\)\1//g;
+
+    # XXX - This is currently unused; not sure if it breaks other MM-users
+    # $makefile =~ s/^pm_to_blib\s+:\s+/pm_to_blib :: /mg;
+
+    open  MAKEFILE, "> $makefile_name" or die "fix_up_makefile: Couldn't open $makefile_name: $!";
+    print MAKEFILE  "$preamble$makefile$postamble" or die $!;
+    close MAKEFILE  or die $!;
+
+    1;
+}
+
+sub preamble {
+    my ($self, $text) = @_;
+    $self->{preamble} = $text . $self->{preamble} if defined $text;
+    $self->{preamble};
+}
+
+sub postamble {
+    my ($self, $text) = @_;
+    $self->{postamble} ||= $self->admin->postamble;
+    $self->{postamble} .= $text if defined $text;
+    $self->{postamble}
+}
+
+1;
+
+__END__
+
+#line 334

Added: jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/Install/Makefile/Version.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/Install/Makefile/Version.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,42 @@
+#line 1
+package Module::Install::Makefile::Version;
+
+use Module::Install::Base;
+ at ISA = qw(Module::Install::Base);
+
+$VERSION = '0.64';
+
+use strict;
+
+sub determine_VERSION {
+    my $self = shift;
+    my @modules = glob('*.pm');
+
+    require File::Find;
+    File::Find::find( sub {
+        push @modules, $File::Find::name =~ /\.pm\z/i;
+    }, 'lib' );
+
+    if (@modules == 1) {
+        eval {
+            $self->version(
+                ExtUtils::MM_Unix->parse_version($modules[0])
+            );
+        };
+        print STDERR $@ if $@;
+
+    } elsif ( my $file = "lib/" . $self->name . ".pm" ) {
+        $file =~ s!-!/!g;
+        $self->version(
+            ExtUtils::MM_Unix->parse_version($file)
+        ) if -f $file;
+
+    }
+
+    $self->version or die << "END_MESSAGE";
+Can't determine a VERSION for this distribution.
+Please call the 'version' or 'version_from' function in Makefile.PL.
+END_MESSAGE
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/Install/Metadata.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/Install/Metadata.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,315 @@
+#line 1
+package Module::Install::Metadata;
+
+use strict 'vars';
+use Module::Install::Base;
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+	$VERSION = '0.64';
+	$ISCORE  = 1;
+	@ISA     = qw{Module::Install::Base};
+}
+
+my @scalar_keys = qw{
+    name module_name abstract author version license
+    distribution_type perl_version tests
+};
+
+my @tuple_keys = qw{
+    build_requires requires recommends bundles
+};
+
+sub Meta            { shift        }
+sub Meta_ScalarKeys { @scalar_keys }
+sub Meta_TupleKeys  { @tuple_keys  }
+
+foreach my $key (@scalar_keys) {
+    *$key = sub {
+        my $self = shift;
+        return $self->{values}{$key} if defined wantarray and !@_;
+        $self->{values}{$key} = shift;
+        return $self;
+    };
+}
+
+foreach my $key (@tuple_keys) {
+    *$key = sub {
+        my $self = shift;
+        return $self->{values}{$key} unless @_;
+
+        my @rv;
+        while (@_) {
+            my $module = shift or last;
+            my $version = shift || 0;
+            if ( $module eq 'perl' ) {
+                $version =~ s{^(\d+)\.(\d+)\.(\d+)}
+                             {$1 + $2/1_000 + $3/1_000_000}e;
+                $self->perl_version($version);
+                next;
+            }
+            my $rv = [ $module, $version ];
+            push @rv, $rv;
+        }
+        push @{ $self->{values}{$key} }, @rv;
+        @rv;
+    };
+}
+
+sub sign {
+    my $self = shift;
+    return $self->{'values'}{'sign'} if defined wantarray and !@_;
+    $self->{'values'}{'sign'} = ( @_ ? $_[0] : 1 );
+    return $self;
+}
+
+sub dynamic_config {
+	my $self = shift;
+	unless ( @_ ) {
+		warn "You MUST provide an explicit true/false value to dynamic_config, skipping\n";
+		return $self;
+	}
+	$self->{'values'}{'dynamic_config'} = $_[0] ? 1 : 0;
+	return $self;
+}
+
+sub all_from {
+    my ( $self, $file ) = @_;
+
+    unless ( defined($file) ) {
+        my $name = $self->name
+            or die "all_from called with no args without setting name() first";
+        $file = join('/', 'lib', split(/-/, $name)) . '.pm';
+        $file =~ s{.*/}{} unless -e $file;
+        die "all_from: cannot find $file from $name" unless -e $file;
+    }
+
+    $self->version_from($file)      unless $self->version;
+    $self->perl_version_from($file) unless $self->perl_version;
+
+    # The remaining probes read from POD sections; if the file
+    # has an accompanying .pod, use that instead
+    my $pod = $file;
+    if ( $pod =~ s/\.pm$/.pod/i and -e $pod ) {
+        $file = $pod;
+    }
+
+    $self->author_from($file)   unless $self->author;
+    $self->license_from($file)  unless $self->license;
+    $self->abstract_from($file) unless $self->abstract;
+}
+
+sub provides {
+    my $self     = shift;
+    my $provides = ( $self->{values}{provides} ||= {} );
+    %$provides = (%$provides, @_) if @_;
+    return $provides;
+}
+
+sub auto_provides {
+    my $self = shift;
+    return $self unless $self->is_admin;
+
+    unless (-e 'MANIFEST') {
+        warn "Cannot deduce auto_provides without a MANIFEST, skipping\n";
+        return $self;
+    }
+
+    # Avoid spurious warnings as we are not checking manifest here.
+
+    local $SIG{__WARN__} = sub {1};
+    require ExtUtils::Manifest;
+    local *ExtUtils::Manifest::manicheck = sub { return };
+
+    require Module::Build;
+    my $build = Module::Build->new(
+        dist_name    => $self->name,
+        dist_version => $self->version,
+        license      => $self->license,
+    );
+    $self->provides(%{ $build->find_dist_packages || {} });
+}
+
+sub feature {
+    my $self     = shift;
+    my $name     = shift;
+    my $features = ( $self->{values}{features} ||= [] );
+
+    my $mods;
+
+    if ( @_ == 1 and ref( $_[0] ) ) {
+        # The user used ->feature like ->features by passing in the second
+        # argument as a reference.  Accomodate for that.
+        $mods = $_[0];
+    } else {
+        $mods = \@_;
+    }
+
+    my $count = 0;
+    push @$features, (
+        $name => [
+            map {
+                ref($_) ? ( ref($_) eq 'HASH' ) ? %$_
+                                                : @$_
+                        : $_
+            } @$mods
+        ]
+    );
+
+    return @$features;
+}
+
+sub features {
+    my $self = shift;
+    while ( my ( $name, $mods ) = splice( @_, 0, 2 ) ) {
+        $self->feature( $name, @$mods );
+    }
+    return $self->{values}->{features}
+    	? @{ $self->{values}->{features} }
+    	: ();
+}
+
+sub no_index {
+    my $self = shift;
+    my $type = shift;
+    push @{ $self->{values}{no_index}{$type} }, @_ if $type;
+    return $self->{values}{no_index};
+}
+
+sub read {
+    my $self = shift;
+    $self->include_deps( 'YAML', 0 );
+
+    require YAML;
+    my $data = YAML::LoadFile('META.yml');
+
+    # Call methods explicitly in case user has already set some values.
+    while ( my ( $key, $value ) = each %$data ) {
+        next unless $self->can($key);
+        if ( ref $value eq 'HASH' ) {
+            while ( my ( $module, $version ) = each %$value ) {
+                $self->can($key)->($self, $module => $version );
+            }
+        }
+        else {
+            $self->can($key)->($self, $value);
+        }
+    }
+    return $self;
+}
+
+sub write {
+    my $self = shift;
+    return $self unless $self->is_admin;
+    $self->admin->write_meta;
+    return $self;
+}
+
+sub version_from {
+    my ( $self, $file ) = @_;
+    require ExtUtils::MM_Unix;
+    $self->version( ExtUtils::MM_Unix->parse_version($file) );
+}
+
+sub abstract_from {
+    my ( $self, $file ) = @_;
+    require ExtUtils::MM_Unix;
+    $self->abstract(
+        bless(
+            { DISTNAME => $self->name },
+            'ExtUtils::MM_Unix'
+        )->parse_abstract($file)
+     );
+}
+
+sub _slurp {
+    my ( $self, $file ) = @_;
+
+    local *FH;
+    open FH, "< $file" or die "Cannot open $file.pod: $!";
+    do { local $/; <FH> };
+}
+
+sub perl_version_from {
+    my ( $self, $file ) = @_;
+
+    if (
+        $self->_slurp($file) =~ m/
+        ^
+        use \s*
+        v?
+        ([\d_\.]+)
+        \s* ;
+    /ixms
+      )
+    {
+        my $v = $1;
+        $v =~ s{_}{}g;
+        $self->perl_version($1);
+    }
+    else {
+        warn "Cannot determine perl version info from $file\n";
+        return;
+    }
+}
+
+sub author_from {
+    my ( $self, $file ) = @_;
+    my $content = $self->_slurp($file);
+    if ($content =~ m/
+        =head \d \s+ (?:authors?)\b \s*
+        ([^\n]*)
+        |
+        =head \d \s+ (?:licen[cs]e|licensing|copyright|legal)\b \s*
+        .*? copyright .*? \d\d\d[\d.]+ \s* (?:\bby\b)? \s*
+        ([^\n]*)
+    /ixms) {
+        my $author = $1 || $2;
+        $author =~ s{E<lt>}{<}g;
+        $author =~ s{E<gt>}{>}g;
+        $self->author($author); 
+    }
+    else {
+        warn "Cannot determine author info from $file\n";
+    }
+}
+
+sub license_from {
+    my ( $self, $file ) = @_;
+
+    if (
+        $self->_slurp($file) =~ m/
+        =head \d \s+
+        (?:licen[cs]e|licensing|copyright|legal)\b
+        (.*?)
+        (=head\\d.*|=cut.*|)
+        \z
+    /ixms
+      )
+    {
+        my $license_text = $1;
+        my @phrases      = (
+            'under the same (?:terms|license) as perl itself' => 'perl',
+            'GNU public license'                              => 'gpl',
+            'GNU lesser public license'                       => 'gpl',
+            'BSD license'                                     => 'bsd',
+            'Artistic license'                                => 'artistic',
+            'GPL'                                             => 'gpl',
+            'LGPL'                                            => 'lgpl',
+            'BSD'                                             => 'bsd',
+            'Artistic'                                        => 'artistic',
+        );
+        while ( my ( $pattern, $license ) = splice( @phrases, 0, 2 ) ) {
+            $pattern =~ s{\s+}{\\s+}g;
+            if ( $license_text =~ /\b$pattern\b/i ) {
+                $self->license($license);
+                return 1;
+            }
+        }
+    }
+
+    warn "Cannot determine license info from $file\n";
+    return 'unknown';
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/Install/Share.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/Install/Share.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,40 @@
+#line 1
+package Module::Install::Share;
+
+use strict;
+use Module::Install::Base;
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+	$VERSION = '0.64';
+	$ISCORE  = 1;
+	@ISA     = qw{Module::Install::Base};
+}
+
+sub install_share {
+	my ($self, $dir) = @_;
+
+	if ( ! defined $dir ) {
+		die "Cannot find the 'share' directory" unless -d 'share';
+		$dir = 'share';
+	}
+
+	$self->postamble(<<"END_MAKEFILE");
+config ::
+\t\$(NOECHO) \$(MOD_INSTALL) \\
+\t\t\"$dir\" \$(INST_AUTODIR)
+
+END_MAKEFILE
+
+	# The above appears to behave incorrectly when used with old versions
+	# of ExtUtils::Install (known-bad on RHEL 3, with 5.8.0)
+	# So when we need to install a share directory, make sure we add a
+	# dependency on a moderately new version of ExtUtils::MakeMaker.
+	$self->build_requires( 'ExtUtils::MakeMaker' => '6.11' );
+}
+
+1;
+
+__END__
+
+#line 98

Added: jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/Install/Win32.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/Install/Win32.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,65 @@
+#line 1
+package Module::Install::Win32;
+
+use strict;
+use Module::Install::Base;
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+	$VERSION = '0.64';
+	$ISCORE  = 1;
+	@ISA     = qw{Module::Install::Base};
+}
+
+# determine if the user needs nmake, and download it if needed
+sub check_nmake {
+	my $self = shift;
+	$self->load('can_run');
+	$self->load('get_file');
+	
+	require Config;
+	return unless (
+		$^O eq 'MSWin32'                     and
+		$Config::Config{make}                and
+		$Config::Config{make} =~ /^nmake\b/i and
+		! $self->can_run('nmake')
+	);
+
+	print "The required 'nmake' executable not found, fetching it...\n";
+
+	require File::Basename;
+	my $rv = $self->get_file(
+		url       => 'http://download.microsoft.com/download/vc15/Patch/1.52/W95/EN-US/Nmake15.exe',
+		ftp_url   => 'ftp://ftp.microsoft.com/Softlib/MSLFILES/Nmake15.exe',
+		local_dir => File::Basename::dirname($^X),
+		size      => 51928,
+		run       => 'Nmake15.exe /o > nul',
+		check_for => 'Nmake.exe',
+		remove    => 1,
+	);
+
+	if (!$rv) {
+        die <<'END_MESSAGE';
+
+-------------------------------------------------------------------------------
+
+Since you are using Microsoft Windows, you will need the 'nmake' utility
+before installation. It's available at:
+
+  http://download.microsoft.com/download/vc15/Patch/1.52/W95/EN-US/Nmake15.exe
+      or
+  ftp://ftp.microsoft.com/Softlib/MSLFILES/Nmake15.exe
+
+Please download the file manually, save it to a directory in %PATH% (e.g.
+C:\WINDOWS\COMMAND\), then launch the MS-DOS command line shell, "cd" to
+that directory, and run "Nmake15.exe" from there; that will create the
+'nmake.exe' file needed by this module.
+
+You may then resume the installation process described in README.
+
+-------------------------------------------------------------------------------
+END_MESSAGE
+	}
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/Install/WriteAll.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/EditInPlace/inc/Module/Install/WriteAll.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,43 @@
+#line 1
+package Module::Install::WriteAll;
+
+use strict;
+use Module::Install::Base;
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+	$VERSION = '0.64';
+	$ISCORE  = 1;
+	@ISA     = qw{Module::Install::Base};
+}
+
+sub WriteAll {
+    my $self = shift;
+    my %args = (
+        meta        => 1,
+        sign        => 0,
+        inline      => 0,
+        check_nmake => 1,
+        @_
+    );
+
+    $self->sign(1)                if $args{sign};
+    $self->Meta->write            if $args{meta};
+    $self->admin->WriteAll(%args) if $self->is_admin;
+
+    if ( $0 =~ /Build.PL$/i ) {
+        $self->Build->write;
+    } else {
+        $self->check_nmake if $args{check_nmake};
+        unless ( $self->makemaker_args->{'PL_FILES'} ) {
+        	$self->makemaker_args( PL_FILES => {} );
+        }
+        if ($args{inline}) {
+            $self->Inline->write;
+        } else {
+            $self->Makefile->write;
+        }
+    }
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/EditInPlace/lib/Jifty/Plugin/EditInPlace.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/EditInPlace/lib/Jifty/Plugin/EditInPlace.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,34 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::EditInPlace;
+use base qw/Jifty::Plugin/;
+
+
+package HTML::Mason::Request::Jifty;
+
+=head2 fetch_comp
+
+=cut
+
+sub fetch_comp {
+    my $self = shift;
+    my $comp = $self->SUPER::fetch_comp(@_);
+    if (not $comp and  Jifty->config->framework('DevelMode') ) {
+        my $comp_name = shift;
+        $comp = $self->interp->make_component( 
+                comp_source => 
+                    "
+                       <span id=\"create-component-$comp_name\">
+<% Jifty->web->link(class => 'inline_create', label => 'Create $comp_name',  onclick => [ { element      => \"#create-component-$comp_name\", replace_with =>  '/__jifty/edit_inline/mason_component/$comp_name'  } ]) %>
+                       </span> 
+                    ");
+            
+            
+    }
+    return $comp;
+}
+
+
+
+1;

Added: jifty/branches/schema-plugins/plugins/EditInPlace/lib/Jifty/Plugin/EditInPlace/Action/FileEditor.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/EditInPlace/lib/Jifty/Plugin/EditInPlace/Action/FileEditor.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,181 @@
+package Jifty::Plugin::EditInPlace::Action::FileEditor;
+
+use base qw/Jifty::Action/;
+use File::Spec;
+use File::Basename ();
+
+
+=head1 NAME
+
+Jifty::Plugin::EditInPlace::Action::FileEditor
+
+=head1 DESCRIPTION
+
+This action allows you to edit mason components (and eventually libraries)
+using Jifty's I<Action> system.  It should only be enabled when you're
+running Jifty in C<DevelMode>. 
+
+=head1 WARNING
+
+B<THIS ACTION LETS YOU REMOTELY EDIT EXECUTABLE CODE>.
+
+B<THIS IS DANGEROUS>
+
+=cut
+
+=head2 new
+
+Create a new C<FileEditor> action.
+
+=cut
+
+sub new {
+    my $class = shift; 
+    my $self = $class->SUPER::new(@_);
+    $self->sticky_on_success(1);
+    $self->get_default_content; 
+    return($self);
+
+}
+
+=head2 arguments
+
+Sets up this action's arguments.
+
+=over
+
+=item path
+
+Where to save the file
+
+=item file_type
+
+(One of mason_component or library)
+
+=item source_path
+
+Where to read the file from.
+
+=item destination_path
+
+Where to write the file to. If the current user can't write to 
+the source_path, defaults to something inside the app's directory.
+
+=item content
+
+The actual content of the file we're editing.
+
+=back
+
+=cut
+
+
+sub arguments {
+    my $self = shift;
+
+    {   file_type => {
+            label       => 'File type',
+            default      => 'mason_component',
+            render_as    => 'Select',
+            valid_values => [{ value => 'mason_component', display => 'Template'} , {value => 'library', display => 'Library'}],
+            constructor  => 1
+        },
+        source_path      => { type => 'text', constructor => 1, label => 'Path' },
+        destination_path => { type => 'text', ajax_validates=> 1, label => 'Save as' },
+        content => { render_as => 'Textarea', cols => 80, rows => 25, label => 'Content' },
+
+    }
+
+}
+
+
+=head2 get_default_content
+
+Finds the version of the C<source_path> (of type C<file_type>) and
+loads it into C<content>.
+
+=cut
+
+sub get_default_content {
+    my $self = shift;
+
+    # Don't override content we already have
+    return if ($self->argument_value('content'));
+    my $path = $self->argument_value('source_path');
+    my $type = $self->argument_value('file_type');
+    my $out = '';
+    my %cfg = Jifty->handler->mason_config;
+    
+    my($local_template_base, $qualified_path);
+    if ($type eq "mason_component") {
+        foreach my $item (@{$cfg{comp_root}}) {
+            $local_template_base = $item->[1] if $item->[0] eq 'application';
+            $qualified_path = File::Spec->catfile($item->[1],$path);
+            # We want the first match
+            last if -f $qualified_path and -r $qualified_path;
+            undef $qualified_path;
+        }
+        $self->argument_value(destination_path => File::Spec->catfile($local_template_base, $path));
+    } else {
+        $qualified_path = "/$path" if -f "/$path" and -r "/$path";
+        $self->argument_value(destination_path => $qualified_path);
+    }
+
+    if ($qualified_path) {
+        local $/;
+        my $filehandle;
+        open ($filehandle, "<$qualified_path")||die "Couldn't read $qualified_path: $!";
+        $self->argument_value(content => <$filehandle>);
+        close($filehandle);
+    }
+}
+
+=head2 validate_destination_path PATH
+
+Returns true if the user can write to the directory C<PATH>. False
+otherwise. Should be refactored to a C<path_writable> routine and a
+trivial validator.
+
+=cut
+
+sub validate_destination_path {
+    my $self = shift;
+    my $value = shift;
+    $self->{'write_to'} = $value;
+    unless ($self->{'write_to'}) {
+        return  $self->validation_error( destination_path => "No destination path set. Where should I write this file?");
+    }
+    if (-f $self->{'write_to'} and not -w $self->{'write_to'}) {
+        return  $self->validation_error( destination_path => "Can't save the file to ".$self->{'write_to'});
+    }
+    return $self->validation_ok( 'write_to' );
+}
+
+
+=head2 take_action
+
+Writes the C<content> out to the C<destination_path>.
+
+=cut
+
+sub take_action {
+    my $self = shift;
+    my $dest  = $self->{'write_to'};
+
+
+    # discard filename. we only want to make the directory ;)
+    Jifty::Util->make_path( File::Basename::dirname( $dest ) );
+    my $writehandle = IO::File->new();
+
+    my $content = $self->argument_value('content');
+    $content =~ s/\r\n/\n/g;
+
+    $writehandle->open(">$dest") || die "Couldn't open $dest for writing: ".$!;
+    $writehandle->print( $content ) || die " Couldn't write to $dest: ".$!;
+    $writehandle->close() || die "Couldn't close filehandle $dest ".$!;
+    $self->result->message("Updated $dest");
+}
+
+
+
+1;

Added: jifty/branches/schema-plugins/plugins/EditInPlace/lib/Jifty/Plugin/EditInPlace/Dispatcher.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/EditInPlace/lib/Jifty/Plugin/EditInPlace/Dispatcher.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,28 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::EditInPlace::Dispatcher;
+use Jifty::Dispatcher -base;
+
+before qr'^/__jifty/(edit|create)(_inline/|)/(.*?)/(.*)$', run {
+    # Claim this as ours -- skip ACLs, etc
+    last_rule;
+};
+
+on qr'^/__jifty/(edit|create)(_inline|)/(.*?)/(.*)$', run {
+    my $editor = Jifty->web->new_action(
+        class     => 'Jifty::Plugin::EditInPlace::Action::FileEditor',
+        moniker   => 'editpage',
+        arguments => {
+            source_path => $4,
+            file_type   => $3,
+        }
+    );
+
+    set editor => $editor; 
+    set path => $4;
+    show "/__jifty/$1_file$2";
+};
+
+
+1;

Added: jifty/branches/schema-plugins/plugins/EditInPlace/share/web/templates/__jifty/create_file_inline
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/EditInPlace/share/web/templates/__jifty/create_file_inline	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,12 @@
+<%attr>
+edit_inline => 0
+</%attr>
+<%args>
+$path => undef
+$line => undef
+</%args>
+<% Jifty->web->link(label => _("Create $path"), 
+                    onclick => { 
+                     replace_with => '/__jifty/edit_inline/mason_component/'.$path,
+                     },
+                     )%>

Added: jifty/branches/schema-plugins/plugins/EditInPlace/share/web/templates/__jifty/edit_file
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/EditInPlace/share/web/templates/__jifty/edit_file	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,15 @@
+<%args>
+$path => undef
+$editor => undef
+$line => undef
+</%args>
+<%init>
+my $title = _("Editing file %1",$editor->argument_value('source_path'));
+</%init>
+<&|/_elements/wrapper, title => $title &>
+<% Jifty->web->form->start %>
+<%$editor->form_field('destination_path')%>
+<%$editor->form_field('content')%>
+<% Jifty->web->return(label => 'Save and return', submit => $editor )%>
+<% Jifty->web->form->end %>
+</&>

Added: jifty/branches/schema-plugins/plugins/EditInPlace/share/web/templates/__jifty/edit_file_inline
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/EditInPlace/share/web/templates/__jifty/edit_file_inline	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,14 @@
+<%args>
+$path => undef
+$editor => undef
+$line => undef
+</%args>
+<% Jifty->web->form->start %>
+<%$editor->form_field('destination_path', render_as => 'hidden')%>
+<%$editor->form_field('content')->render_widget%>
+<% Jifty->web->form->submit(label => 'Save',         onclick => { submit       => $editor,
+                     replace_with => $path,
+                     },
+                     
+                     submit => $editor )%>
+<% Jifty->web->form->end %>

Added: jifty/branches/schema-plugins/plugins/EmailErrors/Makefile.PL
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/EmailErrors/Makefile.PL	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,9 @@
+use inc::Module::Install;
+name('Jifty-Plugin-EmailErrors');
+version('0.01');
+requires('Jifty' => '0.60507');
+&auto_install();
+
+install_share;
+
+WriteAll;

Added: jifty/branches/schema-plugins/plugins/EmailErrors/doc/site_config.yml
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/EmailErrors/doc/site_config.yml	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,7 @@
+---
+framework:
+  Plugins:
+   - EmailErrors:
+       to: email-address at example.com
+       from: other-email at example.com
+       subject: Defaults to 'Jifty error'

Added: jifty/branches/schema-plugins/plugins/EmailErrors/lib/Jifty/Plugin/EmailErrors.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/EmailErrors/lib/Jifty/Plugin/EmailErrors.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,18 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::EmailErrors;
+use base qw/Jifty::Plugin/;
+
+# Your plugin goes here.  If takes any configuration or arguments, you
+# probably want to override L<Jifty::Plugin/init>.
+
+sub init {
+    my $self = shift;
+    my %args = @_;
+    $Jifty::Plugin::EmailErrors::Notification::EmailError::TO = $args{to}     || 'nobody at localhost';
+    $Jifty::Plugin::EmailErrors::Notification::EmailError::FROM = $args{from} || 'nobody at localhost';
+    $Jifty::Plugin::EmailErrors::Notification::EmailError::SUBJECT = $args{subject} || 'Jifty error';
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/EmailErrors/lib/Jifty/Plugin/EmailErrors/Dispatcher.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/EmailErrors/lib/Jifty/Plugin/EmailErrors/Dispatcher.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,13 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::EmailErrors::Dispatcher;
+use Jifty::Dispatcher -base;
+
+after '/__jifty/error/mason_internal_error', run {
+    return if already_run;
+    return unless Jifty->web->request->continuation;
+    Jifty::Plugin::EmailErrors::Notification::EmailError->new->send;
+};
+
+1;

Added: jifty/branches/schema-plugins/plugins/EmailErrors/lib/Jifty/Plugin/EmailErrors/Notification/EmailError.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/EmailErrors/lib/Jifty/Plugin/EmailErrors/Notification/EmailError.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,48 @@
+use warnings;
+use strict;
+
+package Jifty::Plugin::EmailErrors::Notification::EmailError;
+use base qw/Jifty::Notification/;
+
+sub setup {
+    my $self = shift;
+    
+    my $cont = Jifty->web->request->continuation;
+    my $e    = $cont->response->error;
+    my $msg  = $e->message;
+    $msg =~ s/, <\S+> (line|chunk) \d+\././;
+
+    my $info  = $e->analyze_error;
+    my $file  = $info->{file};
+    my @lines = @{ $info->{lines} };
+    my @stack = @{ $info->{frames} };
+
+    $self->to( $Jifty::Plugin::EmailErrors::Notification::EmailError::TO );
+    $self->from( $Jifty::Plugin::EmailErrors::Notification::EmailError::FROM );
+    $self->subject( $Jifty::Plugin::EmailErrors::Notification::EmailError::SUBJECT );
+
+    my $body;
+    $body = "Error in $file, line @lines\n$msg\n";
+    for my $frame (@stack) {
+        next if $frame->filename =~ m{/HTML/Mason/};
+        $body .= "  ".$frame->filename.", line ".$frame->line."\n";
+    }
+
+    $body .= "\n\n";
+    $body .= $self->get_environment;
+    
+    $self->body($body);
+}
+
+sub get_environment {
+    my $self = shift;
+    my $message = '';
+
+    $message = "Environment:\n\n";
+    $message   .= " $_: $ENV{$_}\n"
+      for sort grep {/^(HTTP|REMOTE|REQUEST)_/} keys %ENV;
+
+    return $message;
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/EmailErrors/share/web/templates/.file
==============================================================================

Added: jifty/branches/schema-plugins/plugins/LetMe/Makefile.PL
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/LetMe/Makefile.PL	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,8 @@
+use inc::Module::Install;
+name('Jifty-Plugin-LetMe');
+version('0.01');
+requires('Jifty' => '0.60615');
+
+install_share;
+
+WriteAll;

Added: jifty/branches/schema-plugins/plugins/LetMe/lib/Jifty/Plugin/LetMe.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/LetMe/lib/Jifty/Plugin/LetMe.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,51 @@
+use strict;
+use warnings;
+
+=head1 NAME
+
+Jifty::Plugin::LetMe
+
+=cut
+
+package Jifty::Plugin::LetMe;
+use base qw/Jifty::Plugin/;
+
+=head1 DESCRIPTION
+
+C<Jifty::Plugin::LetMe> provides a simple way to enable URLs generated
+by L<Jifty::LetMe/as_url>.
+
+When a user follows a URL created by
+L<Jifty::LetMe::as_url|Jifty::LetMe/as_url>, C<Jifty::Plugin::LetMe>
+will check if the URL is valid, and, if so, set request arguments for
+each of C<$letme->args>, as well as setting the request argument
+C<let_me> to the decoded LetMe itself. It will then show the Mason
+component C<< '/let/' . $letme->path >>.
+
+By default, we disable all application actions
+(C<I<AppName>::Action::*>) on LetMe URLs. To disable this behavior,
+pass the argument C<DisableActions: 0> to the plugin in your
+C<config.yml>. It's probably a better idea, however, to only enable
+specific actions in your own dispatcher, e.g.:
+
+    after plugin 'Jifty::Plugin::LetMe' =>
+    before qr'^/let' => run {
+        my $let_me = get 'let_me';
+        Jifty->api->allow('ConfirmEmail') if $let_me->path eq 'confirm';
+    };
+
+If a user tried to access a path under C<Jifty::LetMe->base_path> with
+an invalid LetMe, we redirect them to '/error/let_me/invalid_token'.
+
+=cut
+
+our $DISABLE_ACTIONS = 1;
+
+sub init {
+    my $self = shift;
+    my %args = (DisableActions => 1,
+                @_);
+    $DISABLE_ACTIONS = $args{DisableActions};
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/LetMe/lib/Jifty/Plugin/LetMe/Dispatcher.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/LetMe/lib/Jifty/Plugin/LetMe/Dispatcher.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,30 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::LetMe::Dispatcher;
+use Jifty::Dispatcher -base;
+
+my $base_path = Jifty::LetMe->base_path;
+my $letme_path = qr/^\Q$base_path\E/;
+
+before qr{$letme_path(.*)$} => run {
+    my $app = Jifty->config->framework('ApplicationClass');
+    Jifty->api->deny(qr/^\Q$app\E::Action/) if $Jifty::Plugin::LetMe::DISABLE_ACTIONS;
+
+    my $let_me = Jifty::LetMe->new();
+    $let_me->from_token($1);
+    redirect '/error/let_me/invalid_token' unless $let_me->validate;
+
+    Jifty->web->temporary_current_user($let_me->validated_current_user);
+
+    my %args = %{$let_me->args};
+    set $_ => $args{$_} for keys %args;
+    set let_me => $let_me;
+};
+
+on $letme_path => run {
+    my $let_me = get 'let_me';
+    show '/let/' . $let_me->path;
+};
+
+1;

Added: jifty/branches/schema-plugins/plugins/Login/MANIFEST
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/MANIFEST	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,33 @@
+inc/Module/Install.pm
+inc/Module/Install/Base.pm
+inc/Module/Install/Can.pm
+inc/Module/Install/Fetch.pm
+inc/Module/Install/Makefile.pm
+inc/Module/Install/Metadata.pm
+inc/Module/Install/Share.pm
+inc/Module/Install/Win32.pm
+inc/Module/Install/WriteAll.pm
+lib/Jifty/Plugin/Login.pm
+lib/Jifty/Plugin/Login/Action/ConfirmEmail.pm
+lib/Jifty/Plugin/Login/Action/Login.pm
+lib/Jifty/Plugin/Login/Action/Logout.pm
+lib/Jifty/Plugin/Login/Action/RecoverPassword.pm
+lib/Jifty/Plugin/Login/Action/ResetLostPassword.pm
+lib/Jifty/Plugin/Login/Action/SendAccountConfirmation.pm
+lib/Jifty/Plugin/Login/Action/SendPasswordReminder.pm
+lib/Jifty/Plugin/Login/Action/Signup.pm
+lib/Jifty/Plugin/Login/CurrentUser.pm
+lib/Jifty/Plugin/Login/Dispatcher.pm
+lib/Jifty/Plugin/Login/Model/User.pm
+lib/Jifty/Plugin/Login/Notification/ConfirmAddress.pm
+lib/Jifty/Plugin/Login/Notification/ConfirmLostPassword.pm
+Makefile.PL
+MANIFEST			This list of files
+META.yml
+share/web/templates/let/confirm_email
+share/web/templates/let/reset_lost_password
+share/web/templates/chgpasswd
+share/web/templates/login
+share/web/templates/logout
+share/web/templates/passwordreminder
+share/web/templates/signup

Added: jifty/branches/schema-plugins/plugins/Login/META.yml
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/META.yml	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,13 @@
+build_requires: 
+  ExtUtils::MakeMaker: 6.11
+distribution_type: module
+generated_by: Module::Install version 0.63
+license: Perl
+name: Jifty-Plugin-Login
+no_index: 
+  directory: 
+    - inc
+    - t
+requires: 
+  Jifty: 0.60507
+version: 0.01

Added: jifty/branches/schema-plugins/plugins/Login/Makefile.PL
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/Makefile.PL	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,9 @@
+use inc::Module::Install;
+name('Jifty-Plugin-Login');
+version('0.01');
+license('Perl');
+requires('Jifty' => '0.60507');
+
+install_share;
+
+WriteAll;

Added: jifty/branches/schema-plugins/plugins/Login/debian/changelog
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/debian/changelog	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,18 @@
+libjifty-plugin-login-perl (0.01-3) unstable; urgency=low
+
+  * Add dependencies, update fr po
+
+ -- AGOSTINI Yves <agostini at univ-metz.fr>  Wed, 25 Oct 2006 10:46:46 +0200
+
+libjifty-plugin-login-perl (0.01-2) unstable; urgency=low
+
+  * add fr po
+
+ -- AGOSTINI Yves <agostini at univ-metz.fr>  Mon, 23 Oct 2006 15:59:14 +0200
+
+libjifty-plugin-login-perl (0.01-1) unstable; urgency=low
+
+  * Initial Release.
+
+ -- AGOSTINI Yves <agostini at univ-metz.fr>  Mon, 23 Oct 2006 11:21:36 +0200
+

Added: jifty/branches/schema-plugins/plugins/Login/debian/compat
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/debian/compat	Mon Jan 29 21:05:02 2007
@@ -0,0 +1 @@
+4

Added: jifty/branches/schema-plugins/plugins/Login/debian/control
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/debian/control	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,20 @@
+Source: libjifty-plugin-login-perl
+Section: perl
+Priority: optional
+Build-Depends: debhelper (>= 4.0.2)
+Build-Depends-Indep: perl (>= 5.8.0-7)
+Maintainer: AGOSTINI Yves <agostini at univ-metz.fr>
+Standards-Version: 3.6.1
+
+Package: libjifty-plugin-login-perl
+Architecture: all
+Depends: ${perl:Depends}, ${misc:Depends}, libemail-address-perl, 
+ libemail-messageid-perl, libemail-mime-modifier-perl,
+ libemail-mime-encodings-perl,  libemail-mime-perl, libemail-simple-perl, 
+ libemail-simple-creator-perl, libreturn-value-perl, libtime-piece-perl, 
+ libemail-date-perl
+Recommends: libio-all-perl, libnet-perl
+Description:  Jifty::Plugin::Login
+ .
+ .
+ Localized Login plugin for Jifty Framework.

Added: jifty/branches/schema-plugins/plugins/Login/debian/rules
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/debian/rules	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,83 @@
+#!/usr/bin/make -f
+# This debian/rules file is provided as a template for normal perl
+# packages. It was created by Marc Brockschmidt <marc at dch-faq.de> for
+# the Debian Perl Group (http://pkg-perl.alioth.debian.org/) but may
+# be used freely wherever it is useful.
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+# If set to a true value then MakeMaker's prompt function will
+# always return the default without waiting for user input.
+export PERL_MM_USE_DEFAULT=1
+
+PACKAGE=$(shell dh_listpackages)
+
+ifndef PERL
+PERL = /usr/bin/perl
+endif
+
+TMP     =$(CURDIR)/debian/$(PACKAGE)
+
+build: build-stamp
+build-stamp:
+	dh_testdir
+
+	# Add commands to compile the package here
+	$(PERL) Makefile.PL INSTALLDIRS=vendor
+	$(MAKE) OPTIMIZE="-Wall -O2 -g"
+
+	touch build-stamp
+
+clean:
+	dh_testdir
+	dh_testroot
+
+	# Add commands to clean up after the build process here
+	-$(MAKE) distclean
+
+	dh_clean build-stamp install-stamp
+
+install: build install-stamp
+install-stamp:
+	dh_testdir
+	dh_testroot
+	dh_clean -k
+
+	# Add commands to install the package into debian/$PACKAGE_NAME here
+	$(MAKE) test
+	$(MAKE) install DESTDIR=$(TMP) PREFIX=/usr
+
+	# As this is a architecture independent package, we are not
+	# supposed to install stuff to /usr/lib. MakeMaker creates
+	# the dirs, we delete them from the deb:
+	rmdir --ignore-fail-on-non-empty --parents $(TMP)/usr/lib/perl5
+
+	touch install-stamp
+
+binary-arch:
+# We have nothing to do by default.
+
+binary-indep: build install
+	dh_testdir
+	dh_testroot
+#	dh_installcron
+#	dh_installmenu
+#	dh_installexamples
+	dh_installdocs 
+	dh_installchangelogs 
+	dh_perl
+	dh_link
+	dh_strip
+	dh_compress
+	dh_fixperms
+	dh_installdeb
+	dh_gencontrol
+	dh_md5sums
+	dh_builddeb
+
+source diff:                                                                  
+	@echo >&2 'source and diff are obsolete - use dpkg-source -b'; false
+
+binary: binary-indep binary-arch
+.PHONY: build clean binary-indep binary-arch binary

Added: jifty/branches/schema-plugins/plugins/Login/inc/Module/Install.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/inc/Module/Install.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,281 @@
+#line 1
+package Module::Install;
+
+# For any maintainers:
+# The load order for Module::Install is a bit magic.
+# It goes something like this...
+#
+# IF ( host has Module::Install installed, creating author mode ) {
+#     1. Makefile.PL calls "use inc::Module::Install"
+#     2. $INC{inc/Module/Install.pm} set to installed version of inc::Module::Install
+#     3. The installed version of inc::Module::Install loads
+#     4. inc::Module::Install calls "require Module::Install"
+#     5. The ./inc/ version of Module::Install loads
+# } ELSE {
+#     1. Makefile.PL calls "use inc::Module::Install"
+#     2. $INC{inc/Module/Install.pm} set to ./inc/ version of Module::Install
+#     3. The ./inc/ version of Module::Install loads
+# }
+
+use 5.004;
+use strict 'vars';
+
+use vars qw{$VERSION};
+BEGIN {
+    # All Module::Install core packages now require synchronised versions.
+    # This will be used to ensure we don't accidentally load old or
+    # different versions of modules.
+    # This is not enforced yet, but will be some time in the next few
+    # releases once we can make sure it won't clash with custom
+    # Module::Install extensions.
+    $VERSION = '0.63';
+}
+
+# Whether or not inc::Module::Install is actually loaded, the
+# $INC{inc/Module/Install.pm} is what will still get set as long as
+# the caller loaded module this in the documented manner.
+# If not set, the caller may NOT have loaded the bundled version, and thus
+# they may not have a MI version that works with the Makefile.PL. This would
+# result in false errors or unexpected behaviour. And we don't want that.
+my $file = join( '/', 'inc', split /::/, __PACKAGE__ ) . '.pm';
+unless ( $INC{$file} ) {
+    die <<"END_DIE";
+Please invoke ${\__PACKAGE__} with:
+
+    use inc::${\__PACKAGE__};
+
+not:
+
+    use ${\__PACKAGE__};
+
+END_DIE
+}
+
+# If the script that is loading Module::Install is from the future,
+# then make will detect this and cause it to re-run over and over
+# again. This is bad. Rather than taking action to touch it (which
+# is unreliable on some platforms and requires write permissions)
+# for now we should catch this and refuse to run.
+if ( -f $0 and (stat($0))[9] > time ) {
+	die << "END_DIE";
+Your installer $0 has a modification time in the future.
+
+This is known to create infinite loops in make.
+
+Please correct this, then run $0 again.
+
+END_DIE
+}
+
+use Cwd        ();
+use File::Find ();
+use File::Path ();
+use FindBin;
+
+*inc::Module::Install::VERSION = *VERSION;
+ at inc::Module::Install::ISA     = __PACKAGE__;
+
+sub autoload {
+    my $self = shift;
+    my $who  = $self->_caller;
+    my $cwd  = Cwd::cwd();
+    my $sym  = "${who}::AUTOLOAD";
+    $sym->{$cwd} = sub {
+        my $pwd = Cwd::cwd();
+        if ( my $code = $sym->{$pwd} ) {
+            # delegate back to parent dirs
+            goto &$code unless $cwd eq $pwd;
+        }
+        $$sym =~ /([^:]+)$/ or die "Cannot autoload $who - $sym";
+        unshift @_, ($self, $1);
+        goto &{$self->can('call')} unless uc($1) eq $1;
+    };
+}
+
+sub import {
+    my $class = shift;
+    my $self  = $class->new(@_);
+    my $who   = $self->_caller;
+
+    unless ( -f $self->{file} ) {
+        require "$self->{path}/$self->{dispatch}.pm";
+        File::Path::mkpath("$self->{prefix}/$self->{author}");
+        $self->{admin} = "$self->{name}::$self->{dispatch}"->new( _top => $self );
+        $self->{admin}->init;
+        @_ = ($class, _self => $self);
+        goto &{"$self->{name}::import"};
+    }
+
+    *{"${who}::AUTOLOAD"} = $self->autoload;
+    $self->preload;
+
+    # Unregister loader and worker packages so subdirs can use them again
+    delete $INC{"$self->{file}"};
+    delete $INC{"$self->{path}.pm"};
+}
+
+sub preload {
+    my ($self) = @_;
+
+    unless ( $self->{extensions} ) {
+        $self->load_extensions(
+            "$self->{prefix}/$self->{path}", $self
+        );
+    }
+
+    my @exts = @{$self->{extensions}};
+    unless ( @exts ) {
+        my $admin = $self->{admin};
+        @exts = $admin->load_all_extensions;
+    }
+
+    my %seen;
+    foreach my $obj ( @exts ) {
+        while (my ($method, $glob) = each %{ref($obj) . '::'}) {
+            next unless $obj->can($method);
+            next if $method =~ /^_/;
+            next if $method eq uc($method);
+            $seen{$method}++;
+        }
+    }
+
+    my $who = $self->_caller;
+    foreach my $name ( sort keys %seen ) {
+        *{"${who}::$name"} = sub {
+            ${"${who}::AUTOLOAD"} = "${who}::$name";
+            goto &{"${who}::AUTOLOAD"};
+        };
+    }
+}
+
+sub new {
+    my ($class, %args) = @_;
+
+    # ignore the prefix on extension modules built from top level.
+    my $base_path = Cwd::abs_path($FindBin::Bin);
+    unless ( Cwd::abs_path(Cwd::cwd()) eq $base_path ) {
+        delete $args{prefix};
+    }
+
+    return $args{_self} if $args{_self};
+
+    $args{dispatch} ||= 'Admin';
+    $args{prefix}   ||= 'inc';
+    $args{author}   ||= ($^O eq 'VMS' ? '_author' : '.author');
+    $args{bundle}   ||= 'inc/BUNDLES';
+    $args{base}     ||= $base_path;
+    $class =~ s/^\Q$args{prefix}\E:://;
+    $args{name}     ||= $class;
+    $args{version}  ||= $class->VERSION;
+    unless ( $args{path} ) {
+        $args{path}  = $args{name};
+        $args{path}  =~ s!::!/!g;
+    }
+    $args{file}     ||= "$args{base}/$args{prefix}/$args{path}.pm";
+
+    bless( \%args, $class );
+}
+
+sub call {
+	my ($self, $method) = @_;
+	my $obj = $self->load($method) or return;
+        splice(@_, 0, 2, $obj);
+	goto &{$obj->can($method)};
+}
+
+sub load {
+    my ($self, $method) = @_;
+
+    $self->load_extensions(
+        "$self->{prefix}/$self->{path}", $self
+    ) unless $self->{extensions};
+
+    foreach my $obj (@{$self->{extensions}}) {
+        return $obj if $obj->can($method);
+    }
+
+    my $admin = $self->{admin} or die <<"END_DIE";
+The '$method' method does not exist in the '$self->{prefix}' path!
+Please remove the '$self->{prefix}' directory and run $0 again to load it.
+END_DIE
+
+    my $obj = $admin->load($method, 1);
+    push @{$self->{extensions}}, $obj;
+
+    $obj;
+}
+
+sub load_extensions {
+    my ($self, $path, $top) = @_;
+
+    unless ( grep { lc $_ eq lc $self->{prefix} } @INC ) {
+        unshift @INC, $self->{prefix};
+    }
+
+    foreach my $rv ( $self->find_extensions($path) ) {
+        my ($file, $pkg) = @{$rv};
+        next if $self->{pathnames}{$pkg};
+
+        local $@;
+        my $new = eval { require $file; $pkg->can('new') };
+        unless ( $new ) {
+            warn $@ if $@;
+            next;
+        }
+        $self->{pathnames}{$pkg} = delete $INC{$file};
+        push @{$self->{extensions}}, &{$new}($pkg, _top => $top );
+    }
+
+    $self->{extensions} ||= [];
+}
+
+sub find_extensions {
+    my ($self, $path) = @_;
+
+    my @found;
+    File::Find::find( sub {
+        my $file = $File::Find::name;
+        return unless $file =~ m!^\Q$path\E/(.+)\.pm\Z!is;
+        my $subpath = $1;
+        return if lc($subpath) eq lc($self->{dispatch});
+
+        $file = "$self->{path}/$subpath.pm";
+        my $pkg = "$self->{name}::$subpath";
+        $pkg =~ s!/!::!g;
+
+        # If we have a mixed-case package name, assume case has been preserved
+        # correctly.  Otherwise, root through the file to locate the case-preserved
+        # version of the package name.
+        if ( $subpath eq lc($subpath) || $subpath eq uc($subpath) ) {
+            open PKGFILE, "<$subpath.pm" or die "find_extensions: Can't open $subpath.pm: $!";
+            my $in_pod = 0;
+            while ( <PKGFILE> ) {
+                $in_pod = 1 if /^=\w/;
+                $in_pod = 0 if /^=cut/;
+                next if ($in_pod || /^=cut/);  # skip pod text
+                next if /^\s*#/;               # and comments
+                if ( m/^\s*package\s+($pkg)\s*;/i ) {
+                    $pkg = $1;
+                    last;
+                }
+            }
+            close PKGFILE;
+        }
+
+        push @found, [ $file, $pkg ];
+    }, $path ) if -d $path;
+
+    @found;
+}
+
+sub _caller {
+    my $depth = 0;
+    my $call  = caller($depth);
+    while ( $call eq __PACKAGE__ ) {
+        $depth++;
+        $call = caller($depth);
+    }
+    return $call;
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/Login/inc/Module/Install/Base.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/inc/Module/Install/Base.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,70 @@
+#line 1
+package Module::Install::Base;
+
+$VERSION = '0.63';
+
+# Suspend handler for "redefined" warnings
+BEGIN {
+	my $w = $SIG{__WARN__};
+	$SIG{__WARN__} = sub { $w };
+}
+
+### This is the ONLY module that shouldn't have strict on
+# use strict;
+
+#line 41
+
+sub new {
+    my ($class, %args) = @_;
+
+    foreach my $method ( qw(call load) ) {
+        *{"$class\::$method"} = sub {
+            shift()->_top->$method(@_);
+        } unless defined &{"$class\::$method"};
+    }
+
+    bless( \%args, $class );
+}
+
+#line 61
+
+sub AUTOLOAD {
+    my $self = shift;
+    local $@;
+    my $autoload = eval { $self->_top->autoload } or return;
+    goto &$autoload;
+}
+
+#line 76
+
+sub _top { $_[0]->{_top} }
+
+#line 89
+
+sub admin {
+    $_[0]->_top->{admin} or Module::Install::Base::FakeAdmin->new;
+}
+
+sub is_admin {
+    $_[0]->admin->VERSION;
+}
+
+sub DESTROY {}
+
+package Module::Install::Base::FakeAdmin;
+
+my $Fake;
+sub new { $Fake ||= bless(\@_, $_[0]) }
+
+sub AUTOLOAD {}
+
+sub DESTROY {}
+
+# Restore warning handler
+BEGIN {
+	$SIG{__WARN__} = $SIG{__WARN__}->();
+}
+
+1;
+
+#line 138

Added: jifty/branches/schema-plugins/plugins/Login/inc/Module/Install/Can.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/inc/Module/Install/Can.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,82 @@
+#line 1
+package Module::Install::Can;
+
+use strict;
+use Module::Install::Base;
+use Config ();
+### This adds a 5.005 Perl version dependency.
+### This is a bug and will be fixed.
+use File::Spec ();
+use ExtUtils::MakeMaker ();
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+	$VERSION = '0.63';
+	$ISCORE  = 1;
+	@ISA     = qw{Module::Install::Base};
+}
+
+# check if we can load some module
+### Upgrade this to not have to load the module if possible
+sub can_use {
+	my ($self, $mod, $ver) = @_;
+	$mod =~ s{::|\\}{/}g;
+	$mod .= '.pm' unless $mod =~ /\.pm$/i;
+
+	my $pkg = $mod;
+	$pkg =~ s{/}{::}g;
+	$pkg =~ s{\.pm$}{}i;
+
+	local $@;
+	eval { require $mod; $pkg->VERSION($ver || 0); 1 };
+}
+
+# check if we can run some command
+sub can_run {
+	my ($self, $cmd) = @_;
+
+	my $_cmd = $cmd;
+	return $_cmd if (-x $_cmd or $_cmd = MM->maybe_command($_cmd));
+
+	for my $dir ((split /$Config::Config{path_sep}/, $ENV{PATH}), '.') {
+		my $abs = File::Spec->catfile($dir, $_[1]);
+		return $abs if (-x $abs or $abs = MM->maybe_command($abs));
+	}
+
+	return;
+}
+
+# can we locate a (the) C compiler
+sub can_cc {
+	my $self   = shift;
+	my @chunks = split(/ /, $Config::Config{cc}) or return;
+
+	# $Config{cc} may contain args; try to find out the program part
+	while (@chunks) {
+		return $self->can_run("@chunks") || (pop(@chunks), next);
+	}
+
+	return;
+}
+
+# Fix Cygwin bug on maybe_command();
+if ( $^O eq 'cygwin' ) {
+	require ExtUtils::MM_Cygwin;
+	require ExtUtils::MM_Win32;
+	if ( ! defined(&ExtUtils::MM_Cygwin::maybe_command) ) {
+		*ExtUtils::MM_Cygwin::maybe_command = sub {
+			my ($self, $file) = @_;
+			if ($file =~ m{^/cygdrive/}i and ExtUtils::MM_Win32->can('maybe_command')) {
+				ExtUtils::MM_Win32->maybe_command($file);
+			} else {
+				ExtUtils::MM_Unix->maybe_command($file);
+			}
+		}
+	}
+}
+
+1;
+
+__END__
+
+#line 157

Added: jifty/branches/schema-plugins/plugins/Login/inc/Module/Install/Fetch.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/inc/Module/Install/Fetch.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,93 @@
+#line 1
+package Module::Install::Fetch;
+
+use strict;
+use Module::Install::Base;
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+	$VERSION = '0.63';
+	$ISCORE  = 1;
+	@ISA     = qw{Module::Install::Base};
+}
+
+sub get_file {
+    my ($self, %args) = @_;
+    my ($scheme, $host, $path, $file) = 
+        $args{url} =~ m|^(\w+)://([^/]+)(.+)/(.+)| or return;
+
+    if ( $scheme eq 'http' and ! eval { require LWP::Simple; 1 } ) {
+        $args{url} = $args{ftp_url}
+            or (warn("LWP support unavailable!\n"), return);
+        ($scheme, $host, $path, $file) = 
+            $args{url} =~ m|^(\w+)://([^/]+)(.+)/(.+)| or return;
+    }
+
+    $|++;
+    print "Fetching '$file' from $host... ";
+
+    unless (eval { require Socket; Socket::inet_aton($host) }) {
+        warn "'$host' resolve failed!\n";
+        return;
+    }
+
+    return unless $scheme eq 'ftp' or $scheme eq 'http';
+
+    require Cwd;
+    my $dir = Cwd::getcwd();
+    chdir $args{local_dir} or return if exists $args{local_dir};
+
+    if (eval { require LWP::Simple; 1 }) {
+        LWP::Simple::mirror($args{url}, $file);
+    }
+    elsif (eval { require Net::FTP; 1 }) { eval {
+        # use Net::FTP to get past firewall
+        my $ftp = Net::FTP->new($host, Passive => 1, Timeout => 600);
+        $ftp->login("anonymous", 'anonymous at example.com');
+        $ftp->cwd($path);
+        $ftp->binary;
+        $ftp->get($file) or (warn("$!\n"), return);
+        $ftp->quit;
+    } }
+    elsif (my $ftp = $self->can_run('ftp')) { eval {
+        # no Net::FTP, fallback to ftp.exe
+        require FileHandle;
+        my $fh = FileHandle->new;
+
+        local $SIG{CHLD} = 'IGNORE';
+        unless ($fh->open("|$ftp -n")) {
+            warn "Couldn't open ftp: $!\n";
+            chdir $dir; return;
+        }
+
+        my @dialog = split(/\n/, <<"END_FTP");
+open $host
+user anonymous anonymous\@example.com
+cd $path
+binary
+get $file $file
+quit
+END_FTP
+        foreach (@dialog) { $fh->print("$_\n") }
+        $fh->close;
+    } }
+    else {
+        warn "No working 'ftp' program available!\n";
+        chdir $dir; return;
+    }
+
+    unless (-f $file) {
+        warn "Fetching failed: $@\n";
+        chdir $dir; return;
+    }
+
+    return if exists $args{size} and -s $file != $args{size};
+    system($args{run}) if exists $args{run};
+    unlink($file) if $args{remove};
+
+    print(((!exists $args{check_for} or -e $args{check_for})
+        ? "done!" : "failed! ($!)"), "\n");
+    chdir $dir; return !$?;
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/Login/inc/Module/Install/Makefile.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/inc/Module/Install/Makefile.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,208 @@
+#line 1
+package Module::Install::Makefile;
+
+use strict 'vars';
+use Module::Install::Base;
+use ExtUtils::MakeMaker ();
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+	$VERSION = '0.63';
+	$ISCORE  = 1;
+	@ISA     = qw{Module::Install::Base};
+}
+
+sub Makefile { $_[0] }
+
+my %seen = ();
+
+sub prompt {
+    shift;
+
+    # Infinite loop protection
+    my @c = caller();
+    if ( ++$seen{"$c[1]|$c[2]|$_[0]"} > 3 ) {
+        die "Caught an potential prompt infinite loop ($c[1]|$c[2]|$_[0])";
+    }
+
+    # In automated testing, always use defaults
+    if ( $ENV{AUTOMATED_TESTING} and ! $ENV{PERL_MM_USE_DEFAULT} ) {
+        local $ENV{PERL_MM_USE_DEFAULT} = 1;
+        goto &ExtUtils::MakeMaker::prompt;
+    } else {
+        goto &ExtUtils::MakeMaker::prompt;
+    }
+}
+
+sub makemaker_args {
+    my $self = shift;
+    my $args = ($self->{makemaker_args} ||= {});
+    %$args = ( %$args, @_ ) if @_;
+    $args;
+}
+
+# For mm args that take multiple space-seperated args,
+# append an argument to the current list.
+sub makemaker_append {
+    my $self = shift;
+    my $name = shift;
+    my $args = $self->makemaker_args;
+    $args->{name} = defined $args->{$name}
+    	? join( ' ', $args->{name}, @_ )
+    	: join( ' ', @_ );
+}
+
+sub build_subdirs {
+    my $self    = shift;
+    my $subdirs = $self->makemaker_args->{DIR} ||= [];
+    for my $subdir (@_) {
+        push @$subdirs, $subdir;
+    }
+}
+
+sub clean_files {
+    my $self  = shift;
+    my $clean = $self->makemaker_args->{clean} ||= {};
+    %$clean = (
+        %$clean, 
+        FILES => join(' ', grep length, $clean->{FILES}, @_),
+    );
+}
+
+sub realclean_files {
+    my $self  = shift;
+    my $realclean = $self->makemaker_args->{realclean} ||= {};
+    %$realclean = (
+        %$realclean, 
+        FILES => join(' ', grep length, $realclean->{FILES}, @_),
+    );
+}
+
+sub libs {
+    my $self = shift;
+    my $libs = ref $_[0] ? shift : [ shift ];
+    $self->makemaker_args( LIBS => $libs );
+}
+
+sub inc {
+    my $self = shift;
+    $self->makemaker_args( INC => shift );
+}
+
+sub write {
+    my $self = shift;
+    die "&Makefile->write() takes no arguments\n" if @_;
+
+    my $args = $self->makemaker_args;
+    $args->{DISTNAME} = $self->name;
+    $args->{NAME}     = $self->module_name || $self->name || $self->determine_NAME($args);
+    $args->{VERSION}  = $self->version || $self->determine_VERSION($args);
+    $args->{NAME}     =~ s/-/::/g;
+    if ( $self->tests ) {
+        $args->{test} = { TESTS => $self->tests };
+    }
+    if ($] >= 5.005) {
+        $args->{ABSTRACT} = $self->abstract;
+        $args->{AUTHOR}   = $self->author;
+    }
+    if ( eval($ExtUtils::MakeMaker::VERSION) >= 6.10 ) {
+        $args->{NO_META} = 1;
+    }
+    if ( eval($ExtUtils::MakeMaker::VERSION) > 6.17 and $self->sign ) {
+        $args->{SIGN} = 1;
+    }
+    unless ( $self->is_admin ) {
+        delete $args->{SIGN};
+    }
+
+    # merge both kinds of requires into prereq_pm
+    my $prereq = ($args->{PREREQ_PM} ||= {});
+    %$prereq = ( %$prereq, map { @$_ } map { @$_ } grep $_,
+                 ($self->build_requires, $self->requires) );
+
+    # merge both kinds of requires into prereq_pm
+    my $subdirs = ($args->{DIR} ||= []);
+    if ($self->bundles) {
+        foreach my $bundle (@{ $self->bundles }) {
+            my ($file, $dir) = @$bundle;
+            push @$subdirs, $dir if -d $dir;
+            delete $prereq->{$file};
+        }
+    }
+
+    if ( my $perl_version = $self->perl_version ) {
+        eval "use $perl_version; 1"
+            or die "ERROR: perl: Version $] is installed, "
+                . "but we need version >= $perl_version";
+    }
+
+    my %args = map { ( $_ => $args->{$_} ) } grep {defined($args->{$_})} keys %$args;
+    if ($self->admin->preop) {
+        $args{dist} = $self->admin->preop;
+    }
+
+    my $mm = ExtUtils::MakeMaker::WriteMakefile(%args);
+    $self->fix_up_makefile($mm->{FIRST_MAKEFILE} || 'Makefile');
+}
+
+sub fix_up_makefile {
+    my $self          = shift;
+    my $makefile_name = shift;
+    my $top_class     = ref($self->_top) || '';
+    my $top_version   = $self->_top->VERSION || '';
+
+    my $preamble = $self->preamble 
+        ? "# Preamble by $top_class $top_version\n"
+            . $self->preamble
+        : '';
+    my $postamble = "# Postamble by $top_class $top_version\n"
+        . ($self->postamble || '');
+
+    local *MAKEFILE;
+    open MAKEFILE, "< $makefile_name" or die "fix_up_makefile: Couldn't open $makefile_name: $!";
+    my $makefile = do { local $/; <MAKEFILE> };
+    close MAKEFILE or die $!;
+
+    $makefile =~ s/\b(test_harness\(\$\(TEST_VERBOSE\), )/$1'inc', /;
+    $makefile =~ s/( -I\$\(INST_ARCHLIB\))/ -Iinc$1/g;
+    $makefile =~ s/( "-I\$\(INST_LIB\)")/ "-Iinc"$1/g;
+    $makefile =~ s/^(FULLPERL = .*)/$1 "-Iinc"/m;
+    $makefile =~ s/^(PERL = .*)/$1 "-Iinc"/m;
+
+    # Module::Install will never be used to build the Core Perl
+    # Sometimes PERL_LIB and PERL_ARCHLIB get written anyway, which breaks
+    # PREFIX/PERL5LIB, and thus, install_share. Blank them if they exist
+    $makefile =~ s/^PERL_LIB = .+/PERL_LIB =/m;
+    #$makefile =~ s/^PERL_ARCHLIB = .+/PERL_ARCHLIB =/m;
+
+    # Perl 5.005 mentions PERL_LIB explicitly, so we have to remove that as well.
+    $makefile =~ s/("?)-I\$\(PERL_LIB\)\1//g;
+
+    # XXX - This is currently unused; not sure if it breaks other MM-users
+    # $makefile =~ s/^pm_to_blib\s+:\s+/pm_to_blib :: /mg;
+
+    open  MAKEFILE, "> $makefile_name" or die "fix_up_makefile: Couldn't open $makefile_name: $!";
+    print MAKEFILE  "$preamble$makefile$postamble" or die $!;
+    close MAKEFILE  or die $!;
+
+    1;
+}
+
+sub preamble {
+    my ($self, $text) = @_;
+    $self->{preamble} = $text . $self->{preamble} if defined $text;
+    $self->{preamble};
+}
+
+sub postamble {
+    my ($self, $text) = @_;
+    $self->{postamble} ||= $self->admin->postamble;
+    $self->{postamble} .= $text if defined $text;
+    $self->{postamble}
+}
+
+1;
+
+__END__
+
+#line 334

Added: jifty/branches/schema-plugins/plugins/Login/inc/Module/Install/Metadata.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/inc/Module/Install/Metadata.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,315 @@
+#line 1
+package Module::Install::Metadata;
+
+use strict 'vars';
+use Module::Install::Base;
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+	$VERSION = '0.63';
+	$ISCORE  = 1;
+	@ISA     = qw{Module::Install::Base};
+}
+
+my @scalar_keys = qw{
+    name module_name abstract author version license
+    distribution_type perl_version tests
+};
+
+my @tuple_keys = qw{
+    build_requires requires recommends bundles
+};
+
+sub Meta            { shift        }
+sub Meta_ScalarKeys { @scalar_keys }
+sub Meta_TupleKeys  { @tuple_keys  }
+
+foreach my $key (@scalar_keys) {
+    *$key = sub {
+        my $self = shift;
+        return $self->{values}{$key} if defined wantarray and !@_;
+        $self->{values}{$key} = shift;
+        return $self;
+    };
+}
+
+foreach my $key (@tuple_keys) {
+    *$key = sub {
+        my $self = shift;
+        return $self->{values}{$key} unless @_;
+
+        my @rv;
+        while (@_) {
+            my $module = shift or last;
+            my $version = shift || 0;
+            if ( $module eq 'perl' ) {
+                $version =~ s{^(\d+)\.(\d+)\.(\d+)}
+                             {$1 + $2/1_000 + $3/1_000_000}e;
+                $self->perl_version($version);
+                next;
+            }
+            my $rv = [ $module, $version ];
+            push @rv, $rv;
+        }
+        push @{ $self->{values}{$key} }, @rv;
+        @rv;
+    };
+}
+
+sub sign {
+    my $self = shift;
+    return $self->{'values'}{'sign'} if defined wantarray and !@_;
+    $self->{'values'}{'sign'} = ( @_ ? $_[0] : 1 );
+    return $self;
+}
+
+sub dynamic_config {
+	my $self = shift;
+	unless ( @_ ) {
+		warn "You MUST provide an explicit true/false value to dynamic_config, skipping\n";
+		return $self;
+	}
+	$self->{'values'}{'dynamic_config'} = $_[0] ? 1 : 0;
+	return $self;
+}
+
+sub all_from {
+    my ( $self, $file ) = @_;
+
+    unless ( defined($file) ) {
+        my $name = $self->name
+            or die "all_from called with no args without setting name() first";
+        $file = join('/', 'lib', split(/-/, $name)) . '.pm';
+        $file =~ s{.*/}{} unless -e $file;
+        die "all_from: cannot find $file from $name" unless -e $file;
+    }
+
+    $self->version_from($file)      unless $self->version;
+    $self->perl_version_from($file) unless $self->perl_version;
+
+    # The remaining probes read from POD sections; if the file
+    # has an accompanying .pod, use that instead
+    my $pod = $file;
+    if ( $pod =~ s/\.pm$/.pod/i and -e $pod ) {
+        $file = $pod;
+    }
+
+    $self->author_from($file)   unless $self->author;
+    $self->license_from($file)  unless $self->license;
+    $self->abstract_from($file) unless $self->abstract;
+}
+
+sub provides {
+    my $self     = shift;
+    my $provides = ( $self->{values}{provides} ||= {} );
+    %$provides = (%$provides, @_) if @_;
+    return $provides;
+}
+
+sub auto_provides {
+    my $self = shift;
+    return $self unless $self->is_admin;
+
+    unless (-e 'MANIFEST') {
+        warn "Cannot deduce auto_provides without a MANIFEST, skipping\n";
+        return $self;
+    }
+
+    # Avoid spurious warnings as we are not checking manifest here.
+
+    local $SIG{__WARN__} = sub {1};
+    require ExtUtils::Manifest;
+    local *ExtUtils::Manifest::manicheck = sub { return };
+
+    require Module::Build;
+    my $build = Module::Build->new(
+        dist_name    => $self->name,
+        dist_version => $self->version,
+        license      => $self->license,
+    );
+    $self->provides(%{ $build->find_dist_packages || {} });
+}
+
+sub feature {
+    my $self     = shift;
+    my $name     = shift;
+    my $features = ( $self->{values}{features} ||= [] );
+
+    my $mods;
+
+    if ( @_ == 1 and ref( $_[0] ) ) {
+        # The user used ->feature like ->features by passing in the second
+        # argument as a reference.  Accomodate for that.
+        $mods = $_[0];
+    } else {
+        $mods = \@_;
+    }
+
+    my $count = 0;
+    push @$features, (
+        $name => [
+            map {
+                ref($_) ? ( ref($_) eq 'HASH' ) ? %$_
+                                                : @$_
+                        : $_
+            } @$mods
+        ]
+    );
+
+    return @$features;
+}
+
+sub features {
+    my $self = shift;
+    while ( my ( $name, $mods ) = splice( @_, 0, 2 ) ) {
+        $self->feature( $name, @$mods );
+    }
+    return $self->{values}->{features}
+    	? @{ $self->{values}->{features} }
+    	: ();
+}
+
+sub no_index {
+    my $self = shift;
+    my $type = shift;
+    push @{ $self->{values}{no_index}{$type} }, @_ if $type;
+    return $self->{values}{no_index};
+}
+
+sub read {
+    my $self = shift;
+    $self->include_deps( 'YAML', 0 );
+
+    require YAML;
+    my $data = YAML::LoadFile('META.yml');
+
+    # Call methods explicitly in case user has already set some values.
+    while ( my ( $key, $value ) = each %$data ) {
+        next unless $self->can($key);
+        if ( ref $value eq 'HASH' ) {
+            while ( my ( $module, $version ) = each %$value ) {
+                $self->can($key)->($self, $module => $version );
+            }
+        }
+        else {
+            $self->can($key)->($self, $value);
+        }
+    }
+    return $self;
+}
+
+sub write {
+    my $self = shift;
+    return $self unless $self->is_admin;
+    $self->admin->write_meta;
+    return $self;
+}
+
+sub version_from {
+    my ( $self, $file ) = @_;
+    require ExtUtils::MM_Unix;
+    $self->version( ExtUtils::MM_Unix->parse_version($file) );
+}
+
+sub abstract_from {
+    my ( $self, $file ) = @_;
+    require ExtUtils::MM_Unix;
+    $self->abstract(
+        bless(
+            { DISTNAME => $self->name },
+            'ExtUtils::MM_Unix'
+        )->parse_abstract($file)
+     );
+}
+
+sub _slurp {
+    my ( $self, $file ) = @_;
+
+    local *FH;
+    open FH, "< $file" or die "Cannot open $file.pod: $!";
+    do { local $/; <FH> };
+}
+
+sub perl_version_from {
+    my ( $self, $file ) = @_;
+
+    if (
+        $self->_slurp($file) =~ m/
+        ^
+        use \s*
+        v?
+        ([\d_\.]+)
+        \s* ;
+    /ixms
+      )
+    {
+        my $v = $1;
+        $v =~ s{_}{}g;
+        $self->perl_version($1);
+    }
+    else {
+        warn "Cannot determine perl version info from $file\n";
+        return;
+    }
+}
+
+sub author_from {
+    my ( $self, $file ) = @_;
+    my $content = $self->_slurp($file);
+    if ($content =~ m/
+        =head \d \s+ (?:authors?)\b \s*
+        ([^\n]*)
+        |
+        =head \d \s+ (?:licen[cs]e|licensing|copyright|legal)\b \s*
+        .*? copyright .*? \d\d\d[\d.]+ \s* (?:\bby\b)? \s*
+        ([^\n]*)
+    /ixms) {
+        my $author = $1 || $2;
+        $author =~ s{E<lt>}{<}g;
+        $author =~ s{E<gt>}{>}g;
+        $self->author($author); 
+    }
+    else {
+        warn "Cannot determine author info from $file\n";
+    }
+}
+
+sub license_from {
+    my ( $self, $file ) = @_;
+
+    if (
+        $self->_slurp($file) =~ m/
+        =head \d \s+
+        (?:licen[cs]e|licensing|copyright|legal)\b
+        (.*?)
+        (=head\\d.*|=cut.*|)
+        \z
+    /ixms
+      )
+    {
+        my $license_text = $1;
+        my @phrases      = (
+            'under the same (?:terms|license) as perl itself' => 'perl',
+            'GNU public license'                              => 'gpl',
+            'GNU lesser public license'                       => 'gpl',
+            'BSD license'                                     => 'bsd',
+            'Artistic license'                                => 'artistic',
+            'GPL'                                             => 'gpl',
+            'LGPL'                                            => 'lgpl',
+            'BSD'                                             => 'bsd',
+            'Artistic'                                        => 'artistic',
+        );
+        while ( my ( $pattern, $license ) = splice( @phrases, 0, 2 ) ) {
+            $pattern =~ s{\s+}{\\s+}g;
+            if ( $license_text =~ /\b$pattern\b/i ) {
+                $self->license($license);
+                return 1;
+            }
+        }
+    }
+
+    warn "Cannot determine license info from $file\n";
+    return 'unknown';
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/Login/inc/Module/Install/Share.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/inc/Module/Install/Share.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,40 @@
+#line 1
+package Module::Install::Share;
+
+use strict;
+use Module::Install::Base;
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+	$VERSION = '0.63';
+	$ISCORE  = 1;
+	@ISA     = qw{Module::Install::Base};
+}
+
+sub install_share {
+	my ($self, $dir) = @_;
+
+	if ( ! defined $dir ) {
+		die "Cannot find the 'share' directory" unless -d 'share';
+		$dir = 'share';
+	}
+
+	$self->postamble(<<"END_MAKEFILE");
+config ::
+\t\$(NOECHO) \$(MOD_INSTALL) \\
+\t\t\"$dir\" \$(INST_AUTODIR)
+
+END_MAKEFILE
+
+	# The above appears to behave incorrectly when used with old versions
+	# of ExtUtils::Install (known-bad on RHEL 3, with 5.8.0)
+	# So when we need to install a share directory, make sure we add a
+	# dependency on a moderately new version of ExtUtils::MakeMaker.
+	$self->build_requires( 'ExtUtils::MakeMaker' => '6.11' );
+}
+
+1;
+
+__END__
+
+#line 98

Added: jifty/branches/schema-plugins/plugins/Login/inc/Module/Install/Win32.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/inc/Module/Install/Win32.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,65 @@
+#line 1
+package Module::Install::Win32;
+
+use strict;
+use Module::Install::Base;
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+	$VERSION = '0.63';
+	$ISCORE  = 1;
+	@ISA     = qw{Module::Install::Base};
+}
+
+# determine if the user needs nmake, and download it if needed
+sub check_nmake {
+	my $self = shift;
+	$self->load('can_run');
+	$self->load('get_file');
+	
+	require Config;
+	return unless (
+		$^O eq 'MSWin32'                     and
+		$Config::Config{make}                and
+		$Config::Config{make} =~ /^nmake\b/i and
+		! $self->can_run('nmake')
+	);
+
+	print "The required 'nmake' executable not found, fetching it...\n";
+
+	require File::Basename;
+	my $rv = $self->get_file(
+		url       => 'http://download.microsoft.com/download/vc15/Patch/1.52/W95/EN-US/Nmake15.exe',
+		ftp_url   => 'ftp://ftp.microsoft.com/Softlib/MSLFILES/Nmake15.exe',
+		local_dir => File::Basename::dirname($^X),
+		size      => 51928,
+		run       => 'Nmake15.exe /o > nul',
+		check_for => 'Nmake.exe',
+		remove    => 1,
+	);
+
+	if (!$rv) {
+        die <<'END_MESSAGE';
+
+-------------------------------------------------------------------------------
+
+Since you are using Microsoft Windows, you will need the 'nmake' utility
+before installation. It's available at:
+
+  http://download.microsoft.com/download/vc15/Patch/1.52/W95/EN-US/Nmake15.exe
+      or
+  ftp://ftp.microsoft.com/Softlib/MSLFILES/Nmake15.exe
+
+Please download the file manually, save it to a directory in %PATH% (e.g.
+C:\WINDOWS\COMMAND\), then launch the MS-DOS command line shell, "cd" to
+that directory, and run "Nmake15.exe" from there; that will create the
+'nmake.exe' file needed by this module.
+
+You may then resume the installation process described in README.
+
+-------------------------------------------------------------------------------
+END_MESSAGE
+	}
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/Login/inc/Module/Install/WriteAll.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/inc/Module/Install/WriteAll.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,43 @@
+#line 1
+package Module::Install::WriteAll;
+
+use strict;
+use Module::Install::Base;
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+	$VERSION = '0.63';
+	$ISCORE  = 1;
+	@ISA     = qw{Module::Install::Base};
+}
+
+sub WriteAll {
+    my $self = shift;
+    my %args = (
+        meta        => 1,
+        sign        => 0,
+        inline      => 0,
+        check_nmake => 1,
+        @_
+    );
+
+    $self->sign(1)                if $args{sign};
+    $self->Meta->write            if $args{meta};
+    $self->admin->WriteAll(%args) if $self->is_admin;
+
+    if ( $0 =~ /Build.PL$/i ) {
+        $self->Build->write;
+    } else {
+        $self->check_nmake if $args{check_nmake};
+        unless ( $self->makemaker_args->{'PL_FILES'} ) {
+        	$self->makemaker_args( PL_FILES => {} );
+        }
+        if ($args{inline}) {
+            $self->Inline->write;
+        } else {
+            $self->Makefile->write;
+        }
+    }
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,59 @@
+use strict;
+use warnings;
+
+=head1 NAME
+
+Jifty::Plugin::Login
+
+=cut
+
+package Jifty::Plugin::Login;
+use base qw/Jifty::Plugin/;
+
+=head1 SYNOPSIS
+
+ in etc/config.yml
+    Plugins:
+      - Login: {}
+
+ in your application Model/User.pm
+   use strict;
+   package YourApp::Model::User;
+   use base qw/Jifty::Plugin::Login::Model::User/;
+   1;
+
+ in your application, you can use
+  http://localhost:8888/login
+                       /logout
+                       /signup
+                       /chgpasswd
+                       /passwordreminder
+
+=head1 DESCRIPTION
+
+=cut
+
+# Your plugin goes here.  If takes any configuration or arguments, you
+# probably want to override L<Jifty::Plugin/init>.
+{
+    my ($CurrentUserClass, $LoginUserClass);
+
+    sub init {
+	my $self = shift;
+	my %args = @_;
+	my $appname = Jifty->config->framework('ApplicationClass');
+	$LoginUserClass = $args{LoginUserClass}
+	    || Jifty->app_class('Model','User');
+	$CurrentUserClass = Jifty->app_class('CurrentUser')
+    }
+
+    sub CurrentUserClass {
+	return $CurrentUserClass;
+    }
+
+    sub LoginUserClass {
+	return $LoginUserClass;
+    }
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Action/ChangePassword.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Action/ChangePassword.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,87 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Jifty::Plugin::Login::Action::ChangePassword - Change a password
+
+=head1 DESCRIPTION
+
+This is the action run by /chgpasswd where logged user can change is password.
+
+
+=cut
+
+package Jifty::Plugin::Login::Action::ChangePassword;
+use base qw/Jifty::Action Jifty::Plugin::Login/;
+
+=head2 arguments
+
+ChangePassword has the following fields: password, and password_confirm.
+
+=cut
+
+sub arguments {
+    return (
+        {
+            password         => {
+                type => 'password',
+                sticky => 0,
+                label  => _('Password')
+                },
+            password_confirm => {
+                type   => 'password',
+                sticky => 0,
+                label  => _('type your password again')
+            },
+        }
+    );
+}
+
+=head2 take_action
+
+Change the password.
+
+=cut
+
+sub take_action {
+    my $self        = shift;
+    my $LoginUserClass = $self->LoginUserClass;
+    my $CurrentUser = $self->CurrentUserClass;
+    my $u = $LoginUserClass->new( current_user => $CurrentUser->superuser );
+    $u->load_by_cols( email => Jifty->web->current_user->user_object->email );
+
+    unless ($u) {
+        $self->result->error(
+_("You don't exist. I'm not sure how this happened. Really, really sorry. Please email us!")
+        );
+    }
+
+    my $pass   = $self->argument_value('password');
+    my $pass_c = $self->argument_value('password_confirm');
+
+    # Trying to set a password (ie, submitted the form)
+    unless (defined $pass
+        and defined $pass_c
+        and length $pass
+        and $pass eq $pass_c )
+    {
+        $self->result->error(
+_("It looks like you didn't enter the same password into both boxes. Give it another shot?")
+        );
+        return;
+    }
+
+    unless ( $u->set_password($pass) ) {
+        $self->result->error(_("There was an error setting your password."));
+        return;
+    }
+
+    # ok!
+    $self->result->message(_("Your password has been changed.  Welcome back."));
+    return 1;
+
+}
+
+1;
+

Added: jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Action/ConfirmEmail.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Action/ConfirmEmail.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,59 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Jifty::Plugin::Login::Action::ConfirmEmail - Confirm a user's email address
+
+=head1 DESCRIPTION
+
+This is the link in a user's email to confirm that their email
+email is really theirs.  It is not really meant to be rendered on any
+web page, but is used by the confirmation notification.
+
+=cut
+
+package Jifty::Plugin::Login::Action::ConfirmEmail;
+use base qw/Jifty::Action Jifty::Plugin::Login/;
+
+=head2 actions
+
+A null sub, because the superclass wants to make sure we fill in actions
+
+=cut
+
+sub actions { }
+
+=head2 take_action
+
+Set their confirmed status.
+
+=cut
+
+sub take_action {
+    my $self        = shift;
+    my $LoginUserClass   = $self->LoginUserClass;
+    my $CurrentUser = $self->CurrentUserClass;
+    my $u = $LoginUserClass->new( current_user => $CurrentUser->superuser );
+    $u->load_by_cols( email => Jifty->web->current_user->user_object->email );
+
+    if ( $u->email_confirmed ) {
+        $self->result->error(
+            email => _("You have already confirmed your account.") );
+        $self->result->success(1);    # but the action is still a success
+    }
+
+    $u->set_email_confirmed('1');
+
+    # Set up our login message
+    $self->result->message( "Welcome to "
+          . Jifty->config->framework('ApplicationName') . ", "
+          . $u->name
+          . _(". Your email address has now been confirmed.") );
+
+    # Actually do the login thing.
+    Jifty->web->current_user( $CurrentUser->new( id => $u->id ) );
+    return 1;
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Action/Login.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Action/Login.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,112 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Jifty::Plugin::Login::Action::Login
+
+=cut
+
+package Jifty::Plugin::Login::Action::Login;
+use base qw/Jifty::Action Jifty::Plugin::Login/;
+
+=head2 arguments
+
+Return the email and password form fields
+
+=cut
+
+sub arguments {
+    return (
+        {
+            email => {
+                label          => _('Email address'),
+                mandatory      => 1,
+                ajax_validates => 1,
+            },
+
+            password => {
+                type      => 'password',
+                label     => _('Password'),
+                mandatory => 1
+            },
+            remember => {
+                type  => 'checkbox',
+                label => _('Remember me?'),
+                hints =>
+                  _('If you want, your browser can remember your login for you'),
+                default => 0,
+            }
+        }
+    );
+
+}
+
+=head2 validate_email ADDRESS
+
+Makes sure that the email submitted is a legal email address and that there's a user in the database with it.
+
+
+=cut
+
+sub validate_email {
+    my $self  = shift;
+    my $email = shift;
+    my $LoginUserClass = $self->LoginUserClass;
+    my $CurrentUser = $self->CurrentUserClass;
+
+    unless ( $email =~ /\S\@\S/ ) {
+        return $self->validation_error(
+            email => _("That doesn't look like an email address.") );
+    }
+
+    my $u = $LoginUserClass->new( current_user => $CurrentUser->superuser );
+    $u->load_by_cols( email => $email );
+    return $self->validation_error(
+        email => _('No account has that email address.') )
+      unless ( $u->id );
+
+    return $self->validation_ok('email');
+}
+
+=head2 take_action
+
+Actually check the user's password. If it's right, log them in.
+Otherwise, throw an error.
+
+
+=cut
+
+sub take_action {
+    my $self = shift;
+    my $CurrentUser = Jifty->app_class('CurrentUser');
+
+    my $user = $CurrentUser->new( email => $self->argument_value('email') );
+
+    unless ( $user->id
+        && $user->password_is( $self->argument_value('password') ) )
+    {
+        $self->result->error(
+ _('You may have mistyped your email address or password. Give it another shot?')
+        );
+        return;
+    }
+
+    unless ( $user->user_object->email_confirmed ) {
+        $self->result->error(_("You haven't confirmed your account yet."));
+        return;
+    }
+
+    # Set up our login message
+    $self->result->message( _("Welcome back, ") . $user->user_object->name . "." );
+
+    # Actually do the signin thing.
+    Jifty->web->current_user($user);
+    Jifty->web->session->expires(
+        $self->argument_value('remember') ? '+1y' : undef );
+    Jifty->web->session->set_cookie;
+
+    return 1;
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Action/Logout.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Action/Logout.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,36 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Jifty::Plugin::Login::Action::Logout
+
+=cut
+
+package Jifty::Plugin::Login::Action::Logout;
+use base qw/Jifty::Action/;
+
+=head2 arguments
+
+Return the email and password form fields
+
+=cut
+
+sub arguments {
+    return ( {} );
+}
+
+=head2 take_action
+
+Nuke the current user object
+
+=cut
+
+sub take_action {
+    my $self = shift;
+    Jifty->web->current_user(undef);
+    $self->result->message( _("Ok, you're logged out now. Have a good day.") );
+    return 1;
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Action/RecoverPassword.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Action/RecoverPassword.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,8 @@
+
+use warnings;
+use strict;
+
+package Jifty::Plugin::Login::Action::RecoverPassword;
+use base qw/Jifty::Action Jifty::Plugin::Login/;
+
+1;

Added: jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Action/ResetLostPassword.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Action/ResetLostPassword.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,89 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Jifty::Plugin::Login::Action::ResetPassword - Confirm and reset a lost password
+
+=head1 DESCRIPTION
+
+This is the action run by the link in a user's email to confirm that their email
+address is really theirs, when claiming that they lost their password.  
+
+
+=cut
+
+package Jifty::Plugin::Login::Action::ResetLostPassword;
+use base qw/Jifty::Action Jifty::Plugin::Login/;
+
+=head2 arguments
+
+ConfirmEmail has the following fields: address, code, password, and password_confirm.
+Note that it can get the first two from the confirm dhandler.
+
+=cut
+
+sub arguments {
+    return (
+        {
+            password         => { 
+                type => 'password', 
+                sticky => 0, 
+                label  => _('Password') 
+            },
+            password_confirm => {
+                type   => 'password',
+                sticky => 0,
+                label  => _('type your password again')
+            },
+        }
+    );
+}
+
+=head2 take_action
+
+Resets the password.
+
+=cut
+
+sub take_action {
+    my $self        = shift;
+    my $LoginUserClass = $self->LoginUserClass;
+    my $CurrentUser = $self->CurrentUserClass;
+    my $u = $LoginUserClass->new( current_user => $CurrentUser->superuser );
+    $u->load_by_cols( email => Jifty->web->current_user->user_object->email );
+
+    unless ($u) {
+        $self->result->error(
+_("You don't exist. I'm not sure how this happened. Really, really sorry. Please email us!")
+        );
+    }
+
+    my $pass   = $self->argument_value('password');
+    my $pass_c = $self->argument_value('password_confirm');
+
+    # Trying to set a password (ie, submitted the form)
+    unless (defined $pass
+        and defined $pass_c
+        and length $pass
+        and $pass eq $pass_c )
+    {
+        $self->result->error(
+_("It looks like you didn't enter the same password into both boxes. Give it another shot?")
+        );
+        return;
+    }
+
+    unless ( $u->set_password($pass) ) {
+        $self->result->error(_("There was an error setting your password."));
+        return;
+    }
+
+    # Log in!
+    $self->result->message(_("Your password has been reset.  Welcome back."));
+    Jifty->web->current_user( $CurrentUser->new( id => $u->id ) );
+    return 1;
+
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Action/SendAccountConfirmation.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Action/SendAccountConfirmation.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,97 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Jifty::Plugin::Login::Action::SendAccountConfirmation
+
+=cut
+
+package Jifty::Plugin::Login::Action::SendAccountConfirmation;
+use base qw/Jifty::Action Jifty::Plugin::Login/;
+
+__PACKAGE__->mk_accessors(qw(user_object));
+
+=head2 arguments
+
+The field for C<ResendConfirmation> is:
+
+=over 4
+
+=item address: the email address
+
+=back
+
+=cut
+
+sub arguments {
+    return (
+        {
+            address => {
+                label         => _('email address'),
+                mandatory     => 1,
+                default_value => "",
+            },
+        }
+    );
+}
+
+=head2 setup
+
+Create an empty user object to work with
+
+=cut
+
+sub setup {
+    my $self = shift;
+    my $LoginUserClass = $self->LoginUserClass;
+    my $CurrentUser = $self->CurrentUserClass;
+
+    $self->user_object(
+        $LoginUserClass->new( current_user => $CurrentUser->superuser ) );
+}
+
+=head2 validate_address
+
+Make sure their email address is an unconfirmed user.
+
+=cut
+
+sub validate_address {
+    my $self  = shift;
+    my $email = shift;
+    my $LoginUserClass = $self->LoginUserClass;
+    my $CurrentUser = $self->CurrentUserClass;
+
+    return $self->validation_error(
+        address => _("That doesn't look like an email address.") )
+      unless ( $email =~ /\S\@\S/ );
+
+    $self->user_object(
+        $LoginUserClass->new( current_user => $CurrentUser->superuser ) );
+    $self->user_object->load_by_cols( email => $email );
+    return $self->validation_error(
+        address => _("It doesn't look like there's an account by that name.") )
+      unless ( $self->user_object->id );
+
+    return $self->validation_error(
+        address => _("It looks like you're already confirmed.") )
+      if ( $self->user_object->email_confirmed );
+
+    return $self->validation_ok('address');
+}
+
+=head2 take_action
+
+Create a new unconfirmed user and send out a confirmation email.
+
+=cut
+
+sub take_action {
+    my $self = shift;
+    Jifty::Plugin::Login::Notification::ConfirmAddress->new(
+        to => $self->user_object )->send;
+    return $self->result->message(_("Confirmation resent."));
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Action/SendPasswordReminder.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Action/SendPasswordReminder.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,98 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Jifty::Plugin::Login::Action::SendPasswordReminder
+
+=cut
+
+package Jifty::Plugin::Login::Action::SendPasswordReminder;
+use base qw/Jifty::Action Jifty::Plugin::Login/;
+
+
+__PACKAGE__->mk_accessors(qw(user_object));
+
+use Jifty::Plugin::Login::Model::User;
+
+=head2 arguments
+
+The field for C<SendLostPasswordReminder> is:
+
+=over 4
+
+=item address: the email address
+
+=back
+
+=cut
+
+sub arguments {
+    return (
+        {
+            address => {
+                label     => _('email address'),
+                mandatory => 1,
+            },
+        }
+    );
+
+}
+
+=head2 setup
+
+Create an empty user object to work with
+
+=cut
+
+sub setup {
+    my $self = shift;
+    my $LoginUserClass = $self->LoginUserClass;
+    my $CurrentUser = $self->CurrentUserClass;
+
+    # Make a blank user object
+    $self->user_object(
+        $LoginUserClass->new( current_user => $CurrentUser->superuser ) );
+}
+
+=head2 validate_address
+
+Make sure there's actually an account by that name.
+
+=cut
+
+sub validate_address {
+    my $self  = shift;
+    my $email = shift;
+    my $LoginUserClass = $self->LoginUserClass;
+    my $CurrentUser = $self->CurrentUserClass;
+
+    return $self->validation_error(
+        address => _("That doesn't look like an email address.") )
+      unless ( $email =~ /\S\@\S/ );
+
+    $self->user_object(
+        $LoginUserClass->new( current_user => $CurrentUser->superuser ) );
+    $self->user_object->load_by_cols( email => $email );
+    return $self->validation_error(
+        address => _("It doesn't look like there's an account by that name.") )
+      unless ( $self->user_object->id );
+
+    return $self->validation_ok('address');
+}
+
+=head2 take_action
+
+Send out a Reminder email giving a link to a password-reset form.
+
+=cut
+
+sub take_action {
+    my $self = shift;
+    Jifty::Plugin::Login::Notification::ConfirmLostPassword->new(
+        to => $self->user_object )->send;
+    return $self->result->message(
+        _("A link to reset your password has been sent to your email account."));
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Action/Signup.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Action/Signup.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,114 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Jifty::Plugin::Login::Action::Signup
+
+=cut
+
+package Jifty::Plugin::Login::Action::Signup;
+use Jifty::Plugin::Login::Action::CreateUser;
+use base qw/Jifty::Plugin::Login::Action::CreateUser Jifty::Plugin::Login/;
+
+=head2 arguments
+
+
+The fields for C<Signup> are:
+
+=over 4
+
+=item email: the email address
+
+=item password and password_confirm: the requested password
+
+=item name: your full name
+
+=back
+
+=cut
+
+sub arguments {
+    my $self = shift;
+    my $args = $self->SUPER::arguments;
+
+    my %fields = (
+        name             => 1,
+        email            => 1,
+        password         => 1,
+        password_confirm => 1,
+    );
+
+    $args->{'email'}{'ajax_validates'}   = 1;
+    $args->{'password_confirm'}{'label'} = _("Type that again?");
+    $args->{'name'}{'label'} = _("Name");
+    return $args;
+}
+
+=head2 validate_email
+
+Make sure their email address looks sane
+
+=cut
+
+sub validate_email {
+    my $self  = shift;
+    my $email = shift;
+    my $LoginUserClass = $self->LoginUserClass;
+    my $CurrentUser = $self->CurrentUserClass;
+
+    return $self->validation_error(
+        email => _("That doesn't look like an email address.") )
+      unless ( $email =~ /\S\@\S/ );
+
+    my $u = $LoginUserClass->new( current_user => $CurrentUser->superuser );
+    $u->load_by_cols( email => $email );
+    if ( $u->id ) {
+        return $self->validation_error( email =>
+_('It looks like you already have an account. Perhaps you want to <a href="/login">sign in</a> instead?')
+        );
+    }
+
+    return $self->validation_ok('email');
+}
+
+=head2 take_action
+
+Overrides the virtual C<take_action> method on L<Jifty::Action> to call
+the appropriate C<Jifty::Record>'s C<create> method when the action is
+run, thus creating a new object in the database.
+
+Makes sure that the user only specifies things we want them to.
+
+=cut
+
+sub take_action {
+    my $self   = shift;
+    my $LoginUserClass = $self->LoginUserClass;
+    my $CurrentUser = $self->CurrentUserClass;
+    my $record = $LoginUserClass->new( current_user => $CurrentUser->superuser );
+
+    my %values;
+    $values{$_} = $self->argument_value($_) for grep {
+        defined $self->record->column($_) and defined $self->argument_value($_)
+    } $self->argument_names;
+
+    my ($id) = $record->create(%values);
+
+    # Handle errors?
+    unless ( $record->id ) {
+        $self->result->error(
+_("Something bad happened and we couldn't create your account.  Try again later. We're really, really sorry.")
+        );
+        return;
+    }
+
+    $self->result->message( _("Welcome to ")
+          . Jifty->config->framework('ApplicationName') . ", "
+          . $record->name
+          . _(". We've sent a confirmation message to your email box.") );
+
+    return 1;
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/CurrentUser.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/CurrentUser.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,39 @@
+use warnings;
+use strict;
+
+
+package Jifty::Plugin::Login::CurrentUser;
+
+use base qw/Jifty::CurrentUser Jifty::Plugin::Login/;
+
+=head2 new PARAMHASH
+
+Instantiate a new current user object, loading the user by paramhash:
+
+   my $item = Jifty::Plugin::Login::Model::Item->new( Jifty::Plugin::Login::CurrentUser->new(email => 'user at site'));
+
+if you give the param 
+    _bootstrap => 1
+
+your object will be marked as a bootstrap user. You can use that to do an endrun around acls.
+
+=cut
+
+
+
+sub _init {
+    my $self = shift;
+    my %args = (@_);
+    my $LoginUserClass = $self->LoginUserClass;
+
+    if (delete $args{'_bootstrap'} ) {
+        $self->is_bootstrap_user(1);
+    } elsif (keys %args) {
+        $self->user_object($LoginUserClass->new(current_user => $self));
+        $self->user_object->load_by_cols(%args);
+    }
+    $self->SUPER::_init(%args);
+}
+
+
+1;

Added: jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Dispatcher.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Dispatcher.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,97 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Login::Dispatcher;
+use Jifty::Dispatcher -base;
+
+# Put any plugin-specific dispatcher rules here.
+
+# Send a password reminder for a lost password
+on 'passwordreminder' => run {
+    redirect('/') if ( Jifty->web->current_user->id );
+    set 'action' =>
+        Jifty->web->new_action(
+        class => 'SendPasswordReminder',
+        moniker => 'password_reminder'
+    );
+
+    set 'next' => Jifty->web->request->continuation
+        || Jifty::Continuation->new(
+        request => Jifty::Request->new( path => "/" ) );
+
+};
+
+# Change a password
+on 'chgpasswd' => run {
+    redirect('/login') if (! Jifty->web->current_user->id );
+    set 'action' =>
+        Jifty->web->new_action(
+        class => 'ChangePassword',
+        moniker => 'chgpasswdbox'
+    );
+
+    set 'next' => Jifty->web->request->continuation
+        || Jifty::Continuation->new(
+        request => Jifty::Request->new( path => "/" ) );
+
+};
+
+
+# Sign up for an account
+on 'signup' => run {
+    redirect('/') if ( Jifty->web->current_user->id );
+    set 'action' =>
+        Jifty->web->new_action(
+	    class => 'Signup',
+	    moniker => 'signupbox'
+	);
+
+    set 'next' => Jifty->web->request->continuation
+        || Jifty::Continuation->new(
+        request => Jifty::Request->new( path => "/" ) );
+
+};
+
+# Login
+on 'login' => run {
+    set 'action' =>
+        Jifty->web->new_action(
+	    class => 'Login',
+	    moniker => 'loginbox'
+	);
+    set 'next' => Jifty->web->request->continuation
+        || Jifty::Continuation->new(
+        request => Jifty::Request->new( path => "/" ) );
+};
+
+# Log out
+before 'logout' => run {
+    Jifty->web->request->add_action(
+        class   => 'Logout',
+        moniker => 'logout',
+    );
+};
+
+on 'logout' => run {
+    redirect('/');
+};
+
+## LetMes
+before qr'^/let/(.*)' => run {
+    my $let_me = Jifty::LetMe->new();
+    $let_me->from_token($1);
+    redirect '/error/let_me/invalid_token' unless $let_me->validate;
+
+    Jifty->web->temporary_current_user($let_me->validated_current_user);
+
+    my %args = %{$let_me->args};
+    set $_ => $args{$_} for keys %args;
+    set let_me => $let_me;
+};
+
+on qr'^/let/' => run {
+    my $let_me = get 'let_me';
+    show '/let/' . $let_me->path;
+};
+
+1;

Added: jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Model/User.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Model/User.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,142 @@
+package Jifty::Plugin::Login::Model::User;
+use base qw/Jifty::Record Jifty::Plugin::Login/;
+use Jifty::DBI::Schema;
+
+use Jifty::Record schema {
+column
+  name => type is 'text',
+  label is 'Name',
+  is mandatory,
+  is distinct;
+
+column
+  email => type is 'text',
+  label is 'Email address',
+  is mandatory,
+  is distinct;
+
+column
+  password =>,
+  type is 'text',
+  label is 'Password',
+  render_as 'password';
+
+column
+  email_confirmed => label is 'Email address confirmed?',
+  type is 'boolean',
+  render_as 'Unrendered';
+
+column
+  auth_token => type is 'text',
+  render_as 'Unrendered';
+};
+
+
+sub create {
+    my $self  = shift;
+    my %args  = (@_);
+    my (@ret) = $self->SUPER::create(%args);
+    my $confirmAddress = Jifty->app_class('Notification','ConfirmAddress');
+    Jifty::Util->require($confirmAddress);
+
+    if ( $self->id and not $self->email_confirmed ) {
+        $confirmAddress->new( to => $self )->send;
+    }
+    return (@ret);
+}
+
+=head2 password_is STRING
+
+Returns true if and only if the current user's password matches STRING
+
+=cut
+
+sub password_is {
+    my $self   = shift;
+    my $string = shift;
+    return 1 if ( $self->_value('password') eq $string );
+    return 0;
+}
+
+=head2 password
+
+Never display a password
+
+=cut
+
+sub password {
+    return undef;
+
+}
+
+=head2 current_user_can
+
+Allows the current user to see all their own attributes and
+everyone else to see their username.
+
+Allows the current user to update any of their own attributes
+except whether or not their email has been confirmed.
+
+Passes everything else off to the superclass.
+
+=cut
+
+sub current_user_can {
+    my $self  = shift;
+    my $right = shift;
+    my %args  = (@_);
+    # This line breaks admin mode. I like admin mode.
+    #    Carp::confess if ( $right eq 'read' and not $args{'column'} );
+    if (    $right eq 'read'
+        and $self->id == $self->current_user->id )
+    {
+        return 1;
+    }
+    elsif ( $right eq 'read' and $args{'column'} eq 'name' ) {
+        return (1);
+
+    }
+    elsif ( $right eq 'update'
+        and $self->id == $self->current_user->id
+        and $args{'column'} ne 'email_confirmed' )
+    {
+        return (1);
+    }
+
+    return $self->SUPER::current_user_can( $right, %args );
+}
+
+=head2 auth_token
+
+Returns the user's unique authentication token. If the user 
+doesn't have one, sets one and returns it.
+
+=cut
+
+sub auth_token {
+    my $self = shift;
+    return undef
+      unless ( $self->current_user_can( read => column => 'auth_token' ) );
+    my $value = $self->_value('auth_token');
+    unless ($value) {
+        my $digest = Digest::MD5->new();
+        $digest->add( rand(100) );
+        $self->__set( column => 'auth_token', value => $digest->b64digest );
+    }
+    return $self->_value('auth_token');
+
+}
+
+=head2 record_class
+
+Identifies the correct record class for introspection
+
+=cut
+
+sub record_class {
+    my $self = shift;
+    return $self->LoginUserClass;
+
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Notification/ConfirmAddress.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Notification/ConfirmAddress.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,53 @@
+use warnings;
+use strict;
+
+package Jifty::Plugin::Login::Notification::ConfirmAddress;
+use base qw/Jifty::Notification Jifty::Plugin::Login/;
+
+=head1 NAME
+
+Jifty::Plugin::Login::Notification::ConfirmAddress
+
+=head1 ARGUMENTS
+
+C<to>, a L<Jifty::Plugin::Login::Model::User> whose address we are confirming.
+
+=cut
+
+=head2 setup
+
+Sets up the fields of the message.
+
+=cut
+
+sub setup {
+    my $self = shift;
+
+    unless ( UNIVERSAL::isa($self->to, $self->LoginUserClass) ){
+	$self->log->error((ref $self) . " called with invalid user argument");
+	return;
+    } 
+   
+
+    my $letme = Jifty::LetMe->new();
+    $letme->email($self->to->email);
+    $letme->path('confirm_email'); 
+    my $confirm_url = $letme->as_url;
+    my $appname = Jifty->config->framework('ApplicationName');
+
+    $self->subject( _("Welcome to ")."$appname!" );
+    $self->from( Jifty->config->framework('AdminEmail') );
+
+    $self->body(_("
+You're getting this message because you (or somebody claiming to be you)
+signed up for %1.
+
+Before you can use %1, we need to make sure that we got your email
+address right.  Click on the link below to get started:
+
+%2
+",$appname,$confirm_url));
+
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Notification/ConfirmLostPassword.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/lib/Jifty/Plugin/Login/Notification/ConfirmLostPassword.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,54 @@
+use warnings;
+use strict;
+
+package Jifty::Plugin::Login::Notification::ConfirmLostPassword;
+use base qw/Jifty::Notification Jifty::Plugin::Login/;
+
+=head1 NAME
+
+Jifty::Plugin::Login::Notification::ConfirmLostPassword
+
+=head1 ARGUMENTS
+
+C<to>, a L<Jifty::Plugin::Login::Model::User> whose address we are confirming.
+
+=cut
+
+=head2 setup
+
+Sets up the fields of the message.
+
+=cut
+
+sub setup {
+    my $self = shift;
+
+    unless ( UNIVERSAL::isa($self->to, $self->LoginUserClass) ){
+	$self->log->error((ref $self) . " called with invalid user argument");
+	return;
+    }
+
+    my $letme = Jifty::LetMe->new();
+    $letme->email($self->to->email);
+    $letme->path('reset_lost_password');
+    my $confirm_url = $letme->as_url;
+    my $appname = Jifty->config->framework('ApplicationName');
+
+    $self->subject( _("Message from ")."$appname!" );
+    $self->from( Jifty->config->framework('AdminEmail') );
+
+    $self->body(_("
+You're getting this message because you (or somebody claiming to be you)
+request to reset your password for %1.
+
+If you don't want to reset your password just ignore this message.
+
+To reset your password, click on the link below:
+
+%2
+",$appname,$confirm_url));
+
+}
+
+1;
+

Added: jifty/branches/schema-plugins/plugins/Login/share/po/en.po
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/share/po/en.po	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,207 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
+"Language-Team: LANGUAGE <LL at li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: lib/Jifty/Plugin/Login/Notification/ConfirmLostPassword.pm:41
+#. ($appname,$confirm_url)
+msgid ""
+"\n"
+"You're getting this message because you (or somebody claiming to be you)\n"
+"request to reset your password for %1.\n"
+"\n"
+"If you don't want to reset your password just ignore this message.\n"
+"\n"
+"To reset your password, click on the link below:\n"
+"\n"
+"%2\n"
+msgstr ""
+
+#: lib/Jifty/Plugin/Login/Notification/ConfirmAddress.pm:42
+#. ($appname,$confirm_url)
+msgid ""
+"\n"
+"You're getting this message because you (or somebody claiming to be you)\n"
+"signed up for %1.\n"
+"\n"
+"Before you can use %1, we need to make sure that we got your email\n"
+"address right.  Click on the link below to get started:\n"
+"\n"
+"%2\n"
+msgstr ""
+
+#: lib/Jifty/Plugin/Login/Action/Signup.pm:110
+msgid ". We've sent a confirmation message to your email box."
+msgstr ""
+
+#: lib/Jifty/Plugin/Login/Action/ConfirmEmail.pm:52
+msgid ". Your email address has now been confirmed."
+msgstr ""
+
+#: lib/Jifty/Plugin/Login/Action/SendPasswordReminder.pm:95
+msgid "A link to reset your password has been sent to your email account."
+msgstr ""
+
+#: share/web/templates/chgpasswd:11
+msgid "Change"
+msgstr ""
+
+#: share/web/templates/chgpasswd:6
+msgid "Change your password"
+msgstr ""
+
+#: lib/Jifty/Plugin/Login/Action/SendAccountConfirmation.pm:94
+msgid "Confirmation resent."
+msgstr ""
+
+#: share/web/templates/login:15
+msgid "Don't have an account?"
+msgstr ""
+
+#: lib/Jifty/Plugin/Login/Action/Login.pm:23
+msgid "Email address"
+msgstr ""
+
+#: lib/Jifty/Plugin/Login/Action/Login.pm:37
+msgid "If you want, your browser can remember your login for you"
+msgstr ""
+
+#: lib/Jifty/Plugin/Login/Action/SendAccountConfirmation.pm:74 lib/Jifty/Plugin/Login/Action/SendPasswordReminder.pm:78
+msgid "It doesn't look like there's an account by that name."
+msgstr ""
+
+#: lib/Jifty/Plugin/Login/Action/Signup.pm:69
+msgid "It looks like you already have an account. Perhaps you want to <a href=\"/login\">sign in</a> instead?"
+msgstr ""
+
+#: lib/Jifty/Plugin/Login/Action/ChangePassword.pm:70 lib/Jifty/Plugin/Login/Action/ResetLostPassword.pm:72
+msgid "It looks like you didn't enter the same password into both boxes. Give it another shot?"
+msgstr ""
+
+#: lib/Jifty/Plugin/Login/Action/SendAccountConfirmation.pm:78
+msgid "It looks like you're already confirmed."
+msgstr ""
+
+#: share/web/templates/login:13 share/web/templates/login:8
+msgid "Login"
+msgstr ""
+
+#: lib/Jifty/Plugin/Login/Notification/ConfirmLostPassword.pm:38
+msgid "Message from "
+msgstr ""
+
+#: lib/Jifty/Plugin/Login/Action/Signup.pm:45
+msgid "Name"
+msgstr ""
+
+#: share/web/templates/let/reset_lost_password:16
+msgid "New password"
+msgstr ""
+
+#: lib/Jifty/Plugin/Login/Action/Login.pm:66
+msgid "No account has that email address."
+msgstr ""
+
+#: share/web/templates/logout:2
+msgid "Ok, you're now logged out. Have a good day."
+msgstr ""
+
+#: lib/Jifty/Plugin/Login/Action/ChangePassword.pm:30 lib/Jifty/Plugin/Login/Action/Login.pm:30 lib/Jifty/Plugin/Login/Action/ResetLostPassword.pm:32
+msgid "Password"
+msgstr ""
+
+#: lib/Jifty/Plugin/Login/Action/Login.pm:35
+msgid "Remember me?"
+msgstr ""
+
+#: share/web/templates/let/reset_lost_password:9
+msgid "Reset lost password"
+msgstr ""
+
+#: share/web/templates/passwordreminder:18
+msgid "Send"
+msgstr ""
+
+#: share/web/templates/passwordreminder:9
+msgid "Send a password reminder"
+msgstr ""
+
+#: share/web/templates/signup:11 share/web/templates/signup:6
+msgid "Signup"
+msgstr ""
+
+#: lib/Jifty/Plugin/Login/Action/Signup.pm:102
+msgid "Something bad happened and we couldn't create your account.  Try again later. We're really, really sorry."
+msgstr ""
+
+#: lib/Jifty/Plugin/Login/Action/Login.pm:60 lib/Jifty/Plugin/Login/Action/SendAccountConfirmation.pm:67 lib/Jifty/Plugin/Login/Action/SendPasswordReminder.pm:71 lib/Jifty/Plugin/Login/Action/Signup.pm:62
+msgid "That doesn't look like an email address."
+msgstr ""
+
+#: lib/Jifty/Plugin/Login/Action/ChangePassword.pm:76 lib/Jifty/Plugin/Login/Action/ResetLostPassword.pm:78
+msgid "There was an error setting your password."
+msgstr ""
+
+#: lib/Jifty/Plugin/Login/Action/Signup.pm:44
+msgid "Type that again?"
+msgstr ""
+
+#: lib/Jifty/Plugin/Login/Action/Login.pm:101
+msgid "Welcome back, "
+msgstr ""
+
+#: lib/Jifty/Plugin/Login/Action/Signup.pm:107 lib/Jifty/Plugin/Login/Notification/ConfirmAddress.pm:39
+msgid "Welcome to "
+msgstr ""
+
+#: lib/Jifty/Plugin/Login/Action/ChangePassword.pm:56 lib/Jifty/Plugin/Login/Action/ResetLostPassword.pm:58
+msgid "You don't exist. I'm not sure how this happened. Really, really sorry. Please email us!"
+msgstr ""
+
+#: lib/Jifty/Plugin/Login/Action/ConfirmEmail.pm:42
+msgid "You have already confirmed your account."
+msgstr ""
+
+#: lib/Jifty/Plugin/Login/Action/Login.pm:96
+msgid "You haven't confirmed your account yet."
+msgstr ""
+
+#: share/web/templates/passwordreminder:10
+msgid "You lost your password. A reminder will be send to the following mail:"
+msgstr ""
+
+#: lib/Jifty/Plugin/Login/Action/Login.pm:90
+msgid "You may have mistyped your email address or password. Give it another shot?"
+msgstr ""
+
+#: share/web/templates/login:18
+msgid "You're already logged in."
+msgstr ""
+
+#: lib/Jifty/Plugin/Login/Action/ChangePassword.pm:81
+msgid "Your password has been changed.  Welcome back."
+msgstr ""
+
+#: lib/Jifty/Plugin/Login/Action/ResetLostPassword.pm:83
+msgid "Your password has been reset.  Welcome back."
+msgstr ""
+
+#: lib/Jifty/Plugin/Login/Action/SendAccountConfirmation.pm:31 lib/Jifty/Plugin/Login/Action/SendPasswordReminder.pm:34
+msgid "email address"
+msgstr ""
+
+#: lib/Jifty/Plugin/Login/Action/ChangePassword.pm:35 lib/Jifty/Plugin/Login/Action/ResetLostPassword.pm:37
+msgid "type your password again"
+msgstr ""

Added: jifty/branches/schema-plugins/plugins/Login/share/po/fr.po
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/share/po/fr.po	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,223 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# AGOSTINI Yves <agostini at univ-metz.fr>, 2006.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"POT-Creation-Date: 2006-10-24 15:00+ZONE\n"
+"PO-Revision-Date: 2006-11-10 22:42+ZONE\n"
+"Last-Translator: AGOSTINI Yves <agostini at univ-metz.fr>\n"
+"Language-Team: LANGUAGE <LL at li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=ISO8859-1\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: lib/Jifty/Plugin/Login/Notification/ConfirmLostPassword.pm:41
+#. ($appname,$confirm_url)
+msgid ""
+"\n"
+"You're getting this message because you (or somebody claiming to be you)\n"
+"request to reset your password for %1.\n"
+"\n"
+"If you don't want to reset your password just ignore this message.\n"
+"\n"
+"To reset your password, click on the link below:\n"
+"\n"
+"%2\n"
+msgstr ""
+"\n"
+"Vous recevez ce message, parce que quelqu'un demande un nouveau mot de\n"
+"passe pour l'application %1.\n"
+"\n"
+"Si cela ne vous convient pas, ignorez tout simplement ce message.\n"
+"\n"
+"Pour changer votre mot de passe cliquez sur le lien ci-dessous :\n"
+"\n"
+"%2\n"
+
+#: lib/Jifty/Plugin/Login/Notification/ConfirmAddress.pm:42
+#. ($appname,$confirm_url)
+msgid ""
+"\n"
+"You're getting this message because you (or somebody claiming to be you)\n"
+"signed up for %1.\n"
+"\n"
+"Before you can use %1, we need to make sure that we got your email\n"
+"address right.  Click on the link below to get started:\n"
+"\n"
+"%2\n"
+msgstr ""
+"\n"
+"Vous demandez une inscription sur l'application %1.\n"
+"\n"
+"Avant de pouvoir vous connecter, vous devez confirmer votre adresse mail\n"
+"en cliquant sur le lien ci-dessous :\n"
+"\n"
+"%2\n"
+
+#: lib/Jifty/Plugin/Login/Action/Signup.pm:110
+msgid ". We've sent a confirmation message to your email box."
+msgstr ". Une demande de confirmation vous a été envoyée par mail."
+
+#: lib/Jifty/Plugin/Login/Action/ConfirmEmail.pm:52
+msgid ". Your email address has now been confirmed."
+msgstr ". Votre adresse mail a été vérifiée."
+
+#: lib/Jifty/Plugin/Login/Action/SendPasswordReminder.pm:95
+msgid "A link to reset your password has been sent to your email account."
+msgstr "Un lien web pour ré-initialiser votre mot de passe vous a été envoyé par mail."
+
+#: share/web/templates/chgpasswd:11
+msgid "Change"
+msgstr "Valider"
+
+#: share/web/templates/chgpasswd:6
+msgid "Change your password"
+msgstr "Changer votre mot de passe"
+
+#: lib/Jifty/Plugin/Login/Action/SendAccountConfirmation.pm:94
+msgid "Confirmation resent."
+msgstr "Demande de confirmation transmise."
+
+#: share/web/templates/login:15
+msgid "Don't have an account?"
+msgstr "Vous n'avez pas de compte ?"
+
+#: lib/Jifty/Plugin/Login/Action/Login.pm:23
+msgid "Email address"
+msgstr "Adresse mail"
+
+#: lib/Jifty/Plugin/Login/Action/Login.pm:37
+msgid "If you want, your browser can remember your login for you"
+msgstr "Si vous le désirez, votre navigateur peut mémoriser votre identification"
+
+#: lib/Jifty/Plugin/Login/Action/SendAccountConfirmation.pm:74 lib/Jifty/Plugin/Login/Action/SendPasswordReminder.pm:78
+msgid "It doesn't look like there's an account by that name."
+msgstr "Ce compte ne semble pas exister."
+
+#: lib/Jifty/Plugin/Login/Action/Signup.pm:69
+msgid "It looks like you already have an account. Perhaps you want to <a href=\"/login\">sign in</a> instead?"
+msgstr "Vous semblez avoir déjà un compte. Vous désirez peut-être plutôt vous <a href=\"/login\">identifier</a> ?"
+
+#: lib/Jifty/Plugin/Login/Action/ChangePassword.pm:70 lib/Jifty/Plugin/Login/Action/ResetLostPassword.pm:72
+msgid "It looks like you didn't enter the same password into both boxes. Give it another shot?"
+msgstr "Les mots de passe semblent différents. Ré-essayer ?"
+
+#: lib/Jifty/Plugin/Login/Action/SendAccountConfirmation.pm:78
+msgid "It looks like you're already confirmed."
+msgstr "Votre identifiant a déjà été vérifié."
+
+#: share/web/templates/login:13 share/web/templates/login:8
+msgid "Login"
+msgstr "Identification"
+
+#: lib/Jifty/Plugin/Login/Notification/ConfirmLostPassword.pm:38
+msgid "Message from "
+msgstr "Message de "
+
+#: lib/Jifty/Plugin/Login/Action/Signup.pm:45
+msgid "Name"
+msgstr "Nom"
+
+#: share/web/templates/let/reset_lost_password:16
+msgid "New password"
+msgstr "Enregistrer"
+
+#: lib/Jifty/Plugin/Login/Action/Login.pm:66
+msgid "No account has that email address."
+msgstr "Aucun compte n'a cette adresse mail."
+
+#: share/web/templates/logout:2
+msgid "Ok, you're now logged out. Have a good day."
+msgstr "Vous êtes déconnecté. Au revoir."
+
+#: lib/Jifty/Plugin/Login/Action/ChangePassword.pm:30 lib/Jifty/Plugin/Login/Action/Login.pm:30 lib/Jifty/Plugin/Login/Action/ResetLostPassword.pm:32
+msgid "Password"
+msgstr "Mot de passe"
+
+#: lib/Jifty/Plugin/Login/Action/Login.pm:35
+msgid "Remember me?"
+msgstr "S'en rappeller ?"
+
+#: share/web/templates/let/reset_lost_password:9
+msgid "Reset lost password"
+msgstr "Nouveau mot de passe"
+
+#: share/web/templates/passwordreminder:18
+msgid "Send"
+msgstr "Envoyer"
+
+#: share/web/templates/passwordreminder:9
+msgid "Send a password reminder"
+msgstr "Recevoir un nouveau mot de passe"
+
+#: share/web/templates/signup:11 share/web/templates/signup:6
+msgid "Signup"
+msgstr "S'enregistrer"
+
+#: lib/Jifty/Plugin/Login/Action/Signup.pm:102
+msgid "Something bad happened and we couldn't create your account.  Try again later. We're really, really sorry."
+msgstr "Votre compte ne peut pas actuellement être créé. Merci de ré-essayer ultérieurement."
+
+#: lib/Jifty/Plugin/Login/Action/Login.pm:60 lib/Jifty/Plugin/Login/Action/SendAccountConfirmation.pm:67 lib/Jifty/Plugin/Login/Action/SendPasswordReminder.pm:71 lib/Jifty/Plugin/Login/Action/Signup.pm:62
+msgid "That doesn't look like an email address."
+msgstr "Cela ne ressemble pas à une adresse mail."
+
+#: lib/Jifty/Plugin/Login/Action/ChangePassword.pm:76 lib/Jifty/Plugin/Login/Action/ResetLostPassword.pm:78
+msgid "There was an error setting your password."
+msgstr "Erreur lors de la création de votre mot de passe."
+
+#: lib/Jifty/Plugin/Login/Action/Signup.pm:44
+msgid "Type that again?"
+msgstr "Confirmer"
+
+#: lib/Jifty/Plugin/Login/Action/Login.pm:101
+msgid "Welcome back, "
+msgstr "Bienvenue, "
+
+#: lib/Jifty/Plugin/Login/Action/Signup.pm:107 lib/Jifty/Plugin/Login/Notification/ConfirmAddress.pm:39
+msgid "Welcome to "
+msgstr "Bienvenue sur "
+
+#: lib/Jifty/Plugin/Login/Action/ChangePassword.pm:56 lib/Jifty/Plugin/Login/Action/ResetLostPassword.pm:58
+msgid "You don't exist. I'm not sure how this happened. Really, really sorry. Please email us!"
+msgstr "Vous n'existez pas !!!"
+
+#: lib/Jifty/Plugin/Login/Action/ConfirmEmail.pm:42
+msgid "You have already confirmed your account."
+msgstr "Vous avez déjà confirmé votre compte."
+
+#: lib/Jifty/Plugin/Login/Action/Login.pm:96
+msgid "You haven't confirmed your account yet."
+msgstr "Vous n'avez pas encore confirmé votre compte."
+
+#: share/web/templates/passwordreminder:10
+msgid "You lost your password. A reminder will be send to the following mail:"
+msgstr "Vous avez perdu votre mot de passe. Un message d'assistance peut vous être re-transmis à votre mail d'origine :"
+
+#: lib/Jifty/Plugin/Login/Action/Login.pm:90
+msgid "You may have mistyped your email address or password. Give it another shot?"
+msgstr "Erreur dans votre mail ou votre mot de passe. Ré-essayer ?"
+
+#: share/web/templates/login:18
+msgid "You're already logged in."
+msgstr "Vous êtes déjà connecté."
+
+#: lib/Jifty/Plugin/Login/Action/ChangePassword.pm:81
+msgid "Your password has been changed.  Welcome back."
+msgstr ""
+
+#: lib/Jifty/Plugin/Login/Action/ResetLostPassword.pm:83
+msgid "Your password has been reset.  Welcome back."
+msgstr "Votre mot de passe a été ré-initialisé. Bon retour."
+
+#: lib/Jifty/Plugin/Login/Action/SendAccountConfirmation.pm:31 lib/Jifty/Plugin/Login/Action/SendPasswordReminder.pm:34
+msgid "email address"
+msgstr "adresse mail"
+
+#: lib/Jifty/Plugin/Login/Action/ChangePassword.pm:35 lib/Jifty/Plugin/Login/Action/ResetLostPassword.pm:37
+msgid "type your password again"
+msgstr "confirmez votre mot de passe"

Added: jifty/branches/schema-plugins/plugins/Login/share/web/templates/chgpasswd
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/share/web/templates/chgpasswd	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,13 @@
+<%args>
+$action
+$next
+</%args>
+<&|/_elements/wrapper, title => 'Change your password' &>
+<h2><% _("Change your password") %></h2>
+<% Jifty->web->form->start(call => $next, name => "chgpasswdbox") %>
+% foreach my $key ($action->argument_names) {
+<% $action->form_field($key) %>
+% }
+<% Jifty->web->form->submit(label => _('Change'), submit => $action) %>
+<% Jifty->web->form->end %>
+</&>

Added: jifty/branches/schema-plugins/plugins/Login/share/web/templates/let/confirm_email
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/share/web/templates/let/confirm_email	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,7 @@
+<%init>
+Jifty->web->new_action(
+    moniker => 'confirm_email',
+    class   => 'ConfirmEmail',
+)->run;
+Jifty->web->redirect("/");
+</%init>

Added: jifty/branches/schema-plugins/plugins/Login/share/web/templates/let/reset_lost_password
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/share/web/templates/let/reset_lost_password	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,21 @@
+<%init>
+my $action = Jifty->web->new_action(
+    moniker => 'reset_lost_password',
+    class   => 'ResetLostPassword',
+);
+</%init>
+<&|/_elements/wrapper, title => 'Reset lost password' &>
+
+<H2><% _('Reset lost password') %></H2>
+<% Jifty->web->form->start %>
+
+% for ($action->argument_names) {
+<% $action->form_field($_) %>
+% }
+
+<% Jifty->web->form->submit( label => _("New password") ) %>
+
+<% Jifty->web->form->end %>
+
+</&>
+

Added: jifty/branches/schema-plugins/plugins/Login/share/web/templates/login
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/share/web/templates/login	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,22 @@
+<%args>
+$action => undef
+$next => undef
+</%args>
+<&|/_elements/wrapper, title => 'Login' &>
+
+% if (not Jifty->web->current_user->id) {
+<h2><% _('Login') %></h2>
+<% Jifty->web->form->start(call => $next, name => "loginbox") %>
+<% $action->form_field('email') %>
+<% $action->form_field('password') %>
+<% $action->form_field('remember') %>
+<div class="submit_button">
+<% Jifty->web->return(label => _('Login'), to => '/', submit => $action) %>
+</div>
+<% Jifty->web->form->end %>
+<% Jifty->web->tangent( label => _("Don't have an account?"), url => '/signup' )%>
+% }
+% else {
+<% _("You're already logged in.") %>
+% }
+</&>

Added: jifty/branches/schema-plugins/plugins/Login/share/web/templates/passwordreminder
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/share/web/templates/passwordreminder	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,23 @@
+<%init>
+my $action = Jifty->web->new_action(
+    moniker => 'password_reminder',
+    class   => 'SendPasswordReminder',
+);
+</%init>
+<&|/_elements/wrapper, title => 'Send Password Reminder' &>
+
+<H2><% _('Send a password reminder') %></H2>
+<% _("You lost your password. A reminder will be send to the following mail:") %>
+
+<% Jifty->web->form->start %>
+
+% for ($action->argument_names) {
+<% $action->form_field($_) %>
+% }
+
+<% Jifty->web->form->submit( label => _("Send") ) %>
+
+<% Jifty->web->form->end %>
+
+</&>
+

Added: jifty/branches/schema-plugins/plugins/Login/share/web/templates/signup
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Login/share/web/templates/signup	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,13 @@
+<%args>
+$action
+$next
+</%args>
+<&|/_elements/wrapper, title => 'Signup' &>
+<h2><% _("Signup") %></h2>
+<% Jifty->web->form->start(call => $next, name => "signupbox") %>
+% foreach my $key ($action->argument_names) {
+<% $action->form_field($key) %>
+% }
+<% Jifty->web->form->submit(label => _('Signup'), submit => $action) %>
+<% Jifty->web->form->end %>
+</&>

Added: jifty/branches/schema-plugins/plugins/Nothing/Makefile.PL
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Nothing/Makefile.PL	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,8 @@
+use inc::Module::Install;
+name('Jifty-Plugin-Nothing');
+version('0.01');
+requires('Jifty' => '0.60615');
+
+install_share;
+
+WriteAll;

Added: jifty/branches/schema-plugins/plugins/Nothing/lib/Jifty/Plugin/Nothing.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Nothing/lib/Jifty/Plugin/Nothing.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,10 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Nothing;
+use base qw/Jifty::Plugin/;
+
+# Your plugin goes here.  If takes any configuration or arguments, you
+# probably want to override L<Jifty::Plugin/init>.
+
+1;

Added: jifty/branches/schema-plugins/plugins/Nothing/lib/Jifty/Plugin/Nothing/Dispatcher.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Nothing/lib/Jifty/Plugin/Nothing/Dispatcher.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,9 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Nothing::Dispatcher;
+use Jifty::Dispatcher -base;
+
+# Put any plugin-specific dispatcher rules here.
+
+1;

Added: jifty/branches/schema-plugins/plugins/ProfileBehaviour/Makefile.PL
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/ProfileBehaviour/Makefile.PL	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,8 @@
+use inc::Module::Install;
+name('Jifty-Plugin-ProfileBehaviour');
+version('0.01');
+requires('Jifty' => '0.60722');
+
+install_share;
+
+WriteAll;

Added: jifty/branches/schema-plugins/plugins/ProfileBehaviour/lib/Jifty/Plugin/ProfileBehaviour.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/ProfileBehaviour/lib/Jifty/Plugin/ProfileBehaviour.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,10 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::ProfileBehaviour;
+use base qw/Jifty::Plugin/;
+
+# Your plugin goes here.  If takes any configuration or arguments, you
+# probably want to override L<Jifty::Plugin/init>.
+
+1;

Added: jifty/branches/schema-plugins/plugins/ProfileBehaviour/lib/Jifty/Plugin/ProfileBehaviour/Dispatcher.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/ProfileBehaviour/lib/Jifty/Plugin/ProfileBehaviour/Dispatcher.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,9 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::ProfileBehaviour::Dispatcher;
+use Jifty::Dispatcher -base;
+
+# Put any plugin-specific dispatcher rules here.
+
+1;

Added: jifty/branches/schema-plugins/plugins/ProfileBehaviour/share/web/static/css/behaviour-profile.css
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/ProfileBehaviour/share/web/static/css/behaviour-profile.css	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,67 @@
+#behaviour-profile-data {
+    width: 90%;
+    height: 90%;
+    position: fixed;
+    left: 5%;
+    top: 5%;
+    background-color: white;
+    border: 2px solid black;
+    overflow: scroll;
+}
+
+#behaviour-profile-data div.title {
+    font-size: 200%;
+    border-bottom: 2px solid black;
+}
+
+#behaviour-profile-data .section .title {
+   font-size: 150%;
+}
+
+#behaviour-profile-data .title .close {
+   font-size: 80%;
+   text-align: right;
+   text-decoration:none;
+   position: absolute:
+   right: 10px;
+   top: 10px;
+}
+
+#behaviour-profile-data .code code {
+    position: absolute;
+    background-color: lightgrey;
+    border: 1px solid black;
+    z-index: 100;
+    white-space: pre;
+    text-align: left;
+}
+
+#behaviour-profile-data table td.time {
+    text-align:center;
+}
+
+#behaviour-profile-data table td.selector {
+    text-align:right;
+}
+
+#behaviour-profile-data table tr,
+#behaviour-profile-data table td,
+#behaviour-profile-data table th {
+    text-align:center;
+    padding: 0px 5px;
+}
+
+#behaviour-profile-data table .time {
+    border-left: 1px solid black;
+}
+
+#behaviour-profile-data table .total {
+    border-right: 1px solid black;
+}
+
+
+#show-behaviour-profile {
+    position: fixed;
+    left: 0.5em;
+    bottom: 0;
+}

Added: jifty/branches/schema-plugins/plugins/ProfileBehaviour/share/web/static/js/behaviour.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/ProfileBehaviour/share/web/static/js/behaviour.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,227 @@
+/*
+   Modified to fix some bugs, use a different css query engine, and to
+   to use JSAN classes.
+   
+   Based on Behaviour v1.1 by Ben Nolan, June 2005, which was based
+   largely on the work of Simon Willison.
+ 
+   Usage:   
+   
+    var myrules = {
+        'b.someclass' : function(element){
+            element.onclick = function(){
+                alert(this.innerHTML);
+            }
+        },
+        '#someid u' : function(element){
+            element.onmouseover = function(){
+                this.innerHTML = "BLAH!";
+            }
+        }
+    };
+    
+    Behaviour.register(myrules);
+    
+    // Call Behaviour.apply() to re-apply the rules (if you
+    // update the dom, etc).
+
+
+    This Behaviour has been modified to keep track of timing
+    information and render it to an on-screen display for profiling
+    purposes
+*/   
+
+JSAN.use("DOM.Events");
+JSAN.use("Upgrade.Array.push");
+
+var Behaviour = {
+    profileData: {
+	calls: [],
+	applyTime: 0,
+	searchTime: 0,
+	numCalls: 0
+    },
+    list: new Array(),
+    
+    register: function(sheet) {
+        Behaviour.list.push(sheet);
+    },
+    
+    apply: function() {
+	var root = arguments[0];
+	if(root) root = $(root);
+	var _applyStart = new Date();
+	var profile = {
+	    searchTimes: {},
+	    applyTimes: {},
+	    funcs: {},
+	    searchTime: 0,
+	    applyTime: 0,
+	    caller: Behaviour.apply.caller
+	};
+
+        for (var h = 0; sheet = Behaviour.list[h]; h++) {
+            for (var selector in sheet) {
+		var start = new Date();
+                var elements = cssQuery(selector, root);
+		var searchDone = new Date();
+		profile.searchTimes[selector] = searchDone - start;
+		profile.searchTime += profile.searchTimes[selector];
+                
+                if ( !elements ) continue;
+
+                for (var i = 0; element = elements[i]; i++) {
+                    sheet[selector](element);
+		}
+		profile.applyTimes[selector] = new Date() - searchDone;
+		profile.applyTime += profile.applyTimes[selector];
+		profile.funcs[selector] = sheet[selector];
+            }
+        }
+
+	Behaviour.profileData.calls.push(profile);
+	Behaviour.profileData.numCalls++;
+	Behaviour.profileData.searchTime += profile.searchTime;
+	Behaviour.profileData.applyTime += profile.applyTime;
+    },
+
+    showProfile: function() {
+	var pane = this.createElement('div');
+	pane.id = 'behaviour-profile-data';
+
+	var title = this.createElement('div');
+	title.appendChild(document.createTextNode('Behaviour profiling information'));
+	title.className = 'title';
+	var close = this.createElement('a', 'close', '[close]');
+	close.href = '#';
+	close.onclick = function() { Element.remove($('behaviour-profile-data')); }
+
+	pane.appendChild(close);
+	pane.appendChild(title);
+	
+	pane.appendChild(Behaviour._callData());
+
+	document.getElementsByTagName('body')[0].appendChild(pane);
+
+    },
+
+    _callData: function() {
+	list = this.createElement('ul', 'section');
+
+	for( var i = 0; i <  Behaviour.profileData.calls.length; i++ ) {
+	    var call = Behaviour.profileData.calls[i];
+	    var item = this.createElement('li', 'call');
+	    var text = call.caller.length == 0 ? ' (Page load)' : ' (AJAX)';
+	    var title = this.createElement('div', 'title', 'Call ' + i + text);
+	    item.appendChild(title);
+	    
+	    var table = this.createElement('table');
+	    var head = this.createElement('tr');
+	    head.appendChild(this.createElement('th', 'selector', 'Selector'));
+	    head.appendChild(this.createElement('th', 'time search', 'cssQuery time'));
+	    head.appendChild(this.createElement('th', 'time apply', 'Function time'));
+	    head.appendChild(this.createElement('th', 'time total', 'Total time'));
+	    head.appendChild(this.createElement('th'));
+	    table.appendChild(head);
+
+	    var searchTimes = $H(call.searchTimes).keys().sort(function(a,b) {
+		    var timeA = call.searchTimes[a] + call.applyTimes[a];
+		    var timeB = call.searchTimes[b] + call.applyTimes[b];
+		
+		    if(timeA < timeB) {
+			return 1;
+		    } else if(timeA > timeB) {
+			return -1;
+		    } else {
+			return 0;
+		    }
+		});
+	    
+	    for(var j = 0; j < searchTimes.length; j++) {
+		var k = searchTimes[j];
+		var tr = this.createElement('tr');
+		tr.appendChild(this.createElement('td', 'selector', k));
+		tr.appendChild(this.createElement('td', 'time search', call.searchTimes[k]));
+		tr.appendChild(this.createElement('td', 'time apply', call.applyTimes[k]));
+		tr.appendChild(this.createElement('td', 'time total', call.searchTimes[k] + call.applyTimes[k]));
+
+		var code  = this.createElement('td', 'code');
+		var a = this.createElement('a', null, '[code]');
+		a.href = '#';
+		var src = this.createElement('code', null, call.funcs[k]);
+		var id =  'code-' + i + '-' + j;
+		src.id = id;
+		src.style.display = 'none';
+		// Kludge to make the onclick function close over id properly
+		(function (id) {
+		    a.onclick = function() { Element.toggle($(id)); return false; }
+		})(id);
+
+		var div = this.createElement('div');
+		div.appendChild(src);
+		code.appendChild(div);
+		code.appendChild(a);
+		tr.appendChild(code);
+		table.appendChild(tr);
+	    }
+
+	    item.appendChild(table);
+
+	    item.appendChild(this.createElement('div','totals',
+						'Total: '
+						+ call.searchTime + ' search, '
+						+ call.applyTime + ' apply, '
+						+ (call.searchTime + call.applyTime) + ' total'));
+					   
+	    
+	    list.appendChild(item);
+	}
+	
+	return list;
+    },
+
+
+    // Convenience method for the above
+    createElement: function (elt, className, text) {
+	elt = elt ? elt : 'div';
+	var d = document.createElement(elt);
+	if(className) d.className = className;
+	if(text) d.appendChild(document.createTextNode(text));
+	return d;
+    },
+
+    onLoad: function () {
+	// Make sure we only run once
+	if(Behaviour.loaded) return;
+	Behaviour.loaded = true;
+	Behaviour.apply();
+
+	// Add the profiling CSS to the document
+	var head = document.getElementsByTagName('head')[0];
+	var link = document.createElement('link');
+	link.rel = 'stylesheet';
+	link.type = 'text/css';
+	link.href = '/css/behaviour-profile.css';
+	head.appendChild(link);
+
+	var open = this.createElement('a', null, 'Behaviour profile');
+	open.id = 'show-behaviour-profile';
+	open.href ='#';
+	open.onclick = function() { Behaviour.toggleProfile() }
+	var div = this.createElement('div');
+	div.appendChild(open);
+	document.getElementsByTagName('body')[0].appendChild(div);
+    },
+
+    toggleProfile: function () {
+	var e = $('behaviour-profile-data');
+	if(e) {
+	    Element.remove(e);
+	} else {
+	    this.showProfile();
+	}
+    }
+}
+
+
+DOM.Events.addListener( window, "load", function() { Behaviour.onLoad() } );

Added: jifty/branches/schema-plugins/plugins/Users-Identity-File/Makefile.PL
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Users-Identity-File/Makefile.PL	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,8 @@
+use inc::Module::Install;
+name('Jifty-Plugin-Users-Identity-File');
+version('0.01');
+requires('Jifty' => '0.70117');
+
+install_share;
+
+WriteAll;

Added: jifty/branches/schema-plugins/plugins/Users-Identity-File/lib/Jifty/Plugin/Users/Identity/File.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Users-Identity-File/lib/Jifty/Plugin/Users/Identity/File.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,13 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Users::Identity::File;
+use base qw/Jifty::Plugin Jifty::Plugin::Users/;
+
+# Your plugin goes here.  If takes any configuration or arguments, you
+# probably want to override L<Jifty::Plugin/init>.
+
+# ?? TODO
+# in init load file name
+
+1;

Added: jifty/branches/schema-plugins/plugins/Users-Identity-File/lib/Jifty/Plugin/Users/Identity/File/Action/Login.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Users-Identity-File/lib/Jifty/Plugin/Users/Identity/File/Action/Login.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,90 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Jifty::Plugin::Users::Identity::File::Action::Login
+
+=cut
+
+package Jifty::Plugin::Users::Identity::File::Action::Login;
+use base qw/Jifty::Action Jifty::Plugin::Users Jifty::Plugin::Users::Identity::File/;
+
+
+=head2 arguments
+
+Return the ticket form field
+
+=cut
+
+sub arguments {
+    return (
+        {
+            login => {
+                label          => 'login',
+                mandatory      => 1,
+                ajax_validates => 1,
+            },
+            password => {
+                label          => 'password',
+                mandatory      => 1,
+            },
+
+        }
+    );
+
+}
+
+=head2 validate_ticket ST
+
+for ajax_validates
+Makes sure that the ticket submitted is legal.
+
+
+=cut
+
+sub validate_login {
+    my $self  = shift;
+    my $login = shift;
+
+    unless ( $login && $login !~ /^[A-Za-z0-9-]+$/ ) {
+        return $self->validation_error(
+            ticket => _("That doesn't look like a valid ticket.") );
+    }
+
+
+    return $self->validation_ok('login');
+}
+
+
+=head2 take_action
+
+Actually check the user's password. If it's right, log them in.
+Otherwise, throw an error.
+
+
+=cut
+
+sub take_action {
+    my $self = shift;
+    my $login = $self->argument_value('login');
+    my $password = $self->argument_value('password');
+
+    my $LoginUser = $self->UserClass();
+    my $CurrentUser = $self->CurrentUserClass();
+    my $u = $LoginUser->new( current_user => $CurrentUser->superuser );
+
+    $u->load_by_cols( display_name => $login, realm => 'file' );
+    my $id = $u->id;
+    if (!$id) { 
+   	($id) = $u->create(display_name => $login, realm => 'file' ); 
+	}
+    Jifty->log->debug("Login user id: $id"); 
+
+    # Actually do the signin thing.
+     Jifty->web->current_user( $CurrentUser->new( id => $u->id ) );
+
+    return 1;
+}
+
+1;

Added: jifty/branches/schema-plugins/plugins/Users-Identity-File/lib/Jifty/Plugin/Users/Identity/File/Dispatcher.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Users-Identity-File/lib/Jifty/Plugin/Users/Identity/File/Dispatcher.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,21 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Users::Identity::File::Dispatcher;
+use Jifty::Dispatcher -base;
+
+# Put any plugin-specific dispatcher rules here.
+
+# Login
+on 'login' => run {
+    set 'action' =>
+        Jifty->web->new_action(
+        class => 'Login',  #File login
+        moniker => 'fileloginbox'
+    );
+    set 'next' => Jifty->web->request->continuation
+        || Jifty::Continuation->new(
+        request => Jifty::Request->new( path => "/" ) );
+};
+
+1;

Added: jifty/branches/schema-plugins/plugins/Users-Identity-File/share/web/templates/login
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Users-Identity-File/share/web/templates/login	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,20 @@
+<%args>
+$action => undef
+$next => undef
+</%args>
+<&|/_elements/wrapper, title => 'Login' &>
+
+% if (not Jifty->web->current_user->id) {
+<h2><% _('Login') %></h2>
+<% Jifty->web->form->start(call => $next, name => "fileloginbox") %>
+<% $action->form_field('login') %>
+<% $action->form_field('password') %>
+<div class="submit_button">
+<% Jifty->web->return(label => _('Login'), to => '/', submit => $action) %>
+</div>
+<% Jifty->web->form->end %>
+% }
+% else {
+<% _("You're already logged in.") %>
+% }
+</&>

Added: jifty/branches/schema-plugins/plugins/Users/Makefile.PL
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Users/Makefile.PL	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,8 @@
+use inc::Module::Install;
+name('Jifty-Plugin-Users');
+version('0.01');
+requires('Jifty' => '0.70117');
+
+install_share;
+
+WriteAll;

Added: jifty/branches/schema-plugins/plugins/Users/lib/Jifty/Plugin/Users.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Users/lib/Jifty/Plugin/Users.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,52 @@
+use strict;
+use warnings;
+
+=head1 NAME
+
+Jifty::Plugin::Users;
+
+=cut
+
+package Jifty::Plugin::Users;
+use base qw/Jifty::Plugin/;
+
+=head1 SYNOPSIS
+
+EXPERIMENTAL DON'T USE IT
+EXPERIMENTAL DON'T USE IT
+EXPERIMENTAL DON'T USE IT
+
+ in etc/config.yml
+     Plugins:
+	   - Users: {}
+       - Users-Identity-File:
+			store: no
+
+=cut
+
+# Your plugin goes here.  If takes any configuration or arguments, you
+# probably want to override L<Jifty::Plugin/init>.
+
+{
+    my ($CurrentUserClass, $UserClass);
+
+    sub init {
+    my $self = shift;
+    my %args = @_;
+    my $appname = Jifty->config->framework('ApplicationClass');
+    $UserClass = $args{UserClass}
+        || Jifty->app_class('Model','Users');
+    $CurrentUserClass = Jifty->app_class('CurrentUser')
+    }
+
+    sub CurrentUserClass {
+    return $CurrentUserClass;
+    }
+
+    sub UserClass {
+    return $UserClass;
+    }
+}
+
+
+1;

Added: jifty/branches/schema-plugins/plugins/Users/lib/Jifty/Plugin/Users/CurrentUser.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Users/lib/Jifty/Plugin/Users/CurrentUser.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,39 @@
+use warnings;
+use strict;
+
+
+package Jifty::Plugin::Users::CurrentUser;
+
+use base qw/Jifty::CurrentUser Jifty::Plugin::Users/;
+
+=head2 new PARAMHASH
+
+Instantiate a new current user object, loading the user by paramhash:
+
+   my $item = Jifty::Plugin::Login::Model::Item->new( Jifty::Plugin::Login::CurrentUser->new(email => 'user at site'));
+
+if you give the param 
+    _bootstrap => 1
+
+your object will be marked as a bootstrap user. You can use that to do an endrun around acls.
+
+=cut
+
+
+
+sub _init {
+    my $self = shift;
+    my %args = (@_);
+    my $LoginUserClass = $self->UserClass;
+
+    if (delete $args{'_bootstrap'} ) {
+        $self->is_bootstrap_user(1);
+    } elsif (keys %args) {
+        $self->user_object($LoginUserClass->new(current_user => $self));
+        $self->user_object->load_by_cols(%args);
+    }
+    $self->SUPER::_init(%args);
+}
+
+
+1;

Added: jifty/branches/schema-plugins/plugins/Users/lib/Jifty/Plugin/Users/Dispatcher.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Users/lib/Jifty/Plugin/Users/Dispatcher.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,9 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Users::Dispatcher;
+use Jifty::Dispatcher -base;
+
+# Put any plugin-specific dispatcher rules here.
+
+1;

Added: jifty/branches/schema-plugins/plugins/Users/lib/Jifty/Plugin/Users/Model/User.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/plugins/Users/lib/Jifty/Plugin/Users/Model/User.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,141 @@
+package Jifty::Plugin::Users::Model::User;
+use base qw/Jifty::Record Jifty::Plugin::Users/;
+use Jifty::DBI::Schema;
+
+use Jifty::Record schema {
+column
+  display_name => type is 'text',
+  label is 'Name',
+  is mandatory;
+
+column
+  realm => type is 'text',
+  label is 'Identity Plugin',
+  is mandatory;
+
+column
+  created_date => type is 'date';
+
+column
+  updated_date => type is 'date';
+
+column
+  created_by => type is 'text';
+
+column
+  updated_by => type is 'text';
+
+column
+  last_login => type is 'date';
+
+## ???
+column
+  auth_token => type is 'text',
+  render_as 'Unrendered';
+};
+
+
+sub create {
+    my $self  = shift;
+    my %args  = (@_);
+    my (@ret) = $self->SUPER::create(%args);
+ # set  created_date and created_by
+	return (@ret);
+}
+
+=head2 password_is STRING
+
+Returns true if and only if the current user's password matches STRING
+
+=cut
+
+#sub password_is {
+#    my $self   = shift;
+#    my $string = shift;
+#    return 1 if ( $self->_value('password') eq $string );
+#    return 0;
+#}
+
+=head2 password
+
+Never display a password
+
+=cut
+
+sub password {
+    return undef;
+
+}
+
+=head2 current_user_can
+
+Allows the current user to see all their own attributes and
+everyone else to see their username.
+
+Allows the current user to update any of their own attributes
+except whether or not their email has been confirmed.
+
+Passes everything else off to the superclass.
+
+=cut
+
+sub current_user_can {
+    my $self  = shift;
+    my $right = shift;
+    my %args  = (@_);
+    # This line breaks admin mode. I like admin mode.
+    #    Carp::confess if ( $right eq 'read' and not $args{'column'} );
+    if (    $right eq 'read'
+        and $self->id == $self->current_user->id )
+    {
+        return 1;
+    }
+    elsif ( $right eq 'read' and $args{'column'} eq 'display_name' ) {
+        return (1);
+
+    }
+    elsif ( $right eq 'update'
+        and $self->id == $self->current_user->id
+#        and $args{'column'} ne 'email_confirmed' 
+		)
+    {
+        return (1);
+    }
+
+    return $self->SUPER::current_user_can( $right, %args );
+}
+
+=head2 auth_token
+
+Returns the user's unique authentication token. If the user 
+doesn't have one, sets one and returns it.
+
+=cut
+
+sub auth_token {
+    my $self = shift;
+    return undef
+      unless ( $self->current_user_can( read => column => 'auth_token' ) );
+    my $value = $self->_value('auth_token');
+    unless ($value) {
+        my $digest = Digest::MD5->new();
+        $digest->add( rand(100) );
+        $self->__set( column => 'auth_token', value => $digest->b64digest );
+    }
+    return $self->_value('auth_token');
+
+}
+
+=head2 record_class
+
+Identifies the correct record class for introspection
+
+=cut
+
+sub record_class {
+    my $self = shift;
+    return $self->UserClass;
+
+}
+
+1;

Added: jifty/branches/schema-plugins/share/dtd/xhtml-lat1.ent
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/dtd/xhtml-lat1.ent	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,196 @@
+<!-- Portions (C) International Organization for Standardization 1986
+     Permission to copy in any form is granted for use with
+     conforming SGML systems and applications as defined in
+     ISO 8879, provided this notice is included in all copies.
+-->
+<!-- Character entity set. Typical invocation:
+    <!ENTITY % HTMLlat1 PUBLIC
+       "-//W3C//ENTITIES Latin 1 for XHTML//EN"
+       "http://www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent">
+    %HTMLlat1;
+-->
+
+<!ENTITY nbsp   "&#160;"> <!-- no-break space = non-breaking space,
+                                  U+00A0 ISOnum -->
+<!ENTITY iexcl  "&#161;"> <!-- inverted exclamation mark, U+00A1 ISOnum -->
+<!ENTITY cent   "&#162;"> <!-- cent sign, U+00A2 ISOnum -->
+<!ENTITY pound  "&#163;"> <!-- pound sign, U+00A3 ISOnum -->
+<!ENTITY curren "&#164;"> <!-- currency sign, U+00A4 ISOnum -->
+<!ENTITY yen    "&#165;"> <!-- yen sign = yuan sign, U+00A5 ISOnum -->
+<!ENTITY brvbar "&#166;"> <!-- broken bar = broken vertical bar,
+                                  U+00A6 ISOnum -->
+<!ENTITY sect   "&#167;"> <!-- section sign, U+00A7 ISOnum -->
+<!ENTITY uml    "&#168;"> <!-- diaeresis = spacing diaeresis,
+                                  U+00A8 ISOdia -->
+<!ENTITY copy   "&#169;"> <!-- copyright sign, U+00A9 ISOnum -->
+<!ENTITY ordf   "&#170;"> <!-- feminine ordinal indicator, U+00AA ISOnum -->
+<!ENTITY laquo  "&#171;"> <!-- left-pointing double angle quotation mark
+                                  = left pointing guillemet, U+00AB ISOnum -->
+<!ENTITY not    "&#172;"> <!-- not sign = angled dash,
+                                  U+00AC ISOnum -->
+<!ENTITY shy    "&#173;"> <!-- soft hyphen = discretionary hyphen,
+                                  U+00AD ISOnum -->
+<!ENTITY reg    "&#174;"> <!-- registered sign = registered trade mark sign,
+                                  U+00AE ISOnum -->
+<!ENTITY macr   "&#175;"> <!-- macron = spacing macron = overline
+                                  = APL overbar, U+00AF ISOdia -->
+<!ENTITY deg    "&#176;"> <!-- degree sign, U+00B0 ISOnum -->
+<!ENTITY plusmn "&#177;"> <!-- plus-minus sign = plus-or-minus sign,
+                                  U+00B1 ISOnum -->
+<!ENTITY sup2   "&#178;"> <!-- superscript two = superscript digit two
+                                  = squared, U+00B2 ISOnum -->
+<!ENTITY sup3   "&#179;"> <!-- superscript three = superscript digit three
+                                  = cubed, U+00B3 ISOnum -->
+<!ENTITY acute  "&#180;"> <!-- acute accent = spacing acute,
+                                  U+00B4 ISOdia -->
+<!ENTITY micro  "&#181;"> <!-- micro sign, U+00B5 ISOnum -->
+<!ENTITY para   "&#182;"> <!-- pilcrow sign = paragraph sign,
+                                  U+00B6 ISOnum -->
+<!ENTITY middot "&#183;"> <!-- middle dot = Georgian comma
+                                  = Greek middle dot, U+00B7 ISOnum -->
+<!ENTITY cedil  "&#184;"> <!-- cedilla = spacing cedilla, U+00B8 ISOdia -->
+<!ENTITY sup1   "&#185;"> <!-- superscript one = superscript digit one,
+                                  U+00B9 ISOnum -->
+<!ENTITY ordm   "&#186;"> <!-- masculine ordinal indicator,
+                                  U+00BA ISOnum -->
+<!ENTITY raquo  "&#187;"> <!-- right-pointing double angle quotation mark
+                                  = right pointing guillemet, U+00BB ISOnum -->
+<!ENTITY frac14 "&#188;"> <!-- vulgar fraction one quarter
+                                  = fraction one quarter, U+00BC ISOnum -->
+<!ENTITY frac12 "&#189;"> <!-- vulgar fraction one half
+                                  = fraction one half, U+00BD ISOnum -->
+<!ENTITY frac34 "&#190;"> <!-- vulgar fraction three quarters
+                                  = fraction three quarters, U+00BE ISOnum -->
+<!ENTITY iquest "&#191;"> <!-- inverted question mark
+                                  = turned question mark, U+00BF ISOnum -->
+<!ENTITY Agrave "&#192;"> <!-- latin capital letter A with grave
+                                  = latin capital letter A grave,
+                                  U+00C0 ISOlat1 -->
+<!ENTITY Aacute "&#193;"> <!-- latin capital letter A with acute,
+                                  U+00C1 ISOlat1 -->
+<!ENTITY Acirc  "&#194;"> <!-- latin capital letter A with circumflex,
+                                  U+00C2 ISOlat1 -->
+<!ENTITY Atilde "&#195;"> <!-- latin capital letter A with tilde,
+                                  U+00C3 ISOlat1 -->
+<!ENTITY Auml   "&#196;"> <!-- latin capital letter A with diaeresis,
+                                  U+00C4 ISOlat1 -->
+<!ENTITY Aring  "&#197;"> <!-- latin capital letter A with ring above
+                                  = latin capital letter A ring,
+                                  U+00C5 ISOlat1 -->
+<!ENTITY AElig  "&#198;"> <!-- latin capital letter AE
+                                  = latin capital ligature AE,
+                                  U+00C6 ISOlat1 -->
+<!ENTITY Ccedil "&#199;"> <!-- latin capital letter C with cedilla,
+                                  U+00C7 ISOlat1 -->
+<!ENTITY Egrave "&#200;"> <!-- latin capital letter E with grave,
+                                  U+00C8 ISOlat1 -->
+<!ENTITY Eacute "&#201;"> <!-- latin capital letter E with acute,
+                                  U+00C9 ISOlat1 -->
+<!ENTITY Ecirc  "&#202;"> <!-- latin capital letter E with circumflex,
+                                  U+00CA ISOlat1 -->
+<!ENTITY Euml   "&#203;"> <!-- latin capital letter E with diaeresis,
+                                  U+00CB ISOlat1 -->
+<!ENTITY Igrave "&#204;"> <!-- latin capital letter I with grave,
+                                  U+00CC ISOlat1 -->
+<!ENTITY Iacute "&#205;"> <!-- latin capital letter I with acute,
+                                  U+00CD ISOlat1 -->
+<!ENTITY Icirc  "&#206;"> <!-- latin capital letter I with circumflex,
+                                  U+00CE ISOlat1 -->
+<!ENTITY Iuml   "&#207;"> <!-- latin capital letter I with diaeresis,
+                                  U+00CF ISOlat1 -->
+<!ENTITY ETH    "&#208;"> <!-- latin capital letter ETH, U+00D0 ISOlat1 -->
+<!ENTITY Ntilde "&#209;"> <!-- latin capital letter N with tilde,
+                                  U+00D1 ISOlat1 -->
+<!ENTITY Ograve "&#210;"> <!-- latin capital letter O with grave,
+                                  U+00D2 ISOlat1 -->
+<!ENTITY Oacute "&#211;"> <!-- latin capital letter O with acute,
+                                  U+00D3 ISOlat1 -->
+<!ENTITY Ocirc  "&#212;"> <!-- latin capital letter O with circumflex,
+                                  U+00D4 ISOlat1 -->
+<!ENTITY Otilde "&#213;"> <!-- latin capital letter O with tilde,
+                                  U+00D5 ISOlat1 -->
+<!ENTITY Ouml   "&#214;"> <!-- latin capital letter O with diaeresis,
+                                  U+00D6 ISOlat1 -->
+<!ENTITY times  "&#215;"> <!-- multiplication sign, U+00D7 ISOnum -->
+<!ENTITY Oslash "&#216;"> <!-- latin capital letter O with stroke
+                                  = latin capital letter O slash,
+                                  U+00D8 ISOlat1 -->
+<!ENTITY Ugrave "&#217;"> <!-- latin capital letter U with grave,
+                                  U+00D9 ISOlat1 -->
+<!ENTITY Uacute "&#218;"> <!-- latin capital letter U with acute,
+                                  U+00DA ISOlat1 -->
+<!ENTITY Ucirc  "&#219;"> <!-- latin capital letter U with circumflex,
+                                  U+00DB ISOlat1 -->
+<!ENTITY Uuml   "&#220;"> <!-- latin capital letter U with diaeresis,
+                                  U+00DC ISOlat1 -->
+<!ENTITY Yacute "&#221;"> <!-- latin capital letter Y with acute,
+                                  U+00DD ISOlat1 -->
+<!ENTITY THORN  "&#222;"> <!-- latin capital letter THORN,
+                                  U+00DE ISOlat1 -->
+<!ENTITY szlig  "&#223;"> <!-- latin small letter sharp s = ess-zed,
+                                  U+00DF ISOlat1 -->
+<!ENTITY agrave "&#224;"> <!-- latin small letter a with grave
+                                  = latin small letter a grave,
+                                  U+00E0 ISOlat1 -->
+<!ENTITY aacute "&#225;"> <!-- latin small letter a with acute,
+                                  U+00E1 ISOlat1 -->
+<!ENTITY acirc  "&#226;"> <!-- latin small letter a with circumflex,
+                                  U+00E2 ISOlat1 -->
+<!ENTITY atilde "&#227;"> <!-- latin small letter a with tilde,
+                                  U+00E3 ISOlat1 -->
+<!ENTITY auml   "&#228;"> <!-- latin small letter a with diaeresis,
+                                  U+00E4 ISOlat1 -->
+<!ENTITY aring  "&#229;"> <!-- latin small letter a with ring above
+                                  = latin small letter a ring,
+                                  U+00E5 ISOlat1 -->
+<!ENTITY aelig  "&#230;"> <!-- latin small letter ae
+                                  = latin small ligature ae, U+00E6 ISOlat1 -->
+<!ENTITY ccedil "&#231;"> <!-- latin small letter c with cedilla,
+                                  U+00E7 ISOlat1 -->
+<!ENTITY egrave "&#232;"> <!-- latin small letter e with grave,
+                                  U+00E8 ISOlat1 -->
+<!ENTITY eacute "&#233;"> <!-- latin small letter e with acute,
+                                  U+00E9 ISOlat1 -->
+<!ENTITY ecirc  "&#234;"> <!-- latin small letter e with circumflex,
+                                  U+00EA ISOlat1 -->
+<!ENTITY euml   "&#235;"> <!-- latin small letter e with diaeresis,
+                                  U+00EB ISOlat1 -->
+<!ENTITY igrave "&#236;"> <!-- latin small letter i with grave,
+                                  U+00EC ISOlat1 -->
+<!ENTITY iacute "&#237;"> <!-- latin small letter i with acute,
+                                  U+00ED ISOlat1 -->
+<!ENTITY icirc  "&#238;"> <!-- latin small letter i with circumflex,
+                                  U+00EE ISOlat1 -->
+<!ENTITY iuml   "&#239;"> <!-- latin small letter i with diaeresis,
+                                  U+00EF ISOlat1 -->
+<!ENTITY eth    "&#240;"> <!-- latin small letter eth, U+00F0 ISOlat1 -->
+<!ENTITY ntilde "&#241;"> <!-- latin small letter n with tilde,
+                                  U+00F1 ISOlat1 -->
+<!ENTITY ograve "&#242;"> <!-- latin small letter o with grave,
+                                  U+00F2 ISOlat1 -->
+<!ENTITY oacute "&#243;"> <!-- latin small letter o with acute,
+                                  U+00F3 ISOlat1 -->
+<!ENTITY ocirc  "&#244;"> <!-- latin small letter o with circumflex,
+                                  U+00F4 ISOlat1 -->
+<!ENTITY otilde "&#245;"> <!-- latin small letter o with tilde,
+                                  U+00F5 ISOlat1 -->
+<!ENTITY ouml   "&#246;"> <!-- latin small letter o with diaeresis,
+                                  U+00F6 ISOlat1 -->
+<!ENTITY divide "&#247;"> <!-- division sign, U+00F7 ISOnum -->
+<!ENTITY oslash "&#248;"> <!-- latin small letter o with stroke,
+                                  = latin small letter o slash,
+                                  U+00F8 ISOlat1 -->
+<!ENTITY ugrave "&#249;"> <!-- latin small letter u with grave,
+                                  U+00F9 ISOlat1 -->
+<!ENTITY uacute "&#250;"> <!-- latin small letter u with acute,
+                                  U+00FA ISOlat1 -->
+<!ENTITY ucirc  "&#251;"> <!-- latin small letter u with circumflex,
+                                  U+00FB ISOlat1 -->
+<!ENTITY uuml   "&#252;"> <!-- latin small letter u with diaeresis,
+                                  U+00FC ISOlat1 -->
+<!ENTITY yacute "&#253;"> <!-- latin small letter y with acute,
+                                  U+00FD ISOlat1 -->
+<!ENTITY thorn  "&#254;"> <!-- latin small letter thorn,
+                                  U+00FE ISOlat1 -->
+<!ENTITY yuml   "&#255;"> <!-- latin small letter y with diaeresis,
+                                  U+00FF ISOlat1 -->

Added: jifty/branches/schema-plugins/share/dtd/xhtml-special.ent
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/dtd/xhtml-special.ent	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,80 @@
+<!-- Special characters for XHTML -->
+
+<!-- Character entity set. Typical invocation:
+     <!ENTITY % HTMLspecial PUBLIC
+        "-//W3C//ENTITIES Special for XHTML//EN"
+        "http://www.w3.org/TR/xhtml1/DTD/xhtml-special.ent">
+     %HTMLspecial;
+-->
+
+<!-- Portions (C) International Organization for Standardization 1986:
+     Permission to copy in any form is granted for use with
+     conforming SGML systems and applications as defined in
+     ISO 8879, provided this notice is included in all copies.
+-->
+
+<!-- Relevant ISO entity set is given unless names are newly introduced.
+     New names (i.e., not in ISO 8879 list) do not clash with any
+     existing ISO 8879 entity names. ISO 10646 character numbers
+     are given for each character, in hex. values are decimal
+     conversions of the ISO 10646 values and refer to the document
+     character set. Names are Unicode names. 
+-->
+
+<!-- C0 Controls and Basic Latin -->
+<!ENTITY quot    "&#34;"> <!--  quotation mark, U+0022 ISOnum -->
+<!ENTITY amp     "&#38;#38;"> <!--  ampersand, U+0026 ISOnum -->
+<!ENTITY lt      "&#38;#60;"> <!--  less-than sign, U+003C ISOnum -->
+<!ENTITY gt      "&#62;"> <!--  greater-than sign, U+003E ISOnum -->
+<!ENTITY apos	 "&#39;"> <!--  apostrophe = APL quote, U+0027 ISOnum -->
+
+<!-- Latin Extended-A -->
+<!ENTITY OElig   "&#338;"> <!--  latin capital ligature OE,
+                                    U+0152 ISOlat2 -->
+<!ENTITY oelig   "&#339;"> <!--  latin small ligature oe, U+0153 ISOlat2 -->
+<!-- ligature is a misnomer, this is a separate character in some languages -->
+<!ENTITY Scaron  "&#352;"> <!--  latin capital letter S with caron,
+                                    U+0160 ISOlat2 -->
+<!ENTITY scaron  "&#353;"> <!--  latin small letter s with caron,
+                                    U+0161 ISOlat2 -->
+<!ENTITY Yuml    "&#376;"> <!--  latin capital letter Y with diaeresis,
+                                    U+0178 ISOlat2 -->
+
+<!-- Spacing Modifier Letters -->
+<!ENTITY circ    "&#710;"> <!--  modifier letter circumflex accent,
+                                    U+02C6 ISOpub -->
+<!ENTITY tilde   "&#732;"> <!--  small tilde, U+02DC ISOdia -->
+
+<!-- General Punctuation -->
+<!ENTITY ensp    "&#8194;"> <!-- en space, U+2002 ISOpub -->
+<!ENTITY emsp    "&#8195;"> <!-- em space, U+2003 ISOpub -->
+<!ENTITY thinsp  "&#8201;"> <!-- thin space, U+2009 ISOpub -->
+<!ENTITY zwnj    "&#8204;"> <!-- zero width non-joiner,
+                                    U+200C NEW RFC 2070 -->
+<!ENTITY zwj     "&#8205;"> <!-- zero width joiner, U+200D NEW RFC 2070 -->
+<!ENTITY lrm     "&#8206;"> <!-- left-to-right mark, U+200E NEW RFC 2070 -->
+<!ENTITY rlm     "&#8207;"> <!-- right-to-left mark, U+200F NEW RFC 2070 -->
+<!ENTITY ndash   "&#8211;"> <!-- en dash, U+2013 ISOpub -->
+<!ENTITY mdash   "&#8212;"> <!-- em dash, U+2014 ISOpub -->
+<!ENTITY lsquo   "&#8216;"> <!-- left single quotation mark,
+                                    U+2018 ISOnum -->
+<!ENTITY rsquo   "&#8217;"> <!-- right single quotation mark,
+                                    U+2019 ISOnum -->
+<!ENTITY sbquo   "&#8218;"> <!-- single low-9 quotation mark, U+201A NEW -->
+<!ENTITY ldquo   "&#8220;"> <!-- left double quotation mark,
+                                    U+201C ISOnum -->
+<!ENTITY rdquo   "&#8221;"> <!-- right double quotation mark,
+                                    U+201D ISOnum -->
+<!ENTITY bdquo   "&#8222;"> <!-- double low-9 quotation mark, U+201E NEW -->
+<!ENTITY dagger  "&#8224;"> <!-- dagger, U+2020 ISOpub -->
+<!ENTITY Dagger  "&#8225;"> <!-- double dagger, U+2021 ISOpub -->
+<!ENTITY permil  "&#8240;"> <!-- per mille sign, U+2030 ISOtech -->
+<!ENTITY lsaquo  "&#8249;"> <!-- single left-pointing angle quotation mark,
+                                    U+2039 ISO proposed -->
+<!-- lsaquo is proposed but not yet ISO standardized -->
+<!ENTITY rsaquo  "&#8250;"> <!-- single right-pointing angle quotation mark,
+                                    U+203A ISO proposed -->
+<!-- rsaquo is proposed but not yet ISO standardized -->
+
+<!-- Currency Symbols -->
+<!ENTITY euro   "&#8364;"> <!--  euro sign, U+20AC NEW -->

Added: jifty/branches/schema-plugins/share/dtd/xhtml-symbol.ent
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/dtd/xhtml-symbol.ent	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,237 @@
+<!-- Mathematical, Greek and Symbolic characters for XHTML -->
+
+<!-- Character entity set. Typical invocation:
+     <!ENTITY % HTMLsymbol PUBLIC
+        "-//W3C//ENTITIES Symbols for XHTML//EN"
+        "http://www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent">
+     %HTMLsymbol;
+-->
+
+<!-- Portions (C) International Organization for Standardization 1986:
+     Permission to copy in any form is granted for use with
+     conforming SGML systems and applications as defined in
+     ISO 8879, provided this notice is included in all copies.
+-->
+
+<!-- Relevant ISO entity set is given unless names are newly introduced.
+     New names (i.e., not in ISO 8879 list) do not clash with any
+     existing ISO 8879 entity names. ISO 10646 character numbers
+     are given for each character, in hex. values are decimal
+     conversions of the ISO 10646 values and refer to the document
+     character set. Names are Unicode names. 
+-->
+
+<!-- Latin Extended-B -->
+<!ENTITY fnof     "&#402;"> <!-- latin small letter f with hook = function
+                                    = florin, U+0192 ISOtech -->
+
+<!-- Greek -->
+<!ENTITY Alpha    "&#913;"> <!-- greek capital letter alpha, U+0391 -->
+<!ENTITY Beta     "&#914;"> <!-- greek capital letter beta, U+0392 -->
+<!ENTITY Gamma    "&#915;"> <!-- greek capital letter gamma,
+                                    U+0393 ISOgrk3 -->
+<!ENTITY Delta    "&#916;"> <!-- greek capital letter delta,
+                                    U+0394 ISOgrk3 -->
+<!ENTITY Epsilon  "&#917;"> <!-- greek capital letter epsilon, U+0395 -->
+<!ENTITY Zeta     "&#918;"> <!-- greek capital letter zeta, U+0396 -->
+<!ENTITY Eta      "&#919;"> <!-- greek capital letter eta, U+0397 -->
+<!ENTITY Theta    "&#920;"> <!-- greek capital letter theta,
+                                    U+0398 ISOgrk3 -->
+<!ENTITY Iota     "&#921;"> <!-- greek capital letter iota, U+0399 -->
+<!ENTITY Kappa    "&#922;"> <!-- greek capital letter kappa, U+039A -->
+<!ENTITY Lambda   "&#923;"> <!-- greek capital letter lamda,
+                                    U+039B ISOgrk3 -->
+<!ENTITY Mu       "&#924;"> <!-- greek capital letter mu, U+039C -->
+<!ENTITY Nu       "&#925;"> <!-- greek capital letter nu, U+039D -->
+<!ENTITY Xi       "&#926;"> <!-- greek capital letter xi, U+039E ISOgrk3 -->
+<!ENTITY Omicron  "&#927;"> <!-- greek capital letter omicron, U+039F -->
+<!ENTITY Pi       "&#928;"> <!-- greek capital letter pi, U+03A0 ISOgrk3 -->
+<!ENTITY Rho      "&#929;"> <!-- greek capital letter rho, U+03A1 -->
+<!-- there is no Sigmaf, and no U+03A2 character either -->
+<!ENTITY Sigma    "&#931;"> <!-- greek capital letter sigma,
+                                    U+03A3 ISOgrk3 -->
+<!ENTITY Tau      "&#932;"> <!-- greek capital letter tau, U+03A4 -->
+<!ENTITY Upsilon  "&#933;"> <!-- greek capital letter upsilon,
+                                    U+03A5 ISOgrk3 -->
+<!ENTITY Phi      "&#934;"> <!-- greek capital letter phi,
+                                    U+03A6 ISOgrk3 -->
+<!ENTITY Chi      "&#935;"> <!-- greek capital letter chi, U+03A7 -->
+<!ENTITY Psi      "&#936;"> <!-- greek capital letter psi,
+                                    U+03A8 ISOgrk3 -->
+<!ENTITY Omega    "&#937;"> <!-- greek capital letter omega,
+                                    U+03A9 ISOgrk3 -->
+
+<!ENTITY alpha    "&#945;"> <!-- greek small letter alpha,
+                                    U+03B1 ISOgrk3 -->
+<!ENTITY beta     "&#946;"> <!-- greek small letter beta, U+03B2 ISOgrk3 -->
+<!ENTITY gamma    "&#947;"> <!-- greek small letter gamma,
+                                    U+03B3 ISOgrk3 -->
+<!ENTITY delta    "&#948;"> <!-- greek small letter delta,
+                                    U+03B4 ISOgrk3 -->
+<!ENTITY epsilon  "&#949;"> <!-- greek small letter epsilon,
+                                    U+03B5 ISOgrk3 -->
+<!ENTITY zeta     "&#950;"> <!-- greek small letter zeta, U+03B6 ISOgrk3 -->
+<!ENTITY eta      "&#951;"> <!-- greek small letter eta, U+03B7 ISOgrk3 -->
+<!ENTITY theta    "&#952;"> <!-- greek small letter theta,
+                                    U+03B8 ISOgrk3 -->
+<!ENTITY iota     "&#953;"> <!-- greek small letter iota, U+03B9 ISOgrk3 -->
+<!ENTITY kappa    "&#954;"> <!-- greek small letter kappa,
+                                    U+03BA ISOgrk3 -->
+<!ENTITY lambda   "&#955;"> <!-- greek small letter lamda,
+                                    U+03BB ISOgrk3 -->
+<!ENTITY mu       "&#956;"> <!-- greek small letter mu, U+03BC ISOgrk3 -->
+<!ENTITY nu       "&#957;"> <!-- greek small letter nu, U+03BD ISOgrk3 -->
+<!ENTITY xi       "&#958;"> <!-- greek small letter xi, U+03BE ISOgrk3 -->
+<!ENTITY omicron  "&#959;"> <!-- greek small letter omicron, U+03BF NEW -->
+<!ENTITY pi       "&#960;"> <!-- greek small letter pi, U+03C0 ISOgrk3 -->
+<!ENTITY rho      "&#961;"> <!-- greek small letter rho, U+03C1 ISOgrk3 -->
+<!ENTITY sigmaf   "&#962;"> <!-- greek small letter final sigma,
+                                    U+03C2 ISOgrk3 -->
+<!ENTITY sigma    "&#963;"> <!-- greek small letter sigma,
+                                    U+03C3 ISOgrk3 -->
+<!ENTITY tau      "&#964;"> <!-- greek small letter tau, U+03C4 ISOgrk3 -->
+<!ENTITY upsilon  "&#965;"> <!-- greek small letter upsilon,
+                                    U+03C5 ISOgrk3 -->
+<!ENTITY phi      "&#966;"> <!-- greek small letter phi, U+03C6 ISOgrk3 -->
+<!ENTITY chi      "&#967;"> <!-- greek small letter chi, U+03C7 ISOgrk3 -->
+<!ENTITY psi      "&#968;"> <!-- greek small letter psi, U+03C8 ISOgrk3 -->
+<!ENTITY omega    "&#969;"> <!-- greek small letter omega,
+                                    U+03C9 ISOgrk3 -->
+<!ENTITY thetasym "&#977;"> <!-- greek theta symbol,
+                                    U+03D1 NEW -->
+<!ENTITY upsih    "&#978;"> <!-- greek upsilon with hook symbol,
+                                    U+03D2 NEW -->
+<!ENTITY piv      "&#982;"> <!-- greek pi symbol, U+03D6 ISOgrk3 -->
+
+<!-- General Punctuation -->
+<!ENTITY bull     "&#8226;"> <!-- bullet = black small circle,
+                                     U+2022 ISOpub  -->
+<!-- bullet is NOT the same as bullet operator, U+2219 -->
+<!ENTITY hellip   "&#8230;"> <!-- horizontal ellipsis = three dot leader,
+                                     U+2026 ISOpub  -->
+<!ENTITY prime    "&#8242;"> <!-- prime = minutes = feet, U+2032 ISOtech -->
+<!ENTITY Prime    "&#8243;"> <!-- double prime = seconds = inches,
+                                     U+2033 ISOtech -->
+<!ENTITY oline    "&#8254;"> <!-- overline = spacing overscore,
+                                     U+203E NEW -->
+<!ENTITY frasl    "&#8260;"> <!-- fraction slash, U+2044 NEW -->
+
+<!-- Letterlike Symbols -->
+<!ENTITY weierp   "&#8472;"> <!-- script capital P = power set
+                                     = Weierstrass p, U+2118 ISOamso -->
+<!ENTITY image    "&#8465;"> <!-- black-letter capital I = imaginary part,
+                                     U+2111 ISOamso -->
+<!ENTITY real     "&#8476;"> <!-- black-letter capital R = real part symbol,
+                                     U+211C ISOamso -->
+<!ENTITY trade    "&#8482;"> <!-- trade mark sign, U+2122 ISOnum -->
+<!ENTITY alefsym  "&#8501;"> <!-- alef symbol = first transfinite cardinal,
+                                     U+2135 NEW -->
+<!-- alef symbol is NOT the same as hebrew letter alef,
+     U+05D0 although the same glyph could be used to depict both characters -->
+
+<!-- Arrows -->
+<!ENTITY larr     "&#8592;"> <!-- leftwards arrow, U+2190 ISOnum -->
+<!ENTITY uarr     "&#8593;"> <!-- upwards arrow, U+2191 ISOnum-->
+<!ENTITY rarr     "&#8594;"> <!-- rightwards arrow, U+2192 ISOnum -->
+<!ENTITY darr     "&#8595;"> <!-- downwards arrow, U+2193 ISOnum -->
+<!ENTITY harr     "&#8596;"> <!-- left right arrow, U+2194 ISOamsa -->
+<!ENTITY crarr    "&#8629;"> <!-- downwards arrow with corner leftwards
+                                     = carriage return, U+21B5 NEW -->
+<!ENTITY lArr     "&#8656;"> <!-- leftwards double arrow, U+21D0 ISOtech -->
+<!-- Unicode does not say that lArr is the same as the 'is implied by' arrow
+    but also does not have any other character for that function. So lArr can
+    be used for 'is implied by' as ISOtech suggests -->
+<!ENTITY uArr     "&#8657;"> <!-- upwards double arrow, U+21D1 ISOamsa -->
+<!ENTITY rArr     "&#8658;"> <!-- rightwards double arrow,
+                                     U+21D2 ISOtech -->
+<!-- Unicode does not say this is the 'implies' character but does not have 
+     another character with this function so rArr can be used for 'implies'
+     as ISOtech suggests -->
+<!ENTITY dArr     "&#8659;"> <!-- downwards double arrow, U+21D3 ISOamsa -->
+<!ENTITY hArr     "&#8660;"> <!-- left right double arrow,
+                                     U+21D4 ISOamsa -->
+
+<!-- Mathematical Operators -->
+<!ENTITY forall   "&#8704;"> <!-- for all, U+2200 ISOtech -->
+<!ENTITY part     "&#8706;"> <!-- partial differential, U+2202 ISOtech  -->
+<!ENTITY exist    "&#8707;"> <!-- there exists, U+2203 ISOtech -->
+<!ENTITY empty    "&#8709;"> <!-- empty set = null set, U+2205 ISOamso -->
+<!ENTITY nabla    "&#8711;"> <!-- nabla = backward difference,
+                                     U+2207 ISOtech -->
+<!ENTITY isin     "&#8712;"> <!-- element of, U+2208 ISOtech -->
+<!ENTITY notin    "&#8713;"> <!-- not an element of, U+2209 ISOtech -->
+<!ENTITY ni       "&#8715;"> <!-- contains as member, U+220B ISOtech -->
+<!ENTITY prod     "&#8719;"> <!-- n-ary product = product sign,
+                                     U+220F ISOamsb -->
+<!-- prod is NOT the same character as U+03A0 'greek capital letter pi' though
+     the same glyph might be used for both -->
+<!ENTITY sum      "&#8721;"> <!-- n-ary summation, U+2211 ISOamsb -->
+<!-- sum is NOT the same character as U+03A3 'greek capital letter sigma'
+     though the same glyph might be used for both -->
+<!ENTITY minus    "&#8722;"> <!-- minus sign, U+2212 ISOtech -->
+<!ENTITY lowast   "&#8727;"> <!-- asterisk operator, U+2217 ISOtech -->
+<!ENTITY radic    "&#8730;"> <!-- square root = radical sign,
+                                     U+221A ISOtech -->
+<!ENTITY prop     "&#8733;"> <!-- proportional to, U+221D ISOtech -->
+<!ENTITY infin    "&#8734;"> <!-- infinity, U+221E ISOtech -->
+<!ENTITY ang      "&#8736;"> <!-- angle, U+2220 ISOamso -->
+<!ENTITY and      "&#8743;"> <!-- logical and = wedge, U+2227 ISOtech -->
+<!ENTITY or       "&#8744;"> <!-- logical or = vee, U+2228 ISOtech -->
+<!ENTITY cap      "&#8745;"> <!-- intersection = cap, U+2229 ISOtech -->
+<!ENTITY cup      "&#8746;"> <!-- union = cup, U+222A ISOtech -->
+<!ENTITY int      "&#8747;"> <!-- integral, U+222B ISOtech -->
+<!ENTITY there4   "&#8756;"> <!-- therefore, U+2234 ISOtech -->
+<!ENTITY sim      "&#8764;"> <!-- tilde operator = varies with = similar to,
+                                     U+223C ISOtech -->
+<!-- tilde operator is NOT the same character as the tilde, U+007E,
+     although the same glyph might be used to represent both  -->
+<!ENTITY cong     "&#8773;"> <!-- approximately equal to, U+2245 ISOtech -->
+<!ENTITY asymp    "&#8776;"> <!-- almost equal to = asymptotic to,
+                                     U+2248 ISOamsr -->
+<!ENTITY ne       "&#8800;"> <!-- not equal to, U+2260 ISOtech -->
+<!ENTITY equiv    "&#8801;"> <!-- identical to, U+2261 ISOtech -->
+<!ENTITY le       "&#8804;"> <!-- less-than or equal to, U+2264 ISOtech -->
+<!ENTITY ge       "&#8805;"> <!-- greater-than or equal to,
+                                     U+2265 ISOtech -->
+<!ENTITY sub      "&#8834;"> <!-- subset of, U+2282 ISOtech -->
+<!ENTITY sup      "&#8835;"> <!-- superset of, U+2283 ISOtech -->
+<!ENTITY nsub     "&#8836;"> <!-- not a subset of, U+2284 ISOamsn -->
+<!ENTITY sube     "&#8838;"> <!-- subset of or equal to, U+2286 ISOtech -->
+<!ENTITY supe     "&#8839;"> <!-- superset of or equal to,
+                                     U+2287 ISOtech -->
+<!ENTITY oplus    "&#8853;"> <!-- circled plus = direct sum,
+                                     U+2295 ISOamsb -->
+<!ENTITY otimes   "&#8855;"> <!-- circled times = vector product,
+                                     U+2297 ISOamsb -->
+<!ENTITY perp     "&#8869;"> <!-- up tack = orthogonal to = perpendicular,
+                                     U+22A5 ISOtech -->
+<!ENTITY sdot     "&#8901;"> <!-- dot operator, U+22C5 ISOamsb -->
+<!-- dot operator is NOT the same character as U+00B7 middle dot -->
+
+<!-- Miscellaneous Technical -->
+<!ENTITY lceil    "&#8968;"> <!-- left ceiling = APL upstile,
+                                     U+2308 ISOamsc  -->
+<!ENTITY rceil    "&#8969;"> <!-- right ceiling, U+2309 ISOamsc  -->
+<!ENTITY lfloor   "&#8970;"> <!-- left floor = APL downstile,
+                                     U+230A ISOamsc  -->
+<!ENTITY rfloor   "&#8971;"> <!-- right floor, U+230B ISOamsc  -->
+<!ENTITY lang     "&#9001;"> <!-- left-pointing angle bracket = bra,
+                                     U+2329 ISOtech -->
+<!-- lang is NOT the same character as U+003C 'less than sign' 
+     or U+2039 'single left-pointing angle quotation mark' -->
+<!ENTITY rang     "&#9002;"> <!-- right-pointing angle bracket = ket,
+                                     U+232A ISOtech -->
+<!-- rang is NOT the same character as U+003E 'greater than sign' 
+     or U+203A 'single right-pointing angle quotation mark' -->
+
+<!-- Geometric Shapes -->
+<!ENTITY loz      "&#9674;"> <!-- lozenge, U+25CA ISOpub -->
+
+<!-- Miscellaneous Symbols -->
+<!ENTITY spades   "&#9824;"> <!-- black spade suit, U+2660 ISOpub -->
+<!-- black here seems to mean filled as opposed to hollow -->
+<!ENTITY clubs    "&#9827;"> <!-- black club suit = shamrock,
+                                     U+2663 ISOpub -->
+<!ENTITY hearts   "&#9829;"> <!-- black heart suit = valentine,
+                                     U+2665 ISOpub -->
+<!ENTITY diams    "&#9830;"> <!-- black diamond suit, U+2666 ISOpub -->

Added: jifty/branches/schema-plugins/share/dtd/xhtml1-strict.dtd
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/dtd/xhtml1-strict.dtd	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,978 @@
+<!--
+   Extensible HTML version 1.0 Strict DTD
+
+   This is the same as HTML 4 Strict except for
+   changes due to the differences between XML and SGML.
+
+   Namespace = http://www.w3.org/1999/xhtml
+
+   For further information, see: http://www.w3.org/TR/xhtml1
+
+   Copyright (c) 1998-2002 W3C (MIT, INRIA, Keio),
+   All Rights Reserved. 
+
+   This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+   PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+   SYSTEM "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
+
+   $Revision: 1.1 $
+   $Date: 2002/08/01 13:56:03 $
+
+-->
+
+<!--================ Character mnemonic entities =========================-->
+
+<!ENTITY % HTMLlat1 PUBLIC
+   "-//W3C//ENTITIES Latin 1 for XHTML//EN"
+   "dtd/xhtml-lat1.ent">
+%HTMLlat1;
+
+<!ENTITY % HTMLsymbol PUBLIC
+   "-//W3C//ENTITIES Symbols for XHTML//EN"
+   "dtd/xhtml-symbol.ent">
+%HTMLsymbol;
+
+<!ENTITY % HTMLspecial PUBLIC
+   "-//W3C//ENTITIES Special for XHTML//EN"
+   "dtd/xhtml-special.ent">
+%HTMLspecial;
+
+<!--================== Imported Names ====================================-->
+
+<!ENTITY % ContentType "CDATA">
+    <!-- media type, as per [RFC2045] -->
+
+<!ENTITY % ContentTypes "CDATA">
+    <!-- comma-separated list of media types, as per [RFC2045] -->
+
+<!ENTITY % Charset "CDATA">
+    <!-- a character encoding, as per [RFC2045] -->
+
+<!ENTITY % Charsets "CDATA">
+    <!-- a space separated list of character encodings, as per [RFC2045] -->
+
+<!ENTITY % LanguageCode "NMTOKEN">
+    <!-- a language code, as per [RFC3066] -->
+
+<!ENTITY % Character "CDATA">
+    <!-- a single character, as per section 2.2 of [XML] -->
+
+<!ENTITY % Number "CDATA">
+    <!-- one or more digits -->
+
+<!ENTITY % LinkTypes "CDATA">
+    <!-- space-separated list of link types -->
+
+<!ENTITY % MediaDesc "CDATA">
+    <!-- single or comma-separated list of media descriptors -->
+
+<!ENTITY % URI "CDATA">
+    <!-- a Uniform Resource Identifier, see [RFC2396] -->
+
+<!ENTITY % UriList "CDATA">
+    <!-- a space separated list of Uniform Resource Identifiers -->
+
+<!ENTITY % Datetime "CDATA">
+    <!-- date and time information. ISO date format -->
+
+<!ENTITY % Script "CDATA">
+    <!-- script expression -->
+
+<!ENTITY % StyleSheet "CDATA">
+    <!-- style sheet data -->
+
+<!ENTITY % Text "CDATA">
+    <!-- used for titles etc. -->
+
+<!ENTITY % Length "CDATA">
+    <!-- nn for pixels or nn% for percentage length -->
+
+<!ENTITY % MultiLength "CDATA">
+    <!-- pixel, percentage, or relative -->
+
+<!ENTITY % Pixels "CDATA">
+    <!-- integer representing length in pixels -->
+
+<!-- these are used for image maps -->
+
+<!ENTITY % Shape "(rect|circle|poly|default)">
+
+<!ENTITY % Coords "CDATA">
+    <!-- comma separated list of lengths -->
+
+<!--=================== Generic Attributes ===============================-->
+
+<!-- core attributes common to most elements
+  id       document-wide unique id
+  class    space separated list of classes
+  style    associated style info
+  title    advisory title/amplification
+-->
+<!ENTITY % coreattrs
+ "id          ID             #IMPLIED
+  class       CDATA          #IMPLIED
+  style       %StyleSheet;   #IMPLIED
+  title       %Text;         #IMPLIED"
+  >
+
+<!-- internationalization attributes
+  lang        language code (backwards compatible)
+  xml:lang    language code (as per XML 1.0 spec)
+  dir         direction for weak/neutral text
+-->
+<!ENTITY % i18n
+ "lang        %LanguageCode; #IMPLIED
+  xml:lang    %LanguageCode; #IMPLIED
+  dir         (ltr|rtl)      #IMPLIED"
+  >
+
+<!-- attributes for common UI events
+  onclick     a pointer button was clicked
+  ondblclick  a pointer button was double clicked
+  onmousedown a pointer button was pressed down
+  onmouseup   a pointer button was released
+  onmousemove a pointer was moved onto the element
+  onmouseout  a pointer was moved away from the element
+  onkeypress  a key was pressed and released
+  onkeydown   a key was pressed down
+  onkeyup     a key was released
+-->
+<!ENTITY % events
+ "onclick     %Script;       #IMPLIED
+  ondblclick  %Script;       #IMPLIED
+  onmousedown %Script;       #IMPLIED
+  onmouseup   %Script;       #IMPLIED
+  onmouseover %Script;       #IMPLIED
+  onmousemove %Script;       #IMPLIED
+  onmouseout  %Script;       #IMPLIED
+  onkeypress  %Script;       #IMPLIED
+  onkeydown   %Script;       #IMPLIED
+  onkeyup     %Script;       #IMPLIED"
+  >
+
+<!-- attributes for elements that can get the focus
+  accesskey   accessibility key character
+  tabindex    position in tabbing order
+  onfocus     the element got the focus
+  onblur      the element lost the focus
+-->
+<!ENTITY % focus
+ "accesskey   %Character;    #IMPLIED
+  tabindex    %Number;       #IMPLIED
+  onfocus     %Script;       #IMPLIED
+  onblur      %Script;       #IMPLIED"
+  >
+
+<!ENTITY % attrs "%coreattrs; %i18n; %events;">
+
+<!--=================== Text Elements ====================================-->
+
+<!ENTITY % special.pre
+   "br | span | bdo | map">
+
+
+<!ENTITY % special
+   "%special.pre; | object | img ">
+
+<!ENTITY % fontstyle "tt | i | b | big | small ">
+
+<!ENTITY % phrase "em | strong | dfn | code | q |
+                   samp | kbd | var | cite | abbr | acronym | sub | sup ">
+
+<!ENTITY % inline.forms "input | select | textarea | label | button">
+
+<!-- these can occur at block or inline level -->
+<!ENTITY % misc.inline "ins | del | script">
+
+<!-- these can only occur at block level -->
+<!ENTITY % misc "noscript | %misc.inline;">
+
+<!ENTITY % inline "a | %special; | %fontstyle; | %phrase; | %inline.forms;">
+
+<!-- %Inline; covers inline or "text-level" elements -->
+<!ENTITY % Inline "(#PCDATA | %inline; | %misc.inline;)*">
+
+<!--================== Block level elements ==============================-->
+
+<!ENTITY % heading "h1|h2|h3|h4|h5|h6">
+<!ENTITY % lists "ul | ol | dl">
+<!ENTITY % blocktext "pre | hr | blockquote | address">
+
+<!ENTITY % block
+     "p | %heading; | div | %lists; | %blocktext; | fieldset | table">
+
+<!ENTITY % Block "(%block; | form | %misc;)*">
+
+<!-- %Flow; mixes block and inline and is used for list items etc. -->
+<!ENTITY % Flow "(#PCDATA | %block; | form | %inline; | %misc;)*">
+
+<!--================== Content models for exclusions =====================-->
+
+<!-- a elements use %Inline; excluding a -->
+
+<!ENTITY % a.content
+   "(#PCDATA | %special; | %fontstyle; | %phrase; | %inline.forms; | %misc.inline;)*">
+
+<!-- pre uses %Inline excluding big, small, sup or sup -->
+
+<!ENTITY % pre.content
+   "(#PCDATA | a | %fontstyle; | %phrase; | %special.pre; | %misc.inline;
+      | %inline.forms;)*">
+
+<!-- form uses %Block; excluding form -->
+
+<!ENTITY % form.content "(%block; | %misc;)*">
+
+<!-- button uses %Flow; but excludes a, form and form controls -->
+
+<!ENTITY % button.content
+   "(#PCDATA | p | %heading; | div | %lists; | %blocktext; |
+    table | %special; | %fontstyle; | %phrase; | %misc;)*">
+
+<!--================ Document Structure ==================================-->
+
+<!-- the namespace URI designates the document profile -->
+
+<!ELEMENT html (head, body)>
+<!ATTLIST html
+  %i18n;
+  id          ID             #IMPLIED
+  xmlns       %URI;          #FIXED 'http://www.w3.org/1999/xhtml'
+  >
+
+<!--================ Document Head =======================================-->
+
+<!ENTITY % head.misc "(script|style|meta|link|object)*">
+
+<!-- content model is %head.misc; combined with a single
+     title and an optional base element in any order -->
+
+<!ELEMENT head (%head.misc;,
+     ((title, %head.misc;, (base, %head.misc;)?) |
+      (base, %head.misc;, (title, %head.misc;))))>
+
+<!ATTLIST head
+  %i18n;
+  id          ID             #IMPLIED
+  profile     %URI;          #IMPLIED
+  >
+
+<!-- The title element is not considered part of the flow of text.
+       It should be displayed, for example as the page header or
+       window title. Exactly one title is required per document.
+    -->
+<!ELEMENT title (#PCDATA)>
+<!ATTLIST title 
+  %i18n;
+  id          ID             #IMPLIED
+  >
+
+<!-- document base URI -->
+
+<!ELEMENT base EMPTY>
+<!ATTLIST base
+  href        %URI;          #REQUIRED
+  id          ID             #IMPLIED
+  >
+
+<!-- generic metainformation -->
+<!ELEMENT meta EMPTY>
+<!ATTLIST meta
+  %i18n;
+  id          ID             #IMPLIED
+  http-equiv  CDATA          #IMPLIED
+  name        CDATA          #IMPLIED
+  content     CDATA          #REQUIRED
+  scheme      CDATA          #IMPLIED
+  >
+
+<!--
+  Relationship values can be used in principle:
+
+   a) for document specific toolbars/menus when used
+      with the link element in document head e.g.
+        start, contents, previous, next, index, end, help
+   b) to link to a separate style sheet (rel="stylesheet")
+   c) to make a link to a script (rel="script")
+   d) by stylesheets to control how collections of
+      html nodes are rendered into printed documents
+   e) to make a link to a printable version of this document
+      e.g. a PostScript or PDF version (rel="alternate" media="print")
+-->
+
+<!ELEMENT link EMPTY>
+<!ATTLIST link
+  %attrs;
+  charset     %Charset;      #IMPLIED
+  href        %URI;          #IMPLIED
+  hreflang    %LanguageCode; #IMPLIED
+  type        %ContentType;  #IMPLIED
+  rel         %LinkTypes;    #IMPLIED
+  rev         %LinkTypes;    #IMPLIED
+  media       %MediaDesc;    #IMPLIED
+  >
+
+<!-- style info, which may include CDATA sections -->
+<!ELEMENT style (#PCDATA)>
+<!ATTLIST style
+  %i18n;
+  id          ID             #IMPLIED
+  type        %ContentType;  #REQUIRED
+  media       %MediaDesc;    #IMPLIED
+  title       %Text;         #IMPLIED
+  xml:space   (preserve)     #FIXED 'preserve'
+  >
+
+<!-- script statements, which may include CDATA sections -->
+<!ELEMENT script (#PCDATA)>
+<!ATTLIST script
+  id          ID             #IMPLIED
+  charset     %Charset;      #IMPLIED
+  type        %ContentType;  #REQUIRED
+  src         %URI;          #IMPLIED
+  defer       (defer)        #IMPLIED
+  xml:space   (preserve)     #FIXED 'preserve'
+  >
+
+<!-- alternate content container for non script-based rendering -->
+
+<!ELEMENT noscript %Block;>
+<!ATTLIST noscript
+  %attrs;
+  >
+
+<!--=================== Document Body ====================================-->
+
+<!ELEMENT body %Block;>
+<!ATTLIST body
+  %attrs;
+  onload          %Script;   #IMPLIED
+  onunload        %Script;   #IMPLIED
+  >
+
+<!ELEMENT div %Flow;>  <!-- generic language/style container -->
+<!ATTLIST div
+  %attrs;
+  >
+
+<!--=================== Paragraphs =======================================-->
+
+<!ELEMENT p %Inline;>
+<!ATTLIST p
+  %attrs;
+  >
+
+<!--=================== Headings =========================================-->
+
+<!--
+  There are six levels of headings from h1 (the most important)
+  to h6 (the least important).
+-->
+
+<!ELEMENT h1  %Inline;>
+<!ATTLIST h1
+   %attrs;
+   >
+
+<!ELEMENT h2 %Inline;>
+<!ATTLIST h2
+   %attrs;
+   >
+
+<!ELEMENT h3 %Inline;>
+<!ATTLIST h3
+   %attrs;
+   >
+
+<!ELEMENT h4 %Inline;>
+<!ATTLIST h4
+   %attrs;
+   >
+
+<!ELEMENT h5 %Inline;>
+<!ATTLIST h5
+   %attrs;
+   >
+
+<!ELEMENT h6 %Inline;>
+<!ATTLIST h6
+   %attrs;
+   >
+
+<!--=================== Lists ============================================-->
+
+<!-- Unordered list -->
+
+<!ELEMENT ul (li)+>
+<!ATTLIST ul
+  %attrs;
+  >
+
+<!-- Ordered (numbered) list -->
+
+<!ELEMENT ol (li)+>
+<!ATTLIST ol
+  %attrs;
+  >
+
+<!-- list item -->
+
+<!ELEMENT li %Flow;>
+<!ATTLIST li
+  %attrs;
+  >
+
+<!-- definition lists - dt for term, dd for its definition -->
+
+<!ELEMENT dl (dt|dd)+>
+<!ATTLIST dl
+  %attrs;
+  >
+
+<!ELEMENT dt %Inline;>
+<!ATTLIST dt
+  %attrs;
+  >
+
+<!ELEMENT dd %Flow;>
+<!ATTLIST dd
+  %attrs;
+  >
+
+<!--=================== Address ==========================================-->
+
+<!-- information on author -->
+
+<!ELEMENT address %Inline;>
+<!ATTLIST address
+  %attrs;
+  >
+
+<!--=================== Horizontal Rule ==================================-->
+
+<!ELEMENT hr EMPTY>
+<!ATTLIST hr
+  %attrs;
+  >
+
+<!--=================== Preformatted Text ================================-->
+
+<!-- content is %Inline; excluding "img|object|big|small|sub|sup" -->
+
+<!ELEMENT pre %pre.content;>
+<!ATTLIST pre
+  %attrs;
+  xml:space (preserve) #FIXED 'preserve'
+  >
+
+<!--=================== Block-like Quotes ================================-->
+
+<!ELEMENT blockquote %Block;>
+<!ATTLIST blockquote
+  %attrs;
+  cite        %URI;          #IMPLIED
+  >
+
+<!--=================== Inserted/Deleted Text ============================-->
+
+<!--
+  ins/del are allowed in block and inline content, but its
+  inappropriate to include block content within an ins element
+  occurring in inline content.
+-->
+<!ELEMENT ins %Flow;>
+<!ATTLIST ins
+  %attrs;
+  cite        %URI;          #IMPLIED
+  datetime    %Datetime;     #IMPLIED
+  >
+
+<!ELEMENT del %Flow;>
+<!ATTLIST del
+  %attrs;
+  cite        %URI;          #IMPLIED
+  datetime    %Datetime;     #IMPLIED
+  >
+
+<!--================== The Anchor Element ================================-->
+
+<!-- content is %Inline; except that anchors shouldn't be nested -->
+
+<!ELEMENT a %a.content;>
+<!ATTLIST a
+  %attrs;
+  %focus;
+  charset     %Charset;      #IMPLIED
+  type        %ContentType;  #IMPLIED
+  name        NMTOKEN        #IMPLIED
+  href        %URI;          #IMPLIED
+  hreflang    %LanguageCode; #IMPLIED
+  rel         %LinkTypes;    #IMPLIED
+  rev         %LinkTypes;    #IMPLIED
+  shape       %Shape;        "rect"
+  coords      %Coords;       #IMPLIED
+  >
+
+<!--===================== Inline Elements ================================-->
+
+<!ELEMENT span %Inline;> <!-- generic language/style container -->
+<!ATTLIST span
+  %attrs;
+  >
+
+<!ELEMENT bdo %Inline;>  <!-- I18N BiDi over-ride -->
+<!ATTLIST bdo
+  %coreattrs;
+  %events;
+  lang        %LanguageCode; #IMPLIED
+  xml:lang    %LanguageCode; #IMPLIED
+  dir         (ltr|rtl)      #REQUIRED
+  >
+
+<!ELEMENT br EMPTY>   <!-- forced line break -->
+<!ATTLIST br
+  %coreattrs;
+  >
+
+<!ELEMENT em %Inline;>   <!-- emphasis -->
+<!ATTLIST em %attrs;>
+
+<!ELEMENT strong %Inline;>   <!-- strong emphasis -->
+<!ATTLIST strong %attrs;>
+
+<!ELEMENT dfn %Inline;>   <!-- definitional -->
+<!ATTLIST dfn %attrs;>
+
+<!ELEMENT code %Inline;>   <!-- program code -->
+<!ATTLIST code %attrs;>
+
+<!ELEMENT samp %Inline;>   <!-- sample -->
+<!ATTLIST samp %attrs;>
+
+<!ELEMENT kbd %Inline;>  <!-- something user would type -->
+<!ATTLIST kbd %attrs;>
+
+<!ELEMENT var %Inline;>   <!-- variable -->
+<!ATTLIST var %attrs;>
+
+<!ELEMENT cite %Inline;>   <!-- citation -->
+<!ATTLIST cite %attrs;>
+
+<!ELEMENT abbr %Inline;>   <!-- abbreviation -->
+<!ATTLIST abbr %attrs;>
+
+<!ELEMENT acronym %Inline;>   <!-- acronym -->
+<!ATTLIST acronym %attrs;>
+
+<!ELEMENT q %Inline;>   <!-- inlined quote -->
+<!ATTLIST q
+  %attrs;
+  cite        %URI;          #IMPLIED
+  >
+
+<!ELEMENT sub %Inline;> <!-- subscript -->
+<!ATTLIST sub %attrs;>
+
+<!ELEMENT sup %Inline;> <!-- superscript -->
+<!ATTLIST sup %attrs;>
+
+<!ELEMENT tt %Inline;>   <!-- fixed pitch font -->
+<!ATTLIST tt %attrs;>
+
+<!ELEMENT i %Inline;>   <!-- italic font -->
+<!ATTLIST i %attrs;>
+
+<!ELEMENT b %Inline;>   <!-- bold font -->
+<!ATTLIST b %attrs;>
+
+<!ELEMENT big %Inline;>   <!-- bigger font -->
+<!ATTLIST big %attrs;>
+
+<!ELEMENT small %Inline;>   <!-- smaller font -->
+<!ATTLIST small %attrs;>
+
+<!--==================== Object ======================================-->
+<!--
+  object is used to embed objects as part of HTML pages.
+  param elements should precede other content. Parameters
+  can also be expressed as attribute/value pairs on the
+  object element itself when brevity is desired.
+-->
+
+<!ELEMENT object (#PCDATA | param | %block; | form | %inline; | %misc;)*>
+<!ATTLIST object
+  %attrs;
+  declare     (declare)      #IMPLIED
+  classid     %URI;          #IMPLIED
+  codebase    %URI;          #IMPLIED
+  data        %URI;          #IMPLIED
+  type        %ContentType;  #IMPLIED
+  codetype    %ContentType;  #IMPLIED
+  archive     %UriList;      #IMPLIED
+  standby     %Text;         #IMPLIED
+  height      %Length;       #IMPLIED
+  width       %Length;       #IMPLIED
+  usemap      %URI;          #IMPLIED
+  name        NMTOKEN        #IMPLIED
+  tabindex    %Number;       #IMPLIED
+  >
+
+<!--
+  param is used to supply a named property value.
+  In XML it would seem natural to follow RDF and support an
+  abbreviated syntax where the param elements are replaced
+  by attribute value pairs on the object start tag.
+-->
+<!ELEMENT param EMPTY>
+<!ATTLIST param
+  id          ID             #IMPLIED
+  name        CDATA          #IMPLIED
+  value       CDATA          #IMPLIED
+  valuetype   (data|ref|object) "data"
+  type        %ContentType;  #IMPLIED
+  >
+
+<!--=================== Images ===========================================-->
+
+<!--
+   To avoid accessibility problems for people who aren't
+   able to see the image, you should provide a text
+   description using the alt and longdesc attributes.
+   In addition, avoid the use of server-side image maps.
+   Note that in this DTD there is no name attribute. That
+   is only available in the transitional and frameset DTD.
+-->
+
+<!ELEMENT img EMPTY>
+<!ATTLIST img
+  %attrs;
+  src         %URI;          #REQUIRED
+  alt         %Text;         #REQUIRED
+  longdesc    %URI;          #IMPLIED
+  height      %Length;       #IMPLIED
+  width       %Length;       #IMPLIED
+  usemap      %URI;          #IMPLIED
+  ismap       (ismap)        #IMPLIED
+  >
+
+<!-- usemap points to a map element which may be in this document
+  or an external document, although the latter is not widely supported -->
+
+<!--================== Client-side image maps ============================-->
+
+<!-- These can be placed in the same document or grouped in a
+     separate document although this isn't yet widely supported -->
+
+<!ELEMENT map ((%block; | form | %misc;)+ | area+)>
+<!ATTLIST map
+  %i18n;
+  %events;
+  id          ID             #REQUIRED
+  class       CDATA          #IMPLIED
+  style       %StyleSheet;   #IMPLIED
+  title       %Text;         #IMPLIED
+  name        NMTOKEN        #IMPLIED
+  >
+
+<!ELEMENT area EMPTY>
+<!ATTLIST area
+  %attrs;
+  %focus;
+  shape       %Shape;        "rect"
+  coords      %Coords;       #IMPLIED
+  href        %URI;          #IMPLIED
+  nohref      (nohref)       #IMPLIED
+  alt         %Text;         #REQUIRED
+  >
+
+<!--================ Forms ===============================================-->
+<!ELEMENT form %form.content;>   <!-- forms shouldn't be nested -->
+
+<!ATTLIST form
+  %attrs;
+  action      %URI;          #REQUIRED
+  method      (get|post)     "get"
+  enctype     %ContentType;  "application/x-www-form-urlencoded"
+  onsubmit    %Script;       #IMPLIED
+  onreset     %Script;       #IMPLIED
+  accept      %ContentTypes; #IMPLIED
+  accept-charset %Charsets;  #IMPLIED
+  >
+
+<!--
+  Each label must not contain more than ONE field
+  Label elements shouldn't be nested.
+-->
+<!ELEMENT label %Inline;>
+<!ATTLIST label
+  %attrs;
+  for         IDREF          #IMPLIED
+  accesskey   %Character;    #IMPLIED
+  onfocus     %Script;       #IMPLIED
+  onblur      %Script;       #IMPLIED
+  >
+
+<!ENTITY % InputType
+  "(text | password | checkbox |
+    radio | submit | reset |
+    file | hidden | image | button)"
+   >
+
+<!-- the name attribute is required for all but submit & reset -->
+
+<!ELEMENT input EMPTY>     <!-- form control -->
+<!ATTLIST input
+  %attrs;
+  %focus;
+  type        %InputType;    "text"
+  name        CDATA          #IMPLIED
+  value       CDATA          #IMPLIED
+  checked     (checked)      #IMPLIED
+  disabled    (disabled)     #IMPLIED
+  readonly    (readonly)     #IMPLIED
+  size        CDATA          #IMPLIED
+  maxlength   %Number;       #IMPLIED
+  src         %URI;          #IMPLIED
+  alt         CDATA          #IMPLIED
+  usemap      %URI;          #IMPLIED
+  onselect    %Script;       #IMPLIED
+  onchange    %Script;       #IMPLIED
+  accept      %ContentTypes; #IMPLIED
+  >
+
+<!ELEMENT select (optgroup|option)+>  <!-- option selector -->
+<!ATTLIST select
+  %attrs;
+  name        CDATA          #IMPLIED
+  size        %Number;       #IMPLIED
+  multiple    (multiple)     #IMPLIED
+  disabled    (disabled)     #IMPLIED
+  tabindex    %Number;       #IMPLIED
+  onfocus     %Script;       #IMPLIED
+  onblur      %Script;       #IMPLIED
+  onchange    %Script;       #IMPLIED
+  >
+
+<!ELEMENT optgroup (option)+>   <!-- option group -->
+<!ATTLIST optgroup
+  %attrs;
+  disabled    (disabled)     #IMPLIED
+  label       %Text;         #REQUIRED
+  >
+
+<!ELEMENT option (#PCDATA)>     <!-- selectable choice -->
+<!ATTLIST option
+  %attrs;
+  selected    (selected)     #IMPLIED
+  disabled    (disabled)     #IMPLIED
+  label       %Text;         #IMPLIED
+  value       CDATA          #IMPLIED
+  >
+
+<!ELEMENT textarea (#PCDATA)>     <!-- multi-line text field -->
+<!ATTLIST textarea
+  %attrs;
+  %focus;
+  name        CDATA          #IMPLIED
+  rows        %Number;       #REQUIRED
+  cols        %Number;       #REQUIRED
+  disabled    (disabled)     #IMPLIED
+  readonly    (readonly)     #IMPLIED
+  onselect    %Script;       #IMPLIED
+  onchange    %Script;       #IMPLIED
+  >
+
+<!--
+  The fieldset element is used to group form fields.
+  Only one legend element should occur in the content
+  and if present should only be preceded by whitespace.
+-->
+<!ELEMENT fieldset (#PCDATA | legend | %block; | form | %inline; | %misc;)*>
+<!ATTLIST fieldset
+  %attrs;
+  >
+
+<!ELEMENT legend %Inline;>     <!-- fieldset label -->
+<!ATTLIST legend
+  %attrs;
+  accesskey   %Character;    #IMPLIED
+  >
+
+<!--
+ Content is %Flow; excluding a, form and form controls
+--> 
+<!ELEMENT button %button.content;>  <!-- push button -->
+<!ATTLIST button
+  %attrs;
+  %focus;
+  name        CDATA          #IMPLIED
+  value       CDATA          #IMPLIED
+  type        (button|submit|reset) "submit"
+  disabled    (disabled)     #IMPLIED
+  >
+
+<!--======================= Tables =======================================-->
+
+<!-- Derived from IETF HTML table standard, see [RFC1942] -->
+
+<!--
+ The border attribute sets the thickness of the frame around the
+ table. The default units are screen pixels.
+
+ The frame attribute specifies which parts of the frame around
+ the table should be rendered. The values are not the same as
+ CALS to avoid a name clash with the valign attribute.
+-->
+<!ENTITY % TFrame "(void|above|below|hsides|lhs|rhs|vsides|box|border)">
+
+<!--
+ The rules attribute defines which rules to draw between cells:
+
+ If rules is absent then assume:
+     "none" if border is absent or border="0" otherwise "all"
+-->
+
+<!ENTITY % TRules "(none | groups | rows | cols | all)">
+  
+<!-- horizontal alignment attributes for cell contents
+
+  char        alignment char, e.g. char=':'
+  charoff     offset for alignment char
+-->
+<!ENTITY % cellhalign
+  "align      (left|center|right|justify|char) #IMPLIED
+   char       %Character;    #IMPLIED
+   charoff    %Length;       #IMPLIED"
+  >
+
+<!-- vertical alignment attributes for cell contents -->
+<!ENTITY % cellvalign
+  "valign     (top|middle|bottom|baseline) #IMPLIED"
+  >
+
+<!ELEMENT table
+     (caption?, (col*|colgroup*), thead?, tfoot?, (tbody+|tr+))>
+<!ELEMENT caption  %Inline;>
+<!ELEMENT thead    (tr)+>
+<!ELEMENT tfoot    (tr)+>
+<!ELEMENT tbody    (tr)+>
+<!ELEMENT colgroup (col)*>
+<!ELEMENT col      EMPTY>
+<!ELEMENT tr       (th|td)+>
+<!ELEMENT th       %Flow;>
+<!ELEMENT td       %Flow;>
+
+<!ATTLIST table
+  %attrs;
+  summary     %Text;         #IMPLIED
+  width       %Length;       #IMPLIED
+  border      %Pixels;       #IMPLIED
+  frame       %TFrame;       #IMPLIED
+  rules       %TRules;       #IMPLIED
+  cellspacing %Length;       #IMPLIED
+  cellpadding %Length;       #IMPLIED
+  >
+
+<!ATTLIST caption
+  %attrs;
+  >
+
+<!--
+colgroup groups a set of col elements. It allows you to group
+several semantically related columns together.
+-->
+<!ATTLIST colgroup
+  %attrs;
+  span        %Number;       "1"
+  width       %MultiLength;  #IMPLIED
+  %cellhalign;
+  %cellvalign;
+  >
+
+<!--
+ col elements define the alignment properties for cells in
+ one or more columns.
+
+ The width attribute specifies the width of the columns, e.g.
+
+     width=64        width in screen pixels
+     width=0.5*      relative width of 0.5
+
+ The span attribute causes the attributes of one
+ col element to apply to more than one column.
+-->
+<!ATTLIST col
+  %attrs;
+  span        %Number;       "1"
+  width       %MultiLength;  #IMPLIED
+  %cellhalign;
+  %cellvalign;
+  >
+
+<!--
+    Use thead to duplicate headers when breaking table
+    across page boundaries, or for static headers when
+    tbody sections are rendered in scrolling panel.
+
+    Use tfoot to duplicate footers when breaking table
+    across page boundaries, or for static footers when
+    tbody sections are rendered in scrolling panel.
+
+    Use multiple tbody sections when rules are needed
+    between groups of table rows.
+-->
+<!ATTLIST thead
+  %attrs;
+  %cellhalign;
+  %cellvalign;
+  >
+
+<!ATTLIST tfoot
+  %attrs;
+  %cellhalign;
+  %cellvalign;
+  >
+
+<!ATTLIST tbody
+  %attrs;
+  %cellhalign;
+  %cellvalign;
+  >
+
+<!ATTLIST tr
+  %attrs;
+  %cellhalign;
+  %cellvalign;
+  >
+
+
+<!-- Scope is simpler than headers attribute for common tables -->
+<!ENTITY % Scope "(row|col|rowgroup|colgroup)">
+
+<!-- th is for headers, td for data and for cells acting as both -->
+
+<!ATTLIST th
+  %attrs;
+  abbr        %Text;         #IMPLIED
+  axis        CDATA          #IMPLIED
+  headers     IDREFS         #IMPLIED
+  scope       %Scope;        #IMPLIED
+  rowspan     %Number;       "1"
+  colspan     %Number;       "1"
+  %cellhalign;
+  %cellvalign;
+  >
+
+<!ATTLIST td
+  %attrs;
+  abbr        %Text;         #IMPLIED
+  axis        CDATA          #IMPLIED
+  headers     IDREFS         #IMPLIED
+  scope       %Scope;        #IMPLIED
+  rowspan     %Number;       "1"
+  colspan     %Number;       "1"
+  %cellhalign;
+  %cellvalign;
+  >
+

Added: jifty/branches/schema-plugins/share/po/en.po
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/po/en.po	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,350 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
+"Language-Team: LANGUAGE <LL at li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: lib/Jifty/Action/Record/Search.pm:125
+msgid "!=>< allowed"
+msgstr ""
+
+#: lib/Jifty/Action/Record/Search.pm:115
+#. ($label)
+msgid "%1 after"
+msgstr ""
+
+#: lib/Jifty/Action/Record/Search.pm:116
+#. ($label)
+msgid "%1 before"
+msgstr ""
+
+#: lib/Jifty/Action/Record/Search.pm:112
+#. ($label)
+msgid "%1 contains"
+msgstr ""
+
+#: share/web/templates/__jifty/admin/fragments/list/list:87
+#. ($collection-> count)
+msgid "%1 entries"
+msgstr ""
+
+#: lib/Jifty/Action/Record/Search.pm:123
+#. ($label)
+msgid "%1 greater or equal to"
+msgstr ""
+
+#: lib/Jifty/Action/Record/Search.pm:121
+#. ($label)
+msgid "%1 greater than"
+msgstr ""
+
+#: lib/Jifty/Action/Record/Search.pm:108
+#. ($label)
+msgid "%1 is not"
+msgstr ""
+
+#: lib/Jifty/Action/Record/Search.pm:113
+#. ($label)
+msgid "%1 lacks"
+msgstr ""
+
+#: lib/Jifty/Action/Record/Search.pm:124
+#. ($label)
+msgid "%1 less or equal to"
+msgstr ""
+
+#: lib/Jifty/Action/Record/Search.pm:122
+#. ($label)
+msgid "%1 less than"
+msgstr ""
+
+#: share/web/templates/__jifty/error/mason_internal_error:31 share/web/templates/__jifty/error/mason_internal_error:35 share/web/templates/__jifty/error/mason_internal_error:39
+#. ($path, $line)
+#. ($file, $line)
+msgid "%1 line %2"
+msgstr ""
+
+#: share/web/templates/__jifty/halo:119
+#. ($_->[3])
+msgid "%1 seconds"
+msgstr ""
+
+#: lib/Jifty/Action/Record/Search.pm:117
+#. ($label)
+msgid "%1 since"
+msgstr ""
+
+#: lib/Jifty/Action/Record/Search.pm:118
+#. ($label)
+msgid "%1 until"
+msgstr ""
+
+#: lib/Jifty/Action/Record/Search.pm:77
+msgid "(any)"
+msgstr ""
+
+#: lib/Jifty/Web/Form/Field.pm:549
+msgid "@{[$self->current_value]}"
+msgstr ""
+
+#: share/web/templates/__jifty/admin/index.html:19
+msgid "Actions"
+msgstr ""
+
+#: share/web/templates/_elements/wrapper:11
+msgid "Administration mode is enabled."
+msgstr ""
+
+#: share/web/templates/_elements/wrapper:11
+msgid "Alert"
+msgstr ""
+
+#: lib/Jifty/Action/Record/Create.pm:82
+msgid "An error occurred.  Try again later"
+msgstr ""
+
+#: lib/Jifty/Action/Record/Search.pm:129
+msgid "Any field contains"
+msgstr ""
+
+#: share/web/templates/__jifty/admin/action/dhandler:25 share/web/templates/__jifty/admin/model/dhandler:21
+msgid "Back to the admin console"
+msgstr ""
+
+#: share/web/templates/__jifty/admin/index.html:29
+msgid "Back to the application"
+msgstr ""
+
+#: share/web/templates/helpers/calendar.html:4
+#. (_ &><body class="calpopup"><a href="#" onclick="window.close()
+msgid "Calendar"
+msgstr ""
+
+#: share/web/templates/__jifty/admin/fragments/list/update:31
+msgid "Cancel"
+msgstr ""
+
+#: share/web/templates/helpers/calendar.html:4
+msgid "Close window"
+msgstr ""
+
+#: share/web/templates/__jifty/admin/fragments/list/view:31
+msgid "Confirm delete?"
+msgstr ""
+
+#: share/web/templates/__jifty/admin/fragments/list/new_item:25
+msgid "Create"
+msgstr ""
+
+#: lib/Jifty/Action/Record/Create.pm:81
+#. (ref($record)
+msgid "Create of %1 failed: %2"
+msgstr ""
+
+#: lib/Jifty/Action/Record/Create.pm:105
+msgid "Created"
+msgstr ""
+
+#: share/web/templates/__jifty/admin/index.html:3
+msgid "Database Administration"
+msgstr ""
+
+#: share/web/templates/__jifty/admin/fragments/list/view:29
+msgid "Delete"
+msgstr ""
+
+#: lib/Jifty/Action/Record/Delete.pm:76
+msgid "Deleted"
+msgstr ""
+
+#: share/web/templates/__jifty/admin/index.html:28 share/web/templates/__jifty/admin/model/dhandler:20
+msgid "Done?"
+msgstr ""
+
+#: share/web/templates/__jifty/admin/fragments/list/view:40
+msgid "Edit"
+msgstr ""
+
+#: lib/Jifty/Mason/Halo.pm:104
+#. ($comp_name)
+msgid "Edit %1"
+msgstr ""
+
+#: share/web/templates/dhandler:7
+msgid "Go back home..."
+msgstr ""
+
+#: share/web/templates/_elements/sidebar:5
+#. ($u->$method()
+msgid "Hiya, %1."
+msgstr ""
+
+#: share/web/templates/__jifty/admin/index.html:1
+msgid "Jifty Administrative Console"
+msgstr ""
+
+#: share/web/templates/__jifty/online_docs/toc.html:6
+msgid "Jifty Developer Documentation Online"
+msgstr ""
+
+#: share/web/templates/__jifty/online_docs/content.html:6
+msgid "Jifty Pod Online"
+msgstr ""
+
+#: share/web/templates/_elements/wrapper:18
+msgid "Loading..."
+msgstr ""
+
+#: share/web/templates/__jifty/admin/model/dhandler:9
+#. ($object_type)
+msgid "Manage %1 records"
+msgstr ""
+
+#: share/web/templates/__jifty/admin/model/dhandler:11
+msgid "Manage records:"
+msgstr ""
+
+#: share/web/templates/__jifty/error/mason_internal_error:1
+msgid "Mason error"
+msgstr ""
+
+#: share/web/templates/__jifty/admin/index.html:9
+msgid "Models"
+msgstr ""
+
+#: share/web/templates/__jifty/admin/fragments/list/list:113
+msgid "Next Page"
+msgstr ""
+
+#: lib/Jifty/Action/Record/Search.pm:130
+msgid "No field contains"
+msgstr ""
+
+#: share/web/templates/__jifty/admin/fragments/list/list:85
+msgid "No items found"
+msgstr ""
+
+#: lib/Jifty/Web.pm:299
+msgid "No request to handle"
+msgstr ""
+
+#: share/web/templates/__jifty/online_docs/index.html:5
+msgid "Online Documentation"
+msgstr ""
+
+#: share/web/templates/__jifty/admin/fragments/list/list:80
+#. ($page, $collection->pager->last_page)
+msgid "Page %1 of %2"
+msgstr ""
+
+#: lib/Jifty/Record.pm:240 lib/Jifty/Record.pm:319 lib/Jifty/Record.pm:60
+msgid "Permission denied"
+msgstr ""
+
+#: share/web/templates/__jifty/admin/fragments/list/list:108
+msgid "Previous Page"
+msgstr ""
+
+#: share/web/templates/__jifty/admin/action/dhandler:20
+msgid "Run the action"
+msgstr ""
+
+#: share/web/templates/__jifty/halo:111
+msgid "SQL Statements"
+msgstr ""
+
+#: share/web/templates/__jifty/admin/fragments/list/update:21
+msgid "Save"
+msgstr ""
+
+#: share/web/templates/__jifty/online_docs/content.html:50
+msgid "Schema"
+msgstr ""
+
+#: share/web/templates/__jifty/admin/fragments/list/search:18
+msgid "Search"
+msgstr ""
+
+#: share/web/templates/dhandler:1
+msgid "Something's not quite right"
+msgstr ""
+
+#: share/web/templates/__jifty/online_docs/index.html:16 share/web/templates/__jifty/online_docs/index.html:18
+msgid "Table of Contents"
+msgstr ""
+
+#: lib/Jifty/Action.pm:876
+msgid "That doesn't look like a correct value"
+msgstr ""
+
+#: lib/Jifty/Action/Record.pm:248
+msgid "That doesn't look right, but I don't know why"
+msgstr ""
+
+#: lib/Jifty/Action/Record.pm:180
+msgid "The passwords you typed didn't match each other"
+msgstr ""
+
+#: lib/Jifty/Web.pm:362
+msgid "There was an error completing the request.  Please try again later."
+msgstr ""
+
+#: share/web/templates/__jifty/admin/index.html:5
+msgid "This console lets you manage the records in your Jifty database. Below, you should see a list of all your database tables. Feel free to go through and add, delete or modify records."
+msgstr ""
+
+#: share/web/templates/__jifty/admin/index.html:7
+msgid "To disable this administrative console, add \"AdminMode: 0\" under the \"framework:\" settings in the config file (etc/config.yml)."
+msgstr ""
+
+#: share/web/templates/__jifty/admin/fragments/list/list:72
+msgid "Toggle search"
+msgstr ""
+
+#: share/web/templates/__jifty/error/mason_internal_error:6
+msgid "Try again"
+msgstr ""
+
+#: lib/Jifty/Action/Record/Update.pm:156
+msgid "Updated"
+msgstr ""
+
+#: share/web/templates/index.html:1
+msgid "Welcome to your new Jifty application"
+msgstr ""
+
+#: share/web/templates/dhandler:5
+msgid "You got to a page that we don't think exists.  Anyway, the software has logged this error. Sorry about this."
+msgstr ""
+
+#: lib/Jifty/Action.pm:863
+msgid "You need to fill in this field"
+msgstr ""
+
+#: share/web/templates/index.html:3
+#. ('http://hdl.loc.gov/loc.pnp/cph.3c13461')
+msgid "You said you wanted a pony. (Source %1)"
+msgstr ""
+
+#: share/web/templates/_elements/sidebar:7
+msgid "You're not currently signed in."
+msgstr ""
+
+#: share/web/templates/__jifty/admin/fragments/list/header:22
+msgid "asc"
+msgstr ""
+
+#: share/web/templates/__jifty/admin/fragments/list/header:41
+msgid "desc"
+msgstr ""

Added: jifty/branches/schema-plugins/share/po/fr.po
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/po/fr.po	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,354 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# AGOSTINI Yves <agostini at univ-metz.fr>, 2006.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"POT-Creation-Date: 2006-10-24 18:00+ZONE\n"
+"PO-Revision-Date: 2006-11-02 09:34+ZONE\n"
+"Last-Translator: AGOSTINI Yves <agostini at univ-metz.fr>\n"
+"Language-Team: LANGUAGE <LL at li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: lib/Jifty/Action/Record/Search.pm:125
+msgid "!=>< allowed"
+msgstr "!=>< autorisés"
+
+#: lib/Jifty/Action/Record/Search.pm:115
+#. ($label)
+msgid "%1 after"
+msgstr "%1 aprés"
+
+#: lib/Jifty/Action/Record/Search.pm:116
+#. ($label)
+msgid "%1 before"
+msgstr "%1 avant"
+
+#: lib/Jifty/Action/Record/Search.pm:112
+#. ($label)
+msgid "%1 contains"
+msgstr "%1 contient"
+
+#: share/web/templates/__jifty/admin/fragments/list/list:87
+#. ($collection-> count)
+msgid "%1 entries"
+msgstr "%1 enregistements"
+
+#: lib/Jifty/Action/Record/Search.pm:123
+#. ($label)
+msgid "%1 greater or equal to"
+msgstr "%1 supérieur ou égal à"
+
+#: lib/Jifty/Action/Record/Search.pm:121
+#. ($label)
+msgid "%1 greater than"
+msgstr "%1 supérieur à"
+
+#: lib/Jifty/Action/Record/Search.pm:108
+#. ($label)
+msgid "%1 is not"
+msgstr "%1 n'est pas"
+
+#: lib/Jifty/Action/Record/Search.pm:113
+#. ($label)
+msgid "%1 lacks"
+msgstr "%1 ne contient pas"
+
+#: lib/Jifty/Action/Record/Search.pm:124
+#. ($label)
+msgid "%1 less or equal to"
+msgstr "%1 inférieur ou égal"
+
+#: lib/Jifty/Action/Record/Search.pm:122
+#. ($label)
+msgid "%1 less than"
+msgstr "%1 inférieur à"
+
+#: share/web/templates/__jifty/error/mason_internal_error:31 share/web/templates/__jifty/error/mason_internal_error:35 share/web/templates/__jifty/error/mason_internal_error:39
+#. ($path, $line)
+#. ($file, $line)
+msgid "%1 line %2"
+msgstr "%1 ligne %2"
+
+#: share/web/templates/__jifty/halo:119
+#. ($_->[3])
+msgid "%1 seconds"
+msgstr "%1 secondes"
+
+#: lib/Jifty/Action/Record/Search.pm:117
+#. ($label)
+msgid "%1 since"
+msgstr "%1 depuis le"
+
+#: lib/Jifty/Action/Record/Search.pm:118
+#. ($label)
+msgid "%1 until"
+msgstr "%1 depuis"
+
+#: lib/Jifty/Action/Record/Search.pm:77
+msgid "(any)"
+msgstr ""
+
+#: lib/Jifty/Web/Form/Field.pm:549
+msgid "@{[$self->current_value]}"
+msgstr ""
+
+#: share/web/templates/__jifty/admin/index.html:19
+msgid "Actions"
+msgstr ""
+
+#: share/web/templates/_elements/wrapper:11
+msgid "Administration mode is enabled."
+msgstr "Mode d'administration activé."
+
+#: share/web/templates/_elements/wrapper:11
+msgid "Alert"
+msgstr "Alerte"
+
+#: lib/Jifty/Action/Record/Create.pm:82
+msgid "An error occurred.  Try again later"
+msgstr "Erreur. Ré-essayez plus tard"
+
+#: lib/Jifty/Action/Record/Search.pm:129
+msgid "Any field contains"
+msgstr "Un champs contient"
+
+#: share/web/templates/__jifty/admin/action/dhandler:25 share/web/templates/__jifty/admin/model/dhandler:21
+msgid "Back to the admin console"
+msgstr "Retour à l'interface d'admin"
+
+#: share/web/templates/__jifty/admin/index.html:29
+msgid "Back to the application"
+msgstr "Retour à l'application"
+
+#: share/web/templates/helpers/calendar.html:4
+#. (_ &><body class="calpopup"><a href="#" onclick="window.close()
+msgid "Calendar"
+msgstr "Calendrier"
+
+#: share/web/templates/__jifty/admin/fragments/list/update:31
+msgid "Cancel"
+msgstr "Annuler"
+
+#: share/web/templates/helpers/calendar.html:4
+msgid "Close window"
+msgstr "Fermer la fenêtre"
+
+#: share/web/templates/__jifty/admin/fragments/list/view:31
+msgid "Confirm delete?"
+msgstr "Confirmer l'effacement ?"
+
+#: share/web/templates/__jifty/admin/fragments/list/new_item:25
+msgid "Create"
+msgstr "Enregistrer"
+
+#: lib/Jifty/Action/Record/Create.pm:81
+#. (ref($record)
+msgid "Create of %1 failed: %2"
+msgstr "La création de %1 a échoué: %2"
+
+#: lib/Jifty/Action/Record/Create.pm:105
+msgid "Created"
+msgstr "Créé"
+
+#: share/web/templates/__jifty/admin/index.html:3
+msgid "Database Administration"
+msgstr "Administration de la Base de Données"
+
+#: share/web/templates/__jifty/admin/fragments/list/view:29
+msgid "Delete"
+msgstr "Effacer"
+
+#: lib/Jifty/Action/Record/Delete.pm:76
+msgid "Deleted"
+msgstr "Effacé"
+
+#: share/web/templates/__jifty/admin/index.html:28 share/web/templates/__jifty/admin/model/dhandler:20
+msgid "Done?"
+msgstr ""
+
+#: share/web/templates/__jifty/admin/fragments/list/view:40
+msgid "Edit"
+msgstr "Editer"
+
+#: lib/Jifty/Mason/Halo.pm:104
+#. ($comp_name)
+msgid "Edit %1"
+msgstr "Editer %1"
+
+#: share/web/templates/dhandler:7
+msgid "Go back home..."
+msgstr "Retour..."
+
+#: share/web/templates/_elements/sidebar:5
+#. ($u->$method()
+msgid "Hiya, %1."
+msgstr "Bonjour, %1"
+
+#: share/web/templates/__jifty/admin/index.html:1
+msgid "Jifty Administrative Console"
+msgstr ""
+
+#: share/web/templates/__jifty/online_docs/toc.html:6
+msgid "Jifty Developer Documentation Online"
+msgstr ""
+
+#: share/web/templates/__jifty/online_docs/content.html:6
+msgid "Jifty Pod Online"
+msgstr ""
+
+#: share/web/templates/_elements/wrapper:18
+msgid "Loading..."
+msgstr "Chargement..."
+
+#: share/web/templates/__jifty/admin/model/dhandler:9
+#. ($object_type)
+msgid "Manage %1 records"
+msgstr ""
+
+#: share/web/templates/__jifty/admin/model/dhandler:11
+msgid "Manage records:"
+msgstr ""
+
+#: share/web/templates/__jifty/error/mason_internal_error:1
+msgid "Mason error"
+msgstr "Erreur Mason"
+
+#: share/web/templates/__jifty/admin/index.html:9
+msgid "Models"
+msgstr "Modèles"
+
+#: share/web/templates/__jifty/admin/fragments/list/list:113
+msgid "Next Page"
+msgstr "Page Suivante"
+
+#: lib/Jifty/Action/Record/Search.pm:130
+msgid "No field contains"
+msgstr "Aucun champs contient"
+
+#: share/web/templates/__jifty/admin/fragments/list/list:85
+msgid "No items found"
+msgstr "Aucun enregistrement"
+
+#: lib/Jifty/Web.pm:299
+msgid "No request to handle"
+msgstr "Aucune requête à traiter"
+
+#: share/web/templates/__jifty/online_docs/index.html:5
+msgid "Online Documentation"
+msgstr "Documentation en ligne"
+
+#: share/web/templates/__jifty/admin/fragments/list/list:80
+#. ($page, $collection->pager->last_page)
+msgid "Page %1 of %2"
+msgstr "Page %1 / %2"
+
+#: lib/Jifty/Record.pm:240 lib/Jifty/Record.pm:319 lib/Jifty/Record.pm:60
+msgid "Permission denied"
+msgstr "Autorisation refusée"
+
+#: share/web/templates/__jifty/admin/fragments/list/list:108
+msgid "Previous Page"
+msgstr "Page précédente"
+
+#:
+msgid "Record created"
+msgstr "Enregistrement créé"
+
+#: share/web/templates/__jifty/admin/action/dhandler:20
+msgid "Run the action"
+msgstr "Exécuter l'action"
+
+#: share/web/templates/__jifty/halo:111
+msgid "SQL Statements"
+msgstr ""
+
+#: share/web/templates/__jifty/admin/fragments/list/update:21
+msgid "Save"
+msgstr "Enregistrer"
+
+#: share/web/templates/__jifty/online_docs/content.html:50
+msgid "Schema"
+msgstr "Schéma"
+
+#: share/web/templates/__jifty/admin/fragments/list/search:18
+msgid "Search"
+msgstr "Chercher"
+
+#: share/web/templates/dhandler:1
+msgid "Something's not quite right"
+msgstr "Problème"
+
+#: share/web/templates/__jifty/online_docs/index.html:16 share/web/templates/__jifty/online_docs/index.html:18
+msgid "Table of Contents"
+msgstr "Table des matières"
+
+#: lib/Jifty/Action.pm:876
+msgid "That doesn't look like a correct value"
+msgstr "Valeur incorecte"
+
+#: lib/Jifty/Action/Record.pm:248
+msgid "That doesn't look right, but I don't know why"
+msgstr "Problème"
+
+#: lib/Jifty/Action/Record.pm:180
+msgid "The passwords you typed didn't match each other"
+msgstr "Les mots de passe tapés ne sont pas identiques"
+
+#: lib/Jifty/Web.pm:362
+msgid "There was an error completing the request.  Please try again later."
+msgstr "Problème lors de l'exécution de cette requête. Ré-essayez plus tard."
+
+#: share/web/templates/__jifty/admin/index.html:5
+msgid "This console lets you manage the records in your Jifty database. Below, you should see a list of all your database tables. Feel free to go through and add, delete or modify records."
+msgstr "Cette page vous permet de gérer vos enregistrements dans la base. Vous trouverez ci-dessous la listes des tables. Vous pouvez ajouter, effacer ou modifier les enregistements."
+
+#: share/web/templates/__jifty/admin/index.html:7
+msgid "To disable this administrative console, add \"AdminMode: 0\" under the \"framework:\" settings in the config file (etc/config.yml)."
+msgstr "Pour désactiver cette console d'administration, ajoutez \"AdminMode: 0\" dans la rubrique \"framework:\" du fichier de configuration (etc/config.yml)."
+
+#: share/web/templates/__jifty/admin/fragments/list/list:72
+msgid "Toggle search"
+msgstr "Rechercher"
+
+#: share/web/templates/__jifty/error/mason_internal_error:6
+msgid "Try again"
+msgstr "Ré-essayer"
+
+#: lib/Jifty/Action/Record/Update.pm:156
+msgid "Updated"
+msgstr "Modification"
+
+#: share/web/templates/index.html:1
+msgid "Welcome to your new Jifty application"
+msgstr "Bienvenue sur votre nouvelle application Jifty"
+
+#: share/web/templates/dhandler:5
+msgid "You got to a page that we don't think exists.  Anyway, the software has logged this error. Sorry about this."
+msgstr "Cette page n'existe pas."
+
+#: lib/Jifty/Action.pm:863
+msgid "You need to fill in this field"
+msgstr "Information obligatoire"
+
+#: share/web/templates/index.html:3
+#. ('http://hdl.loc.gov/loc.pnp/cph.3c13461')
+msgid "You said you wanted a pony. (Source %1)"
+msgstr ""
+
+#: share/web/templates/_elements/sidebar:7
+msgid "You're not currently signed in."
+msgstr "Vous n'êtes pas encore connecté."
+
+#: share/web/templates/__jifty/admin/fragments/list/header:22
+msgid "asc"
+msgstr ""
+
+#: share/web/templates/__jifty/admin/fragments/list/header:41
+msgid "desc"
+msgstr ""

Added: jifty/branches/schema-plugins/share/po/ja.po
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/po/ja.po	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,350 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kenichi Ishigaki <ishigaki at tcool.org>\n"
+"Language-Team: Japanese\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: lib/Jifty/Action/Record/Search.pm:125
+msgid "!=>< allowed"
+msgstr "! = > < は利用できません"
+
+#: lib/Jifty/Action/Record/Search.pm:115
+#. ($label)
+msgid "%1 after"
+msgstr "%1 より後"
+
+#: lib/Jifty/Action/Record/Search.pm:116
+#. ($label)
+msgid "%1 before"
+msgstr "%1 より前"
+
+#: lib/Jifty/Action/Record/Search.pm:112
+#. ($label)
+msgid "%1 contains"
+msgstr "%1 を含む"
+
+#: share/web/templates/__jifty/admin/fragments/list/list:87
+#. ($collection-> count)
+msgid "%1 entries"
+msgstr "%1 件"
+
+#: lib/Jifty/Action/Record/Search.pm:123
+#. ($label)
+msgid "%1 greater or equal to"
+msgstr "%1 と同じかそれ以上"
+
+#: lib/Jifty/Action/Record/Search.pm:121
+#. ($label)
+msgid "%1 greater than"
+msgstr "%1 より大きい"
+
+#: lib/Jifty/Action/Record/Search.pm:108
+#. ($label)
+msgid "%1 is not"
+msgstr "%1 以外"
+
+#: lib/Jifty/Action/Record/Search.pm:113
+#. ($label)
+msgid "%1 lacks"
+msgstr "%1 を含まない"
+
+#: lib/Jifty/Action/Record/Search.pm:124
+#. ($label)
+msgid "%1 less or equal to"
+msgstr "%1 と同じかそれ以下"
+
+#: lib/Jifty/Action/Record/Search.pm:122
+#. ($label)
+msgid "%1 less than"
+msgstr "%1 より小さい"
+
+#: share/web/templates/__jifty/error/mason_internal_error:31 share/web/templates/__jifty/error/mason_internal_error:35 share/web/templates/__jifty/error/mason_internal_error:39
+#. ($path, $line)
+#. ($file, $line)
+msgid "%1 line %2"
+msgstr "%1 %2行目"
+
+#: share/web/templates/__jifty/halo:119
+#. ($_->[3])
+msgid "%1 seconds"
+msgstr "%1 秒"
+
+#: lib/Jifty/Action/Record/Search.pm:117
+#. ($label)
+msgid "%1 since"
+msgstr "%1 と同じかそれ以降"
+
+#: lib/Jifty/Action/Record/Search.pm:118
+#. ($label)
+msgid "%1 until"
+msgstr "%1 と同じかそれ以前"
+
+#: lib/Jifty/Action/Record/Search.pm:77
+msgid "(any)"
+msgstr "(指定なし)"
+
+#: lib/Jifty/Web/Form/Field.pm:549
+msgid "@{[$self->current_value]}"
+msgstr ""
+
+#: share/web/templates/__jifty/admin/index.html:19
+msgid "Actions"
+msgstr "操作"
+
+#: share/web/templates/_elements/wrapper:11
+msgid "Administration mode is enabled."
+msgstr "管理モードが有効になっています"
+
+#: share/web/templates/_elements/wrapper:11
+msgid "Alert"
+msgstr "注意"
+
+#: lib/Jifty/Action/Record/Create.pm:82
+msgid "An error occurred.  Try again later"
+msgstr "エラーが発生しました。しばらく待ってから再度試してみてください"
+
+#: lib/Jifty/Action/Record/Search.pm:129
+msgid "Any field contains"
+msgstr "検索したい文字列"
+
+#: share/web/templates/__jifty/admin/action/dhandler:25 share/web/templates/__jifty/admin/model/dhandler:21
+msgid "Back to the admin console"
+msgstr "管理画面に戻る"
+
+#: share/web/templates/__jifty/admin/index.html:29
+msgid "Back to the application"
+msgstr "アプリケーションに戻る"
+
+#: share/web/templates/helpers/calendar.html:4
+#. (_ &><body class="calpopup"><a href="#" onclick="window.close()
+msgid "Calendar"
+msgstr "カレンダー"
+
+#: share/web/templates/__jifty/admin/fragments/list/update:31
+msgid "Cancel"
+msgstr "キャンセル"
+
+#: share/web/templates/helpers/calendar.html:4
+msgid "Close window"
+msgstr "閉じる"
+
+#: share/web/templates/__jifty/admin/fragments/list/view:31
+msgid "Confirm delete?"
+msgstr "本当に削除しますか"
+
+#: share/web/templates/__jifty/admin/fragments/list/new_item:25
+msgid "Create"
+msgstr "作成"
+
+#: lib/Jifty/Action/Record/Create.pm:81
+#. (ref($record)
+msgid "Create of %1 failed: %2"
+msgstr "%1 の作成に失敗しました: %2"
+
+#: lib/Jifty/Action/Record/Create.pm:105
+msgid "Created"
+msgstr "作成しました"
+
+#: share/web/templates/__jifty/admin/index.html:3
+msgid "Database Administration"
+msgstr "データベース管理"
+
+#: share/web/templates/__jifty/admin/fragments/list/view:29
+msgid "Delete"
+msgstr "削除"
+
+#: lib/Jifty/Action/Record/Delete.pm:76
+msgid "Deleted"
+msgstr "削除しました"
+
+#: share/web/templates/__jifty/admin/index.html:28 share/web/templates/__jifty/admin/model/dhandler:20
+msgid "Done?"
+msgstr ""
+
+#: share/web/templates/__jifty/admin/fragments/list/view:40
+msgid "Edit"
+msgstr "編集"
+
+#: lib/Jifty/Mason/Halo.pm:104
+#. ($comp_name)
+msgid "Edit %1"
+msgstr "%1 を編集"
+
+#: share/web/templates/dhandler:7
+msgid "Go back home..."
+msgstr "ホームページに戻ります…"
+
+#: share/web/templates/_elements/sidebar:5
+#. ($u->$method()
+msgid "Hiya, %1."
+msgstr "%1 さん、こんにちは"
+
+#: share/web/templates/__jifty/admin/index.html:1
+msgid "Jifty Administrative Console"
+msgstr ""
+
+#: share/web/templates/__jifty/online_docs/toc.html:6
+msgid "Jifty Developer Documentation Online"
+msgstr "開発者向けオンラインドキュメント"
+
+#: share/web/templates/__jifty/online_docs/content.html:6
+msgid "Jifty Pod Online"
+msgstr "オンラインドキュメント"
+
+#: share/web/templates/_elements/wrapper:18
+msgid "Loading..."
+msgstr "読込み中…"
+
+#: share/web/templates/__jifty/admin/model/dhandler:9
+#. ($object_type)
+msgid "Manage %1 records"
+msgstr ""
+
+#: share/web/templates/__jifty/admin/model/dhandler:11
+msgid "Manage records:"
+msgstr ""
+
+#: share/web/templates/__jifty/error/mason_internal_error:1
+msgid "Mason error"
+msgstr "Mason エラー"
+
+#: share/web/templates/__jifty/admin/index.html:9
+msgid "Models"
+msgstr "モデル"
+
+#: share/web/templates/__jifty/admin/fragments/list/list:113
+msgid "Next Page"
+msgstr "次のページ"
+
+#: lib/Jifty/Action/Record/Search.pm:130
+msgid "No field contains"
+msgstr "排除したい文字列"
+
+#: share/web/templates/__jifty/admin/fragments/list/list:85
+msgid "No items found"
+msgstr "見つかりませんでした"
+
+#: lib/Jifty/Web.pm:299
+msgid "No request to handle"
+msgstr "処理できるリクエストがありません"
+
+#: share/web/templates/__jifty/online_docs/index.html:5
+msgid "Online Documentation"
+msgstr "オンラインドキュメント"
+
+#: share/web/templates/__jifty/admin/fragments/list/list:80
+#. ($page, $collection->pager->last_page)
+msgid "Page %1 of %2"
+msgstr "%1 / %2 ページ"
+
+#: lib/Jifty/Record.pm:240 lib/Jifty/Record.pm:319 lib/Jifty/Record.pm:60
+msgid "Permission denied"
+msgstr "権限がありません"
+
+#: share/web/templates/__jifty/admin/fragments/list/list:108
+msgid "Previous Page"
+msgstr "前のページ"
+
+#: share/web/templates/__jifty/admin/action/dhandler:20
+msgid "Run the action"
+msgstr "実行"
+
+#: share/web/templates/__jifty/halo:111
+msgid "SQL Statements"
+msgstr "SQLæ–‡"
+
+#: share/web/templates/__jifty/admin/fragments/list/update:21
+msgid "Save"
+msgstr "保存"
+
+#: share/web/templates/__jifty/online_docs/content.html:50
+msgid "Schema"
+msgstr "スキーマ"
+
+#: share/web/templates/__jifty/admin/fragments/list/search:18
+msgid "Search"
+msgstr "検索"
+
+#: share/web/templates/dhandler:1
+msgid "Something's not quite right"
+msgstr "原因不明のエラーです"
+
+#: share/web/templates/__jifty/online_docs/index.html:16 share/web/templates/__jifty/online_docs/index.html:18
+msgid "Table of Contents"
+msgstr "目次"
+
+#: lib/Jifty/Action.pm:876
+msgid "That doesn't look like a correct value"
+msgstr "値が不適切なようです"
+
+#: lib/Jifty/Action/Record.pm:248
+msgid "That doesn't look right, but I don't know why"
+msgstr "原因不明のエラーです"
+
+#: lib/Jifty/Action/Record.pm:180
+msgid "The passwords you typed didn't match each other"
+msgstr "パスワードが一致しません"
+
+#: lib/Jifty/Web.pm:362
+msgid "There was an error completing the request.  Please try again later."
+msgstr "リクエストの処理中にエラーが発生しました。しばらく待ってから再度試してみてください"
+
+#: share/web/templates/__jifty/admin/index.html:5
+msgid "This console lets you manage the records in your Jifty database. Below, you should see a list of all your database tables. Feel free to go through and add, delete or modify records."
+msgstr "ここでは Jifty データベースのレコードを管理できます。下にデータベースの全テーブルが一覧されているはずですので、自由に表示・作成・削除・修正してください。"
+
+#: share/web/templates/__jifty/admin/index.html:7
+msgid "To disable this administrative console, add \"AdminMode: 0\" under the \"framework:\" settings in the config file (etc/config.yml)."
+msgstr "この管理画面を無効にするには、設定ファイル(etc/config.yml)の \"framework:\" の項目の下に \"AdminMode: 0\" を追加してください。"
+
+#: share/web/templates/__jifty/admin/fragments/list/list:72
+msgid "Toggle search"
+msgstr "検索の切り替え"
+
+#: share/web/templates/__jifty/error/mason_internal_error:6
+msgid "Try again"
+msgstr "再度試してみてください"
+
+#: lib/Jifty/Action/Record/Update.pm:156
+msgid "Updated"
+msgstr "更新しました"
+
+#: share/web/templates/index.html:1
+msgid "Welcome to your new Jifty application"
+msgstr "Jifty の新規アプリケーションです"
+
+#: share/web/templates/dhandler:5
+msgid "You got to a page that we don't think exists.  Anyway, the software has logged this error. Sorry about this."
+msgstr "このページは存在しません。エラーとして記録しておきました。ご不便をお詫びいたします"
+
+#: lib/Jifty/Action.pm:863
+msgid "You need to fill in this field"
+msgstr "必須項目です"
+
+#: share/web/templates/index.html:3
+#. ('http://hdl.loc.gov/loc.pnp/cph.3c13461')
+msgid "You said you wanted a pony. (Source %1)"
+msgstr "こんなのが欲しかったんでしょう?(出典:%1)"
+
+#: share/web/templates/_elements/sidebar:7
+msgid "You're not currently signed in."
+msgstr "ログインしていません"
+
+#: share/web/templates/__jifty/admin/fragments/list/header:22
+msgid "asc"
+msgstr "昇順"
+
+#: share/web/templates/__jifty/admin/fragments/list/header:41
+msgid "desc"
+msgstr "降順"

Added: jifty/branches/schema-plugins/share/po/zh_cn.po
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/po/zh_cn.po	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,357 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2007-01-23 16:55+08\n"
+"Last-Translator: Agent Zhang <agentzh at gmail.com>\n"
+"Language-Team: Simplified Chinese\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: lib/Jifty/Action/Record/Search.pm:125
+msgid "!=>< allowed"
+msgstr "可使用 !=>< 符号"
+
+#: lib/Jifty/Action/Record/Search.pm:115
+#. ($label)
+msgid "%1 after"
+msgstr "%1 晚于"
+
+#: lib/Jifty/Action/Record/Search.pm:116
+#. ($label)
+msgid "%1 before"
+msgstr "%1 早于"
+
+#: lib/Jifty/Action/Record/Search.pm:112
+#. ($label)
+msgid "%1 contains"
+msgstr "%1 包含"
+
+#: share/web/templates/__jifty/admin/fragments/list/list:87
+#. ($collection-> count)
+msgid "%1 entries"
+msgstr "%1 条记录"
+
+#: lib/Jifty/Action/Record/Search.pm:123
+#. ($label)
+msgid "%1 greater or equal to"
+msgstr "%1 至少为"
+
+#: lib/Jifty/Action/Record/Search.pm:121
+#. ($label)
+msgid "%1 greater than"
+msgstr "%1 大于"
+
+#: lib/Jifty/Action/Record/Search.pm:108
+#. ($label)
+msgid "%1 is not"
+msgstr "%1 不等于"
+
+#: lib/Jifty/Action/Record/Search.pm:113
+#. ($label)
+msgid "%1 lacks"
+msgstr "%1 不包含"
+
+#: lib/Jifty/Action/Record/Search.pm:124
+#. ($label)
+msgid "%1 less or equal to"
+msgstr "%1 至多为"
+
+#: lib/Jifty/Action/Record/Search.pm:122
+#. ($label)
+msgid "%1 less than"
+msgstr "%1 小于"
+
+#: share/web/templates/__jifty/error/mason_internal_error:31 share/web/templates/__jifty/error/mason_internal_error:35 share/web/templates/__jifty/error/mason_internal_error:39
+#. ($path, $line)
+#. ($file, $line)
+msgid "%1 line %2"
+msgstr "%1 第 %2 行"
+
+#: share/web/templates/__jifty/halo:119
+#. ($_->[3])
+msgid "%1 seconds"
+msgstr "%1 秒"
+
+#: lib/Jifty/Action/Record/Search.pm:117
+#. ($label)
+msgid "%1 since"
+msgstr "%1 自"
+
+#: lib/Jifty/Action/Record/Search.pm:118
+#. ($label)
+msgid "%1 until"
+msgstr "%1 至"
+
+#: lib/Jifty/Action/Record/Search.pm:77
+msgid "(any)"
+msgstr "(任意)"
+
+#: lib/Jifty/Web/Form/Field.pm:549
+msgid "@{[$self->current_value]}"
+msgstr ""
+
+#: share/web/templates/__jifty/admin/index.html:19
+msgid "Actions"
+msgstr "操作"
+
+#: share/web/templates/_elements/wrapper:11
+msgid "Administration mode is enabled."
+msgstr "系统管理模式开启中."
+
+#: share/web/templates/_elements/wrapper:11
+msgid "Alert"
+msgstr "请注意"
+
+#: lib/Jifty/Action/Record/Create.pm:82
+msgid "An error occurred.  Try again later"
+msgstr "系统无法执行此项操作, 请稍后再试"
+
+#: lib/Jifty/Action/Record/Search.pm:129
+msgid "Any field contains"
+msgstr "任意字段包含"
+
+#: share/web/templates/__jifty/admin/action/dhandler:25 share/web/templates/__jifty/admin/model/dhandler:21
+msgid "Back to the admin console"
+msgstr "回到管理界面"
+
+#: share/web/templates/__jifty/admin/index.html:29
+msgid "Back to the application"
+msgstr "回到应用程序"
+
+#: share/web/templates/helpers/calendar.html:4
+#. (_ &><body class="calpopup"><a href="#" onclick="window.close()
+msgid "Calendar"
+msgstr "日历"
+
+#: share/web/templates/__jifty/admin/fragments/list/update:31
+msgid "Cancel"
+msgstr "取消"
+
+#: share/web/templates/helpers/calendar.html:4
+msgid "Close window"
+msgstr "关闭窗口"
+
+#: share/web/templates/__jifty/admin/fragments/list/view:31
+msgid "Confirm delete?"
+msgstr "确实要删除吗?"
+
+#: share/web/templates/__jifty/admin/fragments/list/new_item:25
+msgid "Create"
+msgstr "创建"
+
+#: lib/Jifty/Action/Record/Create.pm:81
+#. (ref($record)
+msgid "Create of %1 failed: %2"
+msgstr "无法建立 %1: %2"
+
+#: lib/Jifty/Action/Record/Create.pm:105
+msgid "Created"
+msgstr "成功建立项目."
+
+#: share/web/templates/__jifty/admin/index.html:3
+msgid "Database Administration"
+msgstr "数据库管理"
+
+#: share/web/templates/__jifty/admin/fragments/list/view:29
+msgid "Delete"
+msgstr "删除"
+
+#: lib/Jifty/Action/Record/Delete.pm:76
+msgid "Deleted"
+msgstr "成功删除项目."
+
+#:
+msgid "Dismiss"
+msgstr "关闭"
+
+#: share/web/templates/__jifty/admin/index.html:28 share/web/templates/__jifty/admin/model/dhandler:20
+msgid "Done?"
+msgstr "已完成?"
+
+#: share/web/templates/__jifty/admin/fragments/list/view:40
+msgid "Edit"
+msgstr "编辑"
+
+#: lib/Jifty/Mason/Halo.pm:104
+#. ($comp_name)
+msgid "Edit %1"
+msgstr "编辑 %1"
+
+#: share/web/templates/dhandler:7
+msgid "Go back home..."
+msgstr "回到首页..."
+
+#: share/web/templates/_elements/sidebar:5
+#. ($u->$method()
+msgid "Hiya, %1."
+msgstr "您好,%1。"
+
+#: share/web/templates/__jifty/admin/index.html:1
+msgid "Jifty Administrative Console"
+msgstr "Jifty 管理界面"
+
+#: share/web/templates/__jifty/online_docs/toc.html:6
+msgid "Jifty Developer Documentation Online"
+msgstr "Jifty 在线开发文档"
+
+#: share/web/templates/__jifty/online_docs/content.html:6
+msgid "Jifty Pod Online"
+msgstr "Jifty 在线 POD 文件"
+
+#: share/web/templates/_elements/wrapper:18
+msgid "Loading..."
+msgstr "正在加载..."
+
+#: share/web/templates/__jifty/admin/model/dhandler:9
+#. ($object_type)
+msgid "Manage %1 records"
+msgstr "管理 %1 记录"
+
+#: share/web/templates/__jifty/admin/model/dhandler:11
+msgid "Manage records:"
+msgstr "管理记录:"
+
+#: share/web/templates/__jifty/error/mason_internal_error:1
+msgid "Mason error"
+msgstr "Mason 系统错误"
+
+#: share/web/templates/__jifty/admin/index.html:9
+msgid "Models"
+msgstr "模型"
+
+#: share/web/templates/__jifty/admin/fragments/list/list:113
+msgid "Next Page"
+msgstr "下一页"
+
+#: lib/Jifty/Action/Record/Search.pm:130
+msgid "No field contains"
+msgstr "没有字段包含"
+
+#: share/web/templates/__jifty/admin/fragments/list/list:85
+msgid "No items found"
+msgstr "未找到记录"
+
+#: lib/Jifty/Web.pm:299
+msgid "No request to handle"
+msgstr "没有可处理的请求"
+
+#: share/web/templates/__jifty/online_docs/index.html:5
+msgid "Online Documentation"
+msgstr "线上文件"
+
+#: share/web/templates/__jifty/admin/fragments/list/list:80
+#. ($page, $collection->pager->last_page)
+msgid "Page %1 of %2"
+msgstr "第 %1 页, 共 %2 页"
+
+#: lib/Jifty/Record.pm:240 lib/Jifty/Record.pm:319 lib/Jifty/Record.pm:60
+msgid "Permission denied"
+msgstr "权限不足"
+
+#: share/web/templates/__jifty/admin/fragments/list/list:108
+msgid "Previous Page"
+msgstr "上一页"
+
+#:
+msgid "Record created"
+msgstr "成功建立记录."
+
+#: share/web/templates/__jifty/admin/action/dhandler:20
+msgid "Run the action"
+msgstr "执行动作"
+
+#: share/web/templates/__jifty/halo:111
+msgid "SQL Statements"
+msgstr "SQL 陈述式"
+
+#: share/web/templates/__jifty/admin/fragments/list/update:21
+msgid "Save"
+msgstr "保存"
+
+#: share/web/templates/__jifty/online_docs/content.html:50
+msgid "Schema"
+msgstr "模式"
+
+#: share/web/templates/__jifty/admin/fragments/list/search:18
+msgid "Search"
+msgstr "查找"
+
+#: share/web/templates/dhandler:1
+msgid "Something's not quite right"
+msgstr "系统错误"
+
+#: share/web/templates/__jifty/online_docs/index.html:16 share/web/templates/__jifty/online_docs/index.html:18
+msgid "Table of Contents"
+msgstr "目录"
+
+#: lib/Jifty/Action.pm:876
+msgid "That doesn't look like a correct value"
+msgstr "字段格式错误."
+
+#: lib/Jifty/Action/Record.pm:248
+msgid "That doesn't look right, but I don't know why"
+msgstr "字段内容错误."
+
+#: lib/Jifty/Action/Record.pm:180
+msgid "The passwords you typed didn't match each other"
+msgstr "两组口令不匹配."
+
+#: lib/Jifty/Web.pm:362
+msgid "There was an error completing the request.  Please try again later."
+msgstr "系统执行错误, 请稍候再试."
+
+#: share/web/templates/__jifty/admin/index.html:5
+msgid "This console lets you manage the records in your Jifty database. Below, you should see a list of all your database tables. Feel free to go through and add, delete or modify records."
+msgstr "您可利用此界面来管理数据库的内容. 请点选表格名称, 进行增删及编辑."
+
+#: share/web/templates/__jifty/admin/index.html:7
+msgid "To disable this administrative console, add \"AdminMode: 0\" under the \"framework:\" settings in the config file (etc/config.yml)."
+msgstr "如果想停用管理界面, 请在配置文件 (etc/config.yml) 的 \"framework:\" 配置项内加上 \"AdminMode: 0\" 即可."
+
+#: share/web/templates/__jifty/admin/fragments/list/list:72
+msgid "Toggle search"
+msgstr "切换搜寻画面"
+
+#: share/web/templates/__jifty/error/mason_internal_error:6
+msgid "Try again"
+msgstr "重试一下"
+
+#: lib/Jifty/Action/Record/Update.pm:156
+msgid "Updated"
+msgstr "成功更新项目."
+
+#: share/web/templates/index.html:1
+msgid "Welcome to your new Jifty application"
+msgstr "欢迎光临您的崭新的 Jifty 应用程序"
+
+#: share/web/templates/dhandler:5
+msgid "You got to a page that we don't think exists.  Anyway, the software has logged this error. Sorry about this."
+msgstr "抱歉, 此页面不存在, 系统已留下纪录."
+
+#: lib/Jifty/Action.pm:863
+msgid "You need to fill in this field"
+msgstr "此字段非空."
+
+#: share/web/templates/index.html:3
+#. ('http://hdl.loc.gov/loc.pnp/cph.3c13461')
+msgid "You said you wanted a pony. (Source %1)"
+msgstr "您可不正是想要一匹小马吗? (参见 %1)"
+
+#: share/web/templates/_elements/sidebar:7
+msgid "You're not currently signed in."
+msgstr "您目前尚未登录."
+
+#: share/web/templates/__jifty/admin/fragments/list/header:22
+msgid "asc"
+msgstr "升序"
+
+#: share/web/templates/__jifty/admin/fragments/list/header:41
+msgid "desc"
+msgstr "降序"

Added: jifty/branches/schema-plugins/share/po/zh_tw.po
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/po/zh_tw.po	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,357 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Audrey Tang <audreyt at audreyt.org>\n"
+"Language-Team: Traditional Chinese\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: lib/Jifty/Action/Record/Search.pm:125
+msgid "!=>< allowed"
+msgstr "可使用 !=>< 符號"
+
+#: lib/Jifty/Action/Record/Search.pm:115
+#. ($label)
+msgid "%1 after"
+msgstr "%1 晚於"
+
+#: lib/Jifty/Action/Record/Search.pm:116
+#. ($label)
+msgid "%1 before"
+msgstr "%1 æ—©æ–¼"
+
+#: lib/Jifty/Action/Record/Search.pm:112
+#. ($label)
+msgid "%1 contains"
+msgstr "%1 包含"
+
+#: share/web/templates/__jifty/admin/fragments/list/list:87
+#. ($collection-> count)
+msgid "%1 entries"
+msgstr ""
+
+#: lib/Jifty/Action/Record/Search.pm:123
+#. ($label)
+msgid "%1 greater or equal to"
+msgstr "%1 至少為"
+
+#: lib/Jifty/Action/Record/Search.pm:121
+#. ($label)
+msgid "%1 greater than"
+msgstr "%1 大於"
+
+#: lib/Jifty/Action/Record/Search.pm:108
+#. ($label)
+msgid "%1 is not"
+msgstr "%1 不等於"
+
+#: lib/Jifty/Action/Record/Search.pm:113
+#. ($label)
+msgid "%1 lacks"
+msgstr "%1 不包含"
+
+#: lib/Jifty/Action/Record/Search.pm:124
+#. ($label)
+msgid "%1 less or equal to"
+msgstr "%1 至多為"
+
+#: lib/Jifty/Action/Record/Search.pm:122
+#. ($label)
+msgid "%1 less than"
+msgstr "%1 小於"
+
+#: share/web/templates/__jifty/error/mason_internal_error:31 share/web/templates/__jifty/error/mason_internal_error:35 share/web/templates/__jifty/error/mason_internal_error:39
+#. ($path, $line)
+#. ($file, $line)
+msgid "%1 line %2"
+msgstr "%1 第 %2 列"
+
+#: share/web/templates/__jifty/halo:119
+#. ($_->[3])
+msgid "%1 seconds"
+msgstr "%1 秒"
+
+#: lib/Jifty/Action/Record/Search.pm:117
+#. ($label)
+msgid "%1 since"
+msgstr "%1 自"
+
+#: lib/Jifty/Action/Record/Search.pm:118
+#. ($label)
+msgid "%1 until"
+msgstr "%1 至"
+
+#: lib/Jifty/Action/Record/Search.pm:77
+msgid "(any)"
+msgstr "(不限)"
+
+#: lib/Jifty/Web/Form/Field.pm:549
+msgid "@{[$self->current_value]}"
+msgstr ""
+
+#: share/web/templates/__jifty/admin/index.html:19
+msgid "Actions"
+msgstr "操作"
+
+#: share/web/templates/_elements/wrapper:11
+msgid "Administration mode is enabled."
+msgstr "系統管理模式開啟中."
+
+#: share/web/templates/_elements/wrapper:11
+msgid "Alert"
+msgstr "請注意"
+
+#: lib/Jifty/Action/Record/Create.pm:82
+msgid "An error occurred.  Try again later"
+msgstr "系統無法執行此項操作, 請稍後再試"
+
+#: lib/Jifty/Action/Record/Search.pm:129
+msgid "Any field contains"
+msgstr "任意欄位包含"
+
+#: share/web/templates/__jifty/admin/action/dhandler:25 share/web/templates/__jifty/admin/model/dhandler:21
+msgid "Back to the admin console"
+msgstr "回到管理界面"
+
+#: share/web/templates/__jifty/admin/index.html:29
+msgid "Back to the application"
+msgstr "回到應用程式"
+
+#: share/web/templates/helpers/calendar.html:4
+#. (_ &><body class="calpopup"><a href="#" onclick="window.close()
+msgid "Calendar"
+msgstr "日曆"
+
+#: share/web/templates/__jifty/admin/fragments/list/update:31
+msgid "Cancel"
+msgstr "取消"
+
+#: share/web/templates/helpers/calendar.html:4
+msgid "Close window"
+msgstr "關閉視窗"
+
+#: share/web/templates/__jifty/admin/fragments/list/view:31
+msgid "Confirm delete?"
+msgstr "確定要刪除?"
+
+#: share/web/templates/__jifty/admin/fragments/list/new_item:25
+msgid "Create"
+msgstr "建立"
+
+#: lib/Jifty/Action/Record/Create.pm:81
+#. (ref($record)
+msgid "Create of %1 failed: %2"
+msgstr "無法建立 %1: %2"
+
+#: lib/Jifty/Action/Record/Create.pm:105
+msgid "Created"
+msgstr "成功建立項目."
+
+#: share/web/templates/__jifty/admin/index.html:3
+msgid "Database Administration"
+msgstr "資料庫管理"
+
+#: share/web/templates/__jifty/admin/fragments/list/view:29
+msgid "Delete"
+msgstr "刪除"
+
+#: lib/Jifty/Action/Record/Delete.pm:76
+msgid "Deleted"
+msgstr "成功刪除項目."
+
+#:
+msgid "Dismiss"
+msgstr "關閉"
+
+#: share/web/templates/__jifty/admin/index.html:28 share/web/templates/__jifty/admin/model/dhandler:20
+msgid "Done?"
+msgstr "已完成?"
+
+#: share/web/templates/__jifty/admin/fragments/list/view:40
+msgid "Edit"
+msgstr "編輯"
+
+#: lib/Jifty/Mason/Halo.pm:104
+#. ($comp_name)
+msgid "Edit %1"
+msgstr "編輯 %1"
+
+#: share/web/templates/dhandler:7
+msgid "Go back home..."
+msgstr "回首頁..."
+
+#: share/web/templates/_elements/sidebar:5
+#. ($u->$method()
+msgid "Hiya, %1."
+msgstr "%1 您好."
+
+#: share/web/templates/__jifty/admin/index.html:1
+msgid "Jifty Administrative Console"
+msgstr "Jifty 管理介面"
+
+#: share/web/templates/__jifty/online_docs/toc.html:6
+msgid "Jifty Developer Documentation Online"
+msgstr "Jifty 線上開發文件"
+
+#: share/web/templates/__jifty/online_docs/content.html:6
+msgid "Jifty Pod Online"
+msgstr "Jifty 線上 POD 文件"
+
+#: share/web/templates/_elements/wrapper:18
+msgid "Loading..."
+msgstr "請稍候..."
+
+#: share/web/templates/__jifty/admin/model/dhandler:9
+#. ($object_type)
+msgid "Manage %1 records"
+msgstr "管理 %1 記錄"
+
+#: share/web/templates/__jifty/admin/model/dhandler:11
+msgid "Manage records:"
+msgstr "管理記錄:"
+
+#: share/web/templates/__jifty/error/mason_internal_error:1
+msgid "Mason error"
+msgstr "Mason 系統錯誤"
+
+#: share/web/templates/__jifty/admin/index.html:9
+msgid "Models"
+msgstr "模型"
+
+#: share/web/templates/__jifty/admin/fragments/list/list:113
+msgid "Next Page"
+msgstr "下一頁"
+
+#: lib/Jifty/Action/Record/Search.pm:130
+msgid "No field contains"
+msgstr "沒有欄位包含"
+
+#: share/web/templates/__jifty/admin/fragments/list/list:85
+msgid "No items found"
+msgstr "未找到記錄"
+
+#: lib/Jifty/Web.pm:299
+msgid "No request to handle"
+msgstr "沒有可處理的要求"
+
+#: share/web/templates/__jifty/online_docs/index.html:5
+msgid "Online Documentation"
+msgstr "線上文件"
+
+#: share/web/templates/__jifty/admin/fragments/list/list:80
+#. ($page, $collection->pager->last_page)
+msgid "Page %1 of %2"
+msgstr "第 %1 頁, 共 %2 頁"
+
+#: lib/Jifty/Record.pm:240 lib/Jifty/Record.pm:319 lib/Jifty/Record.pm:60
+msgid "Permission denied"
+msgstr "權限不足."
+
+#: share/web/templates/__jifty/admin/fragments/list/list:108
+msgid "Previous Page"
+msgstr "上一頁"
+
+#:
+msgid "Record created"
+msgstr "成功建立項目."
+
+#: share/web/templates/__jifty/admin/action/dhandler:20
+msgid "Run the action"
+msgstr "執行動作"
+
+#: share/web/templates/__jifty/halo:111
+msgid "SQL Statements"
+msgstr "SQL 陳述式"
+
+#: share/web/templates/__jifty/admin/fragments/list/update:21
+msgid "Save"
+msgstr "儲存"
+
+#: share/web/templates/__jifty/online_docs/content.html:50
+msgid "Schema"
+msgstr "綱要"
+
+#: share/web/templates/__jifty/admin/fragments/list/search:18
+msgid "Search"
+msgstr "搜尋"
+
+#: share/web/templates/dhandler:1
+msgid "Something's not quite right"
+msgstr "系統錯誤"
+
+#: share/web/templates/__jifty/online_docs/index.html:16 share/web/templates/__jifty/online_docs/index.html:18
+msgid "Table of Contents"
+msgstr "目錄"
+
+#: lib/Jifty/Action.pm:876
+msgid "That doesn't look like a correct value"
+msgstr "欄位格式錯誤."
+
+#: lib/Jifty/Action/Record.pm:248
+msgid "That doesn't look right, but I don't know why"
+msgstr "欄位內容錯誤."
+
+#: lib/Jifty/Action/Record.pm:180
+msgid "The passwords you typed didn't match each other"
+msgstr "兩組密碼不符合."
+
+#: lib/Jifty/Web.pm:362
+msgid "There was an error completing the request.  Please try again later."
+msgstr "系統執行錯誤, 請稍候再試."
+
+#: share/web/templates/__jifty/admin/index.html:5
+msgid "This console lets you manage the records in your Jifty database. Below, you should see a list of all your database tables. Feel free to go through and add, delete or modify records."
+msgstr "您可利用此界面來管理資料庫的內容. 請點選表格名稱, 進行增刪及編輯."
+
+#: share/web/templates/__jifty/admin/index.html:7
+msgid "To disable this administrative console, add \"AdminMode: 0\" under the \"framework:\" settings in the config file (etc/config.yml)."
+msgstr "如欲停用管理界面, 請在設定檔 (etc/config.yml) 的 \"framework:\" 設定內加上 \"AdminMode: 0\" 即可."
+
+#: share/web/templates/__jifty/admin/fragments/list/list:72
+msgid "Toggle search"
+msgstr "切換搜尋畫面"
+
+#: share/web/templates/__jifty/error/mason_internal_error:6
+msgid "Try again"
+msgstr "再試一下"
+
+#: lib/Jifty/Action/Record/Update.pm:156
+msgid "Updated"
+msgstr "成功更新項目."
+
+#: share/web/templates/index.html:1
+msgid "Welcome to your new Jifty application"
+msgstr "歡迎光臨您嶄新的 Jifty 應用程式"
+
+#: share/web/templates/dhandler:5
+msgid "You got to a page that we don't think exists.  Anyway, the software has logged this error. Sorry about this."
+msgstr "抱歉, 此頁面不存在, 系統已留下紀錄."
+
+#: lib/Jifty/Action.pm:863
+msgid "You need to fill in this field"
+msgstr "此欄位不能留空."
+
+#: share/web/templates/index.html:3
+#. ('http://hdl.loc.gov/loc.pnp/cph.3c13461')
+msgid "You said you wanted a pony. (Source %1)"
+msgstr "您可不正是想要一匹小馬嗎? (參見 %1)"
+
+#: share/web/templates/_elements/sidebar:7
+msgid "You're not currently signed in."
+msgstr "您目前尚未登入."
+
+#: share/web/templates/__jifty/admin/fragments/list/header:22
+msgid "asc"
+msgstr "昇冪"
+
+#: share/web/templates/__jifty/admin/fragments/list/header:41
+msgid "desc"
+msgstr "降冪"

Added: jifty/branches/schema-plugins/share/web/static/css/app-base.css
==============================================================================

Added: jifty/branches/schema-plugins/share/web/static/css/app.css
==============================================================================

Added: jifty/branches/schema-plugins/share/web/static/css/autocomplete.css
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/css/autocomplete.css	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,28 @@
+/* Autocomplete */
+
+.autocomplete {
+    border: 1px solid #666;
+    background: white;
+    z-index: 43;
+}
+
+.autocomplete ul {
+    margin: 0;
+    padding: 0;
+    list-style: none;
+}
+
+.autocomplete li {
+    padding: 0.1em 0 0.1em 0.3em;
+    cursor: default;
+}
+
+.autocomplete .selected {
+    background: rgb(82, 134, 181);
+    color: white;
+}
+
+.autocomplete .hidden_value {
+    display: none;
+}
+

Added: jifty/branches/schema-plugins/share/web/static/css/autohandler
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/css/autohandler	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,5 @@
+<%init>
+$r->content_type('text/css');
+$m->call_next();
+return();
+</%init>

Added: jifty/branches/schema-plugins/share/web/static/css/base.css
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/css/base.css	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,89 @@
+body {
+}
+
+body.calpopup {
+}
+
+.error {
+    color: #a00000;
+}
+
+.warning {
+    color: #00a0a0;
+}
+
+.canonicalization_note {
+    color: #009966;
+}
+
+hr {
+    clear: both;
+} 
+
+.messages .message {
+    display: block;
+}
+
+div#messages,  div#errors {
+     background-color: rgb(240,234,183);
+     border: 1px solid rgb(230,224,173);
+     margin-top: 10px;
+     margin-bottom: 10px;
+     padding: 5px;
+     font-size: 1.2em;
+
+}
+
+div.spacer {
+    clear: both;
+}
+
+.next-page, .prev-page {
+    display: block;
+    float: left;
+    margin: 0.5em 0;
+    padding: 0.2em 0.5em 0.5em 0.5em;
+    border-top: 1px solid gray;
+}
+
+.next-page { padding-right: 1em; }
+.prev-page { padding-left: 1em; }
+
+div#jifty-wait-message {
+    color: red;
+    background: black;
+    font-size: 2em;
+    position: fixed;
+    top: 10px;
+    right: 10px;
+    z-index: 42;
+    float: right;
+}
+
+
+
+div.warning {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+
+ background-color: red;
+ color: white;
+ padding: .5em;
+ border-bottom: 1px solid #000;
+}
+
+div.warning a {
+ color: white;
+
+}
+
+ul.menu {
+  display: block;
+
+}
+
+h1.title {
+  border-bottom: 1px solid black;
+}

Added: jifty/branches/schema-plugins/share/web/static/css/calendar.css
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/css/calendar.css	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,27 @@
+.calendar {
+    text-align: center;
+    margin: 2em 0 0 0;
+}
+
+.calendar td, .calendar th { padding: 0.1em 0.25em 0.1em 0.25em; }
+
+.calendar caption .month {
+    padding: 0 1em 0 1em;
+    font-size: 1.5em;
+}
+.select-free {
+    overflow: hidden;
+    z-index:10;
+}
+
+.select-free iframe {
+    display:none;
+    display/**/:block;
+    position:absolute;
+    top:0;
+    left:0;
+    z-index:-1;
+    filter:mask();
+    width:3000px;
+    height:3000px;
+}

Added: jifty/branches/schema-plugins/share/web/static/css/combobox.css
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/css/combobox.css	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,36 @@
+/*
+%# ComboBox styles... some properties like height and width must be dynamically
+%# set in the JS (at least for now).
+
+.combobox {
+%#    border: 2px inset #333;
+%#    padding-left: 0.5em;
+%#    padding-bottom: 0.1em;
+}
+*/
+
+.combobox .combo-button {
+/*%#    padding: 0 2px 0 2px;*/
+    padding-top: 2px;
+    margin: 0;
+    background: ButtonFace;
+    color: ButtonText;
+    border-right: 2px outset ButtonHighlight;
+    border-bottom: 2px outset ButtonHighlight;
+    border-top: 1px inset #000;
+    border-left: 0px inset #fff; 
+    margin-left: 0;
+    padding-left: 0;
+    cursor: default;
+    font-size: 8pt;
+}
+
+.combobox .combo-text {
+    border: 1px inset #333;
+    margin: 0;
+    padding: 0;
+}
+
+.combobox .combo-list {
+    z-index: 200;
+}

Added: jifty/branches/schema-plugins/share/web/static/css/context-menus.css
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/css/context-menus.css	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,102 @@
+ul.context_menu {
+    clear: none;
+    float: left;
+}
+
+ul.context_menu, ul.context_menu ul {
+    list-style: none;
+    margin-left: 0;
+    padding-left: 0;
+}
+
+.context_menu li.toplevel {
+    float: left;
+    margin: 0 0.5em 0 0;
+    padding: 0.2em;
+
+    border: 1px solid #ccc;
+    border-top-color: white;
+    border-left-color: white;
+
+    color: #793300;
+}
+
+.context_menu li.toplevel ul li {
+    color: gray;
+}
+
+.context_menu li.toplevel .title {
+    font-weight: bold;
+}
+
+.context_menu li.toplevel ul {
+    font-size: 0.9em;
+    display: none;
+
+    padding: 0.1em 0.2em 0.5em 0.1em;
+    background: white;
+    border-top: 1px dotted lightgrey;
+    border-left: 1px solid lightgrey;
+    border-right: 1px solid grey;
+    border-bottom: 1px solid grey;
+}
+
+.context_menu li.toplevel ul a {
+    width: 100%;
+    display: block;
+}
+
+.context_menu .open span.expand a {
+    background-image: url(/static/images/silk/bullet_arrow_up.png);
+}
+
+.context_menu span.expand a {
+    background: url(/static/images/silk/bullet_arrow_down.png) no-repeat center center;
+    padding-left: 16px;
+    border: 1px solid transparent;
+    color: white;
+    margin-left: 0.3em;
+    min-height: 1em;
+}
+
+.context_menu li.toplevel:hover .expand a {
+    border-top: 1px solid grey;
+    border-left: 1px solid grey;
+    border-right: 1px solid lightgrey;
+    border-bottom: 1px solid lightgrey;
+}
+
+.context_menu li.open a {
+    border-bottom-color: transparent;
+}
+
+.context_menu li.open, .context_menu li.closed:hover {
+    border-top: 1px solid lightgrey;
+    border-left: 1px solid lightgrey;
+    border-right: 1px solid grey;
+    border-bottom-color: transparent;
+}
+
+.context_menu li.closed:hover {
+    border-bottom-color: grey;
+}
+
+.context_menu li.toplevel span.expand a:active {
+    border-top: 1px solid white;
+    border-left: 1px solid white;
+    border-right: 1px solid grey;
+    border-bottom: 1px solid grey;
+
+    position: relative;
+    top: 1px;
+    left: 1px;
+}
+
+.context_menu li ul li {
+    padding: 0.2em;
+}
+
+.context_menu li.open ul li:hover {
+    background: #eee;
+}
+

Added: jifty/branches/schema-plugins/share/web/static/css/forms.css
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/css/forms.css	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,212 @@
+div.form {
+    background-color: #f6f6f6;
+    padding: 0.2em;
+    margin: 0 auto;
+}
+
+div.form_field {
+    clear: both;
+    padding-top: 0.5em;
+}
+
+.twocolumn {
+    width: 49%;
+    float: left;
+}
+
+div.form_field label.label, div.form_field span.label {
+    float: left;
+    width: 15%;
+    text-align: right;
+    font-size: 80%;
+    padding: 0.2em 0.5em 0 0;
+}
+
+div.form_field .hints {
+    width: 83%;
+    font-size: 80%;
+    padding-top: 0.2em;
+    float: right;
+    text-align: left;
+    color: #999999;
+}
+
+div.form_field .error, div.form_field .warning, div.form_field .canonicalization_note {
+    float: right;
+    width: 88%;
+    text-align: left;
+}
+
+div.form_field span.formw {
+    float: right;
+    width: 88%;
+    text-align: left;
+}
+
+input.text, input.date, input.password, textarea, select {
+    border-top: 1px solid #7c7c7c;
+    border-left: 1px solid #c3c3c3;
+    border-right: 1px solid #c3c3c3;
+    border-bottom: 1px solid #ddd;
+    background: #fff url(/static/images/css/fieldbg.gif) repeat-x top;
+    padding: 0.2em;
+}
+
+form input.ajaxautocompletes {
+    background: #fff url(/static/images/css/fieldbg-autocomplete.gif) repeat-x top right;
+    padding-right: 18px;
+}
+
+input.date {
+    width: 10em;
+}
+
+.submit_button {
+    display: block;
+    clear: both;
+}
+
+.submit_button input {
+    clear: both;
+    margin: 0.5em 0 0 16%;
+}
+
+input.button {
+    padding: 0.15em 1em;
+    font-weight: bold;
+}
+
+h2.inline input {
+    font-size: 115%;
+}
+
+/*
+These styles are meant to be placed after (and hence override) the current CSS
+for forms.  Some of the rules aren't needed if previous behavior doesn't need to
+be overridden.  ".inline" is our inline form class.
+*/
+
+div.inline .submit_button
+{
+}
+
+div.inline div.form_field {
+    float: left;
+
+    /* to counter previous rules */
+    clear: none;
+
+    /* aesthetic */
+    margin-right: 0.5em;
+}
+
+div.inline div.form_field label.label,
+div.inline div.form_field span,
+div.inline .hints,
+div.inline .error
+{
+    display: block;
+
+    /* to counter previous rules */
+    float: none;
+    width: auto;
+    text-align: left;
+}
+
+
+div.inline .hints {
+    display: none;
+
+}
+
+div.inline div.form_field label.label {
+    /* aesthetic */
+    padding-bottom: 0.1em;
+}
+
+
+/* So the admin ui is one row per line */
+
+.jifty_admin.item.inline {
+     clear: both;
+}
+
+.jifty_admin .editlink {
+    float: right;
+}
+
+.jifty_admin_header {
+    display: inline;
+}
+
+.jifty_admin_header a {
+    color: black;
+    display: block;
+    font-size: 0.6em;
+    height: 100%;
+    text-decoration: none;
+    font-family : sans-serif;
+}
+
+.field {
+    float: left;
+    margin: 0.15em 0.5em 0.15em 0.15em;
+    font-weight: bold;
+}
+
+.down {
+    height : 1.5em;
+    float: left;
+    background: url(/static/images/css/bullet_arrow_down.png) no-repeat bottom center;
+    margin-right: 0.2em;
+}
+
+.down_select {
+    height : 1.5em;
+    float: left;
+    background: url(/static/images/css/bullet_arrow_down.png) no-repeat bottom center;
+    margin-right: 0.2em;
+}
+
+.down_select a {
+    font-weight: bold;
+    color: #900;
+}
+    
+.up {
+    height : 1.5em;
+    float: left;
+    background: url(/static/images/css/bullet_arrow_up.png) no-repeat bottom center;
+    margin-right: 0.2em;
+} 
+
+.up_select {
+    height : 1.5em;
+    float: left;
+    background: url(/static/images/css/bullet_arrow_up.png) no-repeat bottom center;
+    margin-right: 0.2em;
+} 
+
+.up_select a {
+    font-weight: bold;
+    color: #900;
+}
+
+div.subline {
+    display: inline;
+    float: left;  
+}
+
+
+.selected {
+    background: #dddddd;
+}
+
+input.placeholder, textarea.placeholder {
+    color: #666;
+}
+
+form div.mandatory label {
+    font-weight: bold;
+}
+

Added: jifty/branches/schema-plugins/share/web/static/css/halos.css
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/css/halos.css	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,124 @@
+.halo_actions {
+    position: fixed;
+    border: 1px solid black;
+    background: #ccc;
+}
+
+.halo_actions h1 {
+    color: #fff;
+    background-color: #600;
+    border-bottom: 1px solid black;
+    padding: 0.25em;
+    margin: 0;
+    cursor: pointer;
+    font-size: 120%;
+}
+
+.halo_actions h1 a { 
+    color: #fff;
+}
+
+.halo_actions .resize {
+    border: 5px solid #ccc;
+    border-bottom-color: #000;
+    border-right-color: #666;
+    border-left-color: #aaa;
+    cursor: pointer;
+    color: #ccc;
+    display: block;
+    height: 0;
+    width: 0;
+    overflow: hidden;
+}
+
+.halo_actions .section {
+    color: #ccc;
+    background-color: #300;
+    border-top: 1px solid black;
+    margin-top: 0.5em;
+    font-size: 120%;
+    font-weight: bold;
+}
+
+.halo_actions .section a {
+    text-align: center;
+    color: #ccc;
+}
+
+.halo_actions .body {
+    font-size: 110%;
+    margin:0;
+    padding:0 1em;
+}
+
+.halo_actions .body ul {
+    margin:0;
+    padding:0;
+    margin-left: 1em;
+}
+
+.halo_actions .body .path {
+    font-family: monospace;
+    font-size: 120%;
+    text-align: center;
+}
+
+.halo_actions .body .time {
+    font-style: italic;
+    text-align: center;
+}
+
+.halo_actions .body .fixed {
+    font-family: monospace;
+}
+
+.halo_button {
+    color: yellow;
+    position: absolute;
+    z-index: 9999;
+    font-size:2em;
+}
+
+#render_info_tree {
+    position: fixed;
+    background: white;
+    border: 1px solid yellow;
+    right: 1em;
+    bottom: 1em;
+    border: 2px solid #fc0;
+    padding: 1em;
+}
+
+#render_info_tree ul {
+    list-style: none;
+    padding-left: 1em;
+}
+
+#render_info {
+    position: fixed;
+    right:0.5em;
+    bottom:0;
+}
+
+a.inline_edit {
+
+    display: block;
+    width: 0;
+    padding-left: 22px;
+    min-height: 20px;
+
+    background: no-repeat left center;
+    background: url(/static/images/silk/pencil.png) no-repeat 0 0;
+}
+
+a.inline_create {
+
+    display: block;
+    width: 0;
+    padding-left: 22px;
+    min-height: 20px;
+
+    background: no-repeat left center;
+    background: url(/static/images/silk/pencil_add.png) no-repeat 0 0;
+}
+

Added: jifty/branches/schema-plugins/share/web/static/css/keybindings.css
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/css/keybindings.css	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,26 @@
+div#keybindings {
+    color: #666666;
+    margin-top: 2em;
+
+}
+
+dl.keybindings .keybinding {
+    display: inline;
+}
+
+dl.keybindings dt  {
+    margin: 0;
+    font-weight: bold;
+    display: inline;
+
+}
+dl.keybindings dt:after  {
+    content: ":";
+
+}
+dl.keybindings dd  {
+    margin-right: 1.5em;
+    margin-left: 0.5em;
+    display: inline;
+    white-space: nowrap;
+}

Added: jifty/branches/schema-plugins/share/web/static/css/main.css
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/css/main.css	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,13 @@
+ at import "app-base.css";
+ at import "base.css";
+ at import "nav.css";
+ at import "context-menus.css";
+ at import "calendar.css";
+ at import "combobox.css";
+ at import "keybindings.css";
+ at import "forms.css";
+ at import "halos.css";
+ at import "app.css";
+ at import "autocomplete.css";
+ at import "yui/calendar/calendar.css";
+ at import "notices.css";

Added: jifty/branches/schema-plugins/share/web/static/css/nav.css
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/css/nav.css	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,80 @@
+/* from http://www.456bereastreet.com/lab/csstabs/nested/# */
+
+
+div#navigation {
+        width:598px;
+        height:45px;
+        position:relative;
+}
+div#navigation ul {
+        width:598px;
+        font-family:Tahoma,"Trebuchet MS", sans-serif;
+        font-size:11px;
+        line-height:11px;
+        color:#000;
+}
+ul.menu {
+        margin:0;
+        padding:6px 0 5px 0;
+        background-color:#202F66;
+        list-style-type:none;
+        border-bottom:2px solid #000;
+}
+ul.menu li {
+        display:inline;
+        margin:0;
+        padding:0;
+}
+ul.submenu {
+        margin:0;
+        padding:5px 0;
+        background-color:#24568E;
+        list-style-type:none;
+        position: absolute;
+        top: 22px;
+        left:0;
+}
+ul.submenu li {
+        display:inline;
+        margin:0;
+        padding:0;
+}
+ul.submenu li+li {
+        border-left:1px solid #ccc;
+}
+div#navigation a:link,
+div#navigation a:visited,
+div#navigation a:hover {
+        color:#fff;
+        text-decoration:none;
+        padding:5px;
+        background-color:#202F66;
+}
+div#navigation ul.menu a:link,
+div#navigation ul.menu a:visited,
+div#navigation ul.menu a:hover {
+        font-weight:bold;
+        border:1px solid #000;
+        text-transform:uppercase;
+}
+div#navigation ul.menu a:hover {
+        background-color:#24568E;
+}
+div#navigation ul.menu li.active a:link,
+div#navigation ul.menu li.active a:visited,
+div#navigation ul.menu li.active a:hover {
+        background-color:#24568E;
+        font-weight: bold;
+        border-bottom-color:#24568E;
+}
+div#navigation ul.submenu a:link,
+div#navigation ul.submenu a:visited,
+div#navigation ul.submenu a:hover {
+        font-weight:normal;
+        padding:0 5px;
+        background-color:#24568E;
+        border:0;
+}
+div#navigation ul.submenu a:hover {
+        text-decoration:underline;
+}

Added: jifty/branches/schema-plugins/share/web/static/css/notices.css
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/css/notices.css	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,39 @@
+/* IE can't handle this nicely w/ AJAX validation */
+
+html>body .message {
+    background: url(/static/images/silk/information.png) no-repeat center left;
+    padding-left: 20px;
+}
+
+html>body .error {
+    background: url(/static/images/silk/error.png) no-repeat center left;
+    padding-left: 20px;
+}
+
+.error {
+    color: #a00000;
+}
+
+div#messages,  div#errors {
+     background-color: rgb(240,234,183);
+     border: 1px solid rgb(230,224,173);
+     margin-top: 10px;
+     margin-bottom: 10px;
+     padding: 5px 20px 5px 5px;
+     font-size: 1.2em;
+     position: relative;
+}
+
+#dismiss_messages,
+#dismiss_errors
+{
+    padding-left: 16px;
+    background: url(/static/images/silk/cancel_grey.png) no-repeat;
+    position: absolute;
+    top: 0.4em;
+    right: 2px;
+    width: 0;
+    display: block;
+    text-indent: -9999em;
+}
+

Added: jifty/branches/schema-plugins/share/web/static/css/yui/calendar/calendar.css
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/css/yui/calendar/calendar.css	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,191 @@
+/*
+Copyright (c) 2006, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.net/yui/license.txt
+Version 0.12
+*/
+
+.yui-calcontainer {
+	position:relative;
+	padding:5px;
+	background-color:#F7F9FB;
+	border:1px solid #7B9EBD;
+	float:left;
+	overflow:hidden;
+}
+
+.yui-calcontainer iframe {
+	position:absolute;
+	border:none;
+	margin:0;padding:0;
+	left:-1px;
+	top:-1px;
+	z-index:0;
+	width:50em;
+	height:50em;
+}
+
+.yui-calcontainer.multi {
+	padding:0;
+}
+
+.yui-calcontainer.multi .groupcal {
+	padding:5px;
+	background-color:transparent;
+	z-index:1;
+	float:left;
+	position:relative;
+	border:none;
+}
+
+.yui-calcontainer .title {
+	font:100% sans-serif;
+	color:#000;
+	font-weight:bold;
+	margin-bottom:5px;
+	height:25px;
+	position:absolute;
+	top:3px;left:5px;
+	z-index:1;
+}
+
+.yui-calcontainer .close-icon {
+	position:absolute;
+	right:3px;
+	top:3px;
+	border:none;
+	z-index:1;
+}
+
+/* Calendar element styles */
+
+.yui-calendar {
+	font:100% sans-serif;
+	text-align:center;
+	border-spacing:0;
+	border-collapse:separate;
+	position:relative;
+}
+
+.yui-calcontainer.withtitle {
+	padding-top:1.5em;
+}
+
+.yui-calendar .calnavleft {
+	position:absolute;
+	background-repeat:no-repeat;
+	cursor:pointer;
+	top:2px;
+	bottom:0;
+	width:9px;
+	height:12px;   
+	left:2px;
+	z-index:1;
+}
+
+.yui-calendar .calnavright {
+	position:absolute;
+	background-repeat:no-repeat;
+	cursor:pointer;
+	top:2px;
+	bottom:0;
+	width:9px;
+	height:12px;  
+	right:2px;
+	z-index:1;
+}
+
+.yui-calendar td.calcell {
+	padding:.1em .2em;
+	border:1px solid #E0E0E0;
+	text-align:center;
+}
+
+.yui-calendar td.calcell a {
+	color:#003DB8;
+	text-decoration:none;
+}
+
+.yui-calendar td.calcell.today {
+	border:1px solid #000;
+}
+
+.yui-calendar td.calcell.oom {
+	cursor:default;
+	color:#999;
+	background-color:#EEE;
+	border:1px solid #E0E0E0;
+}
+
+.yui-calendar td.calcell.selected {
+	color:#003DB8;
+	background-color:#FFF19F;
+	border:1px solid #FF9900;
+}
+
+.yui-calendar td.calcell.calcellhover {
+	cursor:pointer;
+	color:#FFF;
+	background-color:#FF9900;
+	border:1px solid #FF9900;
+}
+
+.yui-calendar td.calcell.calcellhover a {
+	color:#FFF;
+}
+
+.yui-calendar td.calcell.restricted {
+	text-decoration:line-through;
+}
+
+.yui-calendar td.calcell.previous {
+	color:#CCC;
+}
+
+.yui-calendar td.calcell.highlight1 { background-color:#CCFF99; }
+.yui-calendar td.calcell.highlight2 { background-color:#99CCFF; }
+.yui-calendar td.calcell.highlight3 { background-color:#FFCCCC; }
+.yui-calendar td.calcell.highlight4 { background-color:#CCFF99; }
+
+.yui-calendar .calhead {
+	border:1px solid #E0E0E0;
+	vertical-align:middle;
+	background-color:#FFF;
+}
+
+.yui-calendar .calheader {
+	position:relative;
+	width:100%;
+	text-align:center;
+}
+
+.yui-calendar .calheader img {
+	border:none;
+}
+
+.yui-calendar .calweekdaycell {
+	color:#666;
+	font-weight:normal;
+	text-align:center;
+	width:1.5em;
+}
+
+.yui-calendar .calfoot {
+	background-color:#EEE;
+}
+
+.yui-calendar .calrowhead, .yui-calendar .calrowfoot {
+	color:#666;
+	font-size:9px;
+	font-style:italic;
+	font-weight:normal;
+	width:15px;
+}
+
+.yui-calendar .calrowhead {
+	border-right-width:2px;
+}
+
+/*Specific changes for calendar running under fonts/reset */
+.yui-calendar a:hover {background:inherit;}
+p#clear {clear:left; padding-top:10px;}

Added: jifty/branches/schema-plugins/share/web/static/css/yui/tabview/border_tabs.css
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/css/yui/tabview/border_tabs.css	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,44 @@
+.yui-navset .yui-nav li a, .yui-navset .yui-content {
+    border:1px solid #000;  /* label and content borders */
+}
+
+.yui-navset .yui-nav .selected a, .yui-navset .yui-nav a:hover, .yui-navset .yui-content {
+    background-color:#f6f7ee; /* active tab, tab hover, and content bgcolor */
+}
+
+.yui-navset-top .yui-nav .selected a {
+    border-bottom:0; /* no bottom border for active tab */
+    padding-bottom:1px; /* to match height of other tabs */
+}
+
+.yui-navset-top .yui-content {
+    margin-top:-1px; /* for active tab overlap */
+}
+
+.yui-navset-bottom .yui-nav .selected a {
+    border-top:0; /* no bottom border for active tab */
+    padding-top:1px; /* to match height of other tabs */
+}
+
+.yui-navset-bottom .yui-content {
+    margin-bottom:-1px; /* for active tab overlap */
+}
+
+.yui-navset-left .yui-nav li.selected a {
+    border-right:0; /* no bottom border for active tab */
+    padding-right:1px; /* to match height of other tabs */
+}
+
+.yui-navset-left .yui-content {
+    margin-left:-1px; /* for active tab overlap */
+}
+
+.yui-navset-right .yui-nav li.selected a {
+    border-left:0; /* no bottom border for active tab */
+    padding-left:1px; /* to match height of other tabs */
+}
+
+.yui-navset-right .yui-content {
+    margin-right:-1px; /* for active tab overlap */
+    *margin-right:0; /* except IE */
+}
\ No newline at end of file

Added: jifty/branches/schema-plugins/share/web/static/css/yui/tabview/tabs.css
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/css/yui/tabview/tabs.css	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,66 @@
+/* default space between tabs */
+.yui-navset-top .yui-nav li, .yui-navset-bottom .yui-nav li {
+    margin-right:0.5em; /* horizontal tabs */
+}
+.yui-navset-left .yui-nav li, .yui-navset-right .yui-nav li {
+    margin-bottom:0.5em; /* vertical tabs */
+}
+
+.yui-navset .yui-nav li em { padding:.5em; } /* default tab padding */
+
+/* default width for side tabs */
+.yui-navset-left .yui-nav, .yui-navset-right .yui-nav { width:6em; }
+.yui-navset-left { padding-left:6em; } /* map to nav width */
+.yui-navset-right { padding-right:6em; } /* ditto */
+
+/* core */
+
+.yui-nav, .yui-nav li {
+    margin:0;
+    padding:0;
+    list-style:none;
+}
+.yui-navset li em { font-style:normal; }
+
+.yui-navset {
+    position:relative; /* contain absolute positioned tabs (left/right) */
+    zoom:1;
+}
+
+.yui-navset .yui-content { zoom:1; }
+
+.yui-navset-top .yui-nav li, .yui-navset-bottom .yui-nav li {
+    display:inline-block;
+    display:-moz-inline-stack;
+    *display:inline; /* IE */
+    vertical-align:bottom; /* safari: for overlap */
+    cursor:pointer; /* gecko: due to -moz-inline-stack on anchor */
+    zoom:1; /* IE: kill space between horizontal tabs */
+}
+
+.yui-navset .yui-nav a {
+    outline:0; /* gecko: keep from shifting */
+}
+
+.yui-navset .yui-nav a { position:relative; } /* IE: to allow overlap */
+
+.yui-navset .yui-nav li a {
+    display:block;
+    zoom:1;
+}
+
+.yui-navset-top .yui-nav li a, .yui-navset-bottom .yui-nav li a {
+    display:inline-block;
+    vertical-align:bottom; /* safari: for overlap */
+}
+
+.yui-navset-bottom .yui-nav li a {
+    vertical-align:text-top; /* for inline overlap (reverse for Op border bug) */
+}
+
+.yui-navset .yui-nav li a em { display:block; }
+
+/* position left and right oriented tabs */
+.yui-navset-left .yui-nav, .yui-navset-right .yui-nav { position:absolute; z-index:1; }
+.yui-navset-left .yui-nav { left:0; }
+.yui-navset-right .yui-nav { right:0; }

Added: jifty/branches/schema-plugins/share/web/static/favicon.ico
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/share/web/static/images/css/bullet_arrow_down.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/share/web/static/images/css/bullet_arrow_up.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/share/web/static/images/css/fieldbg-autocomplete.gif
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/share/web/static/images/css/fieldbg.gif
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/share/web/static/images/iepngfix/blank.gif
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/share/web/static/images/pony.jpg
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/share/web/static/images/silk/bullet_arrow_down.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/share/web/static/images/silk/bullet_arrow_up.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/share/web/static/images/silk/calendar.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/share/web/static/images/silk/cancel.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/share/web/static/images/silk/cancel_grey.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/share/web/static/images/silk/error.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/share/web/static/images/silk/information.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/share/web/static/images/silk/pencil.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/share/web/static/images/silk/pencil_add.png
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/share/web/static/images/yui/us/my/bn/x_d.gif
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/share/web/static/images/yui/us/tr/callt.gif
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/share/web/static/images/yui/us/tr/calrt.gif
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/share/web/static/js/app.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/app.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,2 @@
+/* Put your application's custom JS here... */
+

Added: jifty/branches/schema-plugins/share/web/static/js/app_behaviour.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/app_behaviour.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,131 @@
+/*
+
+  This file is intended for you to add application-specific Javascript
+  behaviours. See http://bennolan.com/behaviour/ for an introduction
+  to the Behaviour library.
+
+  Behaviour lets you apply javascript to elements of the DOM using CSS
+  selectors. A simple example:
+
+  var myrules = {
+      "h2.rounded": function(element) {
+  	Rico.Corner.round(element);
+      }
+  };
+          
+  Behaviour.register(myrules);
+
+  In general, you'll rarely if ever have to worry about calling
+  Behaviour.apply() yourself -- Jifty will take care of it on DOM load
+  and on any AJAX updates that it does.
+
+
+  Some Notes About Writing Behaviours
+  ===================================
+
+  * Jifty's Behaviour.js uses the cssQuery[1] library to do our DOM
+    lookups by CSS selector. cssQuery is very powerful, but can be
+    slow as DOM size grows. For best performance, follow these
+    guidelines when writing behaviours, whenever possible:
+
+    * Prefer selectors that begin with '#id'. cssQuery will use
+      document.getElementByID to get the ID in question, meaning we
+      only have to search a small fragment of the DOM by hand
+
+    * Barring that, prefer selectors of the form 'element.class' over
+      simply '.class' selectors. This lets us filter for that element
+      specifically using DOM calls, again hugely reducing the amount
+      of DOM walking we have to do. 
+
+    [1] http://dean.edwards.name/my/cssQuery/
+
+
+  * Behaviour has something of a reputation for leaking memory. The
+    reason for this is a common idiom used in constructing
+    behaviours. Code like:
+
+    Behaviour.register({
+        'a.help': function(e) {
+            e.onclick = function() {
+        	openInHelpWindow(this);
+        	return false;
+            }
+        }
+    });
+
+    will leak memory in Internet Explorer, thanks to how IE handles
+    garbage collection (See the footnote for details). To avoid this,
+    you can use one of the following two idioms:
+
+    (a) declare the onclick function elsewhere:
+
+    function openHelpLink() {
+        openInHelpWindow(this);
+        return false;
+    }
+    
+    Behaviour.register({
+        'a.help': function(e) {
+            e.onclick = openHelpLink;
+        }
+    });
+
+    (b) Set the element to 'null' at the end of the Behaviour function:
+
+    Behaviour.register({
+        'a.help': function(e) {
+            e.onclick = function() {
+        	openInHelpWindow(this);
+        	return false;
+            }
+	    e = null;
+        }
+    });
+
+  * Jifty has recently gained built-in support for limited profiling
+    of Behaviours via the ProfileBehaviour plugin (in the Jifty svn
+    tree). After installing the module, add it to your config.yml,
+    using, e.g:
+
+      Plugins:
+        - ProfileBehaviour: {}
+
+    Once you do this, all pages in your application should have a
+    ``Behaviour profile'' link at the bottom left hand corner of the
+    screen. Click it to get a breakdown of how much time your
+    javascript is spending in which behaviours, and whether the time
+    is spent in looking up the CSS Selector you passed (cssQuery
+    time), or in applying the Behaviour function (function time).
+    
+
+    ** Footnote **
+
+    The reason that code leaks in IE is that Internet Explorer uses
+    reference counting to manage memory in its Javascript engine,
+    which means that circular references are never freed. When you
+    write this code:
+
+    Behaviour.register({
+        'a.help': function(e) {		// <-- FUNCTION A
+            e.onclick = function() {	// <-- FUNCTION B 
+        	openInHelpWindow(this);
+        	return false;
+            }
+        }
+    });
+
+    You are in fact creating a circular data structure because
+    function `B', when it is created, stores a reference to the
+    environment in which it was created, which includes the variable
+    `e'. `e', however, also references function `B' through its
+    `onclick' property, and this a circular chain of references is
+    created, which IE will never garbage collect.
+
+    Solution (a) addresses this by moving function `b' outside of the
+    scope where `e' is defined. Solution (b) addresses it by setting
+    `e' to null in the environment around `b', which means that that
+    environment no longer contains a reference to that DOM node, and
+    the loop no longer exists.
+
+*/
+

Added: jifty/branches/schema-plugins/share/web/static/js/behaviour.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/behaviour.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,59 @@
+/*
+   Modified to fix some bugs, use a different css query engine, and to
+   to use JSAN classes.
+   
+   Based on Behaviour v1.1 by Ben Nolan, June 2005, which was based
+   largely on the work of Simon Willison.
+ 
+   Usage:   
+   
+    var myrules = {
+        'b.someclass' : function(element){
+            element.onclick = function(){
+                alert(this.innerHTML);
+            }
+        },
+        '#someid u' : function(element){
+            element.onmouseover = function(){
+                this.innerHTML = "BLAH!";
+            }
+        }
+    };
+    
+    Behaviour.register(myrules);
+    
+    // Call Behaviour.apply() to re-apply the rules (if you
+    // update the dom, etc).
+
+*/   
+
+JSAN.use("DOM.Events");
+JSAN.use("Upgrade.Array.push");
+
+var Behaviour = {
+    list: [],
+    
+    register: function(sheet) {
+        Behaviour.list.push(sheet);
+    },
+    
+    apply: function() {
+	var root = arguments[0];
+	if(root) root = $(root);
+
+        for (var h = 0; sheet = Behaviour.list[h]; h++) {
+            for (var selector in sheet) {
+		var start = new Date();
+                var elements = cssQuery(selector, root);
+
+                if ( !elements ) continue;
+
+                for (var i = 0; element = elements[i]; i++) {
+                    sheet[selector](element);
+		}
+            }
+        }
+    }
+}    
+
+DOM.Events.addListener( window, "load", function() { Behaviour.apply() } );

Added: jifty/branches/schema-plugins/share/web/static/js/bps_util.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/bps_util.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,100 @@
+// XXX TODO This library should likely be refactored to use behaviour
+
+function focusElementById(id) {
+    var e = document.getElementById(id);
+    if (e) e.focus();
+}
+
+function updateParentField(field, value) {
+    if (window.opener) {
+        window.opener.document.getElementById(field).value = value;
+        window.close();
+    }
+}
+
+function createCalendarLink(id) {
+    return Jifty.Calendar.registerDateWidget( id );
+}
+
+JSAN.use("DOM.Events");
+
+function buttonToLink(e) {
+    var link = document.createElement("a");
+    link.setAttribute("href","#");
+    link.setAttribute("name",e.getAttribute("name"));
+
+    var form = Form.Element.getForm(e);
+    var onclick = e.getAttribute("onclick");
+
+    /* Simple buttons that don't use any JS need us to create an onclick
+       for them that makes sure the original button's name gets passed
+       and the form submitted normally (without any Ajax-ness)
+    */
+    if ( !onclick ) {
+        DOM.Events.addListener( link, "click", function(ev) {
+            var a = ev.target;
+            var hidden = document.createElement("input");
+            hidden.setAttribute("type", "hidden");
+            hidden.setAttribute("name", a.getAttribute("name"));
+            a["virtualform"].appendChild( hidden );
+            if ( a["virtualform"].onsubmit )
+                a["virtualform"].onsubmit();
+            a["virtualform"].submit();
+        });
+    }
+    link.setAttribute("onclick", onclick);
+
+    link.className = e.className;
+    link["virtualform"] = form;
+    link.appendChild(document.createTextNode(e.getAttribute("value")));
+
+    e.parentNode.insertBefore(link, e.nextSibling);
+    e.parentNode.removeChild(e);
+    return link;
+}
+
+// onload handlers
+
+var onLoadStack     = new Array();
+var onLoadLastStack = new Array();
+var onLoadExecuted  = 0;
+
+function onLoadHook(commandStr) {
+    if(typeof(commandStr) == "string") {
+        onLoadStack[onLoadStack.length] = commandStr;
+        return true;
+    }
+    return false;
+}
+
+// some things *really* need to be done after everything else
+function onLoadLastHook(commandStr) {
+    if(typeof(commandStr) == "string"){
+        onLoadLastStack[onLoadLastStack.length] = commandStr;
+        return true;
+    }
+    return false;
+}
+
+function doOnLoadHooks() {
+    if(onLoadExecuted) return;
+    for (var x=0; x < onLoadStack.length; x++) { 
+        eval(onLoadStack[x]);
+    }
+    for (var x=0; x < onLoadLastStack.length; x++) { 
+        eval(onLoadLastStack[x]); 
+    }
+    onLoadExecuted = 1;
+}
+
+
+if (typeof window.onload != 'function') {
+    window.onload = doOnLoadHooks;
+} else {
+    var oldonload = window.onload;
+    
+    window.onload = function() {
+        oldonload();
+        doOnLoadHooks();
+    }
+}

Added: jifty/branches/schema-plugins/share/web/static/js/calendar.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/calendar.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,134 @@
+JSAN.use("DOM.Events");
+
+if ( typeof Jifty == "undefined" ) Jifty = { };
+
+Jifty.Calendar = {
+    registerDateWidget: function(id) {
+        var input = $(id);
+        
+        if ( !input ) return false;
+
+        DOM.Events.addListener( input, "focus", Jifty.Calendar.toggleCalendar );
+        DOM.Events.addListener( input, "blur", Jifty.Calendar.doBlur );
+        return true;
+    },
+
+    dateRegex: /^(\d{4})\W(\d{2})\W(\d{2})/,
+    
+    Options: {
+        NAV_ARROW_LEFT: "/static/images/yui/us/tr/callt.gif",
+        NAV_ARROW_RIGHT: "/static/images/yui/us/tr/calrt.gif",
+        OOM_SELECT: true
+    },
+
+    toggleCalendar: function(ev) {
+        var calId  = "cal_" + ev.target.id;
+        var wrapId = calId + "_wrap";
+        var wrap   = $(wrapId);
+        var input  = ev.target;
+
+        if ( Jifty.Calendar.openCalendar == wrapId ) {
+            Jifty.Calendar.hideOpenCalendar();
+            return;
+        }
+        
+        Jifty.Calendar.hideOpenCalendar();
+        
+        /* We need to delay Jifty's canonicalization until after we've
+           selected a value via the calendar */
+        Form.Element.disableValidation(input);
+        
+        wrap = document.createElement("div");
+        wrap.setAttribute( "id", wrapId );
+        wrap.setAttribute( "className", "select-free" );
+        
+        wrap.style.position = "absolute";
+        wrap.style.left     = Jifty.Utils.findRelativePosX( input ) + "px";
+        wrap.style.top      = Jifty.Utils.findRelativePosY( input ) + input.offsetHeight + "px";
+        wrap.style.zIndex   = 40;
+        
+        input.parentNode.insertBefore( wrap, input.nextSibling );
+
+        var cal;
+        
+        if (Jifty.Calendar.dateRegex.test(input.value) ) {
+            var bits = input.value.match(Jifty.Calendar.dateRegex);
+            cal = new YAHOO.widget.Calendar( calId,
+                                             wrapId,
+                                             { pagedate: bits[2]+"/"+bits[1],
+                                               selected: bits[2]+"/"+bits[3]+"/"+bits[1] }
+                                            );
+        }
+        else {
+            cal = new YAHOO.widget.Calendar( calId, wrapId );
+        }
+        
+        cal.cfg.applyConfig( Jifty.Calendar.Options );
+        cal.cfg.fireQueue();
+        
+        cal.selectEvent.subscribe( Jifty.Calendar.handleSelect, { event: ev, calendar: cal }, true );
+        cal.changePageEvent.subscribe( function() { Jifty.Calendar._blurredCalendar = null; }, null, false );
+        
+        cal.render();
+
+        Jifty.Calendar.openCalendar = wrapId;
+        Jifty.Utils.scrollToShow( wrapId );
+        /*Jifty.Calendar.preventStutter = wrapId;*/
+    },
+
+    handleSelect: function(type, args, obj) {
+        // args = [ [ [yyyy, mm, dd] ] ]
+        var year  = args[0][0][0],
+            month = args[0][0][1],
+            day   = args[0][0][2];
+
+        var input = obj.event.target;
+        
+        input.value = year + "-" + month + "-" + day;
+
+        Jifty.Calendar.hideOpenCalendar();
+    },
+
+    openCalendar: "",
+
+    hideOpenCalendar: function() {
+        if ( Jifty.Calendar.openCalendar && $( Jifty.Calendar.openCalendar ) ) {
+
+            /* Get the input's ID */
+            var inputId = Jifty.Calendar.openCalendar;
+                inputId = inputId.replace(/^cal_/, '');
+                inputId = inputId.replace(/_wrap$/, '');
+
+            Element.remove(Jifty.Calendar.openCalendar);
+
+            var input = $( inputId );
+
+            /* Reenable canonicalization, and do it */
+            Form.Element.enableValidation(input);
+            Form.Element.validate(input);
+
+            Jifty.Calendar.openCalendar = "";
+        }
+    },
+
+    _doneBlurOnce: false,
+    _blurredCalendar: null,
+    doBlur: function(ev) {
+        if ( Jifty.Calendar.openCalendar && !Jifty.Calendar._doneBlurOnce ) {
+            Jifty.Calendar._doneBlurOnce    = true;
+            Jifty.Calendar._blurredCalendar = Jifty.Calendar.openCalendar;
+            setTimeout( Jifty.Calendar.doBlur, 200 );
+            return;
+        }
+        else if ( Jifty.Calendar._doneBlurOnce
+                  && Jifty.Calendar._blurredCalendar == Jifty.Calendar.openCalendar )
+        {
+            Jifty.Calendar.hideOpenCalendar();
+        }
+        Jifty.Calendar._doneBlurOnce    = false;
+        Jifty.Calendar._blurredCalendar = null;
+    }
+};
+
+/*DOM.Events.addListener( window, "click", Jifty.Calendar.hideOpenCalendar );*/
+

Added: jifty/branches/schema-plugins/share/web/static/js/combobox.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/combobox.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,233 @@
+function ComboBox_InitWith(n) {
+    if ( typeof( window.addEventListener ) != "undefined" ) {
+        window.addEventListener("load", ComboBox_Init(n), false);
+    } else if ( typeof( window.attachEvent ) != "undefined" ) {
+        window.attachEvent("onload", ComboBox_Init(n));
+    } else {
+        ComboBox_Init(n)();
+    }
+}
+function ComboBox_Init(n) {
+    return function () {
+        if ( ComboBox_UplevelBrowser( n ) ) {
+            ComboBox_Load( n );
+        }
+    }
+}
+function ComboBox_UplevelBrowser( n ) {
+    if( typeof( document.getElementById ) == "undefined" ) return false;
+    var combo = document.getElementById( n + "_Container" );
+    if( combo == null || typeof( combo ) == "undefined" ) return false;
+    if( typeof( combo.style ) == "undefined" ) return false;
+    if( typeof( combo.innerHTML ) == "undefined" ) return false;
+    return true;
+}
+function ComboBox_Load( comboId ) {
+    var combo  = document.getElementById( comboId + "_Container" );
+    var button = document.getElementById( comboId + "_Button" );
+    var list   = document.getElementById( comboId + "_List" );
+    var text   = document.getElementById( comboId );
+    
+    
+    combo.List = list;
+    combo.Button = button;
+    combo.Text = text;
+    
+    button.Container = combo;
+    button.Toggle = ComboBox_ToggleList;
+    button.onclick = button.Toggle;
+    button.onmouseover = function(e) { this.Container.List.DisableBlur(e); };
+    button.onmouseout = function(e) { this.Container.List.EnableBlur(e); };
+    button.innerHTML = "\u25BC";
+    button.onselectstart = function(e){ return false; };
+    button.style.height = ( list.offsetHeight - 4 ) + "px";
+    
+    text.Container = combo;
+    text.TypeDown = ComboBox_TextTypeDown;
+    text.KeyAccess = ComboBox_TextKeyAccess;
+    text.onkeyup = function(e) { this.KeyAccess(e); this.TypeDown(e); };
+    text.style.width = ( list.offsetWidth ) + "px";
+    
+    list.Container = combo;
+    list.Show = ComboBox_ShowList;
+    list.Hide = ComboBox_HideList;
+    list.EnableBlur = ComboBox_ListEnableBlur;
+    list.DisableBlur = ComboBox_ListDisableBlur;
+    list.Select = ComboBox_ListItemSelect;
+    list.ClearSelection = ComboBox_ListClearSelection;
+    list.KeyAccess = ComboBox_ListKeyAccess;
+    list.FireTextChange = ComboBox_ListFireTextChange;
+    list.onchange = null;
+    list.onclick = function(e){ this.Select(e); this.ClearSelection(); this.FireTextChange(); };
+    list.onkeyup = function(e) { this.KeyAccess(e); };
+    list.EnableBlur(null);
+    list.style.position = "absolute";
+    list.size = ComboBox_GetListSize( list );
+    list.IsShowing = true;
+    list.Hide();
+    
+}
+function ComboBox_InitEvent( e ) {
+    if( typeof( e ) == "undefined" && typeof( window.event ) != "undefined" ) e = window.event;
+    if( e == null ) e = new Object();
+    return e;
+}
+function ComboBox_ListClearSelection() {
+            if ( typeof( this.Container.Text.createTextRange ) == "undefined" ) return;
+    var rNew = this.Container.Text.createTextRange();
+    rNew.moveStart('character', this.Container.Text.value.length) ;
+    rNew.select();
+}
+function ComboBox_GetListSize( theList ) {
+    ComboBox_EnsureListSize( theList );
+    return theList.listSize;
+}
+function ComboBox_EnsureListSize( theList ) {
+    if ( typeof( theList.listSize ) == "undefined" ) {
+        if( typeof( theList.getAttribute ) != "undefined" ) {
+            if( theList.getAttribute( "listSize" ) != null && theList.getAttribute( "listSize" ) != "" ) {
+                theList.listSize = theList.getAttribute( "listSize" );
+                return;
+            }
+        }
+        if( theList.options.length > 0 ) {
+            theList.listSize = theList.options.length;
+            return;
+        }
+        theList.listSize = 4;
+    }
+}
+function ComboBox_ListKeyAccess(e) { //Make enter/space and escape do the right thing :)
+    e = ComboBox_InitEvent( e );
+    if( e.keyCode == 13 || e.keyCode == 32 ) {
+        this.Select();
+        return;
+    }
+    if( e.keyCode == 27 ) {
+        this.Hide();
+        this.Container.Text.focus();
+        return;
+    }
+}
+function ComboBox_TextKeyAccess(e) { //Make alt+arrow expand the list
+    e = ComboBox_InitEvent( e );
+    if( e.altKey && (e.keyCode == 38 || e.keyCode == 40) ) {
+            this.Container.List.Show();
+    }
+}
+function ComboBox_TextTypeDown(e) { //Make the textbox do a type-down on the list
+    e = ComboBox_InitEvent( e );
+    var items = this.Container.List.options;
+    if( this.value == "" ) return;
+    var ctrlKeys = Array( 8, 46, 37, 38, 39, 40, 33, 34, 35, 36, 45, 16, 20 );
+    for( var i = 0; i < ctrlKeys.length; i++ ) {
+        if( e.keyCode == ctrlKeys[i] ) return;
+    }
+    for( var i = 0; i < items.length; i++ ) {
+        var item = items[i];
+        if( item.text.toLowerCase().indexOf( this.value.toLowerCase() ) == 0 ) {
+            this.Container.List.selectedIndex = i;
+            if ( typeof( this.Container.Text.createTextRange ) != "undefined" ) {
+                                    this.Container.List.Select();
+                            }
+            break;
+        }
+    }
+}
+function ComboBox_ListFireTextChange() {
+    var textOnChange = this.Container.Text.onchange;
+            if ( textOnChange != null && typeof(textOnChange) == "function" ) {
+                    textOnChange();
+            }
+}
+function ComboBox_ListEnableBlur(e) {
+    this.onblur = this.Hide;
+}
+function ComboBox_ListDisableBlur(e) {
+    this.onblur = null;
+}
+function ComboBox_ListItemSelect(e) {
+    if( this.options.length > 0 ) {
+        var text = this.Container.Text;
+        var oldValue = text.value;
+        var newValue = this.options[ this.selectedIndex ].value;
+        text.value = newValue;
+        if ( typeof( text.createTextRange ) != "undefined" ) {
+            if (newValue != oldValue) {
+                var rNew = text.createTextRange();
+                rNew.moveStart('character', oldValue.length) ;
+                rNew.select();
+            }
+        }
+    }
+    this.Hide();
+    this.Container.Text.focus();
+}
+function ComboBox_ToggleList(e) {
+    if( this.Container.List.IsShowing == true ) {
+        this.Container.List.Hide();
+    } else {
+        this.Container.List.Show();
+    }
+}
+function ComboBox_ShowList(e) {
+    if ( !this.IsShowing && !this.disabled ) {
+        this.style.width = ( this.Container.offsetWidth ) + "px";
+        this.style.top = ( this.Container.offsetHeight + ComboBox_RecursiveOffsetTop(this.Container,true) ) + "px";
+        this.style.left = ( ComboBox_RecursiveOffsetLeft(this.Container,true) + 1 ) + "px";
+        ComboBox_SetVisibility(this,true);
+        this.focus();
+        this.IsShowing = true;
+    }
+}
+function ComboBox_HideList(e) {
+    if( this.IsShowing ) {
+                    ComboBox_SetVisibility(this,false);
+        this.IsShowing = false;
+    }
+}
+function ComboBox_SetVisibility(theList,isVisible) {
+    var isIE = ( typeof( theList.dataSrc ) != "undefined" ); // dataSrc is an IE-only property which is unlikely to be supported elsewhere
+    var ua = navigator.userAgent.toLowerCase(); 
+    var isSafari = (ua.indexOf('safari') != - 1);
+    if ( isIE || isSafari) {
+        if ( isVisible ) {
+            theList.style.visibility = "visible";
+        } else {
+            theList.style.visibility = "hidden";
+        }
+    } else { 
+        if ( isVisible ) {
+            theList.style.display = "block";
+        } else {
+            theList.style.display = "none";
+        }
+    }
+}
+function ComboBox_RecursiveOffsetTop(thisObject,isFirst) {
+    if(thisObject.offsetParent) {
+        if ( thisObject.style.position == "absolute" && !isFirst && typeof(document.designMode) != "undefined" ) {
+            return 0;
+        }
+        return (thisObject.offsetTop + ComboBox_RecursiveOffsetTop(thisObject.offsetParent,false));
+    } else {
+        return thisObject.offsetTop;
+    }
+}
+function ComboBox_RecursiveOffsetLeft(thisObject,isFirst) {
+    if(thisObject.offsetParent) {
+        if ( thisObject.style.position == "absolute" && !isFirst && typeof(document.designMode) != "undefined" ) {
+            return 0;
+        }
+        return (thisObject.offsetLeft + ComboBox_RecursiveOffsetLeft(thisObject.offsetParent,false));
+    } else {
+        return thisObject.offsetLeft;
+    }
+}
+function ComboBox_SimpleAttach(selectElement,textElement) {
+    textElement.value = selectElement.options[ selectElement.options.selectedIndex ].value;
+    var textOnChange = textElement.onchange;
+    if ( textOnChange != null && typeof( textOnChange ) == "function" ) {
+        textOnChange();
+    }
+}

Added: jifty/branches/schema-plugins/share/web/static/js/context_menu.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/context_menu.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,112 @@
+JSAN.use("DOM.Events");
+
+if (typeof Jifty == "undefined") Jifty = { };
+
+function prepExpandButton(e) {
+    e.onmousedown = function() { this.onfocus = this.blur };
+    e.onmouseup   = function() { this.onfocus = window.clientInformation ? null : window.undefined };
+    e = null;	// Don't leak in IE
+}
+
+Jifty.ContextMenu = {
+    behaviourRules: {
+        "ul.menu li.toplevel span.expand a": prepExpandButton,
+        "ul.context_menu li.toplevel span.expand a": prepExpandButton
+    },
+
+    currently_open:  "",
+    prevent_stutter: "",
+
+    getParentListItem: function(ul) {
+        /* XXX TODO: Put this in the onclick handler? */
+        return ul.parentNode;
+    },
+
+    hideshow: function(id) {
+        var ul = document.getElementById(id);
+
+        Jifty.ContextMenu.prevent_stutter = id;
+
+        if ( ul.style.display == "block" )
+            Jifty.ContextMenu.hide(id);
+        else
+            Jifty.ContextMenu.show(id);
+    },
+
+    hide: function(id) {
+        var ul = document.getElementById(id);
+
+        if ( ul ) {
+            var li = Jifty.ContextMenu.getParentListItem(ul);
+            Element.removeClassName(li, "open");
+            Element.addClassName(li, "closed");
+            
+            ul.style.display = "none";
+        }
+        Jifty.ContextMenu.currently_open = "";
+    },
+
+    show: function(id) {
+        var ul = document.getElementById(id);
+        if ( !ul ) return;
+        
+        if (   Jifty.ContextMenu.currently_open
+            && Jifty.ContextMenu.currently_open != ul.id )
+        {
+            Jifty.ContextMenu.hide(Jifty.ContextMenu.currently_open);
+        }
+    
+        var li = Jifty.ContextMenu.getParentListItem(ul);
+
+        ul.style.position = "absolute";
+        ul.style.width    = li.offsetWidth * 2 + "px";
+        
+        /* Use position: relative based positioning for every browser
+           but IE, which needs to use absolute positioning */
+        if ( Jifty.Utils.isMSIE ) {
+            if ( ul.style.position == "" ) {
+                var x = Jifty.Utils.findPosX( li );
+                var y = Jifty.Utils.findPosY( li ) + li.offsetHeight;
+                
+                ul.style.left = x + "px";
+                ul.style.top  = y + "px";
+            }
+        }
+        else {
+            ul.style.left     = "-1px";
+            ul.style.top      = li.offsetHeight - 2 + "px";
+            li.style.position = "relative";
+        }
+
+        Element.removeClassName(li, "closed");
+        Element.addClassName(li, "open");
+        
+        ul.style.display = "block";
+        Jifty.ContextMenu.currently_open = ul.id;
+        Jifty.Utils.scrollToShow( ul.id );
+    },
+
+    hideOpenMenu: function(event) {
+        /* This is a bloody hack to deal with the Document based listener
+           firing just before our listener on the link.
+           If both fire, the window closes and then opens again.
+         */
+        if (   Jifty.ContextMenu.prevent_stutter
+            && Jifty.ContextMenu.prevent_stutter == Jifty.ContextMenu.currently_open)
+        {
+            Jifty.ContextMenu.prevent_stutter = "";
+            return;
+        }
+        else {
+            Jifty.ContextMenu.prevent_stutter = "";
+        }
+        
+        if (Jifty.ContextMenu.currently_open) {
+            Jifty.ContextMenu.hide(Jifty.ContextMenu.currently_open);
+        }
+    }
+};
+
+DOM.Events.addListener( document, "click", Jifty.ContextMenu.hideOpenMenu );
+Behaviour.register( Jifty.ContextMenu.behaviourRules );
+

Added: jifty/branches/schema-plugins/share/web/static/js/css_browser_selector.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/css_browser_selector.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,15 @@
+// CSS Browser Selector   v0.4
+// Documentation:         http://rafael.adm.br/css_browser_selector
+// License:               http://creativecommons.org/licenses/by/2.5/
+// Author:                Rafael Lima (http://rafael.adm.br)
+// Contributors:          http://rafael.adm.br/css_browser_selector#contributors
+function css_browser_selector() {
+	var ua = navigator.userAgent.toLowerCase(); 
+	var h = document.getElementsByTagName('html')[0];
+	if(ua.indexOf('msie') != -1 && !(ua.indexOf('opera') != -1) && (ua.indexOf('webtv') == -1) ) h.className='ie';
+	else if(ua.indexOf('gecko/') != -1) h.className='gecko';
+	else if(ua.indexOf('opera') != -1) h.className='opera';
+	else if(ua.indexOf('konqueror') != -1) h.className='konqueror';
+	else if(ua.indexOf('applewebkit/') != - 1) h.className='safari';
+}
+css_browser_selector();
\ No newline at end of file

Added: jifty/branches/schema-plugins/share/web/static/js/cssquery/cssQuery-level2.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/cssquery/cssQuery-level2.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,142 @@
+/*
+	cssQuery, version 2.0.2 (2005-08-19)
+	Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
+	License: http://creativecommons.org/licenses/LGPL/2.1/
+*/
+
+cssQuery.addModule("css-level2", function() {
+
+// -----------------------------------------------------------------------
+// selectors
+// -----------------------------------------------------------------------
+
+// child selector
+selectors[">"] = function($results, $from, $tagName, $namespace) {
+	var $element, i, j;
+	for (i = 0; i < $from.length; i++) {
+		var $subset = childElements($from[i]);
+		for (j = 0; ($element = $subset[j]); j++)
+			if (compareTagName($element, $tagName, $namespace))
+				$results.push($element);
+	}
+};
+
+// sibling selector
+selectors["+"] = function($results, $from, $tagName, $namespace) {
+	for (var i = 0; i < $from.length; i++) {
+		var $element = nextElementSibling($from[i]);
+		if ($element && compareTagName($element, $tagName, $namespace))
+			$results.push($element);
+	}
+};
+
+// attribute selector
+selectors["@"] = function($results, $from, $attributeSelectorID) {
+	var $test = attributeSelectors[$attributeSelectorID].test;
+	var $element, i;
+	for (i = 0; ($element = $from[i]); i++)
+		if ($test($element)) $results.push($element);
+};
+
+// -----------------------------------------------------------------------
+// pseudo-classes
+// -----------------------------------------------------------------------
+
+pseudoClasses["first-child"] = function($element) {
+	return !previousElementSibling($element);
+};
+
+pseudoClasses["lang"] = function($element, $code) {
+	$code = new RegExp("^" + $code, "i");
+	while ($element && !$element.getAttribute("lang")) $element = $element.parentNode;
+	return $element && $code.test($element.getAttribute("lang"));
+};
+
+// -----------------------------------------------------------------------
+//  attribute selectors
+// -----------------------------------------------------------------------
+
+// constants
+AttributeSelector.NS_IE = new RegExp("\\:","g");
+AttributeSelector.PREFIX = "@";
+// properties
+AttributeSelector.tests = {};
+// methods
+AttributeSelector.replace = function($match, $attribute, $namespace, $compare, $value) {
+	var $key = this.PREFIX + $match;
+	if (!attributeSelectors[$key]) {
+		$attribute = this.create($attribute, $compare || "", $value || "");
+		// store the selector
+		attributeSelectors[$key] = $attribute;
+		attributeSelectors.push($attribute);
+	}
+	return attributeSelectors[$key].id;
+};
+AttributeSelector.parse = function($selector) {
+	$selector = $selector.replace(this.NS_IE, "|");
+	var $match;
+	while ($match = $selector.match(this.match)) {
+		var $replace = this.replace($match[0], $match[1], $match[2], $match[3], $match[4]);
+		$selector = $selector.replace(this.match, $replace);
+	}
+	return $selector;
+};
+AttributeSelector.create = function($propertyName, $test, $value) {
+	var $attributeSelector = {};
+	$attributeSelector.id = this.PREFIX + attributeSelectors.length;
+	$attributeSelector.name = $propertyName;
+	$test = this.tests[$test];
+	$test = $test ? $test(this.getAttribute($propertyName), getText($value)) : false;
+	$attributeSelector.test = new Function("e", "return " + $test);
+	return $attributeSelector;
+};
+AttributeSelector.getAttribute = function($name) {
+	switch ($name.toLowerCase()) {
+		case "id":
+			return "e.id";
+		case "class":
+			return "e.className";
+		case "for":
+			return "e.htmlFor";
+		case "href":
+			if (isMSIE) {
+				// IE always returns the full path not the fragment in the href attribute
+				//  so we RegExp it out of outerHTML. Opera does the same thing but there
+				//  is no way to get the original attribute.
+				return "String((e.outerHTML.match(/href=\\x22?([^\\s\\x22]*)\\x22?/)||[])[1]||'')";
+			}
+	}
+	return "e.getAttribute('" + $name.replace($NAMESPACE, ":") + "')";
+};
+
+// -----------------------------------------------------------------------
+//  attribute selector tests
+// -----------------------------------------------------------------------
+
+AttributeSelector.tests[""] = function($attribute) {
+	return $attribute;
+};
+
+AttributeSelector.tests["="] = function($attribute, $value) {
+	return $attribute + "==" + Quote.add($value);
+};
+
+AttributeSelector.tests["~="] = function($attribute, $value) {
+	return "/(^| )" + regEscape($value) + "( |$)/.test(" + $attribute + ")";
+};
+
+AttributeSelector.tests["|="] = function($attribute, $value) {
+	return "/^" + regEscape($value) + "(-|$)/.test(" + $attribute + ")";
+};
+
+// -----------------------------------------------------------------------
+//  parsing
+// -----------------------------------------------------------------------
+
+// override parseSelector to parse out attribute selectors
+var _parseSelector = parseSelector;
+parseSelector = function($selector) {
+	return _parseSelector(AttributeSelector.parse($selector));
+};
+
+}); // addModule

Added: jifty/branches/schema-plugins/share/web/static/js/cssquery/cssQuery-level3.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/cssquery/cssQuery-level3.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,150 @@
+/*
+	cssQuery, version 2.0.2 (2005-08-19)
+	Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
+	License: http://creativecommons.org/licenses/LGPL/2.1/
+*/
+
+/* Thanks to Bill Edney */
+
+cssQuery.addModule("css-level3", function() {
+
+// -----------------------------------------------------------------------
+// selectors
+// -----------------------------------------------------------------------
+
+// indirect sibling selector
+selectors["~"] = function($results, $from, $tagName, $namespace) {
+	var $element, i;
+	for (i = 0; ($element = $from[i]); i++) {
+		while ($element = nextElementSibling($element)) {
+			if (compareTagName($element, $tagName, $namespace))
+				$results.push($element);
+		}
+	}
+};
+
+// -----------------------------------------------------------------------
+// pseudo-classes
+// -----------------------------------------------------------------------
+
+// I'm hoping these pseudo-classes are pretty readable. Let me know if
+//  any need explanation.
+
+pseudoClasses["contains"] = function($element, $text) {
+	$text = new RegExp(regEscape(getText($text)));
+	return $text.test(getTextContent($element));
+};
+
+pseudoClasses["root"] = function($element) {
+	return $element == getDocument($element).documentElement;
+};
+
+pseudoClasses["empty"] = function($element) {
+	var $node, i;
+	for (i = 0; ($node = $element.childNodes[i]); i++) {
+		if (thisElement($node) || $node.nodeType == 3) return false;
+	}
+	return true;
+};
+
+pseudoClasses["last-child"] = function($element) {
+	return !nextElementSibling($element);
+};
+
+pseudoClasses["only-child"] = function($element) {
+	$element = $element.parentNode;
+	return firstElementChild($element) == lastElementChild($element);
+};
+
+pseudoClasses["not"] = function($element, $selector) {
+	var $negated = cssQuery($selector, getDocument($element));
+	for (var i = 0; i < $negated.length; i++) {
+		if ($negated[i] == $element) return false;
+	}
+	return true;
+};
+
+pseudoClasses["nth-child"] = function($element, $arguments) {
+	return nthChild($element, $arguments, previousElementSibling);
+};
+
+pseudoClasses["nth-last-child"] = function($element, $arguments) {
+	return nthChild($element, $arguments, nextElementSibling);
+};
+
+pseudoClasses["target"] = function($element) {
+	return $element.id == location.hash.slice(1);
+};
+
+// UI element states
+
+pseudoClasses["checked"] = function($element) {
+	return $element.checked;
+};
+
+pseudoClasses["enabled"] = function($element) {
+	return $element.disabled === false;
+};
+
+pseudoClasses["disabled"] = function($element) {
+	return $element.disabled;
+};
+
+pseudoClasses["indeterminate"] = function($element) {
+	return $element.indeterminate;
+};
+
+// -----------------------------------------------------------------------
+//  attribute selector tests
+// -----------------------------------------------------------------------
+
+AttributeSelector.tests["^="] = function($attribute, $value) {
+	return "/^" + regEscape($value) + "/.test(" + $attribute + ")";
+};
+
+AttributeSelector.tests["$="] = function($attribute, $value) {
+	return "/" + regEscape($value) + "$/.test(" + $attribute + ")";
+};
+
+AttributeSelector.tests["*="] = function($attribute, $value) {
+	return "/" + regEscape($value) + "/.test(" + $attribute + ")";
+};
+
+// -----------------------------------------------------------------------
+//  nth child support (Bill Edney)
+// -----------------------------------------------------------------------
+
+function nthChild($element, $arguments, $traverse) {
+	switch ($arguments) {
+		case "n": return true;
+		case "even": $arguments = "2n"; break;
+		case "odd": $arguments = "2n+1";
+	}
+
+	var $$children = childElements($element.parentNode);
+	function _checkIndex($index) {
+		var $index = ($traverse == nextElementSibling) ? $$children.length - $index : $index - 1;
+		return $$children[$index] == $element;
+	};
+
+	//	it was just a number (no "n")
+	if (!isNaN($arguments)) return _checkIndex($arguments);
+
+	$arguments = $arguments.split("n");
+	var $multiplier = parseInt($arguments[0]);
+	var $step = parseInt($arguments[1]);
+
+	if ((isNaN($multiplier) || $multiplier == 1) && $step == 0) return true;
+	if ($multiplier == 0 && !isNaN($step)) return _checkIndex($step);
+	if (isNaN($step)) $step = 0;
+
+	var $count = 1;
+	while ($element = $traverse($element)) $count++;
+
+	if (isNaN($multiplier) || $multiplier == 1)
+		return ($traverse == nextElementSibling) ? ($count <= $step) : ($step >= $count);
+
+	return ($count % $multiplier) == $step;
+};
+
+}); // addModule

Added: jifty/branches/schema-plugins/share/web/static/js/cssquery/cssQuery-standard.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/cssquery/cssQuery-standard.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,79 @@
+/*
+	cssQuery, version 2.0.2 (2005-08-19)
+	Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
+	License: http://creativecommons.org/licenses/LGPL/2.1/
+*/
+
+cssQuery.addModule("css-standard", function() { // override IE optimisation
+
+// cssQuery was originally written as the CSS engine for IE7. It is
+//  optimised (in terms of size not speed) for IE so this module is
+//  provided separately to provide cross-browser support.
+
+// -----------------------------------------------------------------------
+// browser compatibility
+// -----------------------------------------------------------------------
+
+// sniff for Win32 Explorer
+isMSIE = eval("false;/*@cc_on at if(@\x5fwin32)isMSIE=true at end@*/");
+
+if (!isMSIE) {
+	getElementsByTagName = function($element, $tagName, $namespace) {
+		return $namespace ? $element.getElementsByTagNameNS("*", $tagName) :
+			$element.getElementsByTagName($tagName);
+	};
+
+	compareNamespace = function($element, $namespace) {
+		return !$namespace || ($namespace == "*") || ($element.prefix == $namespace);
+	};
+
+	isXML = document.contentType ? function($element) {
+		return /xml/i.test(getDocument($element).contentType);
+	} : function($element) {
+		return getDocument($element).documentElement.tagName != "HTML";
+	};
+
+	getTextContent = function($element) {
+		// mozilla || opera || other
+		return $element.textContent || $element.innerText || _getTextContent($element);
+	};
+
+	function _getTextContent($element) {
+		var $textContent = "", $node, i;
+		for (i = 0; ($node = $element.childNodes[i]); i++) {
+			switch ($node.nodeType) {
+				case 11: // document fragment
+				case 1: $textContent += _getTextContent($node); break;
+				case 3: $textContent += $node.nodeValue; break;
+			}
+		}
+		return $textContent;
+	};
+
+	
+	selectById = function($results, $from, id) {
+	    var $match, i;
+	    for(i = 0; i < $from.length; i++) {
+		var $match = _getChildById($from[i], id);
+		if($match) $results.push($match);
+	    }
+	    return $results;
+	}
+
+	function _getChildById($from, $id) {
+	    var $match = document.getElementById($id);
+	    if($from == document) {
+		return $match;
+	    }
+	    var $elt = $match;
+	    while($elt) {
+		if($elt == $from) {
+		    return $match;
+		}
+		$elt = $elt.parentNode;
+	    }
+	    return null;
+	}
+
+}
+}); // addModule

Added: jifty/branches/schema-plugins/share/web/static/js/cssquery/cssQuery.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/cssquery/cssQuery.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,359 @@
+/*
+	cssQuery, version 2.0.2 (2005-08-19)
+	Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
+	License: http://creativecommons.org/licenses/LGPL/2.1/
+
+	Modifed by Nelson Elhage <nelhage at bestpractical.com>
+	(2006-06-21) to optimize selection by ID on non-IE browsers
+*/
+
+// the following functions allow querying of the DOM using CSS selectors
+var cssQuery = function() {
+var version = "2.0.2";
+
+// -----------------------------------------------------------------------
+// main query function
+// -----------------------------------------------------------------------
+
+var $COMMA = /\s*,\s*/;
+var cssQuery = function($selector, $$from) {
+try {
+	var $match = [];
+	var $useCache = arguments.callee.caching && !$$from;
+	var $base = ($$from) ? ($$from.constructor == Array) ? $$from : [$$from] : [document];
+	// process comma separated selectors
+	var $$selectors = parseSelector($selector).split($COMMA), i;
+	for (i = 0; i < $$selectors.length; i++) {
+		// convert the selector to a stream
+		$selector = _toStream($$selectors[i]);
+		// faster chop if it starts with id
+		if ($selector.slice(0, 3).join("") == " *#") {
+			$selector = $selector.slice(2);
+			$$from = selectById([], $base, $selector[1]);
+		} else $$from = $base;
+		// process the stream
+		var j = 0, $token, $filter, $arguments, $cacheSelector = "";
+		while (j < $selector.length) {
+			$token = $selector[j++];
+			$filter = $selector[j++];
+			$cacheSelector += $token + $filter;
+			// some pseudo-classes allow arguments to be passed
+			//  e.g. nth-child(even)
+			$arguments = "";
+			if ($selector[j] == "(") {
+				while ($selector[j++] != ")" && j < $selector.length) {
+					$arguments += $selector[j];
+				}
+				$arguments = $arguments.slice(0, -1);
+				$cacheSelector += "(" + $arguments + ")";
+			}
+			// process a token/filter pair use cached results if possible
+			$$from = ($useCache && cache[$cacheSelector]) ?
+				cache[$cacheSelector] : select($$from, $token, $filter, $arguments);
+			if ($useCache) cache[$cacheSelector] = $$from;
+		}
+		$match = $match.concat($$from);
+	}
+	delete cssQuery.error;
+	return $match;
+} catch ($error) {
+	cssQuery.error = $error;
+	return [];
+}};
+
+// -----------------------------------------------------------------------
+// public interface
+// -----------------------------------------------------------------------
+
+cssQuery.toString = function() {
+	return "function cssQuery() {\n  [version " + version + "]\n}";
+};
+
+// caching
+var cache = {};
+cssQuery.caching = false;
+cssQuery.clearCache = function($selector) {
+	if ($selector) {
+		$selector = _toStream($selector).join("");
+		delete cache[$selector];
+	} else cache = {};
+};
+
+// allow extensions
+var modules = {};
+var loaded = false;
+cssQuery.addModule = function($name, $script) {
+	if (loaded) eval("$script=" + String($script));
+	modules[$name] = new $script();;
+};
+
+// hackery
+cssQuery.valueOf = function($code) {
+	return $code ? eval($code) : this;
+};
+
+// -----------------------------------------------------------------------
+// declarations
+// -----------------------------------------------------------------------
+
+var selectors = {};
+var pseudoClasses = {};
+// a safari bug means that these have to be declared here
+var AttributeSelector = {match: /\[([\w-]+(\|[\w-]+)?)\s*(\W?=)?\s*([^\]]*)\]/};
+var attributeSelectors = [];
+
+// -----------------------------------------------------------------------
+// selectors
+// -----------------------------------------------------------------------
+
+// descendant selector
+selectors[" "] = function($results, $from, $tagName, $namespace) {
+	// loop through current selection
+	var $element, i, j;
+	for (i = 0; i < $from.length; i++) {
+		// get descendants
+		var $subset = getElementsByTagName($from[i], $tagName, $namespace);
+		// loop through descendants and add to results selection
+		for (j = 0; ($element = $subset[j]); j++) {
+			if (thisElement($element) && compareNamespace($element, $namespace))
+				$results.push($element);
+		}
+	}
+};
+
+// ID selector
+selectors["#"] = function($results, $from, $id) {
+	// loop through current selection and check ID
+	var $element, j;
+	for (j = 0; ($element = $from[j]); j++) if ($element.id == $id) $results.push($element);
+};
+
+// class selector
+selectors["."] = function($results, $from, $className) {
+	// create a RegExp version of the class
+	$className = new RegExp("(^|\\s)" + $className + "(\\s|$)");
+	// loop through current selection and check class
+	var $element, i;
+	for (i = 0; ($element = $from[i]); i++)
+		if ($className.test($element.className)) $results.push($element);
+};
+
+// pseudo-class selector
+selectors[":"] = function($results, $from, $pseudoClass, $arguments) {
+	// retrieve the cssQuery pseudo-class function
+	var $test = pseudoClasses[$pseudoClass], $element, i;
+	// loop through current selection and apply pseudo-class filter
+	if ($test) for (i = 0; ($element = $from[i]); i++)
+		// if the cssQuery pseudo-class function returns "true" add the element
+		if ($test($element, $arguments)) $results.push($element);
+};
+
+// -----------------------------------------------------------------------
+// pseudo-classes
+// -----------------------------------------------------------------------
+
+pseudoClasses["link"] = function($element) {
+	var $document = getDocument($element);
+	if ($document.links) for (var i = 0; i < $document.links.length; i++) {
+		if ($document.links[i] == $element) return true;
+	}
+};
+
+pseudoClasses["visited"] = function($element) {
+	// can't do this without jiggery-pokery
+};
+
+// -----------------------------------------------------------------------
+// DOM traversal
+// -----------------------------------------------------------------------
+
+// IE5/6 includes comments (LOL) in it's elements collections.
+// so we have to check for this. the test is tagName != "!". LOL (again).
+var thisElement = function($element) {
+	return ($element && $element.nodeType == 1 && $element.tagName != "!") ? $element : null;
+};
+
+// return the previous element to the supplied element
+//  previousSibling is not good enough as it might return a text or comment node
+var previousElementSibling = function($element) {
+	while ($element && ($element = $element.previousSibling) && !thisElement($element)) continue;
+	return $element;
+};
+
+// return the next element to the supplied element
+var nextElementSibling = function($element) {
+	while ($element && ($element = $element.nextSibling) && !thisElement($element)) continue;
+	return $element;
+};
+
+// return the first child ELEMENT of an element
+//  NOT the first child node (though they may be the same thing)
+var firstElementChild = function($element) {
+	return thisElement($element.firstChild) || nextElementSibling($element.firstChild);
+};
+
+var lastElementChild = function($element) {
+	return thisElement($element.lastChild) || previousElementSibling($element.lastChild);
+};
+
+// return child elements of an element (not child nodes)
+var childElements = function($element) {
+	var $childElements = [];
+	$element = firstElementChild($element);
+	while ($element) {
+		$childElements.push($element);
+		$element = nextElementSibling($element);
+	}
+	return $childElements;
+};
+
+// -----------------------------------------------------------------------
+// browser compatibility
+// -----------------------------------------------------------------------
+
+// all of the functions in this section can be overwritten. the default
+//  configuration is for IE. The functions below reflect this. standard
+//  methods are included in a separate module. It would probably be better
+//  the other way round of course but this makes it easier to keep IE7 trim.
+
+var isMSIE = true;
+
+var isXML = function($element) {
+	var $document = getDocument($element);
+	return (typeof $document.mimeType == "unknown") ?
+		/\.xml$/i.test($document.URL) :
+		Boolean($document.mimeType == "XML Document");
+};
+
+// return the element's containing document
+var getDocument = function($element) {
+	return $element.ownerDocument || $element.document;
+};
+
+var getElementsByTagName = function($element, $tagName) {
+	return ($tagName == "*" && $element.all) ? $element.all : $element.getElementsByTagName($tagName);
+};
+
+var compareTagName = function($element, $tagName, $namespace) {
+	if ($tagName == "*") return thisElement($element);
+	if (!compareNamespace($element, $namespace)) return false;
+	if (!isXML($element)) $tagName = $tagName.toUpperCase();
+	return $element.tagName == $tagName;
+};
+
+var compareNamespace = function($element, $namespace) {
+	return !$namespace || ($namespace == "*") || ($element.scopeName == $namespace);
+};
+
+var getTextContent = function($element) {
+	return $element.innerText;
+};
+
+// for IE5.0
+if (![].push) Array.prototype.push = function() {
+	for (var i = 0; i < arguments.length; i++) {
+		this[this.length] = arguments[i];
+	}
+	return this.length;
+};
+
+// -----------------------------------------------------------------------
+// query support
+// -----------------------------------------------------------------------
+
+// select a set of matching elements.
+// "from" is an array of elements.
+// "token" is a character representing the type of filter
+//  e.g. ">" means child selector
+// "filter" represents the tag name, id or class name that is being selected
+// the function returns an array of matching elements
+var $NAMESPACE = /\|/;
+function select($$from, $token, $filter, $arguments) {
+	if ($NAMESPACE.test($filter)) {
+		$filter = $filter.split($NAMESPACE);
+		$arguments = $filter[0];
+		$filter = $filter[1];
+	}
+	var $results = [];
+	if (selectors[$token]) {
+		selectors[$token]($results, $$from, $filter, $arguments);
+	}
+	return $results;
+};
+
+// -----------------------------------------------------------------------
+// parsing
+// -----------------------------------------------------------------------
+
+// convert css selectors to a stream of tokens and filters
+//  it's not a real stream. it's just an array of strings.
+var $STANDARD_SELECT = /^[^\s>+~]/;
+var $$STREAM = /[\s#.:>+~()@]|[^\s#.:>+~()@]+/g;
+function _toStream($selector) {
+	if ($STANDARD_SELECT.test($selector)) $selector = " " + $selector;
+	return $selector.match($$STREAM) || [];
+};
+
+var $WHITESPACE = /\s*([\s>+~(),]|^|$)\s*/g;
+var $IMPLIED_ALL = /([\s>+~,]|[^(]\+|^)([#.:@])/g;
+var parseSelector = function($selector) {
+	return $selector
+	// trim whitespace
+	.replace($WHITESPACE, "$1")
+	// e.g. ".class1" --> "*.class1"
+	.replace($IMPLIED_ALL, "$1*$2");
+};
+
+var selectById = function($results, $from, id) {
+	var $match, i, j;
+	for (i = 0; i < $from.length; i++) {
+		if ($match = $from[i].all.item(id)) {
+			if ($match.id == id) $results.push($match);
+			else if ($match.length != null) {
+				for (j = 0; j < $match.length; j++) {
+					if ($match[j].id == id) $results.push($match[j]);
+				}
+			}
+		}
+	}
+	return $results;
+};
+
+var Quote = {
+	toString: function() {return "'"},
+	match: /^('[^']*')|("[^"]*")$/,
+	test: function($string) {
+		return this.match.test($string);
+	},
+	add: function($string) {
+		return this.test($string) ? $string : this + $string + this;
+	},
+	remove: function($string) {
+		return this.test($string) ? $string.slice(1, -1) : $string;
+	}
+};
+
+var getText = function($text) {
+	return Quote.remove($text);
+};
+
+var $ESCAPE = /([\/()[\]?{}|*+-])/g;
+function regEscape($string) {
+	return $string.replace($ESCAPE, "\\$1");
+};
+
+// -----------------------------------------------------------------------
+// modules
+// -----------------------------------------------------------------------
+
+// -------- >>      insert modules here for packaging       << -------- \\
+
+loaded = true;
+
+// -----------------------------------------------------------------------
+// return the query function
+// -----------------------------------------------------------------------
+
+return cssQuery;
+
+}(); // cssQuery

Added: jifty/branches/schema-plugins/share/web/static/js/dom-drag.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/dom-drag.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,9 @@
+/**************************************************
+ * dom-drag.js
+ * 09.25.2001
+ * www.youngpup.net
+ **************************************************
+ * 10.28.2001 - fixed minor bug where events
+ * sometimes fired off the handle, not the root.
+ **************************************************/
+var Drag = { obj : null, init : function(o, oRoot, minX, maxX, minY, maxY, bSwapHorzRef, bSwapVertRef, fXMapper, fYMapper){ o.onmousedown = Drag.start; o.hmode = bSwapHorzRef ? false : true ; o.vmode = bSwapVertRef ? false : true ; o.root = oRoot && oRoot != null ? oRoot : o ; if (o.hmode && isNaN(parseInt(o.root.style.left ))) o.root.style.left = "0px"; if (o.vmode && isNaN(parseInt(o.root.style.top ))) o.root.style.top = "0px"; if (!o.hmode && isNaN(parseInt(o.root.style.right ))) o.root.style.right = "0px"; if (!o.vmode && isNaN(parseInt(o.root.style.bottom))) o.root.style.bottom = "0px"; o.minX = typeof minX != 'undefined' ? minX : null; o.minY = typeof minY != 'undefined' ? minY : null; o.maxX = typeof maxX != 'undefined' ? maxX : null; o.maxY = typeof maxY != 'undefined' ? maxY : null; o.xMapper = fXMapper ? fXMapper : null; o.yMapper = fYMapper ? fYMapper : null; o.root.onDragStart = new Function(); o.root.onDragEnd = new Function(); o.root.onDrag = new Function();}, start : function(e){ var o = Drag.obj = this; e = Drag.fixE(e); var y = parseInt(o.vmode ? o.root.style.top : o.root.style.bottom); var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right ); o.root.onDragStart(x, y); o.lastMouseX = e.clientX; o.lastMouseY = e.clientY; if (o.hmode) { if (o.minX != null) o.minMouseX = e.clientX - x + o.minX; if (o.maxX != null) o.maxMouseX = o.minMouseX + o.maxX - o.minX;} else { if (o.minX != null) o.maxMouseX = -o.minX + e.clientX + x; if (o.maxX != null) o.minMouseX = -o.maxX + e.clientX + x;} if (o.vmode) { if (o.minY != null) o.minMouseY = e.clientY - y + o.minY; if (o.maxY != null) o.maxMouseY = o.minMouseY + o.maxY - o.minY;} else { if (o.minY != null) o.maxMouseY = -o.minY + e.clientY + y; if (o.maxY != null) o.minMouseY = -o.maxY + e.clientY + y;} document.onmousemove = Drag.drag; document.onmouseup = Drag.end; return false;}, drag : function(e){ e = Drag.fixE(e); var o = Drag.obj; var ey = e.clientY; var ex = e.clientX; var y = parseInt(o.vmode ? o.root.style.top : o.root.style.bottom); var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right ); var nx, ny; if (o.minX != null) ex = o.hmode ? Math.max(ex, o.minMouseX) : Math.min(ex, o.maxMouseX); if (o.maxX != null) ex = o.hmode ? Math.min(ex, o.maxMouseX) : Math.max(ex, o.minMouseX); if (o.minY != null) ey = o.vmode ? Math.max(ey, o.minMouseY) : Math.min(ey, o.maxMouseY); if (o.maxY != null) ey = o.vmode ? Math.min(ey, o.maxMouseY) : Math.max(ey, o.minMouseY); nx = x + ((ex - o.lastMouseX) * (o.hmode ? 1 : -1)); ny = y + ((ey - o.lastMouseY) * (o.vmode ? 1 : -1)); if(o.xMapper){ nx = o.xMapper(y) } else if (o.yMapper) { ny = o.yMapper(x); } Drag.obj.root.style[o.hmode ? "left" : "right"] = nx + "px"; Drag.obj.root.style[o.vmode ? "top" : "bottom"] = ny + "px"; Drag.obj.lastMouseX = ex; Drag.obj.lastMouseY = ey; Drag.obj.root.onDrag(nx, ny); return false;}, end : function(){ document.onmousemove = null; document.onmouseup = null; Drag.obj.root.onDragEnd( parseInt(Drag.obj.root.style[Drag.obj.hmode ? "left" : "right"]), parseInt(Drag.obj.root.style[Drag.obj.vmode ? "top" : "bottom"])); Drag.obj = null;}, fixE : function(e){ if (typeof e == 'undefined') e = window.event; if (typeof e.layerX == 'undefined') e.layerX = e.offsetX; if (typeof e.layerY == 'undefined') e.layerY = e.offsetY; return e;} };
\ No newline at end of file

Added: jifty/branches/schema-plugins/share/web/static/js/formatDate.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/formatDate.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,356 @@
+// formatDate :
+// a PHP date like function, for formatting date strings
+// authored by Svend Tofte <www.svendtofte.com>
+// the code is in the public domain
+//
+// see http://www.svendtofte.com/code/date_format/
+// and http://www.php.net/date
+//
+// thanks to 
+//  - Daniel Berlin <mail at daniel-berlin.de>,
+//    major overhaul and improvements
+//  - Matt Bannon,
+//    correcting some stupid bugs in my days-in-the-months list!
+//
+// input : format string
+// time : epoch time (seconds, and optional)
+//
+// if time is not passed, formatting is based on 
+// the current "this" date object's set time.
+//
+// supported switches are
+// a, A, B, c, d, D, F, g, G, h, H, i, I (uppercase i), j, l (lowecase L), 
+// L, m, M, n, N, O, P, r, s, S, t, U, w, W, y, Y, z, Z
+// 
+// unsupported (as compared to date in PHP 5.1.3)
+// T, e, o
+
+Date.prototype.formatDate = function (input,time) {
+    
+    var daysLong =    ["Sunday", "Monday", "Tuesday", "Wednesday", 
+                       "Thursday", "Friday", "Saturday"];
+    var daysShort =   ["Sun", "Mon", "Tue", "Wed", 
+                       "Thu", "Fri", "Sat"];
+    var monthsShort = ["Jan", "Feb", "Mar", "Apr",
+                       "May", "Jun", "Jul", "Aug", "Sep",
+                       "Oct", "Nov", "Dec"];
+    var monthsLong =  ["January", "February", "March", "April",
+                       "May", "June", "July", "August", "September",
+                       "October", "November", "December"];
+
+    var switches = { // switches object
+        
+        a : function () {
+            // Lowercase Ante meridiem and Post meridiem
+            return date.getHours() > 11? "pm" : "am";
+        },
+        
+        A : function () {
+            // Uppercase Ante meridiem and Post meridiem
+            return (this.a().toUpperCase ());
+        },
+    
+        B : function (){
+            // Swatch internet time. code simply grabbed from ppk,
+            // since I was feeling lazy:
+            // http://www.xs4all.nl/~ppk/js/beat.html
+            var off = (date.getTimezoneOffset() + 60)*60;
+            var theSeconds = (date.getHours() * 3600) + 
+                             (date.getMinutes() * 60) + 
+                              date.getSeconds() + off;
+            var beat = Math.floor(theSeconds/86.4);
+            if (beat > 1000) beat -= 1000;
+            if (beat < 0) beat += 1000;
+            if ((String(beat)).length == 1) beat = "00"+beat;
+            if ((String(beat)).length == 2) beat = "0"+beat;
+            return beat;
+        },
+        
+        c : function () {
+            // ISO 8601 date (e.g.: "2004-02-12T15:19:21+00:00"), as per
+            // http://www.cl.cam.ac.uk/~mgk25/iso-time.html
+            return (this.Y() + "-" + this.m() + "-" + this.d() + "T" + 
+                    this.h() + ":" + this.i() + ":" + this.s() + this.P());
+        },
+        
+        d : function () {
+            // Day of the month, 2 digits with leading zeros
+            var j = String(this.j());
+            return (j.length == 1 ? "0"+j : j);
+        },
+        
+        D : function () {
+            // A textual representation of a day, three letters
+            return daysShort[date.getDay()];
+        },
+        
+        F : function () {
+            // A full textual representation of a month
+            return monthsLong[date.getMonth()];
+        },
+        
+        g : function () {
+            // 12-hour format of an hour without leading zeros
+            return date.getHours() > 12? date.getHours()-12 : date.getHours();
+        },
+        
+        G : function () {
+            // 24-hour format of an hour without leading zeros
+            return date.getHours();
+        },
+        
+        h : function () {
+            // 12-hour format of an hour with leading zeros
+            var g = String(this.g());
+            return (g.length == 1 ? "0"+g : g);
+        },
+        
+        H : function () {
+            // 24-hour format of an hour with leading zeros
+            var G = String(this.G());
+            return (G.length == 1 ? "0"+G : G);
+        },
+        
+        i : function () {
+            // Minutes with leading zeros
+            var min = String (date.getMinutes ());
+            return (min.length == 1 ? "0" + min : min);
+        },
+        
+        I : function () {
+            // Whether or not the date is in daylight saving time (DST)
+            // note that this has no bearing in actual DST mechanics,
+            // and is just a pure guess. buyer beware.
+            var noDST = new Date ("January 1 " + this.Y() + " 00:00:00");
+            return (noDST.getTimezoneOffset () == 
+                    date.getTimezoneOffset () ? 0 : 1);
+        },
+        
+        j : function () {
+            // Day of the month without leading zeros
+            return date.getDate();
+        },
+        
+        l : function () {
+            // A full textual representation of the day of the week
+            return daysLong[date.getDay()];
+        },
+        
+        L : function () {
+            // leap year or not. 1 if leap year, 0 if not.
+            // the logic should match iso's 8601 standard.
+            // http://www.uic.edu/depts/accc/software/isodates/leapyear.html
+            var Y = this.Y();
+            if (         
+                (Y % 4 == 0 && Y % 100 != 0) ||
+                (Y % 4 == 0 && Y % 100 == 0 && Y % 400 == 0)
+                ) {
+                return 1;
+            } else {
+                return 0;
+            }
+        },
+        
+        m : function () {
+            // Numeric representation of a month, with leading zeros
+            var n = String(this.n());
+            return (n.length == 1 ? "0"+n : n);
+        },
+        
+        M : function () {
+            // A short textual representation of a month, three letters
+            return monthsShort[date.getMonth()];
+        },
+        
+        n : function () {
+            // Numeric representation of a month, without leading zeros
+            return date.getMonth()+1;
+        },
+        
+        N : function () {
+            // ISO-8601 numeric representation of the day of the week
+            var w = this.w();
+            return (w == 0 ? 7 : w);
+        },
+        
+        O : function () {
+            // Difference to Greenwich time (GMT) in hours
+            var os = Math.abs(date.getTimezoneOffset());
+            var h = String(Math.floor(os/60));
+            var m = String(os%60);
+            h.length == 1? h = "0"+h:1;
+            m.length == 1? m = "0"+m:1;
+            return date.getTimezoneOffset() < 0 ? "+"+h+m : "-"+h+m;
+        },
+        
+        P : function () {
+            // Difference to GMT, with colon between hours and minutes
+            var O = this.O();
+            return (O.substr(0, 3) + ":" + O.substr(3, 2));
+        },      
+        
+        r : function () {
+            // RFC 822 formatted date
+            var r; // result
+            //  Thu         ,     21               Dec              2000
+            r = this.D() + ", " + this.d() + " " + this.M() + " " + this.Y() +
+            //    16          :    01          :    07               0200
+            " " + this.H() + ":" + this.i() + ":" + this.s() + " " + this.O();
+            return r;
+        },
+
+        s : function () {
+            // Seconds, with leading zeros
+            var sec = String (date.getSeconds ());
+            return (sec.length == 1 ? "0" + sec : sec);
+        },        
+        
+        S : function () {
+            // English ordinal suffix for the day of the month, 2 characters
+            switch (date.getDate ()) {
+                case  1: return ("st"); 
+                case  2: return ("nd"); 
+                case  3: return ("rd");
+                case 21: return ("st"); 
+                case 22: return ("nd"); 
+                case 23: return ("rd");
+                case 31: return ("st");
+                default: return ("th");
+            }
+        },
+        
+        t : function () {
+            // thanks to Matt Bannon for some much needed code-fixes here!
+            var daysinmonths = [null,31,28,31,30,31,30,31,31,30,31,30,31];
+            if (this.L()==1 && this.n()==2) return 29; // ~leap day
+            return daysinmonths[this.n()];
+        },
+        
+        U : function () {
+            // Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT)
+            return Math.round(date.getTime()/1000);
+        },
+
+        w : function () {
+            // Numeric representation of the day of the week
+            return date.getDay();
+        },
+        
+        W : function () {
+            // Weeknumber, as per ISO specification:
+            // http://www.cl.cam.ac.uk/~mgk25/iso-time.html
+        
+            var DoW = this.N ();
+            var DoY = this.z ();
+
+            // If the day is 3 days before New Year's Eve and is Thursday or earlier,
+            // it's week 1 of next year.
+            var daysToNY = 364 + this.L () - DoY;
+            if (daysToNY <= 2 && DoW <= (3 - daysToNY)) {
+                return 1;
+            }
+
+            // If the day is within 3 days after New Year's Eve and is Friday or later,
+            // it belongs to the old year.
+            if (DoY <= 2 && DoW >= 5) {
+                return new Date (this.Y () - 1, 11, 31).formatDate ("W");
+            }
+            
+            var nyDoW = new Date (this.Y (), 0, 1).getDay ();
+            nyDoW = nyDoW != 0 ? nyDoW - 1 : 6;
+
+            if (nyDoW <= 3) { // First day of the year is a Thursday or earlier
+                return (1 + Math.floor ((DoY + nyDoW) / 7));
+            } else {  // First day of the year is a Friday or later
+                return (1 + Math.floor ((DoY - (7 - nyDoW)) / 7));
+            }
+        },
+        
+        y : function () {
+            // A two-digit representation of a year
+            var y = String(this.Y());
+            return y.substring(y.length-2,y.length);
+        },        
+        
+        Y : function () {
+            // A full numeric representation of a year, 4 digits
+    
+            // we first check, if getFullYear is supported. if it
+            // is, we just use that. ppks code is nice, but wont
+            // work with dates outside 1900-2038, or something like that
+            if (date.getFullYear) {
+                var newDate = new Date("January 1 2001 00:00:00 +0000");
+                var x = newDate .getFullYear();
+                if (x == 2001) {              
+                    // i trust the method now
+                    return date.getFullYear();
+                }
+            }
+            // else, do this:
+            // codes thanks to ppk:
+            // http://www.xs4all.nl/~ppk/js/introdate.html
+            var x = date.getYear();
+            var y = x % 100;
+            y += (y < 38) ? 2000 : 1900;
+            return y;
+        },
+
+        
+        z : function () {
+            // The day of the year, zero indexed! 0 through 366
+            var t = new Date("January 1 " + this.Y() + " 00:00:00");
+            var diff = date.getTime() - t.getTime();
+            return Math.floor(diff/1000/60/60/24);
+        },
+
+        Z : function () {
+            // Timezone offset in seconds
+            return (date.getTimezoneOffset () * -60);
+        }        
+    
+    }
+
+    function getSwitch(str) {
+        if (switches[str] != undefined) {
+            return switches[str]();
+        } else {
+            return str;
+        }
+    }
+
+    var date;
+    if (time) {
+        var date = new Date (time);
+    } else {
+        var date = this;
+    }
+
+    var formatString = input.split("");
+    var i = 0;
+    while (i < formatString.length) {
+        if (formatString[i] == "\\") {
+            // this is our way of allowing users to escape stuff
+            formatString.splice(i,1);
+        } else {
+            formatString[i] = getSwitch(formatString[i]);
+        }
+        i++;
+    }
+    
+    return formatString.join("");
+}
+
+
+// Some (not all) predefined format strings from PHP 5.1.1, which 
+// offer standard date representations.
+// See: http://www.php.net/manual/en/ref.datetime.php#datetime.constants
+//
+
+// Atom      "2005-08-15T15:52:01+00:00"
+Date.DATE_ATOM    = "Y-m-d\\TH:i:sP";
+// ISO-8601  "2005-08-15T15:52:01+0000"
+Date.DATE_ISO8601 = "Y-m-d\\TH:i:sO";
+// RFC 2822  "Mon, 15 Aug 2005 15:52:01 +0000"
+Date.DATE_RFC2822 = "D, d M Y H:i:s O";
+// W3C       "2005-08-15T15:52:01+00:00"
+Date.DATE_W3C     = "Y-m-d\\TH:i:sP";

Added: jifty/branches/schema-plugins/share/web/static/js/halo.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/halo.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,64 @@
+var halo_shown = null;
+
+var halo_top;
+var halo_left;
+var halo_width;
+
+function halo_toggle (id) {
+    if (halo_shown && (id != halo_shown)) {
+        halo_top   = $('halo-'+halo_shown+'-menu').style.top;
+        halo_left  = $('halo-'+halo_shown+'-menu').style.left;
+        halo_width = $('halo-'+halo_shown+'-menu').style.width;
+        Element.hide('halo-'+halo_shown+'-menu');
+    }
+    $('halo-'+id+'-menu').style.top   = halo_top;
+    $('halo-'+id+'-menu').style.left  = halo_left;
+    $('halo-'+id+'-menu').style.width = halo_width;
+    Element.toggle('halo-'+id+'-menu');
+
+    Drag.init( $('halo-'+id+'-title'), $('halo-'+id+'-menu') );
+    init_resize($('halo-'+id+'-resize'), $('halo-'+id+'-menu') );
+
+    var e = $('halo-'+id);
+    if (Element.visible('halo-'+id+'-menu')) {
+        halo_shown = id;
+        Element.setStyle(e, {background: '#ffff80'});
+    } else {
+        halo_top   = $('halo-'+halo_shown+'-menu').style.top;
+        halo_left  = $('halo-'+halo_shown+'-menu').style.left;
+        halo_width = $('halo-'+halo_shown+'-menu').style.width;
+        halo_shown = null;
+        Element.setStyle(e, {background: 'inherit'});
+    }
+
+    return false;
+}
+
+function halo_over (id) {
+    var e = $('halo-'+id);
+    if (e) {
+        Element.setStyle(e, {background: '#ffff80'});
+    }
+}
+
+function halo_out (id) {
+    var e = $('halo-'+id);
+    if (e && ! Element.visible('halo-'+id+'-menu')) {
+        Element.setStyle(e, {background: 'inherit'});
+    }
+}
+
+function init_resize (e, w) {
+    e.xFrom = e.yFrom = 0;
+    Drag.init(e, null, null, null, null, null, true, true );
+    e.onDrag = function(x, y) {
+        resizeX( x, e, w );
+    };
+}
+
+function resizeX (x, grip, window) {
+    var width = parseInt( window.style.width );
+    var newWidth = Math.abs( width - ( x - grip.xFrom ) ) + 'px';
+    window.style.width = newWidth;
+    grip.xFrom = x;
+}

Added: jifty/branches/schema-plugins/share/web/static/js/iepngfix.htc
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/iepngfix.htc	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,55 @@
+<public:component>
+<public:attach event="onpropertychange" onevent="doFix()" />
+
+<script>
+
+// IE5.5+ PNG Alpha Fix v1.0 by Angus Turnbull http://www.twinhelix.com
+// Free usage permitted as long as this notice remains intact.
+
+// This must be a path to a blank image. That's all the configuration you need here.
+
+/*
+  USAGE: 
+  All you have to do is include this one line in your CSS file, with the
+  tag names to which you want the script applied:
+
+  img, div { behavior: url(iepngfix.htc) }
+
+  Alternatively, you can specify that this will apply to all tags like so:
+
+  * { behavior: url(iepngfix.htc) }
+*/
+
+var blankImg = '/static/images/iepngfix/blank.gif';
+
+var f = 'DXImageTransform.Microsoft.AlphaImageLoader';
+
+function filt(s, m) {
+ if (filters[f]) {
+  filters[f].enabled = s ? true : false;
+  if (s) with (filters[f]) { src = s; sizingMethod = m }
+ } else if (s) style.filter = 'progid:'+f+'(src="'+s+'",sizingMethod="'+m+'")';
+}
+
+function doFix() {
+ if ((parseFloat(navigator.userAgent.match(/MSIE (\S+)/)[1]) < 5.5) ||
+  (event && !/(background|src)/.test(event.propertyName))) return;
+
+ if (tagName == 'IMG') {
+  if ((/\.png$/i).test(src)) {
+   filt(src, 'image');  // was 'scale'
+   src = blankImg;
+  } else if (src.indexOf(blankImg) < 0) filt();
+ } else if (style.backgroundImage) {
+  if (style.backgroundImage.match(/^url[("']+(.*\.png)[)"']+$/i)) {
+   var s = RegExp.$1;
+   style.backgroundImage = '';
+   filt(s, 'crop');
+  } else filt();
+ }
+}
+
+doFix();
+
+</script>
+</public:component>

Added: jifty/branches/schema-plugins/share/web/static/js/jifty.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/jifty.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,1100 @@
+/* An empty class so we can create things inside it */
+var Jifty = Class.create();
+
+/* Actions */
+var Action = Class.create();
+Action.prototype = {
+    // New takes the moniker (a string), and an optional array of form
+    // elements to additionally take into consideration
+    initialize: function(moniker) {
+        this.moniker = moniker;
+
+        // Extra form parameters
+        this.extras = $A();
+        if (arguments.length > 1) {
+            this.extras = arguments[1];
+        }
+
+        this.register = $('J:A-' + this.moniker);  // Simple case -- no ordering information
+        if (! this.register) {
+            // We need to go looking -- this also goes looking through this.extras, from above
+            var elements = $A(document.getElementsByTagName('input'));
+            for (var i = 0; i < this.extras.length; i++)
+                elements.push(this.extras[i]);
+            for (var i = 0; i < elements.length; i++) {
+                if ((Form.Element.getMoniker(elements[i]) == this.moniker)
+                 && (Form.Element.getType(elements[i]) == "registration")) {
+                    this.register = elements[i];
+                    break;
+                }
+            }
+        }
+
+        if (this.register) {
+            this.form = Form.Element.getForm(this.register);
+            this.actionClass = this.register.value;
+        }
+    },
+
+    // Returns an Array of all fields in this Action
+    fields: function() {
+        if(!this.cached_fields) {
+            var elements = new Array;
+            var possible = Form.getElements(this.form);
+            // Also pull from extra query parameters
+            for (var i = 0; i < this.extras.length; i++)
+                possible.push(this.extras[i]);
+
+            for (var i = 0; i < possible.length; i++) {
+                if (Form.Element.getMoniker(possible[i]) == this.moniker)
+                    elements.push(possible[i]);
+            }
+            this.cached_fields = elements;
+        }
+        return this.cached_fields;
+    },
+
+    buttons: function() {
+        var elements = new Array();
+        var possible = Form.getElements(this.form);
+        for(var i = 0; i < possible.length; i++) {
+            if(possible[i].nodeName == 'INPUT' && possible[i].getAttribute("type") == 'submit') {
+                actions = Form.Element.buttonActions(possible[i]);
+                //If the button has no actions explicitly associated
+                //with it, it's associated with all the actions in the
+                //form
+                if(   actions.length == 0
+                   || actions.indexOf(this.moniker) >= 0) {
+                    elements.push(possible[i]);
+                }
+            }
+        }
+        return elements;
+    },
+
+    getField: function(name) {
+        var elements = this.fields();
+        for (var i = 0; i < elements.length; i++) {
+            if (Form.Element.getField(elements[i]) == name)
+                return elements[i];
+        }
+        return null;
+    },
+
+    // Serialize and return all fields needed for this action
+    serialize: function() {
+        var fields = this.fields();
+        var serialized = new Array;
+
+        for (var i = 0; i < fields.length; i++) {
+            serialized.push(Form.Element.serialize(fields[i]));
+        }
+        return serialized.join('&');
+    },
+
+    // Returns true if there is a file upload form as one of our elements
+    hasUpload: function() {
+        var fields = this.fields();
+        for (var i = 0; i < fields.length; i++) {
+            if ((fields[i].getAttribute("type") == "file") && fields[i].value)
+                return true;
+        }
+        return false;
+    },
+
+    // Return the action as a data structure suitable to be JSON'd
+    data_structure: function() {
+        var a = {};
+        a['moniker'] = this.moniker;
+        a['class']   = this.actionClass;
+
+        a['fields']  = {};
+        var fields = this.fields();
+        for (var i = 0; i < fields.length; i++) {
+            var f = fields[i];
+
+            if (   (Form.Element.getType(f) != "registration")
+                && (Form.Element.getValue(f) != null)
+                && (!Jifty.Placeholder.hasPlaceholder(f)))
+            {
+                if (! a['fields'][Form.Element.getField(f)])
+                    a['fields'][Form.Element.getField(f)] = {};
+                var field = Form.Element.getField(f);
+                var type = Form.Element.getType(f);
+                    
+                a['fields'][field][type] = this._mergeValues(a['fields'][field][type],
+                                                             Form.Element.getValue(f));
+            }
+        }
+
+        return a;
+    },
+
+    _mergeValues: function() {
+        var oldval = arguments[0];
+        var newval = arguments[1];
+        if(!oldval) return newval;
+        if(oldval.constructor != Array) {
+            oldval = [oldval];
+        }
+        oldval.push(newval);
+        return oldval;
+    },
+
+    // Validate the action
+    validate: function() {
+        show_wait_message();
+        var id = this.register.id;
+
+        new Ajax.Request(
+            '/__jifty/validator.xml',  // Right now, the URL is actually completely irrelevant
+            {
+                asynchronous: 1,
+                method: "get",
+                parameters: this.serialize() + "&J:VALIDATE=1",
+                onComplete:
+                    function (request) {
+                        var response  = request.responseXML.documentElement;
+                        for (var action = response.firstChild; action != null; action = action.nextSibling) {
+                            if ((action.nodeName == 'validationaction') && (action.getAttribute("id") == id)) {
+                                for (var field = action.firstChild; field != null; field = field.nextSibling) {
+                                    // Possibilities for field.nodeName: it could be #text (whitespace),
+                                    // or 'blank' (the field was blank, don't mess with the error div), or 'ok'
+                                    // (clear the error and warning div!) or 'error' (fill in the error div, clear 
+                                    // the warning div!) or 'warning' (fill in the warning div and clear the error div!)
+                                    if (field.nodeName == 'error' || field.nodeName == 'warning') {
+                                        var err_div = document.getElementById(field.getAttribute("id"));
+                                        if (err_div != null) {
+                                            err_div.innerHTML = field.firstChild.data;
+                                        }
+                                    } else if (field.nodeName == 'ok') {
+                                        var err_div = document.getElementById(field.getAttribute("id"));
+                                        if (err_div != null) {
+                                            err_div.innerHTML = '';
+                                        }
+                                    }
+                                }
+                            } else if ((action.nodeName == 'canonicalizeaction') && (action.getAttribute("id") == id)) {
+                                for (var field = action.firstChild; field != null; field = field.nextSibling) {
+                                    // Possibilities for field.nodeName: it could be 'ignored', 'blank' , 'update', or 'info'
+                                    // info is a separate action from the update
+                                    if (field.nodeName == 'canonicalization_note')  {
+                                        var note_div= document.getElementById(field.getAttribute("id"));
+                                        if (note_div != null) {
+                                            note_div.innerHTML = field.firstChild.data;
+                                        }
+                                    }
+
+                                    if (field.nodeName == 'update') {
+                                        var field_name = field.getAttribute("name");
+                                        for (var form_number = 0 ; form_number < document.forms.length; form_number++) {
+                                            if (document.forms[form_number].elements[field_name] == null)
+                                                continue;
+                                            document.forms[form_number].elements[field_name].value = field.firstChild.data;
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                        return true;
+                    }
+            }
+        ); 
+        hide_wait_message();
+        return false;
+    },
+
+    submit: function() {
+        show_wait_message();
+        new Ajax.Request(
+            '/empty',
+            { parameters: this.serialize() }
+        );
+        hide_wait_message();
+    },
+
+    disable_input_fields: function() {
+        var disable = function() {
+            var elt = arguments[0];
+            // Disabling hidden elements seems to  make IE sad for some reason
+            if(elt.type != 'hidden') {
+                // Triggers https://bugzilla.mozilla.org/show_bug.cgi?id=236791
+                elt.blur();
+                elt.disabled = true;
+            }
+        };
+        this.fields().each(disable);
+        this.buttons().each(disable);
+    },
+
+    enable_input_fields: function() {
+        var enable = function() { arguments[0].disabled = false; };
+        this.fields().each( enable );
+        this.buttons().each( enable );
+    }
+};
+
+
+
+/* Forms */
+Object.extend(Form, {
+    // Return an Array of Actions that are in this form
+    getActions: function (element) {
+        var elements = new Array;
+        var possible = Form.getElements(element);
+
+        for (var i = 0; i < possible.length; i++) {
+            if (Form.Element.getType(possible[i]) == "registration")
+                elements.push(new Action(Form.Element.getMoniker(possible[i])));
+        }
+        
+        return elements;
+    },
+
+    clearPlaceholders: function(element) {
+        var elements = Form.getElements(element);
+        for(var i = 0; i < elements.length; i++) {
+            Jifty.Placeholder.clearPlaceholder(elements[i]);
+        }
+    }
+});
+
+
+
+/* Fields */
+Object.extend(Form.Element, {
+    // Get the moniker for this form element
+    // Takes an element or an element id
+    getMoniker: function (element) {
+        element = $(element);    
+
+        if (/^J:A(:F)+-[^-]+-.+$/.test(element.name)) {
+            var bits = element.name.match(/^J:A(?::F)+-[^-]+-(.+)$/);
+            return bits[1];
+        } else if (/^J:A-(\d+-)?.+$/.test(element.name)) {
+            var bits = element.name.match(/^J:A-(?:\d+-)?(.+)$/);
+            return bits[1];
+        } else {
+            return null;
+        }
+    },
+
+    // Get the Action for this form element
+    // Takes an element or an element id
+    getAction: function (element) {
+        element = $(element);    
+
+        var moniker = Form.Element.getMoniker(element);
+        return new Action(moniker);
+    },
+
+    // Returns the name of the field
+    getField: function (element) {
+        element = $(element);    
+
+        if (/^J:A(:F)+-[^-]+-.+$/.test(element.name)) {
+            var bits = element.name.match(/^J:A(?::F)+-([^-]+)-.+$/);
+            return bits[1];
+        } else {
+            return null;
+        }
+    },
+
+    // The type of Jifty form element
+    getType: function (element) {
+        element = $(element);
+
+        if (/^J:A-/.test(element.name)) {
+            return "registration";
+        } else if (/^J:A:F-/.test(element.name)) {
+            return "value";
+        } else if (/^J:A:F:F-/.test(element.name)) {
+            return "fallback";
+        } else {
+            return null;
+        }
+    },
+
+    // Validates the action this form element is part of
+    validate: function (element) {
+            if(!Element.hasClassName(element, 'validation_disabled')) {
+                Form.Element.getAction(element).validate();
+            }
+    },
+
+    // Temporarily disable validation
+            disableValidation: function(element) {
+            Element.addClassName(element, 'validation_disabled');
+        },
+
+            //Reenable validation            
+            enableValidation: function(element) {
+            Element.removeClassName(element, 'validation_disabled');
+        },
+
+
+    // Look up the form that this element is part of -- this is sometimes
+    // more complicated than you'd think because the form may not exist
+    // anymore, or the element may have been inserted into a new form.
+    // Hence, we may need to walk the DOM.
+    getForm: function (element) {
+        element = $(element);
+
+        if (element.virtualform)
+            return element.virtualform;
+
+        if (element.form)
+            return element.form;
+
+        for (var elt = element.parentNode; elt != null; elt = elt.parentNode) {
+            if (elt.nodeName == 'FORM') {
+                element.form = elt;
+                return elt;
+            } 
+       }
+        return null;
+    },
+
+    buttonArguments: function(element) {
+        element = $(element);
+        if (!element)
+            return $H();
+
+        if (((element.nodeName != 'INPUT') || (element.getAttribute("type") != "submit"))
+         && ((element.nodeName != 'A')     || (! element.getAttribute("name"))))
+            return $H();
+
+        var extras = $H();
+
+        // Split other arguments out, if we're on a button
+        var pairs = element.getAttribute("name").split("|");
+        for (var i = 0; i < pairs.length; i++) {
+            var bits = pairs[i].split('=',2);
+            extras[bits[0]] = bits[1];
+        }
+        return extras;
+    },
+
+    buttonActions: function(element) {
+        element = $(element);
+        var actions = Form.Element.buttonArguments(element)['J:ACTIONS'];
+        if(actions) {
+            return actions.split(",");
+        } else {
+            return new Array();
+        }
+    },  
+
+    buttonFormElements: function(element) {
+        element = $(element);
+
+        var extras = $A();
+        var args = Form.Element.buttonArguments(element);
+        var keys = args.keys();
+        for (var i = 0; i < keys.length; i++) {
+            var e = document.createElement("input");
+            e.setAttribute("type", "hidden");
+            e.setAttribute("name", keys[i]);
+            e.setAttribute("value", args[keys[i]]);
+            e['virtualform'] = Form.Element.getForm(element);
+            extras.push(e);
+        }
+        return extras;
+    },
+
+    /* Someday Jifty may have the concept of "default"
+       buttons.  For now, this clicks the first button that will
+       submit the action associated with the form element.
+     */
+    clickDefaultButton: function(element) {
+        var action = Form.Element.getAction( element );
+        if ( action ) {
+            var buttons = action.buttons();
+            for ( var i = 0; i < buttons.length; i++ ) {
+                var b = buttons[i];
+                if ( Form.Element.buttonActions( b ).indexOf( action.moniker ) >= 0 ) {
+                    b.click();
+                    return true;
+                }
+            }
+        }
+        return false;
+    },
+
+    handleEnter: function(event) {
+        /* Trap "Enter" */
+        if (    event.keyCode == 13
+             && !event.metaKey && !event.altKey && !event.ctrlKey )
+        {
+            if ( Form.Element.clickDefaultButton( event.target ) )
+                event.preventDefault();
+        }
+    }
+
+});
+
+JSAN.use("DOM.Events");
+
+
+// Form elements should focus if the CSS says so.
+Behaviour.register( { ".focus": function(e) {
+    /* Check to see if the element is already focused */
+    if ( !Element.hasClassName(e, "focused") ) {
+        e.focus();
+        Element.addClassName(e, "focused");
+    }
+    } });
+
+
+// Form elements should AJAX validate if the CSS says so
+Behaviour.register({
+    'input.ajaxvalidation, textarea.ajaxvalidation, input.ajaxcanonicalization, textarea.ajaxcanonicalization': function(elt) {
+        DOM.Events.addListener(elt, "blur", function () {
+                Form.Element.validate(elt);
+            });
+    },
+    'input.date': function(e) {
+        if ( !Element.hasClassName( e, 'has_calendar_link' ) ) {
+            createCalendarLink(e);
+            Element.addClassName( e, 'has_calendar_link' );
+        }
+    },
+    'input.button_as_link': function(e) {
+        buttonToLink(e);
+    },
+    "input.date, input.text": function(e) {
+        /* XXX TODO: Figure out how to make our enter handler detect
+           when the autocomplete is active so we can use it on autocompleted
+           fields
+         */
+        if (   !Element.hasClassName( e, "jifty_enter_handler_attached" )
+            && !Element.hasClassName( e, "ajaxautocompletes" ) )
+        {
+            /* Do not use keydown as the event, it will not work as expected in Safari */
+            DOM.Events.addListener( e, "keypress", Form.Element.handleEnter );
+            Element.addClassName( e, "jifty_enter_handler_attached" );
+        }
+    },
+    ".messages": function(e) {
+        if (   !Element.hasClassName( e, "jifty_enter_handler_attached" ) ) {
+            e.innerHTML= 
+              '<a  href="#" id="dismiss_'+e.id+'" title="Dismiss" onmousedown="this.onfocus=this.blur;" onmouseup="this.onfocus=window.clientInformation?null:window.undefined" onclick="Effect.Fade(this.parentNode); return false;">Dismiss</a>' + e.innerHTML;
+
+            Element.addClassName( e, "jifty_enter_handler_attached" );
+        }
+    }
+});
+
+
+/* Regions */
+// Keep track of the fragments on the page
+var fragments = $H();
+var Region = Class.create();
+Region.prototype = {
+    initialize: function(name, args, path, parent) {
+        this.name = name;
+        this.args = $H(args);
+        this.path = path;
+        this.parent = parent ? fragments[parent] : null;
+        if (fragments[name]) {
+            // If this fragment already existed, we want to wipe out
+            // whatever evil lies we might have said earlier; do this
+            // by clearing out everything that looks relevant
+            var keys = current_args.keys();
+            for (var i = 0; i < keys.length; i++) {
+                var k = keys[i];
+                var parsed = k.match(/^(.*?)\.(.*)/);
+                if ((parsed != null) && (parsed.length == 3) && (parsed[1] == this.name)) {
+                    delete current_args[k];
+                }
+            }
+        }
+
+        fragments[name] = this;
+    },
+
+    setPath: function(supplied) {
+        // Merge in from current_args
+        var keys = current_args.keys();
+        for (var i = 0; i < keys.length; i++) {
+            var k = keys[i];
+            if (k == this.name) {
+                this.path = current_args[k];
+            }
+        }
+
+        // Update with supplied
+        if (supplied != null) {
+            this.path = supplied;
+        }
+        
+        // Propagate back to current args
+        current_args[this.name] = this.path;
+
+        // Return new value
+        return this.path;
+    },
+
+    setArgs: function(supplied) {
+        supplied = $H(supplied);
+        // Merge in current args
+        var keys = current_args.keys();
+        for (var i = 0; i < keys.length; i++) {
+            var k = keys[i];
+            var parsed = k.match(/^(.*?)\.(.*)/);
+            if ((parsed != null) && (parsed.length == 3) && (parsed[1] == this.name)) {
+                this.args[parsed[2]] = current_args[k];
+            }
+        }
+
+        // Merge in any supplied parameters
+        this.args = this.args.merge(supplied);
+
+        // Fill supplied parameters into current args
+        keys = supplied.keys();
+        for (var i = 0; i < keys.length; i++) {
+            var k = keys[i];
+            current_args[this.name+'.'+k] = supplied[k];
+        }
+        
+        // Return new values
+        return this.args;
+    },
+
+    data_structure: function(path, args) {
+        // Set the path and args, if given
+        if (path)
+            this.setPath(path);
+        if (args)
+            this.setArgs(args);
+
+        // If we have a parent, find our not-qualified name
+        var shortname = this.name;
+        if (this.parent) {
+            shortname = this.name.substr(this.parent.name.length + 1);
+        }
+
+        // Return a nummy data structure
+        return {
+            name: shortname,
+            path: this.path,
+            args: this.args,
+            parent: this.parent ? this.parent.data_structure(null,null) : null
+        }
+    }
+};
+
+
+// Keep track of the state variables.
+var current_args = $H();
+
+// Prepare element for use in update()
+//  - 'fragment' is a hash, see fragments in update()
+
+function prepare_element_for_update(f) {
+        var name = f['region'];
+
+        // Find where we are going to go
+        var element = $('region-' + f['region']);
+        if (f['element']) {
+            var possible = cssQuery(f['element']);
+            if (possible.length == 0)
+                element = null;
+            else
+                element = possible[0];
+        }
+        f['element'] = element;
+
+        // If we can't find out where we're going, bail
+        if (element == null)
+            return;
+
+        // If we're removing the element, do it now
+        // XXX TODO: Effects on this?
+        if (f['mode'] == "Delete") {
+            fragments[name] = null;
+            Element.remove(element);
+            return;
+        }
+
+        f['is_new'] = (fragments[name] ? false : true);
+        // If it's new, we need to create it so we can dump it
+        if (f['is_new']) {
+            // Find what region we're inside
+            f['parent'] = null;
+            if (f['mode'] && ((f['mode'] == "Before") || (f['mode'] == "After")))
+                element = element.parentNode;
+            while ((element != null) && (element.getAttribute) && (f['parent'] == null)) {
+                if (/^region-/.test(element.getAttribute("id")))
+                    f['parent'] = element.getAttribute("id").replace(/^region-/,"");
+                element = element.parentNode;
+            }
+
+            if (f['parent']) {
+                f['region'] = name = f['parent'] + '-' + name;
+            }
+
+            // Make the region (for now)
+            new Region(name, f['args'], f['path'], f['parent']);
+        } else if ((f['path'] != null) && f['toggle'] && (f['path'] == fragments[name].path)) {
+            // If they set the 'toggle' flag, and clicking wouldn't change the path
+            Element.update(element, '');
+            fragments[name].path = null;
+            return;
+        } else if (f['path'] == null) {
+            // If they didn't know the path, fill it in now
+            f['path'] == fragments[name].path;
+        }
+
+    return f;    
+}
+// applying updates from a fragment
+//   - fragment: the fragment from the server
+//   - f: fragment spec
+var apply_fragment_updates = function(fragment, f) {
+    // We found the right fragment
+    var dom_fragment = fragments[f['region']];
+    var new_dom_args = $H();
+    var element = f['element'];
+    for (var fragment_bit = fragment.firstChild;
+	 fragment_bit != null;
+	 fragment_bit = fragment_bit.nextSibling) {
+	if (fragment_bit.nodeName == 'argument') {
+	    // First, update the fragment's arguments
+	    // with what the server actually used --
+	    // this is needed in case there was
+	    // argument mapping going on
+	    var textContent = '';
+	    if (fragment_bit.textContent) {
+		textContent = fragment_bit.textContent;
+	    } else if (fragment_bit.firstChild) {
+		textContent = fragment_bit.firstChild.nodeValue;
+	    }
+	    new_dom_args[fragment_bit.getAttribute("name")] = textContent;
+	} else if (fragment_bit.nodeName.toLowerCase() == 'content') {
+	    var textContent = '';
+	    if (fragment_bit.textContent) {
+		textContent = fragment_bit.textContent;
+	    } else if (fragment_bit.firstChild) {
+		textContent = fragment_bit.firstChild.nodeValue;
+	    }
+                    
+	    // Once we find it, do the insertion
+	    if (f['mode'] && (f['mode'] != 'Replace')) {
+		var insertion = eval('Insertion.'+f['mode']);
+		new insertion(element, textContent.stripScripts());
+	    } else {
+		Element.update(element, textContent.stripScripts());
+	    }
+	    // We need to give the browser some "settle" time before we eval scripts in the body
+	    setTimeout((function() { this.evalScripts() }).bind(textContent), 10);
+	    Behaviour.apply(element);
+	}
+    }
+    dom_fragment.setArgs(new_dom_args);
+
+    // Also, set us up the effect
+    if (f['effect']) {
+	try {
+	    var effect = eval('Effect.'+f['effect']);
+	    var effect_args  = f['effect_args'] || {};
+	    if (effect) {
+		if (f['is_new'])
+		    Element.hide($('region-'+f['region']));
+		(effect)($('region-'+f['region']), effect_args);
+	    }
+	} catch ( e ) {
+	    // Don't be sad if the effect doesn't exist
+	}
+    }
+}
+
+// Update a region.  Takes a hash of named parameters, including:
+//  - 'actions' is an array of monikers to submit
+//  - 'fragments' is an array of hashes, which may have:
+//     - 'region' is the name of the region to update
+//     - 'args' is a hash of arguments to override
+//     - 'path' is the path of the fragment (if this is a new fragment)
+//     - 'element' is the CSS selector of the element to update, if 'region' isn't supplied
+//     - 'mode' is one of 'Replace', or the name of a Prototype Insertion
+//     - 'effect' is the name of a Prototype Effect
+function update() {
+    // If we don't have XMLHttpRequest, bail and fallback on full-page
+    // loads
+    if(!Ajax.getTransport()) return true;
+    // XXX: prevent default behavior in IE
+    if(window.event) {
+        window.event.returnValue = false;
+    }
+
+    show_wait_message();
+    var named_args = arguments[0];
+    var trigger    = arguments[1];
+
+    // The YAML/JSON data structure that will be sent
+    var request = $H();
+
+    // Set request base path
+    request['path'] = '/__jifty/webservices/xml';
+
+    // Grab extra arguments (from a button)
+    var button_args = Form.Element.buttonFormElements(trigger);
+
+    // Build actions structure
+    request['actions'] = $H();
+    for (var moniker in named_args['actions']) {
+        var disable = named_args['actions'][moniker];
+        var a = new Action(moniker, button_args);
+        if (a.register) {
+            if (a.hasUpload())
+                return true;
+            if(disable) {
+                a.disable_input_fields();
+            }
+            request['actions'][moniker] = a.data_structure();
+        }
+    }
+
+    request['fragments'] = $H();
+    // Build fragments structure
+    for (var i = 0; i < named_args['fragments'].length; i++) {
+        var f = named_args['fragments'][i];
+        f = prepare_element_for_update(f);
+        if (!f) continue;
+        // Update with all new values
+        var name = f['region'];
+        var fragment_request = fragments[name].data_structure(f['path'], f['args']);
+
+        if (f['is_new'])
+            // Ask for the wrapper if we are making a new region
+            fragment_request['wrapper'] = 1;
+
+        // Push it onto the request stack
+        request['fragments'][name] = fragment_request;
+    }
+
+    // And when we get the result back..
+    var onSuccess = function(transport, object) {
+        // Grab the XML response
+        var response = transport.responseXML.documentElement;
+        // Loop through the result looking for it
+        for (var response_fragment = response.firstChild;
+             response_fragment != null && response_fragment.nodeName == 'fragment';
+             response_fragment = response_fragment.nextSibling) {
+
+            var f; 
+            for (var i = 0; i < named_args['fragments'].length; i++) {
+                f = named_args['fragments'][i];
+                if (response_fragment.getAttribute("id") == f['region'])
+                    break;
+            }
+            if (response_fragment.getAttribute("id") != f['region'])
+                continue;
+
+	    try {
+            apply_fragment_updates(response_fragment, f);
+	    }catch (e) { alert(e) }
+        }
+        for (var result = response.firstChild;
+             result != null;
+             result = result.nextSibling) {
+            if (result.nodeName == 'result') {
+                for (var key = result.firstChild;
+                     key != null;
+                     key = key.nextSibling) {
+                    show_action_result(result.getAttribute("moniker"),key);
+                }
+            }
+        }
+    };
+    var onFailure = function(transport, object) {
+        hide_wait_message_now();
+
+        alert("Unable to connect to server.\n\nTry again in a few minutes.");
+
+        Jifty.failedRequest = transport;
+
+        var keys = request["actions"].keys();
+        for ( var i = 0; i < keys.length; i++ ) {
+            var a = new Action( request["actions"][ keys[i] ].moniker );
+            a.enable_input_fields();
+        }
+    };
+
+    // Build variable structure
+    request['variables'] = $H();
+    var keys = current_args.keys();
+    for (var i = 0; i < keys.length; i++) {
+        var k = keys[i];
+        request['variables']['region-'+k] = current_args[k];
+    }
+
+    // Push any state variables which we set into the forms
+    for (var i = 0; i < document.forms.length; i++) {
+        var form = document.forms[i];
+        var keys = current_args.keys();
+        for (var j = 0; j < keys.length; j++) {
+            var n = keys[j];
+            if (form['J:V-region-'+n]) {
+                form['J:V-region-'+n].value = current_args[n];
+            } else {
+                var hidden = document.createElement('input');
+                hidden.setAttribute('type',  'hidden');
+                hidden.setAttribute('name',  'J:V-region-'+n);
+                hidden.setAttribute('id',    'J:V-region-'+n);
+                hidden.setAttribute('value', current_args[n]);
+                form.appendChild(hidden);
+            }
+        }
+    }
+
+    // Set up our options
+    var options = { postBody: JSON.stringify(request),
+                    onSuccess: onSuccess,
+                    onException: onFailure,
+                    onFailure: onFailure,
+                    onComplete: function(){hide_wait_message()},
+                    requestHeaders: ['Content-Type', 'text/x-json']
+    };
+
+    // Go!
+    new Ajax.Request(document.URL,
+                     options
+                    );
+    return false;
+}
+
+function trace( msg ){
+  if( typeof( jsTrace ) != 'undefined' ){
+    jsTrace.send( msg );
+  }
+}
+
+
+function show_wait_message (){
+    if ($('jifty-wait-message'))
+        new Effect.Appear('jifty-wait-message', {duration: 0.5});
+}
+
+function hide_wait_message (){
+    if ($('jifty-wait-message'))
+        new Effect.Fade('jifty-wait-message', {duration: 0.2});
+}
+
+function hide_wait_message_now() {
+    if ($('jifty-wait-message'))
+        Element.hide('jifty-wait-message');
+}
+
+function show_action_result() {
+    var popup = $('jifty-result-popup');
+    if(!popup) return;
+
+    var moniker = arguments[0];
+    var result = arguments[1];
+    var status = result.nodeName;
+
+    if (status == 'field') {
+        // If this is a field, it has kids which are <error> or <message> -- loop through them
+        for (var key = result.firstChild;
+             key != null;
+             key = key.nextSibling) {
+            show_action_result(moniker,key);
+        }
+        return;
+    }
+
+    /* This is a workaround for Safari, which does not support textContent */
+    var text = result.textContent
+                    ? result.textContent
+                    : (result.firstChild ? result.firstChild.nodeValue : '');
+
+    if(status != 'message' && status != 'error') return;
+
+    var node = document.createElement('div');
+    var node_id = 'result-' + moniker;
+    node.setAttribute('id', node_id);
+    node.className = "popup_notification result-" + status;
+    node.innerHTML = text;
+    
+    var wrap1 = document.createElement("div");
+    wrap1.className = "dropshadow_wrap1";
+    var wrap2 = document.createElement("div");
+    wrap2.className = "dropshadow_wrap2";
+    var wrap3 = document.createElement("div");
+    wrap3.className = "dropshadow_wrap3";
+
+    wrap1.appendChild(wrap2);
+    wrap2.appendChild(wrap3);
+    wrap3.appendChild(node);
+    
+    if(popup.hasChildNodes()) {
+        popup.insertBefore(wrap1, popup.firstChild);
+    } else {
+        popup.appendChild(wrap1);
+    }
+    
+    setTimeout(function () {
+           new Effect.Fade(wrap1, {duration: 3.0});
+    }, 3500);
+}
+
+Jifty.Autocompleter = Class.create();
+Object.extend(Object.extend(Jifty.Autocompleter.prototype, Ajax.Autocompleter.prototype), {
+  initialize: function(field, div) {
+    this.field  = $(field);
+    this.action = Form.Element.getAction(this.field);
+    this.url    = '/__jifty/autocomplete.xml';
+
+    Event.observe(this.field, "focus", this.onFocus.bindAsEventListener(this));
+    this.baseInitialize(this.field, $(div), {
+        minChars: "0",
+        beforeShow: this.beforeShow,
+        beforeHide: this.beforeHide,
+        frequency: 0.1,
+        onShow: this.onShow,
+        onHide: this.onHide,
+        afterUpdateElement: this.afterUpdate
+    });
+  },
+
+  onShow: function(element, update) {
+      if(!update.style.position || update.style.position=='absolute') {
+        update.style.position = 'absolute';
+        Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight});
+      }
+      Element.show( update );
+  },
+
+  onHide: function(element, update) {
+      Element.hide( update );
+  },
+
+  beforeShow: function(obj) {
+    /* Prevents the race for canonicalization and updating
+       via autocomplete */
+    if ( obj.element.onblur ) {
+        obj.element._onblur = obj.element.onblur;
+        obj.element.onblur  = null;
+    }
+  },
+
+  beforeHide: function(obj) {
+    /* Restore onblur and config option */
+    if ( obj.element._onblur ) {
+        obj.element.onblur  = obj.element._onblur;
+        obj.element._onblur = null;
+    }
+  },
+
+  onFocus: function(event) {
+    this.changed  = true;
+    this.hasFocus = true;
+
+    if (this.observer)
+        clearTimeout(this.observer);
+    
+    this.onObserverEvent();
+  },
+
+  afterUpdate: function(field, selection) {
+     Form.Element.validate(field);
+  },
+  
+  getUpdatedChoices: function() {
+      var request = { path: this.url, actions: {} };
+
+      var a = $H();
+      a['moniker'] = 'autocomplete';
+      a['class']   = 'Jifty::Action::Autocomplete';
+      a['fields']  = $H();
+      a['fields']['moniker']  = this.action.moniker;
+      a['fields']['argument'] = Form.Element.getField(this.field);
+      request['actions']['autocomplete'] = a;
+      request['actions'][this.action.moniker] = this.action.data_structure();
+      request['actions'][this.action.moniker]['active']  = 0;
+
+      var options = { postBody: JSON.stringify(request),
+                      onComplete: this.onComplete.bind(this),
+                      requestHeaders: ['Content-Type', 'text/x-json']
+      };
+
+      new Ajax.Request(this.url,
+                       options
+                       );
+  }
+
+
+});
+
+Jifty.Placeholder = Class.create();
+Object.extend(Jifty.Placeholder.prototype, {
+  element: null,
+  text: null,
+
+  initialize: function(element, text) {
+     this.element = $(element);
+     this.text = text;
+     Event.observe(element, 'focus', this.onFocus.bind(this));
+     Event.observe(element, 'blur', this.onBlur.bind(this));
+     this.onBlur();
+
+     var form = Form.Element.getForm(element);
+     
+     if(form && !form.hasPlaceholders) {
+         form.hasPlaceholders = true;
+         // We can't attach this event via DOM event methods because 
+         // we need to call form.submit() sometimes and still have a good
+         // way to call this event handler
+         form.onsubmit = function () { Form.clearPlaceholders(form); };
+     }
+  },
+
+  onBlur: function() {
+     /* On browser back/forward, the placeholder text will be remembered
+        for the field, so we want to add the class if the value is the same
+        as the placeholder text.  This does have the effect of making it
+        impossible to submit a field with the same value as the placeholder. */
+     if (this.element.value == '' || this.element.value == this.text) {
+       Element.addClassName(this.element, 'placeholder');
+       this.element.value = this.text;
+     }
+  },
+
+  onFocus: function() {
+     Jifty.Placeholder.clearPlaceholder(this.element);
+  }
+
+});
+
+Object.extend(Jifty.Placeholder, {
+
+   hasPlaceholder: function(elt) {
+     return Element.hasClassName(elt, 'placeholder');
+  },
+            
+  clearPlaceholder: function(elt) {
+     if(Jifty.Placeholder.hasPlaceholder(elt)) {
+       elt.value = '';
+       Element.removeClassName(elt, 'placeholder');
+     }
+  }
+
+});
+
+
+// Define hasOwnProperty for Safari
+if( !Object.prototype.hasOwnProperty ) {
+    Object.prototype.hasOwnProperty = function( property ) {
+        try {
+            var prototype = this.constructor.prototype;
+            while( prototype ) {
+                if( prototype[ property ] == this[ property ] ) {
+                    return false;
+                }
+                prototype = prototype.prototype;
+            }
+        } catch( e ) {}
+        return true;
+    }
+}

Added: jifty/branches/schema-plugins/share/web/static/js/jifty_smoothscroll.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/jifty_smoothscroll.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,36 @@
+
+if (typeof Jifty == "undefined") Jifty = { };
+
+Jifty.SmoothScroll = {
+    interval: '',
+    steps:    20,
+
+    scrollTo: function( y ) {
+        if ( Jifty.SmoothScroll.interval )
+            clearInterval( Jifty.SmoothScroll.interval );
+        
+        var stepsize = parseInt( ( y - Jifty.Utils.getScrollTop() ) / Jifty.SmoothScroll.steps );
+
+        Jifty.SmoothScroll.interval
+            = setInterval('Jifty.SmoothScroll.scrollWindow('+stepsize+','+y+')',10);
+    },
+            
+    scrollWindow: function( amount, y ) {
+        var curpos  = Jifty.Utils.getScrollTop();
+        var isAbove = ( curpos < y );
+        
+        window.scrollTo( 0, curpos + amount );
+        
+        var newpos     = Jifty.Utils.getScrollTop();
+        var newIsAbove = ( newpos < y );
+        
+        if ( ( isAbove != newIsAbove ) || ( curpos == newpos ) ) {
+            /* We've just scrolled past the destination, or we haven't moved
+               from the last scroll (i.e., we're at the bottom of the page),
+               so scroll exactly to the position
+             */
+            window.scrollTo( 0, y );
+            clearInterval( Jifty.SmoothScroll.interval );
+        }
+    }
+};

Added: jifty/branches/schema-plugins/share/web/static/js/jifty_subs.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/jifty_subs.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,71 @@
+if (typeof Jifty == "undefined") Jifty = { };
+
+{
+
+    /* onPushHandler is called for each new pushed element.
+       (currently, this is always a <pushfrag>).  This routine takes
+       the pushed element and extracts render mode (Before, After,
+       Replace, Delete) , region name and other rendering information.
+       Then it calls jifty's "apply_fragment_updates to the item
+       inside the <pushfrag> (the actual fragment);
+
+	f is the specification for the new fragment. (region, path,
+	mode and other infomration extracted from the fragment)
+
+       */
+
+    var onPushHandler = function(t) {
+    	var mode = t.getAttribute('mode');
+    	var rid =  t.firstChild.getAttribute('id');
+    	var f = { region: rid, path: '', mode: mode };
+    	f = prepare_element_for_update(f);
+    	apply_fragment_updates(t.firstChild, f);
+    };
+
+
+
+    
+    /* This function constructs a new Jifty.Subs object and sets
+    up a callback with HTTP.Push to run our onPushHandler each time
+    a new element is added to the hidden iframe's body.
+
+    We could instead say "sets up our transport. every time the
+    transport gets a new item, call onPushHandler" */
+
+    /* Jifty.Subs.start() will connect to the iframe transport */
+
+    Jifty.Subs = function(args) {
+    	var window_id = args.window_id; // XXX: not yet
+    	var uri = args.uri;
+    	if (!uri)
+    	    uri = "/=/subs?";
+    	//var push = new HTTP.Push({ "uri": uri, interval : 100, "onPush" : onPushHandler});
+    	
+    	this.start = function() {
+    	    //push.start();
+
+	    new Ajax.PeriodicalUpdater({},'/=/subs?forever=0',
+	    {
+	        'decay': 1, 'frequency': 0,
+	        'asynchronous':true, 
+	        'evalScripts':false,
+	        'method': 'get',
+	        'onSuccess': onSuccess,
+	        'onFailure': onFailure
+	    });
+	    	};
+    }
+
+
+    function onSuccess(req, json) {
+        var container = document.createElement('div');
+        container.innerHTML = req.responseText;
+        var frags = container.getElementsByTagName('pushfrag');
+        for(var i = 0 ; i < frags.length; i++) {
+            onPushHandler(frags[i]);
+        }
+    }
+    function onFailure(req) { }
+
+}
+

Added: jifty/branches/schema-plugins/share/web/static/js/jifty_utils.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/jifty_utils.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,147 @@
+
+if (typeof Jifty == "undefined") Jifty = { };
+
+Jifty.Utils = {
+    /* From http://blog.firetree.net/2005/07/04/javascript-find-position/ */
+    findPosX: function(obj)
+    {
+      var curleft = 0;
+      if(obj.offsetParent)
+          while(1) 
+          {
+            curleft += obj.offsetLeft;
+            if(!obj.offsetParent)
+              break;
+            obj = obj.offsetParent;
+          }
+      else if(obj.x)
+          curleft += obj.x;
+      return curleft;
+    },
+
+    findPosY: function(obj)
+    {
+      var curtop = 0;
+      if(obj.offsetParent)
+          while(1)
+          {
+            curtop += obj.offsetTop;
+            if(!obj.offsetParent)
+              break;
+            obj = obj.offsetParent;
+          }
+      else if(obj.y)
+          curtop += obj.y;
+      return curtop;
+    },
+
+    findRelativePosX: function(e) {
+        var parent  = e.parentNode;
+        var parentx = 0;
+        
+        while ( parent ) {
+            if ( !parent.style ) break;
+            
+            var pos = Element.getStyle( parent, "position" );
+            if ( pos == "relative" || pos == "absolute" ) {
+                parentx = Jifty.Utils.findPosX( parent );
+                break;
+            }
+            parent = parent.parentNode;
+        }
+        
+        return Jifty.Utils.findPosX( e ) - parentx;
+    },
+
+    findRelativePosY: function(e) {
+        var parent  = e.parentNode;
+        var parenty = 0;
+        
+        while ( parent ) {
+            if ( !parent.style ) break;
+            
+            var pos = Element.getStyle( parent, "position" );
+            if ( pos == "relative" || pos == "absolute" ) {
+                parenty = Jifty.Utils.findPosY( parent );
+                break;
+            }
+            parent = parent.parentNode;
+        }
+        
+        return Jifty.Utils.findPosY( e ) - parenty;
+    },
+
+    isMSIE: false,
+
+    _browser: null,
+    browser: function() {
+        if ( Jifty.Utils._browser ) return Jifty.Utils._browser;
+
+        if ( Jifty.Utils.isMSIE ) {
+            Jifty.Utils._browser = "msie";
+            return "msie";
+        }
+
+        var browser = "unknown";
+        
+        if      ( Jifty.Utils._checkUAFor("konqueror"))   browser = "konqueror";
+        else if ( Jifty.Utils._checkUAFor("safari"))      browser = "safari";
+        else if ( Jifty.Utils._checkUAFor("omniweb"))     browser = "omniweb";
+        else if ( Jifty.Utils._checkUAFor("opera"))       browser = "opera";
+        else if ( Jifty.Utils._checkUAFor("webtv"))       browser = "webtv";
+        else if ( Jifty.Utils._checkUAFor("icab"))        browser = "icab";
+        else if ( Jifty.Utils._checkUAFor("msie"))        browser = "msie";
+        else if ( !Jifty.Utils._checkUAFor("compatible")) browser = "mozilla";
+        
+        Jifty.Utils._browser = browser;
+        return browser;
+    },
+
+    _ua: navigator.userAgent.toLowerCase(),
+    _checkUAFor: function(str) {
+        return Jifty.Utils._ua.indexOf( str ) + 1;
+    },
+
+    findScreenHeight: function() {
+        if ( window.innerHeight ) {
+            return window.innerHeight;
+        }
+        else {
+            if ( document.documentElement.clientHeight ) {
+                return document.documentElement.clientHeight;
+            }
+            else {
+                if ( document.body.clientHeight ) {
+                    return document.body.clientHeight;
+                }
+            }
+        }
+    },
+
+    getScrollTop: function() {
+        if (document.body && document.body.scrollTop)
+            return document.body.scrollTop;
+        if (document.documentElement && document.documentElement.scrollTop)
+            return document.documentElement.scrollTop;
+        if (window.pageYOffset)
+            return window.pageYOffset;
+        return 0; 
+    },
+
+    scrollToShow: function(id) {
+        var ul        = $(id);
+        var y         = Jifty.Utils.findPosY( ul ) + ul.offsetHeight + 10;
+        var scrollTop = Jifty.Utils.getScrollTop();
+        var screen    = Jifty.Utils.findScreenHeight() + scrollTop;
+        var diff      = y - screen;
+        
+        if ( diff > 0 )
+             Jifty.SmoothScroll.scrollTo( scrollTop + diff );
+    }
+};
+
+/* This sets Jifty.Utils.isMSIE to true in IE */
+/*@cc_on
+    Jifty.Utils.isMSIE = true;
+@*/
+

Added: jifty/branches/schema-plugins/share/web/static/js/jsTrace.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/jsTrace.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,12 @@
+/*------------------------------------------------------------------------------
+Function:       jsTrace()
+Author:         Aaron Gustafson (aaron at easy-designs dot net)
+Creation Date:  26 October 2005
+Version:        1.0
+Homepage:       http://www.easy-designs.net/code/jsTrace/
+License:        Creative Commons Attribution-ShareAlike 2.0 License
+                http://creativecommons.org/licenses/by-sa/2.0/
+Note:           If you change or improve on this script, please let us know by 
+                emailing the author (above) with a link to your demo page.
+------------------------------------------------------------------------------*/
+var jsTrace = { debugging_on: false, window: null, viewport: null, init: function(){ if( !document.getElementsByTagName || !document.getElementById || !document.createElement || !document.createTextNode ) return; jsTrace.createWindow(); jsTrace.debugging_on = true;}, createWindow: function(){ jsTrace.window = document.createElement( 'div' ); jsTrace.window.style.background = '#000'; jsTrace.window.style.font = '80% "Lucida Grande", "Lucida Sans Unicode", Helvetica, Arial, sans-serif'; jsTrace.window.style.padding = '2px'; jsTrace.window.style.position = 'absolute'; jsTrace.window.style.top = '50px'; jsTrace.window.style.left = '700px'; jsTrace.window.style.height = '360px'; jsTrace.window.style.zIndex = '100'; jsTrace.window.style.minHeight = '150px'; jsTrace.window.style.width = '190px'; jsTrace.window.style.minWidth = '150px'; var x = document.createElement('span'); x.style.border = '1px solid #000'; x.style.cursor = 'pointer'; x.style.color = '#000'; x.style.display = 'block'; x.style.lineHeight = '.5em'; x.style.padding = '0 0 3px'; x.style.position = 'absolute'; x.style.top = '4px'; x.style.right = '4px'; jsTrace.addEvent( x, 'click', function(){ jsTrace.kill();} ); x.setAttribute( 'title', 'Close jsTrace Debugger' ); x.appendChild( document.createTextNode( 'x' ) ); jsTrace.window.appendChild( x ); var sh = document.createElement('div'); sh.style.position = 'absolute'; sh.style.bottom = '3px'; sh.style.right = '3px'; var sg = document.createElement('span'); sg.style.border = '5px solid #ccc'; sg.style.borderLeftColor = sg.style.borderTopColor = '#000'; sg.style.cursor = 'pointer'; sg.style.color = '#ccc'; sg.style.display = 'block'; sg.style.height = '0'; sg.style.width = '0'; sg.style.overflow = 'hidden'; sg.setAttribute( 'title', 'Resize the jsTrace Debugger' ); if( typeof( Drag ) != 'undefined' ){ sg.xFrom = 0; sg.yFrom = 0; Drag.init( sg, null, null, null, null, null, true, true ); sg.onDrag = function( x, y ){ jsTrace.resizeX( x, this ); jsTrace.resizeY( y, this );}; sh.appendChild( sg ); jsTrace.window.appendChild( sh );} var tools = document.createElement( 'div' ); tools.style.fontSize = '.7em'; tools.style.fontVariant = 'small-caps'; tools.style.lineHeight = '10px'; tools.style.position = 'absolute'; tools.style.bottom = '5px'; tools.style.left = '3px'; var dl = document.createElement( 'span' ); dl.style.color = '#ccc'; dl.style.padding = '0 10px 0 0'; dl.style.overflow = 'hidden'; dl.style.cursor = 'pointer'; dl.setAttribute( 'title', 'Add a Delimeter' ); dl.appendChild( document.createTextNode( 'delimit' ) ); jsTrace.addEvent( dl, 'click', function(){ jsTrace.sendDelimeter();} ); tools.appendChild( dl ); var cl = document.createElement( 'span' ); cl.style.color = '#ccc'; cl.style.padding = '0 10px 0 0'; cl.style.overflow = 'hidden'; cl.style.cursor = 'pointer'; cl.setAttribute( 'title', 'Add a Delimeter' ); cl.appendChild( document.createTextNode( 'clear' ) ); jsTrace.addEvent( cl, 'click', function(){ jsTrace.clearWindow();} ); tools.appendChild( cl ); jsTrace.window.appendChild( tools ); var header = document.createElement( 'h3' ); header.style.background = '#ccc'; header.style.color = '#000'; header.style.cursor = 'pointer'; header.style.fontSize = '1em'; header.style.fontVariant = 'small-caps'; header.style.margin = '0 0 2px'; header.style.padding = '5px 10px'; header.style.lineHeight = '15px'; header.appendChild( document.createTextNode( 'jsTrace Debugger' ) ); jsTrace.window.appendChild( header ); jsTrace.viewport = document.createElement( 'pre' ); jsTrace.viewport.style.border = '1px solid #ccc'; jsTrace.viewport.style.color = '#ebebeb'; jsTrace.viewport.style.fontSize = '1.2em'; jsTrace.viewport.style.margin = '0'; jsTrace.viewport.style.padding = '0 3px'; jsTrace.viewport.style.position = 'absolute'; jsTrace.viewport.style.top = '30px'; jsTrace.viewport.style.left = '2px'; jsTrace.viewport.style.overflow = 'auto'; jsTrace.viewport.style.width = ( parseInt( jsTrace.window.style.width ) - 8 ) + 'px'; jsTrace.viewport.style.height = ( parseInt( jsTrace.window.style.height ) - 45 ) + 'px'; jsTrace.window.appendChild( jsTrace.viewport ); document.getElementsByTagName( 'body' )[0].appendChild( jsTrace.window ); if( typeof( Drag ) != 'undefined' ){ Drag.init( header, jsTrace.window );} }, resizeX: function( x, grip ){ var width = parseInt( jsTrace.window.style.width ); var newWidth = Math.abs( width - ( x - grip.xFrom ) ) + 'px'; if( parseInt( newWidth ) < parseInt( jsTrace.window.style.minWidth ) ) newWidth = jsTrace.window.style.minWidth; jsTrace.window.style.width = newWidth; grip.xFrom = x; jsTrace.viewport.style.width = ( parseInt( jsTrace.window.style.width ) - 8 ) + 'px';}, resizeY: function( y, grip ){ var height = parseInt( jsTrace.window.style.height ); var newHeight = Math.abs( height - ( y - grip.yFrom ) ) + 'px'; if( parseInt( newHeight ) < parseInt( jsTrace.window.style.minHeight ) ) newHeight = jsTrace.window.style.minHeight; jsTrace.window.style.height = newHeight; grip.yFrom = y; jsTrace.viewport.style.height = ( parseInt( jsTrace.window.style.height ) - 45 ) + 'px';}, send: function( text ){ text = text + "<br />"; jsTrace.viewport.innerHTML += text;}, sendDelimeter: function(){ jsTrace.send( '<span style="color: #f00">--------------------</span>' );}, clearWindow: function(){ jsTrace.viewport.innerHTML = '';}, kill: function() { jsTrace.window.parentNode.removeChild( jsTrace.window ); jsTrace.debugging_on = false;}, addEvent: function( obj, type, fn ){ if (obj.addEventListener) obj.addEventListener( type, fn, false ); else if (obj.attachEvent) { obj["e"+type+fn] = fn; obj[type+fn] = function() { obj["e"+type+fn]( window.event );}; obj.attachEvent( "on"+type, obj[type+fn] );} }, removeEvent: function ( obj, type, fn ) { if (obj.removeEventListener) obj.removeEventListener( type, fn, false ); else if (obj.detachEvent) { obj.detachEvent( "on"+type, obj[type+fn] ); obj[type+fn] = null; obj["e"+type+fn] = null;} } }; jsTrace.addEvent( window, 'load', jsTrace.init ); 
\ No newline at end of file

Added: jifty/branches/schema-plugins/share/web/static/js/jsan/DOM/Events.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/jsan/DOM/Events.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,262 @@
+/**
+
+=head1 NAME
+
+DOM.Events - Event registration abstraction layer
+
+=head1 SYNOPSIS
+
+  JSAN.use("DOM.Events");
+
+  function handleClick(e) {
+      e.currentTarget.style.backgroundColor = "#68b";
+  }
+
+  DOM.Events.addListener(window, "load", function () {
+      alert("The page is loaded.");
+  });
+
+  DOM.Events.addListener(window, "load", function () {
+      // this listener won't interfere with the first one
+      var divs = document.getElementsByTagName("div");
+      for(var i=0; i<divs.length; i++) {
+          DOM.Events.addListener(divs[i], "click", handleClick);
+      }
+  });
+
+=head1 DESCRIPTION
+
+This library lets you use a single interface to listen for and handle all DOM events
+to reduce browser-specific code branching.  It also helps in dealing with Internet
+Explorer's memory leak problem by automatically unsetting all event listeners when
+the page is unloaded (for IE only).
+
+=cut
+
+*/
+
+(function () {
+	if(typeof DOM == "undefined") DOM = {};
+	DOM.Events = {};
+	
+    DOM.Events.VERSION = "0.02";
+	DOM.Events.EXPORT = [];
+	DOM.Events.EXPORT_OK = ["addListener", "removeListener"];
+	DOM.Events.EXPORT_TAGS = {
+		":common": DOM.Events.EXPORT,
+		":all": [].concat(DOM.Events.EXPORT, DOM.Events.EXPORT_OK)
+	};
+	
+	// list of event listeners set by addListener
+	// offset 0 is null to prevent 0 from being used as a listener identifier
+	var listenerList = [null];
+	
+/**
+
+=head2 Functions
+
+All functions are kept inside the namespace C<DOM.Events> and aren't exported
+automatically.
+
+=head3 addListener( S<I<HTMLElement> element,> S<I<string> eventType,>
+S<I<Function> handler> S<[, I<boolean> makeCompatible = true] )>
+
+Registers an event listener/handler on an element.  The C<eventType> string should
+I<not> be prefixed with "on" (e.g. "mouseover" not "onmouseover"). If C<makeCompatible>
+is C<true> (the default), the handler is put inside a wrapper that lets you handle the
+events using parts of the DOM Level 2 Events model, even in Internet Explorer (and
+behave-alikes). Specifically:
+
+=over
+
+=item *
+
+The event object is passed as the first argument to the event handler, so you don't
+have to access it through C<window.event>.
+
+=item *
+
+The event object has the properties C<target>, C<currentTarget>, and C<relatedTarget>
+and the methods C<preventDefault()> and C<stopPropagation()> that behave as described
+in the DOM Level 2 Events specification (for the most part).
+
+=item *
+
+If possible, the event object for mouse events will have the properties C<pageX> and
+C<pageY> that contain the mouse's position relative to the document at the time the
+event occurred.
+
+=item *
+
+If you attempt to set a duplicate event handler on an element, the duplicate will
+still be added (this is different from the DOM2 Events model, where duplicates are
+discarded).
+
+=back
+
+If C<makeCompatible> is C<false>, the arguments are simply passed to the browser's
+native event registering facilities, which means you'll have to deal with event
+incompatibilities yourself. However, if you don't need to access the event information,
+doing it this way can be slightly faster and it gives you the option of unsetting the
+handler with a different syntax (see below).
+
+The return value is a positive integer identifier for the listener that can be used to
+unregister it later on in your script.
+
+=cut
+
+*/
+    
+	DOM.Events.addListener = function(elt, ev, func, makeCompatible) {
+		var usedFunc = func;
+        var id = listenerList.length;
+		if(makeCompatible == true || makeCompatible == undefined) {
+			usedFunc = makeCompatibilityWrapper(elt, ev, func);
+		}
+		if(elt.addEventListener) {
+			elt.addEventListener(ev, usedFunc, false);
+			listenerList[id] = [elt, ev, usedFunc];
+			return id;
+		}
+		else if(elt.attachEvent) {
+			elt.attachEvent("on" + ev, usedFunc);
+			listenerList[id] = [elt, ev, usedFunc];
+			return id;
+		}
+		else return false;
+	};
+	
+/**
+
+=head3 removeListener( S<I<integer> identifier> )
+
+Unregisters the event listener associated with the given identifier so that it will
+no longer be called when the event fires.
+
+  var listener = DOM.Events.addListener(myElement, "mousedown", myHandler);
+  // later on ...
+  DOM.Events.removeListener(listener);
+
+=head3 removeListener( S<I<HTMLElement> element,> S<I<string> eventType,> S<I<Function> handler )>
+
+This alternative syntax can be also be used to unset an event listener, but it can only
+be used if C<makeCompatible> was C<false> when it was set.
+
+=cut
+
+*/
+
+	DOM.Events.removeListener = function() {
+		var elt, ev, func;
+		if(arguments.length == 1 && listenerList[arguments[0]]) {
+			elt  = listenerList[arguments[0]][0];
+			ev   = listenerList[arguments[0]][1];
+			func = listenerList[arguments[0]][2];
+			delete listenerList[arguments[0]];
+		}
+		else if(arguments.length == 3) {
+			elt  = arguments[0];
+			ev   = arguments[1];
+			func = arguments[2];
+		}
+		else return;
+		
+		if(elt.removeEventListener) {
+			elt.removeEventListener(ev, func, false);
+		}
+		else if(elt.detachEvent) {
+			elt.detachEvent("on" + ev, func);
+		}
+	};
+	
+    var rval;
+    
+    function makeCompatibilityWrapper(elt, ev, func) {
+        return function (e) {
+            rval = true;
+            if(e == undefined && window.event != undefined)
+                e = window.event;
+            if(e.target == undefined && e.srcElement != undefined)
+                e.target = e.srcElement;
+            if(e.currentTarget == undefined)
+                e.currentTarget = elt;
+            if(e.relatedTarget == undefined) {
+                if(ev == "mouseover" && e.fromElement != undefined)
+                    e.relatedTarget = e.fromElement;
+                else if(ev == "mouseout" && e.toElement != undefined)
+                    e.relatedTarget = e.toElement;
+            }
+            if(e.pageX == undefined) {
+                if(document.body.scrollTop != undefined) {
+                    e.pageX = e.clientX + document.body.scrollLeft;
+                    e.pageY = e.clientY + document.body.scrollTop;
+                }
+                if(document.documentElement != undefined
+                && document.documentElement.scrollTop != undefined) {
+                    if(document.documentElement.scrollTop > 0
+                    || document.documentElement.scrollLeft > 0) {
+                        e.pageX = e.clientX + document.documentElement.scrollLeft;
+                        e.pageY = e.clientY + document.documentElement.scrollTop;
+                    }
+                }
+            }
+            if(e.stopPropagation == undefined)
+                e.stopPropagation = IEStopPropagation;
+            if(e.preventDefault == undefined)
+                e.preventDefault = IEPreventDefault;
+            if(e.cancelable == undefined) e.cancelable = true;
+            func(e);
+            return rval;
+        };
+    }
+    
+    function IEStopPropagation() {
+        if(window.event) window.event.cancelBubble = true;
+    }
+    
+    function IEPreventDefault() {
+        rval = false;
+    }
+
+	function cleanUpIE () {
+		for(var i=0; i<listenerList.length; i++) {
+			var listener = listenerList[i];
+			if(listener) {
+				var elt = listener[0];
+                var ev = listener[1];
+                var func = listener[2];
+				elt.detachEvent("on" + ev, func);
+			}
+		}
+        listenerList = null;
+	}
+
+	if(!window.addEventListener && window.attachEvent) {
+		window.attachEvent("onunload", cleanUpIE);
+	}
+
+})();
+
+/**
+
+=head1 SEE ALSO
+
+DOM Level 2 Events Specification,
+L<http://www.w3.org/TR/DOM-Level-2-Events/>
+
+Understanding and Solving Internet Explorer Leak Patterns,
+L<http://msdn.microsoft.com/library/default.asp?url=/library/en-us/IETechCol/dnwebgen/ie_leak_patterns.asp>
+
+=head1 AUTHOR
+
+Justin Constantino, <F<goflyapig at gmail.com>>.
+
+=head1 COPYRIGHT
+
+  Copyright (c) 2005 Justin Constantino.  All rights reserved.
+  This module is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public Licence.
+
+=cut
+
+*/
\ No newline at end of file

Added: jifty/branches/schema-plugins/share/web/static/js/jsan/JSAN.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/jsan/JSAN.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,303 @@
+/*
+ * This is not a stock JSAN.js.  To increase browser compatibility,
+ * s/'/"/g has been applied to the file and the range of valid response
+ * codes for JSAN.Request has been expanded (lifted from Prototype, in
+ * fact).
+ */
+
+/*
+
+*/
+
+var JSAN = function () { JSAN.addRepository(arguments) };
+
+JSAN.VERSION = "0.10-jifty2";
+
+/*
+
+*/
+
+JSAN.globalScope   = self;
+JSAN.includePath   = [".", "lib"];
+JSAN.errorLevel    = "none";
+JSAN.errorMessage  = "";
+JSAN.loaded        = {};
+
+/*
+
+*/
+
+JSAN.use = function () {
+    var classdef = JSAN.require(arguments[0]);
+    if (!classdef) return null;
+
+    var importList = JSAN._parseUseArgs.apply(JSAN, arguments).importList;
+    JSAN.exporter(classdef, importList);
+
+    return classdef;
+}
+
+/*
+
+*/
+
+JSAN.require = function (pkg) {
+    var path = JSAN._convertPackageToPath(pkg);
+    if (JSAN.loaded[path]) {
+        return JSAN.loaded[path];
+    }
+
+    try {
+        var classdef = eval(pkg);
+        if (typeof classdef != "undefined") return classdef;
+    } catch (e) { /* nice try, eh? */ }
+
+
+    for (var i = 0; i < JSAN.includePath.length; i++) {
+        var js;
+        try{
+            var url = JSAN._convertPathToUrl(path, JSAN.includePath[i]);
+                js  = JSAN._loadJSFromUrl(url);
+        } catch (e) {
+            if (i == JSAN.includePath.length - 1) throw e;
+        }
+        if (js != null) {
+            var classdef = JSAN._createScript(js, pkg);
+            JSAN.loaded[path] = classdef;
+            return classdef;
+        }
+    }
+    return false;
+
+}
+
+/*
+
+*/
+
+JSAN.exporter = function () {
+    JSAN._exportItems.apply(JSAN, arguments);
+}
+
+/*
+
+*/
+
+JSAN.addRepository = function () {
+    var temp = JSAN._flatten( arguments );
+    // Need to go in reverse to do something as simple as unshift( @foo, @_ );
+    for ( var i = temp.length - 1; i >= 0; i-- )
+        JSAN.includePath.unshift(temp[i]);
+    return JSAN;
+}
+
+JSAN._flatten = function( list1 ) {
+    var list2 = new Array();
+    for ( var i = 0; i < list1.length; i++ ) {
+        if ( typeof list1[i] == "object" ) {
+            list2 = JSAN._flatten( list1[i], list2 );
+        }
+        else {
+            list2.push( list1[i] );
+        }
+    }
+    return list2;
+};
+
+JSAN._findMyPath = function () {
+    if (document) {
+        var scripts = document.getElementsByTagName("script");
+        for ( var i = 0; i < scripts.length; i++ ) {
+            var src = scripts[i].getAttribute("src");
+            if (src) {
+                var inc = src.match(/^(.*?)\/?JSAN.js/);
+                if (inc && inc[1]) {
+                    var repo = inc[1];
+                    for (var j = 0; j < JSAN.includePath.length; j++) {
+                        if (JSAN.includePath[j] == repo) {
+                            return;
+                        }
+                    }
+                    JSAN.addRepository(repo);
+                }
+            }
+        }
+    }
+}
+JSAN._findMyPath();
+
+JSAN._convertPathToUrl = function (path, repository) {
+    return repository.concat("/" + path);
+};
+    
+
+JSAN._convertPackageToPath = function (pkg) {
+    var path = pkg.replace(/\./g, "/");
+        path = path.concat(".js");
+    return path;
+}
+
+JSAN._parseUseArgs = function () {
+    var pkg        = arguments[0];
+    var importList = [];
+
+    for (var i = 1; i < arguments.length; i++)
+        importList.push(arguments[i]);
+
+    return {
+        pkg:        pkg,
+        importList: importList
+    }
+}
+
+JSAN._loadJSFromUrl = function (url) {
+    return new JSAN.Request().getText(url);
+}
+
+JSAN._findExportInList = function (list, request) {
+    if (list == null) return false;
+    for (var i = 0; i < list.length; i++)
+        if (list[i] == request)
+            return true;
+    return false;
+}
+
+JSAN._findExportInTag = function (tags, request) {
+    if (tags == null) return [];
+    for (var i in tags)
+        if (i == request)
+            return tags[i];
+    return [];
+}
+
+JSAN._exportItems = function (classdef, importList) {
+    var exportList  = new Array();
+    var EXPORT      = classdef.EXPORT;
+    var EXPORT_OK   = classdef.EXPORT_OK;
+    var EXPORT_TAGS = classdef.EXPORT_TAGS;
+    
+    if (importList.length > 0) {
+       importList = JSAN._flatten( importList );
+
+       for (var i = 0; i < importList.length; i++) {
+            var request = importList[i];
+            if (   JSAN._findExportInList(EXPORT,    request)
+                || JSAN._findExportInList(EXPORT_OK, request)) {
+                exportList.push(request);
+                continue;
+            }
+            var list = JSAN._findExportInTag(EXPORT_TAGS, request);
+            for (var i = 0; i < list.length; i++) {
+                exportList.push(list[i]);
+            }
+        }
+    } else {
+        exportList = EXPORT;
+    }
+    JSAN._exportList(classdef, exportList);
+}
+
+JSAN._exportList = function (classdef, exportList) {
+    if (typeof(exportList) != "object") return null;
+    for (var i = 0; i < exportList.length; i++) {
+        var name = exportList[i];
+
+        if (JSAN.globalScope[name] == null)
+            JSAN.globalScope[name] = classdef[name];
+    }
+}
+
+JSAN._makeNamespace = function(js, pkg) {
+    var spaces = pkg.split(".");
+    var parent = JSAN.globalScope;
+    eval(js);
+    var classdef = eval(pkg);
+    for (var i = 0; i < spaces.length; i++) {
+        var name = spaces[i];
+        if (i == spaces.length - 1) {
+            if (typeof parent[name] == "undefined") {
+                parent[name] = classdef;
+                if ( typeof classdef["prototype"] != "undefined" ) {
+                    parent[name].prototype = classdef.prototype;
+                }
+            }
+        } else {
+            if (parent[name] == undefined) {
+                parent[name] = {};
+            }
+        }
+
+        parent = parent[name];
+    }
+    return classdef;
+}
+
+JSAN._handleError = function (msg, level) {
+    if (!level) level = JSAN.errorLevel;
+    JSAN.errorMessage = msg;
+
+    switch (level) {
+        case "none":
+            break;
+        case "warn":
+            alert(msg);
+            break;
+        case "die":
+        default:
+            throw new Error(msg);
+            break;
+    }
+}
+
+JSAN._createScript = function (js, pkg) {
+    try {
+        return JSAN._makeNamespace(js, pkg);
+    } catch (e) {
+        JSAN._handleError("Could not create namespace[" + pkg + "]: " + e);
+    }
+    return null;
+}
+
+
+JSAN.prototype = {
+    use: function () { JSAN.use.apply(JSAN, arguments) }
+};
+
+
+// Low-Level HTTP Request
+JSAN.Request = function (jsan) {
+    if (JSAN.globalScope.XMLHttpRequest) {
+        this._req = new XMLHttpRequest();
+    } else {
+        this._req = new ActiveXObject("Microsoft.XMLHTTP");
+    }
+}
+
+JSAN.Request.prototype = {
+    _req:  null,
+    
+    getText: function (url) {
+        this._req.open("GET", url, false);
+        try {
+            this._req.send(null);
+            if ( this.responseIsSuccess() )
+                return this._req.responseText;
+        } catch (e) {
+            JSAN._handleError("File not found: " + url);
+            return null;
+        };
+
+        JSAN._handleError("File not found: " + url);
+        return null;
+    },
+
+    responseIsSuccess: function() {
+        return this._req.status == undefined
+            || this._req.status == 0
+            || (this._req.status >= 200 && this._req.status < 300);
+    }
+};
+
+/*
+
+*/

Added: jifty/branches/schema-plugins/share/web/static/js/jsan/Push.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/jsan/Push.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,76 @@
+/*
+
+*/
+
+if (typeof(HTTP) == "undefined") { HTTP = {}; }
+
+HTTP.Push = {};
+HTTP.Push.VERSION = '0.04';
+
+/*
+
+*/
+
+HTTP.Push = function(args) {
+  if (args == undefined) { throw "Push must be passed an argument hash!"; }
+  if (args.uri == undefined) { throw "Must specify push URI!"; }
+  if (args.onPush == undefined) { throw "Must specify onPush handler!"; }
+  if (args.interval == undefined) { args.interval = 100; }
+
+
+  var body = document.getElementsByTagName("body")[0];
+  var iframe = document.createElement("iframe");
+  iframe.style.border = "0px";
+  iframe.style.height = "0px";
+  iframe.style.width = "0px";
+  iframe.src = args.uri;
+
+  var interval = undefined;
+
+/*
+
+*/
+
+  this.start = function() {
+    body.appendChild(iframe);
+    interval = setInterval(function() { flushIframe(); }, args.interval);
+  }
+
+
+// TODO: make the stop function work in IE
+//   this.stop = function() {
+//     body.removeChild(iframe);
+//     clearInterval(interval);
+//   }
+
+
+  function flushIframe() {
+    var doc;
+    if (iframe.contentDocument) {          // For NS6
+      doc = iframe.contentDocument;
+    } else if (iframe.contentWindow) {     // For IE5.5 and IE6
+      doc = iframe.contentWindow.document;
+    } else if (iframe.document) {          // For IE5
+      doc = iframe.document;
+    } else {
+      return;
+    }
+  
+    var body = doc.body;
+  
+    while (body && body.hasChildNodes()) {
+      var node = body.firstChild;
+      try {
+         args.onPush(node);
+      }
+      catch (e) { };
+      body.removeChild(node);
+    }
+  }
+
+  return this;
+}
+
+/*
+
+*/

Added: jifty/branches/schema-plugins/share/web/static/js/jsan/Upgrade.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/jsan/Upgrade.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,121 @@
+/*
+Keep package scanners happy
+Upgrade.VERSION = 0.04;
+*/
+
+/*
+
+=head1 NAME
+
+Upgrade - Upgrade older JavaScript implementations to support newer features
+
+=head1 SYNOPSIS
+
+  // Upgrade Array.push if the client does not have it
+  JSAN.use("Upgrade.Array.push");
+
+=head1 DESCRIPTION
+
+Many many different JavaScript toolkits start with something like the following:
+
+  // Provides Array.push for implementations that don't have it
+  if ( ! Array.prototype.push ) {
+      Array.prototype.push = function () {
+          var l = this.length;
+          for ( var i = 0; i < arguments.length; i++ ) {
+              this[l+i] = arguments[i];
+          }
+          return this.length;
+      }
+  }
+
+These provide implementations of expected or required functions/classes
+for older JavaScript implementations that do not provide them natively,
+in effect "upgrading" the client at run-time.
+
+In fact, due to its flexibility JavaScript is a language ideally suited
+to this sort of behaviour.
+
+C<Upgrade> is a JSAN package that provides standard implementations for
+many of these standard functions. If your code relies on a particular
+function that you later find to be not as common as you might have
+initially thought, you can simply add a dependency on that function within
+the C<Upgrade> namespace, and if an implementation exists the standard code
+to implement it will be added it the current environment (when it doesn't
+already have it).
+
+Rather than one huge file that provides a "compatibility layer" and upgrades
+verything all at once, C<Upgrade> is broken down into a large number of
+maller .js files, each implementing one function or class.
+
+Generally these functions are ones defined in the ECMA standard, and those
+that aren't, such as C<HTMLHttpRequest> are not provided by Upgrade (as much
+as we would like to) :)
+
+=head1 USING UPGRADE
+
+The C<Upgrade> namespace acts as a parallel root to the global namespace.
+For any function you want to upgrade, you can then simply prepend
+C<"Upgrade"> to it.
+
+For example, to do the very common upgrade for the C<Array.push> function,
+you simply add C<JSAN.use("Upgrade.Array.push") to your module (or manually
+load the C<Upgrade/Array/push.js> file).
+
+One advantage of using these standard implementations rather than your own
+is that when a number of modules with C<Upgrade> depedencies are merged
+together by C<JSAN::Concat> or another package merger it results in only
+a single copy of the upgrading code at the appropriate place in the code.
+
+=head1 UPGRADABLE FUNCTIONS
+
+While implementations are provided seperately, rather than document them
+this way we will instead defined all functions available in the C<Upgrade>
+package here.
+
+=head2 Upgrade.Array.push
+
+This provides the same standard implementation of the instance method
+C<Array.push> (located at Array.prototype.push) as used by all of the
+major frameworks.
+
+=head2 Upgrade.Function.apply
+
+This provides a version of the instance method C<Function.apply>
+(located at C<Function.prototype.apply>) adapted from an implementation
+found in the L<Prototype> framework, which was itself adapted from an
+implementation found on L<http://www.youngpup.net/>.
+
+=head1 METHODS
+
+The C<Upgrade> module itself does not at this time provide any
+functionality, and only acts as a source of documentation.
+
+Likewise, nothing is ever actually created at or beneath the C<Upgrade>
+namespace, but serves as an address mechanism for determining which
+.js files to load. Each of these files only inserts functions into the
+core tree and do not create any additional useless namespace variables.
+
+=head1 SUPPORT
+
+Until the JSAN RT gains package-specific queues, bugs or new functions
+to add to Upgrade should be reported to the jsan-authors mailing list.
+
+For B<non-support> issues or questions, contact the author.
+
+=head1 AUTHOR
+
+Adam Kennedy <jsan at ali.as>, L<http://ali.as/>
+
+=head1 COPYRIGHT
+
+Copyright (c) 2005 Adam Kennedy. All rights reserved.
+This program is free software; you can redistribute it and/or modify
+it under the the terms of the Perl dual GPL/Artistic license.
+
+The full text of the license can be found in the
+LICENSE file included with this package
+
+=cut
+
+*/

Added: jifty/branches/schema-plugins/share/web/static/js/jsan/Upgrade/Array/push.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/jsan/Upgrade/Array/push.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,14 @@
+/*
+Upgrade.Array.push.VERSION = 0.04;
+*/
+
+// Provides Array.push for implementations that don't have it
+if ( ! Array.prototype.push ) {
+	Array.prototype.push = function () {
+		var l = this.length;
+		for ( var i = 0; i < arguments.length; i++ ) {
+			this[l+i] = arguments[i];
+		}
+		return this.length;
+	}
+}

Added: jifty/branches/schema-plugins/share/web/static/js/jsan/Upgrade/Function/apply.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/jsan/Upgrade/Function/apply.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,19 @@
+/*
+Upgrade.Function.Apply.VERSION = 0.04;
+*/
+// Adapted from a Prototype adaptation of code
+// originally from http://www.youngpup.net/
+if ( ! Function.prototype.apply ) {
+	Function.prototype.apply = function(o, p) {
+		var pstr = new Array();
+		if ( ! o ) o = window;
+		if ( ! p ) p = new Array();
+		for ( var i = 0; i < p.length; i++ ) {
+			pstr[i] = 'p[' + i + ']';
+		}
+    		o.__apply__ = this;
+		var rv = eval('o.__apply__(' + pstr[i].join(', ') + ')' );
+		o.__apply__ = null;
+		return rv;
+	}
+}

Added: jifty/branches/schema-plugins/share/web/static/js/json.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/json.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,235 @@
+/*
+Copyright (c) 2005 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+/*
+    The global object JSON contains two methods.
+
+    JSON.stringify(value) takes a JavaScript value and produces a JSON text.
+    The value must not be cyclical.
+
+    JSON.parse(text) takes a JSON text and produces a JavaScript value. It will
+    throw a 'JSONError' exception if there is an error.
+*/
+var JSON = {
+    copyright: '(c)2005 JSON.org',
+    license: 'http://www.crockford.com/JSON/license.html',
+/*
+    Stringify a JavaScript value, producing a JSON text.
+*/
+    stringify: function (v) {
+        var a = [];
+
+/*
+    Emit a string.
+*/
+        function e(s) {
+            a[a.length] = s;
+        }
+
+/*
+    Convert a value.
+*/
+        function g(x) {
+            var c, i, l, v;
+
+            switch (typeof x) {
+            case 'object':
+                if (x) {
+                    if (x instanceof Array) {
+                        e('[');
+                        l = a.length;
+                        for (i = 0; i < x.length; i += 1) {
+                            v = x[i];
+                            if (typeof v != 'undefined' &&
+                                    typeof v != 'function') {
+                                if (l < a.length) {
+                                    e(',');
+                                }
+                                g(v);
+                            }
+                        }
+                        e(']');
+                        return;
+                    } else if (typeof x.toString != 'undefined') {
+                        e('{');
+                        l = a.length;
+                        for (i in x) {
+                            v = x[i];
+                            if (x.hasOwnProperty(i) &&
+                                    typeof v != 'undefined' &&
+                                    typeof v != 'function') {
+                                if (l < a.length) {
+                                    e(',');
+                                }
+                                g(i);
+                                e(':');
+                                g(v);
+                            }
+                        }
+                        return e('}');
+                    }
+                }
+                e('null');
+                return;
+            case 'number':
+                e(isFinite(x) ? +x : 'null');
+                return;
+            case 'string':
+                l = x.length;
+                e('"');
+                for (i = 0; i < l; i += 1) {
+                    c = x.charAt(i);
+                    if (c >= ' ') {
+                        if (c == '\\' || c == '"') {
+                            e('\\');
+                        }
+                        e(c);
+                    } else {
+                        switch (c) {
+                            case '\b':
+                                e('\\b');
+                                break;
+                            case '\f':
+                                e('\\f');
+                                break;
+                            case '\n':
+                                e('\\n');
+                                break;
+                            case '\r':
+                                e('\\r');
+                                break;
+                            case '\t':
+                                e('\\t');
+                                break;
+                            default:
+                                c = c.charCodeAt();
+                                e('\\u00' + Math.floor(c / 16).toString(16) +
+                                    (c % 16).toString(16));
+                        }
+                    }
+                }
+                e('"');
+                return;
+            case 'boolean':
+                e(String(x));
+                return;
+            default:
+                e('null');
+                return;
+            }
+        }
+        g(v);
+        return a.join('');
+    },
+/*
+    Parse a JSON text, producing a JavaScript value.
+*/
+    parse: function (text) {
+        var p = /^\s*(([,:{}\[\]])|"(\\.|[^\x00-\x1f"\\])*"|-?\d+(\.\d*)?([eE][+-]?\d+)?|true|false|null)\s*/,
+            token,
+            operator;
+
+        function error(m, t) {
+            throw {
+                name: 'JSONError',
+                message: m,
+                text: t || operator || token
+            };
+        }
+
+        function next(b) {
+            if (b && b != operator) {
+                error("Expected '" + b + "'");
+            }
+            if (text) {
+                var t = p.exec(text);
+                if (t) {
+                    if (t[2]) {
+                        token = null;
+                        operator = t[2];
+                    } else {
+                        operator = null;
+                        try {
+                            token = eval(t[1]);
+                        } catch (e) {
+                            error("Bad token", t[1]);
+                        }
+                    }
+                    text = text.substring(t[0].length);
+                } else {
+                    error("Unrecognized token", text);
+                }
+            } else {
+                token = operator = undefined;
+            }
+        }
+
+
+        function val() {
+            var k, o;
+            switch (operator) {
+            case '{':
+                next('{');
+                o = {};
+                if (operator != '}') {
+                    for (;;) {
+                        if (operator || typeof token != 'string') {
+                            error("Missing key");
+                        }
+                        k = token;
+                        next();
+                        next(':');
+                        o[k] = val();
+                        if (operator != ',') {
+                            break;
+                        }
+                        next(',');
+                    }
+                }
+                next('}');
+                return o;
+            case '[':
+                next('[');
+                o = [];
+                if (operator != ']') {
+                    for (;;) {
+                        o.push(val());
+                        if (operator != ',') {
+                            break;
+                        }
+                        next(',');
+                    }
+                }
+                next(']');
+                return o;
+            default:
+                if (operator !== null) {
+                    error("Missing value");
+                }
+                k = token;
+                next();
+                return k;
+            }
+        }
+        next();
+        return val();
+    }
+};

Added: jifty/branches/schema-plugins/share/web/static/js/key_bindings.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/key_bindings.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,116 @@
+// Copyright 2004-2006, Best Practical Solutions, LLC
+// This Library is licensed to you under the same terms as Perl 5.x
+
+JSAN.use("DOM.Events");
+
+if ( typeof Jifty == "undefined" ) Jifty = {};
+
+Jifty.KeyBindings = {
+    bindings: new Array(),
+    listener: null,
+
+    activate: function() {
+        if ( Jifty.KeyBindings.listener )
+            return;
+        
+        Jifty.KeyBindings.listener = DOM.Events.addListener(
+                                        document,
+                                        "keydown",
+                                        Jifty.KeyBindings.doClick
+                                     );
+    },
+
+    deactivate: function() {
+        DOM.Events.removeListener(Jifty.KeyBindings.listener);
+    },
+
+    doClick: function(e) {
+        if (e.target.nodeType == 3) // defeat Safari bug
+            e.target = e.target.parentNode;
+       
+        /* XXX TODO: Is there a better way to do this and still support
+                     opera?
+         */            
+        if (    !e.metaKey && !e.altKey && !e.ctrlKey
+             && !e.target.nodeName.match(/^(INPUT|TEXTAREA)$/) )
+        {
+            var code    = String.fromCharCode(e.keyCode);
+            var binding = Jifty.KeyBindings.get(code);
+            
+            if (binding) {
+                e.preventDefault();
+                
+                if (binding["action"] == "goto") {
+                    document.location = (binding["data"]);
+                }
+                else if (binding["action"] == "focus") {
+                    var elements = document.getElementsByName(binding["data"]);
+                    elements[0].focus();
+                }
+                else if (binding["action"] == "click") {
+                    var elements = document.getElementsByName(binding["data"]);
+                    elements[0].click();
+                }
+            }
+        }
+    },
+
+    add: function(key, action, data, label) {
+        var binding = new Array();
+        binding["action"]  = action;
+        binding["data"]    = data;
+        binding["label"]   = label;
+        Jifty.KeyBindings.bindings[key] = binding;
+    },
+
+    get: function(key) {
+        return Jifty.KeyBindings.bindings[key];
+    },
+
+    writeLegend: function(e) {
+        if (    !document.createElement
+             || !document.createTextNode
+             || Element.hasClassName(e, 'keybindings-written') )
+            return;
+        
+        
+        /* definition list */
+        var dl = document.createElement("dl");
+        dl.className = "keybindings";
+
+    
+        /* terms of the list */
+        
+        for (var key in Jifty.KeyBindings.bindings) {
+            if ( Jifty.KeyBindings.get(key)["label"] ) {
+                var div = document.createElement("div");
+                div.className = "keybinding";
+                
+                var dt = document.createElement("dt");
+                dt.appendChild( document.createTextNode( key ) );
+
+                var dd = document.createElement("dd");
+                dd.appendChild( document.createTextNode( Jifty.KeyBindings.get(key)["label"] ) );
+                
+                div.appendChild( dt );
+                div.appendChild( dd );
+                dl.appendChild( div );
+            }
+        }
+        
+        if ( dl.hasChildNodes() ) {
+            var label = document.createElement("div");
+            label.className = "keybindings_label";
+            label.appendChild( document.createTextNode("Hotkeys:") );
+            
+            e.appendChild( label );
+            e.appendChild( dl );
+            Element.addClassName(e, 'keybindings-written');
+        
+            /* since we wrote the legend, now obey it */
+            Jifty.KeyBindings.activate();
+        }
+    }
+}
+
+Behaviour.register({ "#keybindings": Jifty.KeyBindings.writeLegend });

Added: jifty/branches/schema-plugins/share/web/static/js/prototype.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/prototype.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,1790 @@
+/*  Prototype JavaScript framework, version 1.4.0
+ *  (c) 2005 Sam Stephenson <sam at conio.net>
+ *
+ *  THIS FILE IS AUTOMATICALLY GENERATED. When sending patches, please diff
+ *  against the source tree, available from the Prototype darcs repository.
+ *
+ *  Prototype is freely distributable under the terms of an MIT-style license.
+ *
+ *  For details, see the Prototype web site: http://prototype.conio.net/
+ *
+/*--------------------------------------------------------------------------*/
+
+var Prototype = {
+  Version: '1.4.0',
+  ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)',
+
+  emptyFunction: function() {},
+  K: function(x) {return x}
+}
+
+var Class = {
+  create: function() {
+    return function() {
+      this.initialize.apply(this, arguments);
+    }
+  }
+}
+
+var Abstract = new Object();
+
+Object.extend = function(destination, source) {
+  for (property in source) {
+    destination[property] = source[property];
+  }
+  return destination;
+}
+
+Object.inspect = function(object) {
+  try {
+    if (object == undefined) return 'undefined';
+    if (object == null) return 'null';
+    return object.inspect ? object.inspect() : object.toString();
+  } catch (e) {
+    if (e instanceof RangeError) return '...';
+    throw e;
+  }
+}
+
+Function.prototype.bind = function() {
+  var __method = this, args = $A(arguments), object = args.shift();
+  return function() {
+    return __method.apply(object, args.concat($A(arguments)));
+  }
+}
+
+Function.prototype.bindAsEventListener = function(object) {
+  var __method = this;
+  return function(event) {
+    return __method.call(object, event || window.event);
+  }
+}
+
+Object.extend(Number.prototype, {
+  toColorPart: function() {
+    var digits = this.toString(16);
+    if (this < 16) return '0' + digits;
+    return digits;
+  },
+
+  succ: function() {
+    return this + 1;
+  },
+
+  times: function(iterator) {
+    $R(0, this, true).each(iterator);
+    return this;
+  }
+});
+
+var Try = {
+  these: function() {
+    var returnValue;
+
+    for (var i = 0; i < arguments.length; i++) {
+      var lambda = arguments[i];
+      try {
+        returnValue = lambda();
+        break;
+      } catch (e) {}
+    }
+
+    return returnValue;
+  }
+}
+
+/*--------------------------------------------------------------------------*/
+
+var PeriodicalExecuter = Class.create();
+PeriodicalExecuter.prototype = {
+  initialize: function(callback, frequency) {
+    this.callback = callback;
+    this.frequency = frequency;
+    this.currentlyExecuting = false;
+
+    this.registerCallback();
+  },
+
+  registerCallback: function() {
+    setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
+  },
+
+  onTimerEvent: function() {
+    if (!this.currentlyExecuting) {
+      try {
+        this.currentlyExecuting = true;
+        this.callback();
+      } finally {
+        this.currentlyExecuting = false;
+      }
+    }
+  }
+}
+
+/*--------------------------------------------------------------------------*/
+
+function $() {
+  var elements = new Array();
+
+  for (var i = 0; i < arguments.length; i++) {
+    var element = arguments[i];
+    if (typeof element == 'string')
+      element = document.getElementById(element);
+
+    if (arguments.length == 1)
+      return element;
+
+    elements.push(element);
+  }
+
+  return elements;
+}
+Object.extend(String.prototype, {
+  stripTags: function() {
+    return this.replace(/<\/?[^>]+>/gi, '');
+  },
+
+  stripScripts: function() {
+    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
+  },
+
+  extractScripts: function() {
+    var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
+    var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
+    return (this.match(matchAll) || []).map(function(scriptTag) {
+      return (scriptTag.match(matchOne) || ['', ''])[1];
+    });
+  },
+
+  evalScripts: function() {
+    return this.extractScripts().map(eval);
+  },
+
+  escapeHTML: function() {
+    var div = document.createElement('div');
+    var text = document.createTextNode(this);
+    div.appendChild(text);
+    return div.innerHTML;
+  },
+
+  unescapeHTML: function() {
+    var div = document.createElement('div');
+    div.innerHTML = this.stripTags();
+    return div.childNodes[0] ? div.childNodes[0].nodeValue : '';
+  },
+
+  toQueryParams: function() {
+    var pairs = this.match(/^\??(.*)$/)[1].split('&');
+    return pairs.inject({}, function(params, pairString) {
+      var pair = pairString.split('=');
+      params[pair[0]] = pair[1];
+      return params;
+    });
+  },
+
+  toArray: function() {
+    return this.split('');
+  },
+
+  camelize: function() {
+    var oStringList = this.split('-');
+    if (oStringList.length == 1) return oStringList[0];
+
+    var camelizedString = this.indexOf('-') == 0
+      ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1)
+      : oStringList[0];
+
+    for (var i = 1, len = oStringList.length; i < len; i++) {
+      var s = oStringList[i];
+      camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
+    }
+
+    return camelizedString;
+  },
+
+  inspect: function() {
+    return "'" + this.replace('\\', '\\\\').replace("'", '\\\'') + "'";
+  }
+});
+
+String.prototype.parseQuery = String.prototype.toQueryParams;
+
+var $break    = new Object();
+var $continue = new Object();
+
+var Enumerable = {
+  each: function(iterator) {
+    var index = 0;
+    try {
+      this._each(function(value) {
+        try {
+          iterator(value, index++);
+        } catch (e) {
+          if (e != $continue) throw e;
+        }
+      });
+    } catch (e) {
+      if (e != $break) throw e;
+    }
+  },
+
+  all: function(iterator) {
+    var result = true;
+    this.each(function(value, index) {
+      result = result && !!(iterator || Prototype.K)(value, index);
+      if (!result) throw $break;
+    });
+    return result;
+  },
+
+  any: function(iterator) {
+    var result = true;
+    this.each(function(value, index) {
+      if (result = !!(iterator || Prototype.K)(value, index))
+        throw $break;
+    });
+    return result;
+  },
+
+  collect: function(iterator) {
+    var results = [];
+    this.each(function(value, index) {
+      results.push(iterator(value, index));
+    });
+    return results;
+  },
+
+  detect: function (iterator) {
+    var result;
+    this.each(function(value, index) {
+      if (iterator(value, index)) {
+        result = value;
+        throw $break;
+      }
+    });
+    return result;
+  },
+
+  findAll: function(iterator) {
+    var results = [];
+    this.each(function(value, index) {
+      if (iterator(value, index))
+        results.push(value);
+    });
+    return results;
+  },
+
+  grep: function(pattern, iterator) {
+    var results = [];
+    this.each(function(value, index) {
+      var stringValue = value.toString();
+      if (stringValue.match(pattern))
+        results.push((iterator || Prototype.K)(value, index));
+    })
+    return results;
+  },
+
+  include: function(object) {
+    var found = false;
+    this.each(function(value) {
+      if (value == object) {
+        found = true;
+        throw $break;
+      }
+    });
+    return found;
+  },
+
+  inject: function(memo, iterator) {
+    this.each(function(value, index) {
+      memo = iterator(memo, value, index);
+    });
+    return memo;
+  },
+
+  invoke: function(method) {
+    var args = $A(arguments).slice(1);
+    return this.collect(function(value) {
+      return value[method].apply(value, args);
+    });
+  },
+
+  max: function(iterator) {
+    var result;
+    this.each(function(value, index) {
+      value = (iterator || Prototype.K)(value, index);
+      if (value >= (result || value))
+        result = value;
+    });
+    return result;
+  },
+
+  min: function(iterator) {
+    var result;
+    this.each(function(value, index) {
+      value = (iterator || Prototype.K)(value, index);
+      if (value <= (result || value))
+        result = value;
+    });
+    return result;
+  },
+
+  partition: function(iterator) {
+    var trues = [], falses = [];
+    this.each(function(value, index) {
+      ((iterator || Prototype.K)(value, index) ?
+        trues : falses).push(value);
+    });
+    return [trues, falses];
+  },
+
+  pluck: function(property) {
+    var results = [];
+    this.each(function(value, index) {
+      results.push(value[property]);
+    });
+    return results;
+  },
+
+  reject: function(iterator) {
+    var results = [];
+    this.each(function(value, index) {
+      if (!iterator(value, index))
+        results.push(value);
+    });
+    return results;
+  },
+
+  sortBy: function(iterator) {
+    return this.collect(function(value, index) {
+      return {value: value, criteria: iterator(value, index)};
+    }).sort(function(left, right) {
+      var a = left.criteria, b = right.criteria;
+      return a < b ? -1 : a > b ? 1 : 0;
+    }).pluck('value');
+  },
+
+  toArray: function() {
+    return this.collect(Prototype.K);
+  },
+
+  zip: function() {
+    var iterator = Prototype.K, args = $A(arguments);
+    if (typeof args.last() == 'function')
+      iterator = args.pop();
+
+    var collections = [this].concat(args).map($A);
+    return this.map(function(value, index) {
+      iterator(value = collections.pluck(index));
+      return value;
+    });
+  },
+
+  inspect: function() {
+    return '#<Enumerable:' + this.toArray().inspect() + '>';
+  }
+}
+
+Object.extend(Enumerable, {
+  map:     Enumerable.collect,
+  find:    Enumerable.detect,
+  select:  Enumerable.findAll,
+  member:  Enumerable.include,
+  entries: Enumerable.toArray
+});
+var $A = Array.from = function(iterable) {
+  if (!iterable) return [];
+  if (iterable.toArray) {
+    return iterable.toArray();
+  } else {
+    var results = [];
+    for (var i = 0; i < iterable.length; i++)
+      results.push(iterable[i]);
+    return results;
+  }
+}
+
+Object.extend(Array.prototype, Enumerable);
+
+Array.prototype._reverse = Array.prototype.reverse;
+
+Object.extend(Array.prototype, {
+  _each: function(iterator) {
+    for (var i = 0; i < this.length; i++)
+      iterator(this[i]);
+  },
+
+  clear: function() {
+    this.length = 0;
+    return this;
+  },
+
+  first: function() {
+    return this[0];
+  },
+
+  last: function() {
+    return this[this.length - 1];
+  },
+
+  compact: function() {
+    return this.select(function(value) {
+      return value != undefined || value != null;
+    });
+  },
+
+  flatten: function() {
+    return this.inject([], function(array, value) {
+      return array.concat(value.constructor == Array ?
+        value.flatten() : [value]);
+    });
+  },
+
+  without: function() {
+    var values = $A(arguments);
+    return this.select(function(value) {
+      return !values.include(value);
+    });
+  },
+
+  indexOf: function(object) {
+    for (var i = 0; i < this.length; i++)
+      if (this[i] == object) return i;
+    return -1;
+  },
+
+  reverse: function(inline) {
+    return (inline !== false ? this : this.toArray())._reverse();
+  },
+
+  shift: function() {
+    var result = this[0];
+    for (var i = 0; i < this.length - 1; i++)
+      this[i] = this[i + 1];
+    this.length--;
+    return result;
+  },
+
+  inspect: function() {
+    return '[' + this.map(Object.inspect).join(', ') + ']';
+  }
+});
+var Hash = {
+  _each: function(iterator) {
+    for (key in this) {
+      var value = this[key];
+      if (typeof value == 'function') continue;
+
+      var pair = [key, value];
+      pair.key = key;
+      pair.value = value;
+      iterator(pair);
+    }
+  },
+
+  keys: function() {
+    return this.pluck('key');
+  },
+
+  values: function() {
+    return this.pluck('value');
+  },
+
+  merge: function(hash) {
+    return $H(hash).inject($H(this), function(mergedHash, pair) {
+      mergedHash[pair.key] = pair.value;
+      return mergedHash;
+    });
+  },
+
+  toQueryString: function() {
+    return this.map(function(pair) {
+      return pair.map(encodeURIComponent).join('=');
+    }).join('&');
+  },
+
+  inspect: function() {
+    return '#<Hash:{' + this.map(function(pair) {
+      return pair.map(Object.inspect).join(': ');
+    }).join(', ') + '}>';
+  }
+}
+
+function $H(object) {
+  var hash = Object.extend({}, object || {});
+  Object.extend(hash, Enumerable);
+  Object.extend(hash, Hash);
+  return hash;
+}
+ObjectRange = Class.create();
+Object.extend(ObjectRange.prototype, Enumerable);
+Object.extend(ObjectRange.prototype, {
+  initialize: function(start, end, exclusive) {
+    this.start = start;
+    this.end = end;
+    this.exclusive = exclusive;
+  },
+
+  _each: function(iterator) {
+    var value = this.start;
+    do {
+      iterator(value);
+      value = value.succ();
+    } while (this.include(value));
+  },
+
+  include: function(value) {
+    if (value < this.start)
+      return false;
+    if (this.exclusive)
+      return value < this.end;
+    return value <= this.end;
+  }
+});
+
+var $R = function(start, end, exclusive) {
+  return new ObjectRange(start, end, exclusive);
+}
+
+var Ajax = {
+  getTransport: function() {
+    return Try.these(
+      function() {return new ActiveXObject('Msxml2.XMLHTTP')},
+      function() {return new ActiveXObject('Microsoft.XMLHTTP')},
+      function() {return new XMLHttpRequest()}
+    ) || false;
+  },
+
+  activeRequestCount: 0
+}
+
+Ajax.Responders = {
+  responders: [],
+
+  _each: function(iterator) {
+    this.responders._each(iterator);
+  },
+
+  register: function(responderToAdd) {
+    if (!this.include(responderToAdd))
+      this.responders.push(responderToAdd);
+  },
+
+  unregister: function(responderToRemove) {
+    this.responders = this.responders.without(responderToRemove);
+  },
+
+  dispatch: function(callback, request, transport, json) {
+    this.each(function(responder) {
+      if (responder[callback] && typeof responder[callback] == 'function') {
+        try {
+          responder[callback].apply(responder, [request, transport, json]);
+        } catch (e) {}
+      }
+    });
+  }
+};
+
+Object.extend(Ajax.Responders, Enumerable);
+
+Ajax.Responders.register({
+  onCreate: function() {
+    Ajax.activeRequestCount++;
+  },
+
+  onComplete: function() {
+    Ajax.activeRequestCount--;
+  }
+});
+
+Ajax.Base = function() {};
+Ajax.Base.prototype = {
+  setOptions: function(options) {
+    this.options = {
+      method:       'post',
+      asynchronous: true,
+      parameters:   ''
+    }
+    Object.extend(this.options, options || {});
+  },
+
+  responseIsSuccess: function() {
+    return this.transport.status == undefined
+        || this.transport.status == 0
+        || (this.transport.status >= 200 && this.transport.status < 300);
+  },
+
+  responseIsFailure: function() {
+    return !this.responseIsSuccess();
+  }
+}
+
+Ajax.Request = Class.create();
+Ajax.Request.Events =
+  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
+
+Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
+  initialize: function(url, options) {
+    this.transport = Ajax.getTransport();
+    this.setOptions(options);
+    this.request(url);
+  },
+
+  request: function(url) {
+    var parameters = this.options.parameters || '';
+    if (parameters.length > 0) parameters += '&_=';
+
+    try {
+      this.url = url;
+      if (this.options.method == 'get' && parameters.length > 0)
+        this.url += (this.url.match(/\?/) ? '&' : '?') + parameters;
+
+      Ajax.Responders.dispatch('onCreate', this, this.transport);
+
+      this.transport.open(this.options.method, this.url,
+        this.options.asynchronous);
+
+      if (this.options.asynchronous) {
+        this.transport.onreadystatechange = this.onStateChange.bind(this);
+        setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10);
+      }
+
+      this.setRequestHeaders();
+
+      var body = this.options.postBody ? this.options.postBody : parameters;
+      this.transport.send(this.options.method == 'post' ? body : null);
+
+    } catch (e) {
+      this.dispatchException(e);
+    }
+  },
+
+  setRequestHeaders: function() {
+    var requestHeaders =
+      ['X-Requested-With', 'XMLHttpRequest',
+       'X-Prototype-Version', Prototype.Version];
+
+    if (this.options.method == 'post') {
+      var hasContentType = 0;
+      if (this.options.requestHeaders)
+          for (var i = 0; i < this.options.requestHeaders.length; i += 2) 
+              if (this.options.requestHeaders[i] == 'Content-Type')
+                  hasContentType = 1;
+      if (hasContentType == 0)
+          requestHeaders.push('Content-Type', 'application/x-www-form-urlencoded');
+
+      /* Force "Connection: close" for Mozilla browsers to work around
+       * a bug where XMLHttpReqeuest sends an incorrect Content-length
+       * header. See Mozilla Bugzilla #246651.
+       */
+      if (this.transport.overrideMimeType)
+        requestHeaders.push('Connection', 'close');
+    }
+
+    if (this.options.requestHeaders)
+      requestHeaders.push.apply(requestHeaders, this.options.requestHeaders);
+
+    for (var i = 0; i < requestHeaders.length; i += 2)
+      this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]);
+  },
+
+  onStateChange: function() {
+    var readyState = this.transport.readyState;
+    if (readyState != 1)
+      this.respondToReadyState(this.transport.readyState);
+  },
+
+  header: function(name) {
+    try {
+      return this.transport.getResponseHeader(name);
+    } catch (e) {}
+  },
+
+  evalJSON: function() {
+    try {
+      return eval(this.header('X-JSON'));
+    } catch (e) {}
+  },
+
+  evalResponse: function() {
+    try {
+      return eval(this.transport.responseText);
+    } catch (e) {
+      this.dispatchException(e);
+    }
+  },
+
+  respondToReadyState: function(readyState) {
+    var event = Ajax.Request.Events[readyState];
+    var transport = this.transport, json = this.evalJSON();
+
+    if (event == 'Complete') {
+      try {
+        (this.options['on' + this.transport.status]
+         || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
+         || Prototype.emptyFunction)(transport, json);
+      } catch (e) {
+        this.dispatchException(e);
+      }
+
+      if ((this.header('Content-type') || '').match(/^text\/javascript/i))
+        this.evalResponse();
+    }
+
+    try {
+      (this.options['on' + event] || Prototype.emptyFunction)(transport, json);
+      Ajax.Responders.dispatch('on' + event, this, transport, json);
+    } catch (e) {
+      this.dispatchException(e);
+    }
+
+    /* Avoid memory leak in MSIE: clean up the oncomplete event handler */
+    if (event == 'Complete')
+      this.transport.onreadystatechange = Prototype.emptyFunction;
+  },
+
+  dispatchException: function(exception) {
+    (this.options.onException || Prototype.emptyFunction)(this, exception);
+    Ajax.Responders.dispatch('onException', this, exception);
+  }
+});
+
+Ajax.Updater = Class.create();
+
+Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
+  initialize: function(container, url, options) {
+    this.containers = {
+      success: container.success ? $(container.success) : $(container),
+      failure: container.failure ? $(container.failure) :
+        (container.success ? null : $(container))
+    }
+
+    this.transport = Ajax.getTransport();
+    this.setOptions(options);
+
+    var onComplete = this.options.onComplete || Prototype.emptyFunction;
+    this.options.onComplete = (function(transport, object) {
+      this.updateContent();
+      onComplete(transport, object);
+    }).bind(this);
+
+    this.request(url);
+  },
+
+  updateContent: function() {
+    var receiver = this.responseIsSuccess() ?
+      this.containers.success : this.containers.failure;
+    var response = this.transport.responseText;
+
+    if (!this.options.evalScripts)
+      response = response.stripScripts();
+
+    if (receiver) {
+      if (this.options.insertion) {
+        new this.options.insertion(receiver, response);
+      } else {
+        Element.update(receiver, response);
+      }
+    }
+
+    if (this.responseIsSuccess()) {
+      if (this.onComplete)
+        setTimeout(this.onComplete.bind(this), 10);
+    }
+  }
+});
+
+Ajax.PeriodicalUpdater = Class.create();
+Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
+  initialize: function(container, url, options) {
+    this.setOptions(options);
+    this.onComplete = this.options.onComplete;
+
+    this.frequency = (this.options.frequency || 2);
+    this.decay = (this.options.decay || 1);
+
+    this.updater = {};
+    this.container = container;
+    this.url = url;
+
+    this.start();
+  },
+
+  start: function() {
+    this.options.onComplete = this.updateComplete.bind(this);
+    this.onTimerEvent();
+  },
+
+  stop: function() {
+    this.updater.onComplete = undefined;
+    clearTimeout(this.timer);
+    (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
+  },
+
+  updateComplete: function(request) {
+    if (this.options.decay) {
+      this.decay = (request.responseText == this.lastText ?
+        this.decay * this.options.decay : 1);
+
+      this.lastText = request.responseText;
+    }
+    this.timer = setTimeout(this.onTimerEvent.bind(this),
+      this.decay * this.frequency * 1000);
+  },
+
+  onTimerEvent: function() {
+    this.updater = new Ajax.Updater(this.container, this.url, this.options);
+  }
+});
+document.getElementsByClassName = function(className, parentElement) {
+  var children = ($(parentElement) || document.body).getElementsByTagName('*');
+  return $A(children).inject([], function(elements, child) {
+    if (child.className.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
+      elements.push(child);
+    return elements;
+  });
+}
+
+/*--------------------------------------------------------------------------*/
+
+if (!window.Element) {
+  var Element = new Object();
+}
+
+Object.extend(Element, {
+  visible: function(element) {
+    return $(element).style.display != 'none';
+  },
+
+  toggle: function() {
+    for (var i = 0; i < arguments.length; i++) {
+      var element = $(arguments[i]);
+      Element[Element.visible(element) ? 'hide' : 'show'](element);
+    }
+  },
+
+  hide: function() {
+    for (var i = 0; i < arguments.length; i++) {
+      var element = $(arguments[i]);
+      element.style.display = 'none';
+    }
+  },
+
+  show: function() {
+    for (var i = 0; i < arguments.length; i++) {
+      var element = $(arguments[i]);
+      element.style.display = '';
+    }
+  },
+
+  remove: function(element) {
+    element = $(element);
+    element.parentNode.removeChild(element);
+  },
+
+  update: function(element, html) {
+    $(element).innerHTML = html.stripScripts();
+    setTimeout(function() {html.evalScripts()}, 10);
+  },
+
+  getHeight: function(element) {
+    element = $(element);
+    return element.offsetHeight;
+  },
+
+  classNames: function(element) {
+    return new Element.ClassNames(element);
+  },
+
+  hasClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    return Element.classNames(element).include(className);
+  },
+
+  addClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    return Element.classNames(element).add(className);
+  },
+
+  removeClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    return Element.classNames(element).remove(className);
+  },
+
+  // removes whitespace-only text node children
+  cleanWhitespace: function(element) {
+    element = $(element);
+    for (var i = 0; i < element.childNodes.length; i++) {
+      var node = element.childNodes[i];
+      if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
+        Element.remove(node);
+    }
+  },
+
+  empty: function(element) {
+    return $(element).innerHTML.match(/^\s*$/);
+  },
+
+  scrollTo: function(element) {
+    element = $(element);
+    var x = element.x ? element.x : element.offsetLeft,
+        y = element.y ? element.y : element.offsetTop;
+    window.scrollTo(x, y);
+  },
+
+  getStyle: function(element, style) {
+    element = $(element);
+    var value = element.style[style.camelize()];
+    if (!value) {
+      if (document.defaultView && document.defaultView.getComputedStyle) {
+        var css = document.defaultView.getComputedStyle(element, null);
+        value = css ? css.getPropertyValue(style) : null;
+      } else if (element.currentStyle) {
+        value = element.currentStyle[style.camelize()];
+      }
+    }
+
+    if (window.opera && ['left', 'top', 'right', 'bottom'].include(style))
+      if (Element.getStyle(element, 'position') == 'static') value = 'auto';
+
+    return value == 'auto' ? null : value;
+  },
+
+  setStyle: function(element, style) {
+    element = $(element);
+    for (name in style)
+      element.style[name.camelize()] = style[name];
+  },
+
+  getDimensions: function(element) {
+    element = $(element);
+    if (Element.getStyle(element, 'display') != 'none')
+      return {width: element.offsetWidth, height: element.offsetHeight};
+
+    // All *Width and *Height properties give 0 on elements with display none,
+    // so enable the element temporarily
+    var els = element.style;
+    var originalVisibility = els.visibility;
+    var originalPosition = els.position;
+    els.visibility = 'hidden';
+    els.position = 'absolute';
+    els.display = '';
+    var originalWidth = element.clientWidth;
+    var originalHeight = element.clientHeight;
+    els.display = 'none';
+    els.position = originalPosition;
+    els.visibility = originalVisibility;
+    return {width: originalWidth, height: originalHeight};
+  },
+
+  makePositioned: function(element) {
+    element = $(element);
+    var pos = Element.getStyle(element, 'position');
+    if (pos == 'static' || !pos) {
+      element._madePositioned = true;
+      element.style.position = 'relative';
+      // Opera returns the offset relative to the positioning context, when an
+      // element is position relative but top and left have not been defined
+      if (window.opera) {
+        element.style.top = 0;
+        element.style.left = 0;
+      }
+    }
+  },
+
+  undoPositioned: function(element) {
+    element = $(element);
+    if (element._madePositioned) {
+      element._madePositioned = undefined;
+      element.style.position =
+        element.style.top =
+        element.style.left =
+        element.style.bottom =
+        element.style.right = '';
+    }
+  },
+
+  makeClipping: function(element) {
+    element = $(element);
+    if (element._overflow) return;
+    element._overflow = element.style.overflow;
+    if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden')
+      element.style.overflow = 'hidden';
+  },
+
+  undoClipping: function(element) {
+    element = $(element);
+    if (element._overflow) return;
+    element.style.overflow = element._overflow;
+    element._overflow = undefined;
+  }
+});
+
+var Toggle = new Object();
+Toggle.display = Element.toggle;
+
+/*--------------------------------------------------------------------------*/
+
+Abstract.Insertion = function(adjacency) {
+  this.adjacency = adjacency;
+}
+
+Abstract.Insertion.prototype = {
+  initialize: function(element, content) {
+    this.element = $(element);
+    this.content = content.stripScripts();
+
+    if (this.adjacency && this.element.insertAdjacentHTML) {
+      try {
+        this.element.insertAdjacentHTML(this.adjacency, this.content);
+      } catch (e) {
+        if (this.element.tagName.toLowerCase() == 'tbody') {
+          this.insertContent(this.contentFromAnonymousTable());
+        } else {
+          throw e;
+        }
+      }
+    } else {
+      this.range = this.element.ownerDocument.createRange();
+      if (this.initializeRange) this.initializeRange();
+      this.insertContent([this.range.createContextualFragment(this.content)]);
+    }
+
+    setTimeout(function() {content.evalScripts()}, 10);
+  },
+
+  contentFromAnonymousTable: function() {
+    var div = document.createElement('div');
+    div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>';
+    return $A(div.childNodes[0].childNodes[0].childNodes);
+  }
+}
+
+var Insertion = new Object();
+
+Insertion.Before = Class.create();
+Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), {
+  initializeRange: function() {
+    this.range.setStartBefore(this.element);
+  },
+
+  insertContent: function(fragments) {
+    fragments.each((function(fragment) {
+      this.element.parentNode.insertBefore(fragment, this.element);
+    }).bind(this));
+  }
+});
+
+Insertion.Top = Class.create();
+Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), {
+  initializeRange: function() {
+    this.range.selectNodeContents(this.element);
+    this.range.collapse(true);
+  },
+
+  insertContent: function(fragments) {
+    fragments.reverse(false).each((function(fragment) {
+      this.element.insertBefore(fragment, this.element.firstChild);
+    }).bind(this));
+  }
+});
+
+Insertion.Bottom = Class.create();
+Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), {
+  initializeRange: function() {
+    this.range.selectNodeContents(this.element);
+    this.range.collapse(this.element);
+  },
+
+  insertContent: function(fragments) {
+    fragments.each((function(fragment) {
+      this.element.appendChild(fragment);
+    }).bind(this));
+  }
+});
+
+Insertion.After = Class.create();
+Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), {
+  initializeRange: function() {
+    this.range.setStartAfter(this.element);
+  },
+
+  insertContent: function(fragments) {
+    fragments.each((function(fragment) {
+      this.element.parentNode.insertBefore(fragment,
+        this.element.nextSibling);
+    }).bind(this));
+  }
+});
+
+/*--------------------------------------------------------------------------*/
+
+Element.ClassNames = Class.create();
+Element.ClassNames.prototype = {
+  initialize: function(element) {
+    this.element = $(element);
+  },
+
+  _each: function(iterator) {
+    this.element.className.split(/\s+/).select(function(name) {
+      return name.length > 0;
+    })._each(iterator);
+  },
+
+  set: function(className) {
+    this.element.className = className;
+  },
+
+  add: function(classNameToAdd) {
+    if (this.include(classNameToAdd)) return;
+    this.set(this.toArray().concat(classNameToAdd).join(' '));
+  },
+
+  remove: function(classNameToRemove) {
+    if (!this.include(classNameToRemove)) return;
+    this.set(this.select(function(className) {
+      return className != classNameToRemove;
+    }).join(' '));
+  },
+
+  toString: function() {
+    return this.toArray().join(' ');
+  }
+}
+
+Object.extend(Element.ClassNames.prototype, Enumerable);
+var Field = {
+  clear: function() {
+    for (var i = 0; i < arguments.length; i++)
+      $(arguments[i]).value = '';
+  },
+
+  focus: function(element) {
+    $(element).focus();
+  },
+
+  present: function() {
+    for (var i = 0; i < arguments.length; i++)
+      if ($(arguments[i]).value == '') return false;
+    return true;
+  },
+
+  select: function(element) {
+    $(element).select();
+  },
+
+  activate: function(element) {
+    element = $(element);
+    element.focus();
+    if (element.select)
+      element.select();
+  }
+}
+
+/*--------------------------------------------------------------------------*/
+
+var Form = {
+  serialize: function(form) {
+    var elements = Form.getElements($(form));
+    var queryComponents = new Array();
+
+    for (var i = 0; i < elements.length; i++) {
+      var queryComponent = Form.Element.serialize(elements[i]);
+      if (queryComponent)
+        queryComponents.push(queryComponent);
+    }
+
+    return queryComponents.join('&');
+  },
+
+  getElements: function(form) {
+    form = $(form);
+    var elements = new Array();
+
+    for (tagName in Form.Element.Serializers) {
+      var tagElements = form.getElementsByTagName(tagName);
+      for (var j = 0; j < tagElements.length; j++)
+        elements.push(tagElements[j]);
+    }
+    return elements;
+  },
+
+  getInputs: function(form, typeName, name) {
+    form = $(form);
+    var inputs = form.getElementsByTagName('input');
+
+    if (!typeName && !name)
+      return inputs;
+
+    var matchingInputs = new Array();
+    for (var i = 0; i < inputs.length; i++) {
+      var input = inputs[i];
+      if ((typeName && input.type != typeName) ||
+          (name && input.name != name))
+        continue;
+      matchingInputs.push(input);
+    }
+
+    return matchingInputs;
+  },
+
+  disable: function(form) {
+    var elements = Form.getElements(form);
+    for (var i = 0; i < elements.length; i++) {
+      var element = elements[i];
+      element.blur();
+      element.disabled = 'true';
+    }
+  },
+
+  enable: function(form) {
+    var elements = Form.getElements(form);
+    for (var i = 0; i < elements.length; i++) {
+      var element = elements[i];
+      element.disabled = '';
+    }
+  },
+
+  findFirstElement: function(form) {
+    return Form.getElements(form).find(function(element) {
+      return element.type != 'hidden' && !element.disabled &&
+        ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
+    });
+  },
+
+  focusFirstElement: function(form) {
+    Field.activate(Form.findFirstElement(form));
+  },
+
+  reset: function(form) {
+    $(form).reset();
+  }
+}
+
+Form.Element = {
+  serialize: function(element) {
+    element = $(element);
+    var method = element.tagName.toLowerCase();
+    var parameter = Form.Element.Serializers[method](element);
+
+    if (parameter) {
+      var key = encodeURIComponent(parameter[0]);
+      if (key.length == 0) return;
+
+      if (parameter[1].constructor != Array)
+        parameter[1] = [parameter[1]];
+
+      return parameter[1].map(function(value) {
+        return key + '=' + encodeURIComponent(value);
+      }).join('&');
+    }
+  },
+
+  getValue: function(element) {
+    element = $(element);
+    var method = element.tagName.toLowerCase();
+    var parameter = Form.Element.Serializers[method](element);
+
+    if (parameter)
+      return parameter[1];
+  }
+}
+
+Form.Element.Serializers = {
+  input: function(element) {
+    switch (element.type.toLowerCase()) {
+      case 'submit':
+      case 'hidden':
+      case 'password':
+      case 'text':
+        return Form.Element.Serializers.textarea(element);
+      case 'checkbox':
+      case 'radio':
+        return Form.Element.Serializers.inputSelector(element);
+    }
+    return false;
+  },
+
+  inputSelector: function(element) {
+    if (element.checked)
+      return [element.name, element.value];
+  },
+
+  textarea: function(element) {
+    return [element.name, element.value];
+  },
+
+  select: function(element) {
+    return Form.Element.Serializers[element.type == 'select-one' ?
+      'selectOne' : 'selectMany'](element);
+  },
+
+  selectOne: function(element) {
+    var value = '', opt, index = element.selectedIndex;
+    if (index >= 0) {
+      opt = element.options[index];
+      value = opt.value;
+      if (!value && !('value' in opt))
+        value = opt.text;
+    }
+    return [element.name, value];
+  },
+
+  selectMany: function(element) {
+    var value = new Array();
+    for (var i = 0; i < element.length; i++) {
+      var opt = element.options[i];
+      if (opt.selected) {
+        var optValue = opt.value;
+        if (!optValue && !('value' in opt))
+          optValue = opt.text;
+        value.push(optValue);
+      }
+    }
+    return [element.name, value];
+  }
+}
+
+/*--------------------------------------------------------------------------*/
+
+var $F = Form.Element.getValue;
+
+/*--------------------------------------------------------------------------*/
+
+Abstract.TimedObserver = function() {}
+Abstract.TimedObserver.prototype = {
+  initialize: function(element, frequency, callback) {
+    this.frequency = frequency;
+    this.element   = $(element);
+    this.callback  = callback;
+
+    this.lastValue = this.getValue();
+    this.registerCallback();
+  },
+
+  registerCallback: function() {
+    setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
+  },
+
+  onTimerEvent: function() {
+    var value = this.getValue();
+    if (this.lastValue != value) {
+      this.callback(this.element, value);
+      this.lastValue = value;
+    }
+  }
+}
+
+Form.Element.Observer = Class.create();
+Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
+  getValue: function() {
+    return Form.Element.getValue(this.element);
+  }
+});
+
+Form.Observer = Class.create();
+Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
+  getValue: function() {
+    return Form.serialize(this.element);
+  }
+});
+
+/*--------------------------------------------------------------------------*/
+
+Abstract.EventObserver = function() {}
+Abstract.EventObserver.prototype = {
+  initialize: function(element, callback) {
+    this.element  = $(element);
+    this.callback = callback;
+
+    this.lastValue = this.getValue();
+    if (this.element.tagName.toLowerCase() == 'form')
+      this.registerFormCallbacks();
+    else
+      this.registerCallback(this.element);
+  },
+
+  onElementEvent: function() {
+    var value = this.getValue();
+    if (this.lastValue != value) {
+      this.callback(this.element, value);
+      this.lastValue = value;
+    }
+  },
+
+  registerFormCallbacks: function() {
+    var elements = Form.getElements(this.element);
+    for (var i = 0; i < elements.length; i++)
+      this.registerCallback(elements[i]);
+  },
+
+  registerCallback: function(element) {
+    if (element.type) {
+      switch (element.type.toLowerCase()) {
+        case 'checkbox':
+        case 'radio':
+          Event.observe(element, 'click', this.onElementEvent.bind(this));
+          break;
+        case 'password':
+        case 'text':
+        case 'textarea':
+        case 'select-one':
+        case 'select-multiple':
+          Event.observe(element, 'change', this.onElementEvent.bind(this));
+          break;
+      }
+    }
+  }
+}
+
+Form.Element.EventObserver = Class.create();
+Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
+  getValue: function() {
+    return Form.Element.getValue(this.element);
+  }
+});
+
+Form.EventObserver = Class.create();
+Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
+  getValue: function() {
+    return Form.serialize(this.element);
+  }
+});
+if (!window.Event) {
+  var Event = new Object();
+}
+
+Object.extend(Event, {
+  KEY_BACKSPACE: 8,
+  KEY_TAB:       9,
+  KEY_RETURN:   13,
+  KEY_ESC:      27,
+  KEY_LEFT:     37,
+  KEY_UP:       38,
+  KEY_RIGHT:    39,
+  KEY_DOWN:     40,
+  KEY_DELETE:   46,
+
+  element: function(event) {
+    return event.target || event.srcElement;
+  },
+
+  isLeftClick: function(event) {
+    return (((event.which) && (event.which == 1)) ||
+            ((event.button) && (event.button == 1)));
+  },
+
+  pointerX: function(event) {
+    return event.pageX || (event.clientX +
+      (document.documentElement.scrollLeft || document.body.scrollLeft));
+  },
+
+  pointerY: function(event) {
+    return event.pageY || (event.clientY +
+      (document.documentElement.scrollTop || document.body.scrollTop));
+  },
+
+  stop: function(event) {
+    if (event.preventDefault) {
+      event.preventDefault();
+      event.stopPropagation();
+    } else {
+      event.returnValue = false;
+      event.cancelBubble = true;
+    }
+  },
+
+  // find the first node with the given tagName, starting from the
+  // node the event was triggered on; traverses the DOM upwards
+  findElement: function(event, tagName) {
+    var element = Event.element(event);
+    while (element.parentNode && (!element.tagName ||
+        (element.tagName.toUpperCase() != tagName.toUpperCase())))
+      element = element.parentNode;
+    return element;
+  },
+
+  observers: false,
+
+  _observeAndCache: function(element, name, observer, useCapture) {
+    if (!this.observers) this.observers = [];
+    if (element.addEventListener) {
+      this.observers.push([element, name, observer, useCapture]);
+      element.addEventListener(name, observer, useCapture);
+    } else if (element.attachEvent) {
+      this.observers.push([element, name, observer, useCapture]);
+      element.attachEvent('on' + name, observer);
+    }
+  },
+
+  unloadCache: function() {
+    if (!Event.observers) return;
+    for (var i = 0; i < Event.observers.length; i++) {
+      Event.stopObserving.apply(this, Event.observers[i]);
+      Event.observers[i][0] = null;
+    }
+    Event.observers = false;
+  },
+
+  observe: function(element, name, observer, useCapture) {
+    var element = $(element);
+    useCapture = useCapture || false;
+
+    if (name == 'keypress' &&
+        (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
+        || element.attachEvent))
+      name = 'keydown';
+
+    this._observeAndCache(element, name, observer, useCapture);
+  },
+
+  stopObserving: function(element, name, observer, useCapture) {
+    var element = $(element);
+    useCapture = useCapture || false;
+
+    if (name == 'keypress' &&
+        (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
+        || element.detachEvent))
+      name = 'keydown';
+
+    if (element.removeEventListener) {
+      element.removeEventListener(name, observer, useCapture);
+    } else if (element.detachEvent) {
+      element.detachEvent('on' + name, observer);
+    }
+  }
+});
+
+/* prevent memory leaks in IE */
+Event.observe(window, 'unload', Event.unloadCache, false);
+var Position = {
+  // set to true if needed, warning: firefox performance problems
+  // NOT neeeded for page scrolling, only if draggable contained in
+  // scrollable elements
+  includeScrollOffsets: false,
+
+  // must be called before calling withinIncludingScrolloffset, every time the
+  // page is scrolled
+  prepare: function() {
+    this.deltaX =  window.pageXOffset
+                || document.documentElement.scrollLeft
+                || document.body.scrollLeft
+                || 0;
+    this.deltaY =  window.pageYOffset
+                || document.documentElement.scrollTop
+                || document.body.scrollTop
+                || 0;
+  },
+
+  realOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.scrollTop  || 0;
+      valueL += element.scrollLeft || 0;
+      element = element.parentNode;
+    } while (element);
+    return [valueL, valueT];
+  },
+
+  cumulativeOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      element = element.offsetParent;
+    } while (element);
+    return [valueL, valueT];
+  },
+
+  positionedOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      element = element.offsetParent;
+      if (element) {
+        p = Element.getStyle(element, 'position');
+        if (p == 'relative' || p == 'absolute') break;
+      }
+    } while (element);
+    return [valueL, valueT];
+  },
+
+  offsetParent: function(element) {
+    if (element.offsetParent) return element.offsetParent;
+    if (element == document.body) return element;
+
+    while ((element = element.parentNode) && element != document.body)
+      if (Element.getStyle(element, 'position') != 'static')
+        return element;
+
+    return document.body;
+  },
+
+  // caches x/y coordinate pair to use with overlap
+  within: function(element, x, y) {
+    if (this.includeScrollOffsets)
+      return this.withinIncludingScrolloffsets(element, x, y);
+    this.xcomp = x;
+    this.ycomp = y;
+    this.offset = this.cumulativeOffset(element);
+
+    return (y >= this.offset[1] &&
+            y <  this.offset[1] + element.offsetHeight &&
+            x >= this.offset[0] &&
+            x <  this.offset[0] + element.offsetWidth);
+  },
+
+  withinIncludingScrolloffsets: function(element, x, y) {
+    var offsetcache = this.realOffset(element);
+
+    this.xcomp = x + offsetcache[0] - this.deltaX;
+    this.ycomp = y + offsetcache[1] - this.deltaY;
+    this.offset = this.cumulativeOffset(element);
+
+    return (this.ycomp >= this.offset[1] &&
+            this.ycomp <  this.offset[1] + element.offsetHeight &&
+            this.xcomp >= this.offset[0] &&
+            this.xcomp <  this.offset[0] + element.offsetWidth);
+  },
+
+  // within must be called directly before
+  overlap: function(mode, element) {
+    if (!mode) return 0;
+    if (mode == 'vertical')
+      return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
+        element.offsetHeight;
+    if (mode == 'horizontal')
+      return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
+        element.offsetWidth;
+  },
+
+  clone: function(source, target) {
+    source = $(source);
+    target = $(target);
+    target.style.position = 'absolute';
+    var offsets = this.cumulativeOffset(source);
+    target.style.top    = offsets[1] + 'px';
+    target.style.left   = offsets[0] + 'px';
+    target.style.width  = source.offsetWidth + 'px';
+    target.style.height = source.offsetHeight + 'px';
+  },
+
+  page: function(forElement) {
+    var valueT = 0, valueL = 0;
+
+    var element = forElement;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+
+      // Safari fix
+      if (element.offsetParent==document.body)
+        if (Element.getStyle(element,'position')=='absolute') break;
+
+    } while (element = element.offsetParent);
+
+    element = forElement;
+    do {
+      valueT -= element.scrollTop  || 0;
+      valueL -= element.scrollLeft || 0;
+    } while (element = element.parentNode);
+
+    return [valueL, valueT];
+  },
+
+  clone: function(source, target) {
+    var options = Object.extend({
+      setLeft:    true,
+      setTop:     true,
+      setWidth:   true,
+      setHeight:  true,
+      offsetTop:  0,
+      offsetLeft: 0
+    }, arguments[2] || {})
+
+    // find page position of source
+    source = $(source);
+    var p = Position.page(source);
+
+    // find coordinate system to use
+    target = $(target);
+    var delta = [0, 0];
+    var parent = null;
+    // delta [0,0] will do fine with position: fixed elements,
+    // position:absolute needs offsetParent deltas
+    if (Element.getStyle(target,'position') == 'absolute') {
+      parent = Position.offsetParent(target);
+      delta = Position.page(parent);
+    }
+
+    // correct by body offsets (fixes Safari)
+    if (parent == document.body) {
+      delta[0] -= document.body.offsetLeft;
+      delta[1] -= document.body.offsetTop;
+    }
+
+    // set position
+    if(options.setLeft)   target.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
+    if(options.setTop)    target.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
+    if(options.setWidth)  target.style.width = source.offsetWidth + 'px';
+    if(options.setHeight) target.style.height = source.offsetHeight + 'px';
+  },
+
+  absolutize: function(element) {
+    element = $(element);
+    if (element.style.position == 'absolute') return;
+    Position.prepare();
+
+    var offsets = Position.positionedOffset(element);
+    var top     = offsets[1];
+    var left    = offsets[0];
+    var width   = element.clientWidth;
+    var height  = element.clientHeight;
+
+    element._originalLeft   = left - parseFloat(element.style.left  || 0);
+    element._originalTop    = top  - parseFloat(element.style.top || 0);
+    element._originalWidth  = element.style.width;
+    element._originalHeight = element.style.height;
+
+    element.style.position = 'absolute';
+    element.style.top    = top + 'px';;
+    element.style.left   = left + 'px';;
+    element.style.width  = width + 'px';;
+    element.style.height = height + 'px';;
+  },
+
+  relativize: function(element) {
+    element = $(element);
+    if (element.style.position == 'relative') return;
+    Position.prepare();
+
+    element.style.position = 'relative';
+    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
+    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
+
+    element.style.top    = top + 'px';
+    element.style.left   = left + 'px';
+    element.style.height = element._originalHeight;
+    element.style.width  = element._originalWidth;
+  }
+}
+
+// Safari returns margins on body which is incorrect if the child is absolutely
+// positioned.  For performance reasons, redefine Position.cumulativeOffset for
+// KHTML/WebKit only.
+if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
+  Position.cumulativeOffset = function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      if (element.offsetParent == document.body)
+        if (Element.getStyle(element, 'position') == 'absolute') break;
+
+      element = element.offsetParent;
+    } while (element);
+
+    return [valueL, valueT];
+  }
+}

Added: jifty/branches/schema-plugins/share/web/static/js/rico.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/rico.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,2843 @@
+/*
+ * This is a slightly modified version of Rico which has improved
+ * corner rounding methods that try to take care of preserving padding.
+ * 
+ * Replacing it with a stock Rico probably isn't a good idea unless you
+ * repatch it or don't plan on using any of the corner rounding.
+ *
+ *
+ * WARNING: The patched version of Rico.Corner.round is *extremely*
+ * slow on Mozilla/Firefox. Whenever possible, use the
+ * -moz-border-radius CSS property in Mozilla browsers, and
+ * conditionally only call it for any other browser, where its
+ * performance is far more acceptable.
+ *
+ */
+
+/**
+  *
+  *  Copyright 2005 Sabre Airline Solutions
+  *
+  *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
+  *  file except in compliance with the License. You may obtain a copy of the License at
+  *
+  *         http://www.apache.org/licenses/LICENSE-2.0
+  *
+  *  Unless required by applicable law or agreed to in writing, software distributed under the
+  *  License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+  *  either express or implied. See the License for the specific language governing permissions
+  *  and limitations under the License.
+  **/
+
+
+//-------------------- rico.js
+var Rico = {
+  Version: '1.1.2.jifty.r963',
+  prototypeVersion: parseFloat(Prototype.Version.split(".")[0] + "." + Prototype.Version.split(".")[1])
+}
+
+if((typeof Prototype=='undefined') || Rico.prototypeVersion < 1.3)
+      throw("Rico requires the Prototype JavaScript framework >= 1.3");
+
+Rico.ArrayExtensions = new Array();
+
+if (Object.prototype.extend) {
+   Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Object.prototype.extend;
+}else{
+  Object.prototype.extend = function(object) {
+    return Object.extend.apply(this, [this, object]);
+  }
+  Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Object.prototype.extend;
+}
+
+if (Array.prototype.push) {
+   Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Array.prototype.push;
+}
+
+if (!Array.prototype.remove) {
+   Array.prototype.remove = function(dx) {
+      if( isNaN(dx) || dx > this.length )
+         return false;
+      for( var i=0,n=0; i<this.length; i++ )
+         if( i != dx )
+            this[n++]=this[i];
+      this.length-=1;
+   };
+  Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Array.prototype.remove;
+}
+
+if (!Array.prototype.removeItem) {
+   Array.prototype.removeItem = function(item) {
+      for ( var i = 0 ; i < this.length ; i++ )
+         if ( this[i] == item ) {
+            this.remove(i);
+            break;
+         }
+   };
+  Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Array.prototype.removeItem;
+}
+
+if (!Array.prototype.indices) {
+   Array.prototype.indices = function() {
+      var indexArray = new Array();
+      for ( index in this ) {
+         var ignoreThis = false;
+         for ( var i = 0 ; i < Rico.ArrayExtensions.length ; i++ ) {
+            if ( this[index] == Rico.ArrayExtensions[i] ) {
+               ignoreThis = true;
+               break;
+            }
+         }
+         if ( !ignoreThis )
+            indexArray[ indexArray.length ] = index;
+      }
+      return indexArray;
+   }
+  Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Array.prototype.indices;
+}
+
+// Create the loadXML method and xml getter for Mozilla
+if ( window.DOMParser &&
+	  window.XMLSerializer &&
+	  window.Node && Node.prototype && Node.prototype.__defineGetter__ ) {
+
+   if (!Document.prototype.loadXML) {
+      Document.prototype.loadXML = function (s) {
+         var doc2 = (new DOMParser()).parseFromString(s, "text/xml");
+         while (this.hasChildNodes())
+            this.removeChild(this.lastChild);
+
+         for (var i = 0; i < doc2.childNodes.length; i++) {
+            this.appendChild(this.importNode(doc2.childNodes[i], true));
+         }
+      };
+	}
+
+	Document.prototype.__defineGetter__( "xml",
+	   function () {
+		   return (new XMLSerializer()).serializeToString(this);
+	   }
+	 );
+}
+
+document.getElementsByTagAndClassName = function(tagName, className) {
+  if ( tagName == null )
+     tagName = '*';
+
+  var children = document.getElementsByTagName(tagName) || document.all;
+  var elements = new Array();
+
+  if ( className == null )
+    return children;
+
+  for (var i = 0; i < children.length; i++) {
+    var child = children[i];
+    var classNames = child.className.split(' ');
+    for (var j = 0; j < classNames.length; j++) {
+      if (classNames[j] == className) {
+        elements.push(child);
+        break;
+      }
+    }
+  }
+
+  return elements;
+}
+
+
+//-------------------- ricoAccordion.js
+Rico.Accordion = Class.create();
+
+Rico.Accordion.prototype = {
+
+   initialize: function(container, options) {
+      this.container            = $(container);
+      this.lastExpandedTab      = null;
+      this.accordionTabs        = new Array();
+      this.setOptions(options);
+      this._attachBehaviors();
+      if(!container) return;
+
+      this.container.style.borderBottom = '1px solid ' + this.options.borderColor;
+      // validate onloadShowTab
+       if (this.options.onLoadShowTab >= this.accordionTabs.length)
+        this.options.onLoadShowTab = 0;
+
+      // set the initial visual state...
+      for ( var i=0 ; i < this.accordionTabs.length ; i++ )
+      {
+        if (i != this.options.onLoadShowTab){
+         this.accordionTabs[i].collapse();
+         this.accordionTabs[i].content.style.display = 'none';
+        }
+      }
+      this.lastExpandedTab = this.accordionTabs[this.options.onLoadShowTab];
+      if (this.options.panelHeight == 'auto'){
+          var tabToCheck = (this.options.onloadShowTab === 0)? 1 : 0;
+          var titleBarSize = parseInt(RicoUtil.getElementsComputedStyle(this.accordionTabs[tabToCheck].titleBar, 'height'));
+          if (isNaN(titleBarSize))
+            titleBarSize = this.accordionTabs[tabToCheck].titleBar.offsetHeight;
+          
+          var totalTitleBarSize = this.accordionTabs.length * titleBarSize;
+          var parentHeight = parseInt(RicoUtil.getElementsComputedStyle(this.container.parentNode, 'height'));
+          if (isNaN(parentHeight))
+            parentHeight = this.container.parentNode.offsetHeight;
+          
+          this.options.panelHeight = parentHeight - totalTitleBarSize-2;
+      }
+      
+      this.lastExpandedTab.content.style.height = this.options.panelHeight + "px";
+      this.lastExpandedTab.showExpanded();
+      this.lastExpandedTab.titleBar.style.fontWeight = this.options.expandedFontWeight;
+
+   },
+
+   setOptions: function(options) {
+      this.options = {
+         expandedBg          : '#63699c',
+         hoverBg             : '#63699c',
+         collapsedBg         : '#6b79a5',
+         expandedTextColor   : '#ffffff',
+         expandedFontWeight  : 'bold',
+         hoverTextColor      : '#ffffff',
+         collapsedTextColor  : '#ced7ef',
+         collapsedFontWeight : 'normal',
+         hoverTextColor      : '#ffffff',
+         borderColor         : '#1f669b',
+         panelHeight         : 200,
+         onHideTab           : null,
+         onShowTab           : null,
+         onLoadShowTab       : 0
+      }
+      Object.extend(this.options, options || {});
+   },
+
+   showTabByIndex: function( anIndex, animate ) {
+      var doAnimate = arguments.length == 1 ? true : animate;
+      this.showTab( this.accordionTabs[anIndex], doAnimate );
+   },
+
+   showTab: function( accordionTab, animate ) {
+     if ( this.lastExpandedTab == accordionTab )
+        return;
+
+      var doAnimate = arguments.length == 1 ? true : animate;
+
+      if ( this.options.onHideTab )
+         this.options.onHideTab(this.lastExpandedTab);
+
+      this.lastExpandedTab.showCollapsed(); 
+      var accordion = this;
+      var lastExpandedTab = this.lastExpandedTab;
+
+      this.lastExpandedTab.content.style.height = (this.options.panelHeight - 1) + 'px';
+      accordionTab.content.style.display = '';
+
+      accordionTab.titleBar.style.fontWeight = this.options.expandedFontWeight;
+
+      if ( doAnimate ) {
+         new Rico.Effect.AccordionSize( this.lastExpandedTab.content,
+                                   accordionTab.content,
+                                   1,
+                                   this.options.panelHeight,
+                                   100, 10,
+                                   { complete: function() {accordion.showTabDone(lastExpandedTab)} } );
+         this.lastExpandedTab = accordionTab;
+      }
+      else {
+         this.lastExpandedTab.content.style.height = "1px";
+         accordionTab.content.style.height = this.options.panelHeight + "px";
+         this.lastExpandedTab = accordionTab;
+         this.showTabDone(lastExpandedTab);
+      }
+   },
+
+   showTabDone: function(collapsedTab) {
+      collapsedTab.content.style.display = 'none';
+      this.lastExpandedTab.showExpanded();
+      if ( this.options.onShowTab )
+         this.options.onShowTab(this.lastExpandedTab);
+   },
+
+   _attachBehaviors: function() {
+      var panels = this._getDirectChildrenByTag(this.container, 'DIV');
+      for ( var i = 0 ; i < panels.length ; i++ ) {
+
+         var tabChildren = this._getDirectChildrenByTag(panels[i],'DIV');
+         if ( tabChildren.length != 2 )
+            continue; // unexpected
+
+         var tabTitleBar   = tabChildren[0];
+         var tabContentBox = tabChildren[1];
+         this.accordionTabs.push( new Rico.Accordion.Tab(this,tabTitleBar,tabContentBox) );
+      }
+   },
+
+   _getDirectChildrenByTag: function(e, tagName) {
+      var kids = new Array();
+      var allKids = e.childNodes;
+      for( var i = 0 ; i < allKids.length ; i++ )
+         if ( allKids[i] && allKids[i].tagName && allKids[i].tagName == tagName )
+            kids.push(allKids[i]);
+      return kids;
+   }
+
+};
+
+Rico.Accordion.Tab = Class.create();
+
+Rico.Accordion.Tab.prototype = {
+
+   initialize: function(accordion, titleBar, content) {
+      this.accordion = accordion;
+      this.titleBar  = titleBar;
+      this.content   = content;
+      this._attachBehaviors();
+   },
+
+   collapse: function() {
+      this.showCollapsed();
+      this.content.style.height = "1px";
+   },
+
+   showCollapsed: function() {
+      this.expanded = false;
+      this.titleBar.style.backgroundColor = this.accordion.options.collapsedBg;
+      this.titleBar.style.color           = this.accordion.options.collapsedTextColor;
+      this.titleBar.style.fontWeight      = this.accordion.options.collapsedFontWeight;
+      this.content.style.overflow = "hidden";
+   },
+
+   showExpanded: function() {
+      this.expanded = true;
+      this.titleBar.style.backgroundColor = this.accordion.options.expandedBg;
+      this.titleBar.style.color           = this.accordion.options.expandedTextColor;
+      this.content.style.overflow         = "auto";
+   },
+
+   titleBarClicked: function(e) {
+      if ( this.accordion.lastExpandedTab == this )
+         return;
+      this.accordion.showTab(this);
+   },
+
+   hover: function(e) {
+		this.titleBar.style.backgroundColor = this.accordion.options.hoverBg;
+		this.titleBar.style.color           = this.accordion.options.hoverTextColor;
+   },
+
+   unhover: function(e) {
+      if ( this.expanded ) {
+         this.titleBar.style.backgroundColor = this.accordion.options.expandedBg;
+         this.titleBar.style.color           = this.accordion.options.expandedTextColor;
+      }
+      else {
+         this.titleBar.style.backgroundColor = this.accordion.options.collapsedBg;
+         this.titleBar.style.color           = this.accordion.options.collapsedTextColor;
+      }
+   },
+
+   _attachBehaviors: function() {
+      this.content.style.border = "1px solid " + this.accordion.options.borderColor;
+      this.content.style.borderTopWidth    = "0px";
+      this.content.style.borderBottomWidth = "0px";
+      this.content.style.margin            = "0px";
+
+      this.titleBar.onclick     = this.titleBarClicked.bindAsEventListener(this);
+      this.titleBar.onmouseover = this.hover.bindAsEventListener(this);
+      this.titleBar.onmouseout  = this.unhover.bindAsEventListener(this);
+   }
+
+};
+
+
+//-------------------- ricoAjaxEngine.js
+Rico.AjaxEngine = Class.create();
+
+Rico.AjaxEngine.prototype = {
+
+   initialize: function() {
+      this.ajaxElements = new Array();
+      this.ajaxObjects  = new Array();
+      this.requestURLS  = new Array();
+      this.options = {};
+   },
+
+   registerAjaxElement: function( anId, anElement ) {
+      if ( !anElement )
+         anElement = $(anId);
+      this.ajaxElements[anId] = anElement;
+   },
+
+   registerAjaxObject: function( anId, anObject ) {
+      this.ajaxObjects[anId] = anObject;
+   },
+
+   registerRequest: function (requestLogicalName, requestURL) {
+      this.requestURLS[requestLogicalName] = requestURL;
+   },
+
+   sendRequest: function(requestName, options) {
+      // Allow for backwards Compatibility
+      if ( arguments.length >= 2 )
+       if (typeof arguments[1] == 'string')
+         options = {parameters: this._createQueryString(arguments, 1)};
+      this.sendRequestWithData(requestName, null, options);
+   },
+
+   sendRequestWithData: function(requestName, xmlDocument, options) {
+      var requestURL = this.requestURLS[requestName];
+      if ( requestURL == null )
+         return;
+
+      // Allow for backwards Compatibility
+      if ( arguments.length >= 3 )
+        if (typeof arguments[2] == 'string')
+          options.parameters = this._createQueryString(arguments, 2);
+
+      new Ajax.Request(requestURL, this._requestOptions(options,xmlDocument));
+   },
+
+   sendRequestAndUpdate: function(requestName,container,options) {
+      // Allow for backwards Compatibility
+      if ( arguments.length >= 3 )
+        if (typeof arguments[2] == 'string')
+          options.parameters = this._createQueryString(arguments, 2);
+
+      this.sendRequestWithDataAndUpdate(requestName, null, container, options);
+   },
+
+   sendRequestWithDataAndUpdate: function(requestName,xmlDocument,container,options) {
+      var requestURL = this.requestURLS[requestName];
+      if ( requestURL == null )
+         return;
+
+      // Allow for backwards Compatibility
+      if ( arguments.length >= 4 )
+        if (typeof arguments[3] == 'string')
+          options.parameters = this._createQueryString(arguments, 3);
+
+      var updaterOptions = this._requestOptions(options,xmlDocument);
+
+      new Ajax.Updater(container, requestURL, updaterOptions);
+   },
+
+   // Private -- not part of intended engine API --------------------------------------------------------------------
+
+   _requestOptions: function(options,xmlDoc) {
+      var requestHeaders = ['X-Rico-Version', Rico.Version ];
+      var sendMethod = 'post';
+      if ( xmlDoc == null )
+        if (Rico.prototypeVersion < 1.4)
+        requestHeaders.push( 'Content-type', 'text/xml' );
+      else
+          sendMethod = 'get';
+      (!options) ? options = {} : '';
+
+      if (!options._RicoOptionsProcessed){
+      // Check and keep any user onComplete functions
+        if (options.onComplete)
+             options.onRicoComplete = options.onComplete;
+        // Fix onComplete
+        if (options.overrideOnComplete)
+          options.onComplete = options.overrideOnComplete;
+        else
+          options.onComplete = this._onRequestComplete.bind(this);
+        options._RicoOptionsProcessed = true;
+      }
+
+     // Set the default options and extend with any user options
+     this.options = {
+                     requestHeaders: requestHeaders,
+                     parameters:     options.parameters,
+                     postBody:       xmlDoc,
+                     method:         sendMethod,
+                     onComplete:     options.onComplete
+                    };
+     // Set any user options:
+     Object.extend(this.options, options);
+     return this.options;
+   },
+
+   _createQueryString: function( theArgs, offset ) {
+      var queryString = ""
+      for ( var i = offset ; i < theArgs.length ; i++ ) {
+          if ( i != offset )
+            queryString += "&";
+
+          var anArg = theArgs[i];
+
+          if ( anArg.name != undefined && anArg.value != undefined ) {
+            queryString += anArg.name +  "=" + escape(anArg.value);
+          }
+          else {
+             var ePos  = anArg.indexOf('=');
+             var argName  = anArg.substring( 0, ePos );
+             var argValue = anArg.substring( ePos + 1 );
+             queryString += argName + "=" + escape(argValue);
+          }
+      }
+      return queryString;
+   },
+
+   _onRequestComplete : function(request) {
+      if(!request)
+          return;
+      // User can set an onFailure option - which will be called by prototype
+      if (request.status != 200)
+        return;
+
+      var response = request.responseXML.getElementsByTagName("ajax-response");
+      if (response == null || response.length != 1)
+         return;
+      this._processAjaxResponse( response[0].childNodes );
+      
+      // Check if user has set a onComplete function
+      var onRicoComplete = this.options.onRicoComplete;
+      if (onRicoComplete != null)
+          onRicoComplete();
+   },
+
+   _processAjaxResponse: function( xmlResponseElements ) {
+      for ( var i = 0 ; i < xmlResponseElements.length ; i++ ) {
+         var responseElement = xmlResponseElements[i];
+
+         // only process nodes of type element.....
+         if ( responseElement.nodeType != 1 )
+            continue;
+
+         var responseType = responseElement.getAttribute("type");
+         var responseId   = responseElement.getAttribute("id");
+
+         if ( responseType == "object" )
+            this._processAjaxObjectUpdate( this.ajaxObjects[ responseId ], responseElement );
+         else if ( responseType == "element" )
+            this._processAjaxElementUpdate( this.ajaxElements[ responseId ], responseElement );
+         else
+            alert('unrecognized AjaxResponse type : ' + responseType );
+      }
+   },
+
+   _processAjaxObjectUpdate: function( ajaxObject, responseElement ) {
+      ajaxObject.ajaxUpdate( responseElement );
+   },
+
+   _processAjaxElementUpdate: function( ajaxElement, responseElement ) {
+      ajaxElement.innerHTML = RicoUtil.getContentAsString(responseElement);
+   }
+
+}
+
+var ajaxEngine = new Rico.AjaxEngine();
+
+
+//-------------------- ricoColor.js
+Rico.Color = Class.create();
+
+Rico.Color.prototype = {
+
+   initialize: function(red, green, blue) {
+      this.rgb = { r: red, g : green, b : blue };
+   },
+
+   setRed: function(r) {
+      this.rgb.r = r;
+   },
+
+   setGreen: function(g) {
+      this.rgb.g = g;
+   },
+
+   setBlue: function(b) {
+      this.rgb.b = b;
+   },
+
+   setHue: function(h) {
+
+      // get an HSB model, and set the new hue...
+      var hsb = this.asHSB();
+      hsb.h = h;
+
+      // convert back to RGB...
+      this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, hsb.b);
+   },
+
+   setSaturation: function(s) {
+      // get an HSB model, and set the new hue...
+      var hsb = this.asHSB();
+      hsb.s = s;
+
+      // convert back to RGB and set values...
+      this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, hsb.b);
+   },
+
+   setBrightness: function(b) {
+      // get an HSB model, and set the new hue...
+      var hsb = this.asHSB();
+      hsb.b = b;
+
+      // convert back to RGB and set values...
+      this.rgb = Rico.Color.HSBtoRGB( hsb.h, hsb.s, hsb.b );
+   },
+
+   darken: function(percent) {
+      var hsb  = this.asHSB();
+      this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, Math.max(hsb.b - percent,0));
+   },
+
+   brighten: function(percent) {
+      var hsb  = this.asHSB();
+      this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, Math.min(hsb.b + percent,1));
+   },
+
+   blend: function(other) {
+      this.rgb.r = Math.floor((this.rgb.r + other.rgb.r)/2);
+      this.rgb.g = Math.floor((this.rgb.g + other.rgb.g)/2);
+      this.rgb.b = Math.floor((this.rgb.b + other.rgb.b)/2);
+   },
+
+   isBright: function() {
+      var hsb = this.asHSB();
+      return this.asHSB().b > 0.5;
+   },
+
+   isDark: function() {
+      return ! this.isBright();
+   },
+
+   asRGB: function() {
+      return "rgb(" + this.rgb.r + "," + this.rgb.g + "," + this.rgb.b + ")";
+   },
+
+   asHex: function() {
+      return "#" + this.rgb.r.toColorPart() + this.rgb.g.toColorPart() + this.rgb.b.toColorPart();
+   },
+
+   asHSB: function() {
+      return Rico.Color.RGBtoHSB(this.rgb.r, this.rgb.g, this.rgb.b);
+   },
+
+   toString: function() {
+      return this.asHex();
+   }
+
+};
+
+Rico.Color.createFromHex = function(hexCode) {
+  if(hexCode.length==4) {
+    var shortHexCode = hexCode; 
+    var hexCode = '#';
+    for(var i=1;i<4;i++) hexCode += (shortHexCode.charAt(i) + 
+shortHexCode.charAt(i));
+  }
+   if ( hexCode.indexOf('#') == 0 )
+      hexCode = hexCode.substring(1);
+   var red   = hexCode.substring(0,2);
+   var green = hexCode.substring(2,4);
+   var blue  = hexCode.substring(4,6);
+   return new Rico.Color( parseInt(red,16), parseInt(green,16), parseInt(blue,16) );
+}
+
+/**
+ * Factory method for creating a color from the background of
+ * an HTML element.
+ */
+Rico.Color.createColorFromBackground = function(elem) {
+
+   var actualColor = RicoUtil.getElementsComputedStyle($(elem), "backgroundColor", "background-color");
+
+   if ( actualColor == "transparent" && elem.parentNode )
+      return Rico.Color.createColorFromBackground(elem.parentNode);
+
+   if ( actualColor == null )
+      return new Rico.Color(255,255,255);
+
+   if ( actualColor.indexOf("rgb(") == 0 ) {
+      var colors = actualColor.substring(4, actualColor.length - 1 );
+      var colorArray = colors.split(",");
+      return new Rico.Color( parseInt( colorArray[0] ),
+                            parseInt( colorArray[1] ),
+                            parseInt( colorArray[2] )  );
+
+   }
+   else if ( actualColor.indexOf("#") == 0 ) {
+      return Rico.Color.createFromHex(actualColor);
+   }
+   else
+      return new Rico.Color(255,255,255);
+}
+
+Rico.Color.HSBtoRGB = function(hue, saturation, brightness) {
+
+   var red   = 0;
+	var green = 0;
+	var blue  = 0;
+
+   if (saturation == 0) {
+      red = parseInt(brightness * 255.0 + 0.5);
+	   green = red;
+	   blue = red;
+	}
+	else {
+      var h = (hue - Math.floor(hue)) * 6.0;
+      var f = h - Math.floor(h);
+      var p = brightness * (1.0 - saturation);
+      var q = brightness * (1.0 - saturation * f);
+      var t = brightness * (1.0 - (saturation * (1.0 - f)));
+
+      switch (parseInt(h)) {
+         case 0:
+            red   = (brightness * 255.0 + 0.5);
+            green = (t * 255.0 + 0.5);
+            blue  = (p * 255.0 + 0.5);
+            break;
+         case 1:
+            red   = (q * 255.0 + 0.5);
+            green = (brightness * 255.0 + 0.5);
+            blue  = (p * 255.0 + 0.5);
+            break;
+         case 2:
+            red   = (p * 255.0 + 0.5);
+            green = (brightness * 255.0 + 0.5);
+            blue  = (t * 255.0 + 0.5);
+            break;
+         case 3:
+            red   = (p * 255.0 + 0.5);
+            green = (q * 255.0 + 0.5);
+            blue  = (brightness * 255.0 + 0.5);
+            break;
+         case 4:
+            red   = (t * 255.0 + 0.5);
+            green = (p * 255.0 + 0.5);
+            blue  = (brightness * 255.0 + 0.5);
+            break;
+          case 5:
+            red   = (brightness * 255.0 + 0.5);
+            green = (p * 255.0 + 0.5);
+            blue  = (q * 255.0 + 0.5);
+            break;
+	    }
+	}
+
+   return { r : parseInt(red), g : parseInt(green) , b : parseInt(blue) };
+}
+
+Rico.Color.RGBtoHSB = function(r, g, b) {
+
+   var hue;
+   var saturation;
+   var brightness;
+
+   var cmax = (r > g) ? r : g;
+   if (b > cmax)
+      cmax = b;
+
+   var cmin = (r < g) ? r : g;
+   if (b < cmin)
+      cmin = b;
+
+   brightness = cmax / 255.0;
+   if (cmax != 0)
+      saturation = (cmax - cmin)/cmax;
+   else
+      saturation = 0;
+
+   if (saturation == 0)
+      hue = 0;
+   else {
+      var redc   = (cmax - r)/(cmax - cmin);
+    	var greenc = (cmax - g)/(cmax - cmin);
+    	var bluec  = (cmax - b)/(cmax - cmin);
+
+    	if (r == cmax)
+    	   hue = bluec - greenc;
+    	else if (g == cmax)
+    	   hue = 2.0 + redc - bluec;
+      else
+    	   hue = 4.0 + greenc - redc;
+
+    	hue = hue / 6.0;
+    	if (hue < 0)
+    	   hue = hue + 1.0;
+   }
+
+   return { h : hue, s : saturation, b : brightness };
+}
+
+
+//-------------------- ricoCorner.js
+Rico.Corner = {
+
+   round: function(e, options) {
+      var e = $(e);
+      this._setOptions(options);
+
+      var color = this.options.color;
+      if ( this.options.color == "fromElement" )
+         color = this._background(e);
+
+      var bgColor = this.options.bgColor;
+      if ( this.options.bgColor == "fromParent" )
+         bgColor = this._background(e.offsetParent);
+
+      this._roundCornersImpl(e, color, bgColor);
+   },
+
+   _roundCornersImpl: function(e, color, bgColor) {
+      if(this.options.border)
+         this._renderBorder(e,bgColor);
+      if(this._isTopRounded())
+         this._roundTopCorners(e,color,bgColor);
+      if(this._isBottomRounded())
+         this._roundBottomCorners(e,color,bgColor);
+   },
+
+   _renderBorder: function(el,bgColor) {
+      var borderValue = "1px solid " + this._borderColor(bgColor);
+      var borderL = "border-left: "  + borderValue;
+      var borderR = "border-right: " + borderValue;
+      var style   = "style='" + borderL + ";" + borderR +  "'";
+      el.innerHTML = "<div " + style + ">" + el.innerHTML + "</div>"
+   },
+
+   _roundTopCorners: function(el, color, bgColor) {
+      var corner = this._createCorner(el, bgColor);
+      for(var i=0 ; i < this.options.numSlices ; i++ )
+         corner.appendChild(this._createCornerSlice(color,bgColor,i,"top"));
+      el.style.paddingTop = 0;
+      el.insertBefore(corner,el.firstChild);
+   },
+
+   _roundBottomCorners: function(el, color, bgColor) {
+      var corner = this._createCorner(el, bgColor);
+      for(var i=(this.options.numSlices-1) ; i >= 0 ; i-- )
+         corner.appendChild(this._createCornerSlice(color,bgColor,i,"bottom"));
+      el.style.paddingBottom = 0;
+      el.appendChild(corner);
+   },
+
+   _createCorner: function(el, bgColor) {
+      var corner = document.createElement("div");
+      corner.style.backgroundColor = (this._isTransparent() ? "transparent" : bgColor);
+      var paddingLeft = "-" + RicoUtil.getElementsComputedStyle(el, "paddingLeft", "padding-left");
+      var paddingRight = "-" + RicoUtil.getElementsComputedStyle(el, "paddingRight", "padding-right");
+      
+      corner.style.marginRight = paddingRight;
+      corner.style.marginLeft  = paddingLeft;
+
+      return corner;
+   },
+
+   _createCornerSlice: function(color,bgColor, n, position) {
+      var slice = document.createElement("span");
+
+      var inStyle = slice.style;
+      inStyle.backgroundColor = color;
+      inStyle.display  = "block";
+      inStyle.height   = "1px";
+      inStyle.overflow = "hidden";
+      inStyle.fontSize = "1px";
+
+      var borderColor = this._borderColor(color,bgColor);
+      if ( this.options.border && n == 0 ) {
+         inStyle.borderTopStyle    = "solid";
+         inStyle.borderTopWidth    = "1px";
+         inStyle.borderLeftWidth   = "0px";
+         inStyle.borderRightWidth  = "0px";
+         inStyle.borderBottomWidth = "0px";
+         inStyle.height            = "0px"; // assumes css compliant box model
+         inStyle.borderColor       = borderColor;
+      }
+      else if(borderColor) {
+         inStyle.borderColor = borderColor;
+         inStyle.borderStyle = "solid";
+         inStyle.borderWidth = "0px 1px";
+      }
+
+      if ( !this.options.compact && (n == (this.options.numSlices-1)) )
+         inStyle.height = "2px";
+
+      this._setMargin(slice, n, position);
+      this._setBorder(slice, n, position);
+      return slice;
+   },
+
+   _setOptions: function(options) {
+      this.options = {
+         corners : "all",
+         color   : "fromElement",
+         bgColor : "fromParent",
+         blend   : true,
+         border  : false,
+         compact : false
+      }
+      Object.extend(this.options, options || {});
+
+      this.options.numSlices = this.options.compact ? 2 : 4;
+      if ( this._isTransparent() )
+         this.options.blend = false;
+   },
+
+   _whichSideTop: function() {
+      if ( this._hasString(this.options.corners, "all", "top") )
+         return "";
+
+      if ( this.options.corners.indexOf("tl") >= 0 && this.options.corners.indexOf("tr") >= 0 )
+         return "";
+
+      if (this.options.corners.indexOf("tl") >= 0)
+         return "left";
+      else if (this.options.corners.indexOf("tr") >= 0)
+          return "right";
+      return "";
+   },
+
+   _whichSideBottom: function() {
+      if ( this._hasString(this.options.corners, "all", "bottom") )
+         return "";
+
+      if ( this.options.corners.indexOf("bl")>=0 && this.options.corners.indexOf("br")>=0 )
+         return "";
+
+      if(this.options.corners.indexOf("bl") >=0)
+         return "left";
+      else if(this.options.corners.indexOf("br")>=0)
+         return "right";
+      return "";
+   },
+
+   _borderColor : function(color,bgColor) {
+      if ( color == "transparent" )
+         return bgColor;
+      else if ( this.options.border )
+         return this.options.border;
+      else if ( this.options.blend )
+         return this._blend( bgColor, color );
+      else
+         return "";
+   },
+
+
+   _setMargin: function(el, n, corners) {
+      var marginSize = this._marginSize(n);
+      var whichSide = corners == "top" ? this._whichSideTop() : this._whichSideBottom();
+
+      if ( whichSide == "left" ) {
+         el.style.marginLeft = marginSize + "px"; el.style.marginRight = "0px";
+      }
+      else if ( whichSide == "right" ) {
+         el.style.marginRight = marginSize + "px"; el.style.marginLeft  = "0px";
+      }
+      else {
+         el.style.marginLeft = marginSize + "px"; el.style.marginRight = marginSize + "px";
+      }
+   },
+
+   _setBorder: function(el,n,corners) {
+      var borderSize = this._borderSize(n);
+      var whichSide = corners == "top" ? this._whichSideTop() : this._whichSideBottom();
+      if ( whichSide == "left" ) {
+         el.style.borderLeftWidth = borderSize + "px"; el.style.borderRightWidth = "0px";
+      }
+      else if ( whichSide == "right" ) {
+         el.style.borderRightWidth = borderSize + "px"; el.style.borderLeftWidth  = "0px";
+      }
+      else {
+         el.style.borderLeftWidth = borderSize + "px"; el.style.borderRightWidth = borderSize + "px";
+      }
+      if (this.options.border != false)
+        el.style.borderLeftWidth = borderSize + "px"; el.style.borderRightWidth = borderSize + "px";
+   },
+
+   _marginSize: function(n) {
+      if ( this._isTransparent() )
+         return 0;
+
+      var marginSizes          = [ 5, 3, 2, 1 ];
+      var blendedMarginSizes   = [ 3, 2, 1, 0 ];
+      var compactMarginSizes   = [ 2, 1 ];
+      var smBlendedMarginSizes = [ 1, 0 ];
+
+      if ( this.options.compact && this.options.blend )
+         return smBlendedMarginSizes[n];
+      else if ( this.options.compact )
+         return compactMarginSizes[n];
+      else if ( this.options.blend )
+         return blendedMarginSizes[n];
+      else
+         return marginSizes[n];
+   },
+
+   _borderSize: function(n) {
+      var transparentBorderSizes = [ 5, 3, 2, 1 ];
+      var blendedBorderSizes     = [ 2, 1, 1, 1 ];
+      var compactBorderSizes     = [ 1, 0 ];
+      var actualBorderSizes      = [ 0, 2, 0, 0 ];
+
+      if ( this.options.compact && (this.options.blend || this._isTransparent()) )
+         return 1;
+      else if ( this.options.compact )
+         return compactBorderSizes[n];
+      else if ( this.options.blend )
+         return blendedBorderSizes[n];
+      else if ( this.options.border )
+         return actualBorderSizes[n];
+      else if ( this._isTransparent() )
+         return transparentBorderSizes[n];
+      return 0;
+   },
+
+   _hasString: function(str) { for(var i=1 ; i<arguments.length ; i++) if (str.indexOf(arguments[i]) >= 0) return true; return false; },
+   _blend: function(c1, c2) { var cc1 = Rico.Color.createFromHex(c1); cc1.blend(Rico.Color.createFromHex(c2)); return cc1; },
+   _background: function(el) { try { return Rico.Color.createColorFromBackground(el).asHex(); } catch(err) { return "#ffffff"; } },
+   _isTransparent: function() { return this.options.color == "transparent"; },
+   _isTopRounded: function() { return this._hasString(this.options.corners, "all", "top", "tl", "tr"); },
+   _isBottomRounded: function() { return this._hasString(this.options.corners, "all", "bottom", "bl", "br"); },
+   _hasSingleTextChild: function(el) { return el.childNodes.length == 1 && el.childNodes[0].nodeType == 3; }
+}
+
+
+//-------------------- ricoDragAndDrop.js
+Rico.DragAndDrop = Class.create();
+
+Rico.DragAndDrop.prototype = {
+
+   initialize: function() {
+      this.dropZones                = new Array();
+      this.draggables               = new Array();
+      this.currentDragObjects       = new Array();
+      this.dragElement              = null;
+      this.lastSelectedDraggable    = null;
+      this.currentDragObjectVisible = false;
+      this.interestedInMotionEvents = false;
+      this._mouseDown = this._mouseDownHandler.bindAsEventListener(this);
+      this._mouseMove = this._mouseMoveHandler.bindAsEventListener(this);
+      this._mouseUp = this._mouseUpHandler.bindAsEventListener(this);
+   },
+
+   registerDropZone: function(aDropZone) {
+      this.dropZones[ this.dropZones.length ] = aDropZone;
+   },
+
+   deregisterDropZone: function(aDropZone) {
+      var newDropZones = new Array();
+      var j = 0;
+      for ( var i = 0 ; i < this.dropZones.length ; i++ ) {
+         if ( this.dropZones[i] != aDropZone )
+            newDropZones[j++] = this.dropZones[i];
+      }
+
+      this.dropZones = newDropZones;
+   },
+
+   clearDropZones: function() {
+      this.dropZones = new Array();
+   },
+
+   registerDraggable: function( aDraggable ) {
+      this.draggables[ this.draggables.length ] = aDraggable;
+      this._addMouseDownHandler( aDraggable );
+   },
+
+   clearSelection: function() {
+      for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
+         this.currentDragObjects[i].deselect();
+      this.currentDragObjects = new Array();
+      this.lastSelectedDraggable = null;
+   },
+
+   hasSelection: function() {
+      return this.currentDragObjects.length > 0;
+   },
+
+   setStartDragFromElement: function( e, mouseDownElement ) {
+      this.origPos = RicoUtil.toDocumentPosition(mouseDownElement);
+      this.startx = e.screenX - this.origPos.x
+      this.starty = e.screenY - this.origPos.y
+      //this.startComponentX = e.layerX ? e.layerX : e.offsetX;
+      //this.startComponentY = e.layerY ? e.layerY : e.offsetY;
+      //this.adjustedForDraggableSize = false;
+
+      this.interestedInMotionEvents = this.hasSelection();
+      this._terminateEvent(e);
+   },
+
+   updateSelection: function( draggable, extendSelection ) {
+      if ( ! extendSelection )
+         this.clearSelection();
+
+      if ( draggable.isSelected() ) {
+         this.currentDragObjects.removeItem(draggable);
+         draggable.deselect();
+         if ( draggable == this.lastSelectedDraggable )
+            this.lastSelectedDraggable = null;
+      }
+      else {
+         this.currentDragObjects[ this.currentDragObjects.length ] = draggable;
+         draggable.select();
+         this.lastSelectedDraggable = draggable;
+      }
+   },
+
+   _mouseDownHandler: function(e) {
+      if ( arguments.length == 0 )
+         e = event;
+
+      // if not button 1 ignore it...
+      var nsEvent = e.which != undefined;
+      if ( (nsEvent && e.which != 1) || (!nsEvent && e.button != 1))
+         return;
+
+      var eventTarget      = e.target ? e.target : e.srcElement;
+      var draggableObject  = eventTarget.draggable;
+
+      var candidate = eventTarget;
+      while (draggableObject == null && candidate.parentNode) {
+         candidate = candidate.parentNode;
+         draggableObject = candidate.draggable;
+      }
+   
+      if ( draggableObject == null )
+         return;
+
+      this.updateSelection( draggableObject, e.ctrlKey );
+
+      // clear the drop zones postion cache...
+      if ( this.hasSelection() )
+         for ( var i = 0 ; i < this.dropZones.length ; i++ )
+            this.dropZones[i].clearPositionCache();
+
+      this.setStartDragFromElement( e, draggableObject.getMouseDownHTMLElement() );
+   },
+
+
+   _mouseMoveHandler: function(e) {
+      var nsEvent = e.which != undefined;
+      if ( !this.interestedInMotionEvents ) {
+         //this._terminateEvent(e);
+         return;
+      }
+
+      if ( ! this.hasSelection() )
+         return;
+
+      if ( ! this.currentDragObjectVisible )
+         this._startDrag(e);
+
+      if ( !this.activatedDropZones )
+         this._activateRegisteredDropZones();
+
+      //if ( !this.adjustedForDraggableSize )
+      //   this._adjustForDraggableSize(e);
+
+      this._updateDraggableLocation(e);
+      this._updateDropZonesHover(e);
+
+      this._terminateEvent(e);
+   },
+
+   _makeDraggableObjectVisible: function(e)
+   {
+      if ( !this.hasSelection() )
+         return;
+
+      var dragElement;
+      if ( this.currentDragObjects.length > 1 )
+         dragElement = this.currentDragObjects[0].getMultiObjectDragGUI(this.currentDragObjects);
+      else
+         dragElement = this.currentDragObjects[0].getSingleObjectDragGUI();
+
+      // go ahead and absolute position it...
+      if ( RicoUtil.getElementsComputedStyle(dragElement, "position")  != "absolute" )
+         dragElement.style.position = "absolute";
+
+      // need to parent him into the document...
+      if ( dragElement.parentNode == null || dragElement.parentNode.nodeType == 11 )
+         document.body.appendChild(dragElement);
+
+      this.dragElement = dragElement;
+      this._updateDraggableLocation(e);
+
+      this.currentDragObjectVisible = true;
+   },
+
+   /**
+   _adjustForDraggableSize: function(e) {
+      var dragElementWidth  = this.dragElement.offsetWidth;
+      var dragElementHeight = this.dragElement.offsetHeight;
+      if ( this.startComponentX > dragElementWidth )
+         this.startx -= this.startComponentX - dragElementWidth + 2;
+      if ( e.offsetY ) {
+         if ( this.startComponentY > dragElementHeight )
+            this.starty -= this.startComponentY - dragElementHeight + 2;
+      }
+      this.adjustedForDraggableSize = true;
+   },
+   **/
+
+   _leftOffset: function(e) {
+	   return e.offsetX ? document.body.scrollLeft : 0
+	},
+
+   _topOffset: function(e) {
+	   return e.offsetY ? document.body.scrollTop:0
+	},
+
+		
+   _updateDraggableLocation: function(e) {
+      var dragObjectStyle = this.dragElement.style;
+      dragObjectStyle.left = (e.screenX + this._leftOffset(e) - this.startx) + "px"
+      dragObjectStyle.top  = (e.screenY + this._topOffset(e) - this.starty) + "px";
+   },
+
+   _updateDropZonesHover: function(e) {
+      var n = this.dropZones.length;
+      for ( var i = 0 ; i < n ; i++ ) {
+         if ( ! this._mousePointInDropZone( e, this.dropZones[i] ) )
+            this.dropZones[i].hideHover();
+      }
+
+      for ( var i = 0 ; i < n ; i++ ) {
+         if ( this._mousePointInDropZone( e, this.dropZones[i] ) ) {
+            if ( this.dropZones[i].canAccept(this.currentDragObjects) )
+               this.dropZones[i].showHover();
+         }
+      }
+   },
+
+   _startDrag: function(e) {
+      for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
+         this.currentDragObjects[i].startDrag();
+
+      this._makeDraggableObjectVisible(e);
+   },
+
+   _mouseUpHandler: function(e) {
+      if ( ! this.hasSelection() )
+         return;
+
+      var nsEvent = e.which != undefined;
+      if ( (nsEvent && e.which != 1) || (!nsEvent && e.button != 1))
+         return;
+
+      this.interestedInMotionEvents = false;
+
+      if ( this.dragElement == null ) {
+         this._terminateEvent(e);
+         return;
+      }
+
+      if ( this._placeDraggableInDropZone(e) )
+         this._completeDropOperation(e);
+      else {
+         this._terminateEvent(e);
+         new Rico.Effect.Position( this.dragElement,
+                              this.origPos.x,
+                              this.origPos.y,
+                              200,
+                              20,
+                              { complete : this._doCancelDragProcessing.bind(this) } );
+      }
+
+     Event.stopObserving(document.body, "mousemove", this._mouseMove);
+     Event.stopObserving(document.body, "mouseup",  this._mouseUp);
+   },
+
+   _retTrue: function () {
+      return true;
+   },
+
+   _completeDropOperation: function(e) {
+      if ( this.dragElement != this.currentDragObjects[0].getMouseDownHTMLElement() ) {
+         if ( this.dragElement.parentNode != null )
+            this.dragElement.parentNode.removeChild(this.dragElement);
+      }
+
+      this._deactivateRegisteredDropZones();
+      this._endDrag();
+      this.clearSelection();
+      this.dragElement = null;
+      this.currentDragObjectVisible = false;
+      this._terminateEvent(e);
+   },
+
+   _doCancelDragProcessing: function() {
+      this._cancelDrag();
+
+        if ( this.dragElement != this.currentDragObjects[0].getMouseDownHTMLElement() && this.dragElement)
+           if ( this.dragElement.parentNode != null )
+              this.dragElement.parentNode.removeChild(this.dragElement);
+
+
+      this._deactivateRegisteredDropZones();
+      this.dragElement = null;
+      this.currentDragObjectVisible = false;
+   },
+
+   _placeDraggableInDropZone: function(e) {
+      var foundDropZone = false;
+      var n = this.dropZones.length;
+      for ( var i = 0 ; i < n ; i++ ) {
+         if ( this._mousePointInDropZone( e, this.dropZones[i] ) ) {
+            if ( this.dropZones[i].canAccept(this.currentDragObjects) ) {
+               this.dropZones[i].hideHover();
+               this.dropZones[i].accept(this.currentDragObjects);
+               foundDropZone = true;
+               break;
+            }
+         }
+      }
+
+      return foundDropZone;
+   },
+
+   _cancelDrag: function() {
+      for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
+         this.currentDragObjects[i].cancelDrag();
+   },
+
+   _endDrag: function() {
+      for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
+         this.currentDragObjects[i].endDrag();
+   },
+
+   _mousePointInDropZone: function( e, dropZone ) {
+
+      var absoluteRect = dropZone.getAbsoluteRect();
+
+      return e.clientX  > absoluteRect.left + this._leftOffset(e) &&
+             e.clientX  < absoluteRect.right + this._leftOffset(e) &&
+             e.clientY  > absoluteRect.top + this._topOffset(e)   &&
+             e.clientY  < absoluteRect.bottom + this._topOffset(e);
+   },
+
+   _addMouseDownHandler: function( aDraggable )
+   {
+       htmlElement  = aDraggable.getMouseDownHTMLElement();
+      if ( htmlElement  != null ) { 
+         htmlElement.draggable = aDraggable;
+         Event.observe(htmlElement , "mousedown", this._onmousedown.bindAsEventListener(this));
+         Event.observe(htmlElement, "mousedown", this._mouseDown);
+      }
+   },
+
+   _activateRegisteredDropZones: function() {
+      var n = this.dropZones.length;
+      for ( var i = 0 ; i < n ; i++ ) {
+         var dropZone = this.dropZones[i];
+         if ( dropZone.canAccept(this.currentDragObjects) )
+            dropZone.activate();
+      }
+
+      this.activatedDropZones = true;
+   },
+
+   _deactivateRegisteredDropZones: function() {
+      var n = this.dropZones.length;
+      for ( var i = 0 ; i < n ; i++ )
+         this.dropZones[i].deactivate();
+      this.activatedDropZones = false;
+   },
+
+   _onmousedown: function () {
+     Event.observe(document.body, "mousemove", this._mouseMove);
+     Event.observe(document.body, "mouseup",  this._mouseUp);
+   },
+
+   _terminateEvent: function(e) {
+      if ( e.stopPropagation != undefined )
+         e.stopPropagation();
+      else if ( e.cancelBubble != undefined )
+         e.cancelBubble = true;
+
+      if ( e.preventDefault != undefined )
+         e.preventDefault();
+      else
+         e.returnValue = false;
+   },
+
+
+	   initializeEventHandlers: function() {
+	      if ( typeof document.implementation != "undefined" &&
+	         document.implementation.hasFeature("HTML",   "1.0") &&
+	         document.implementation.hasFeature("Events", "2.0") &&
+	         document.implementation.hasFeature("CSS",    "2.0") ) {
+	         document.addEventListener("mouseup",   this._mouseUpHandler.bindAsEventListener(this),  false);
+	         document.addEventListener("mousemove", this._mouseMoveHandler.bindAsEventListener(this), false);
+	      }
+              else if (document.attachEvent) {
+                 document.attachEvent( "onmouseup",   this._mouseUpHandler.bindAsEventListener(this) );
+                 document.attachEvent( "onmousemove", this._mouseMoveHandler.bindAsEventListener(this) );
+              } else {
+               document.onmouseup = this._mouseUpHandler.bindAsEventListener(this);
+               document.onmousemove = this._mouseMoveHandler.bindAsEventListener(this);
+	      }
+	   }
+	}
+
+	var dndMgr = new Rico.DragAndDrop();
+	dndMgr.initializeEventHandlers();
+
+
+//-------------------- ricoDraggable.js
+Rico.Draggable = Class.create();
+
+Rico.Draggable.prototype = {
+
+   initialize: function( type, htmlElement ) {
+      this.type          = type;
+      this.htmlElement   = $(htmlElement);
+      this.selected      = false;
+   },
+
+   /**
+    *   Returns the HTML element that should have a mouse down event
+    *   added to it in order to initiate a drag operation
+    *
+    **/
+   getMouseDownHTMLElement: function() {
+      return this.htmlElement;
+   },
+
+   select: function() {
+      this.selected = true;
+
+      if ( this.showingSelected )
+         return;
+
+      var htmlElement = this.getMouseDownHTMLElement();
+
+      var color = Rico.Color.createColorFromBackground(htmlElement);
+      color.isBright() ? color.darken(0.033) : color.brighten(0.033);
+
+      this.saveBackground = RicoUtil.getElementsComputedStyle(htmlElement, "backgroundColor", "background-color");
+      htmlElement.style.backgroundColor = color.asHex();
+      this.showingSelected = true;
+   },
+
+   deselect: function() {
+      this.selected = false;
+      if ( !this.showingSelected )
+         return;
+
+      var htmlElement = this.getMouseDownHTMLElement();
+
+      htmlElement.style.backgroundColor = this.saveBackground;
+      this.showingSelected = false;
+   },
+
+   isSelected: function() {
+      return this.selected;
+   },
+
+   startDrag: function() {
+   },
+
+   cancelDrag: function() {
+   },
+
+   endDrag: function() {
+   },
+
+   getSingleObjectDragGUI: function() {
+      return this.htmlElement;
+   },
+
+   getMultiObjectDragGUI: function( draggables ) {
+      return this.htmlElement;
+   },
+
+   getDroppedGUI: function() {
+      return this.htmlElement;
+   },
+
+   toString: function() {
+      return this.type + ":" + this.htmlElement + ":";
+   }
+
+}
+
+
+//-------------------- ricoDropzone.js
+Rico.Dropzone = Class.create();
+
+Rico.Dropzone.prototype = {
+
+   initialize: function( htmlElement ) {
+      this.htmlElement  = $(htmlElement);
+      this.absoluteRect = null;
+   },
+
+   getHTMLElement: function() {
+      return this.htmlElement;
+   },
+
+   clearPositionCache: function() {
+      this.absoluteRect = null;
+   },
+
+   getAbsoluteRect: function() {
+      if ( this.absoluteRect == null ) {
+         var htmlElement = this.getHTMLElement();
+         var pos = RicoUtil.toViewportPosition(htmlElement);
+
+         this.absoluteRect = {
+            top:    pos.y,
+            left:   pos.x,
+            bottom: pos.y + htmlElement.offsetHeight,
+            right:  pos.x + htmlElement.offsetWidth
+         };
+      }
+      return this.absoluteRect;
+   },
+
+   activate: function() {
+      var htmlElement = this.getHTMLElement();
+      if (htmlElement == null  || this.showingActive)
+         return;
+
+      this.showingActive = true;
+      this.saveBackgroundColor = htmlElement.style.backgroundColor;
+
+      var fallbackColor = "#ffea84";
+      var currentColor = Rico.Color.createColorFromBackground(htmlElement);
+      if ( currentColor == null )
+         htmlElement.style.backgroundColor = fallbackColor;
+      else {
+         currentColor.isBright() ? currentColor.darken(0.2) : currentColor.brighten(0.2);
+         htmlElement.style.backgroundColor = currentColor.asHex();
+      }
+   },
+
+   deactivate: function() {
+      var htmlElement = this.getHTMLElement();
+      if (htmlElement == null || !this.showingActive)
+         return;
+
+      htmlElement.style.backgroundColor = this.saveBackgroundColor;
+      this.showingActive = false;
+      this.saveBackgroundColor = null;
+   },
+
+   showHover: function() {
+      var htmlElement = this.getHTMLElement();
+      if ( htmlElement == null || this.showingHover )
+         return;
+
+      this.saveBorderWidth = htmlElement.style.borderWidth;
+      this.saveBorderStyle = htmlElement.style.borderStyle;
+      this.saveBorderColor = htmlElement.style.borderColor;
+
+      this.showingHover = true;
+      htmlElement.style.borderWidth = "1px";
+      htmlElement.style.borderStyle = "solid";
+      //htmlElement.style.borderColor = "#ff9900";
+      htmlElement.style.borderColor = "#ffff00";
+   },
+
+   hideHover: function() {
+      var htmlElement = this.getHTMLElement();
+      if ( htmlElement == null || !this.showingHover )
+         return;
+
+      htmlElement.style.borderWidth = this.saveBorderWidth;
+      htmlElement.style.borderStyle = this.saveBorderStyle;
+      htmlElement.style.borderColor = this.saveBorderColor;
+      this.showingHover = false;
+   },
+
+   canAccept: function(draggableObjects) {
+      return true;
+   },
+
+   accept: function(draggableObjects) {
+      var htmlElement = this.getHTMLElement();
+      if ( htmlElement == null )
+         return;
+
+      n = draggableObjects.length;
+      for ( var i = 0 ; i < n ; i++ )
+      {
+         var theGUI = draggableObjects[i].getDroppedGUI();
+         if ( RicoUtil.getElementsComputedStyle( theGUI, "position" ) == "absolute" )
+         {
+            theGUI.style.position = "static";
+            theGUI.style.top = "";
+            theGUI.style.top = "";
+         }
+         htmlElement.appendChild(theGUI);
+      }
+   }
+}
+
+
+//-------------------- ricoEffects.js
+
+Rico.Effect = {};
+
+Rico.Effect.SizeAndPosition = Class.create();
+Rico.Effect.SizeAndPosition.prototype = {
+
+   initialize: function(element, x, y, w, h, duration, steps, options) {
+      this.element = $(element);
+      this.x = x;
+      this.y = y;
+      this.w = w;
+      this.h = h;
+      this.duration = duration;
+      this.steps    = steps;
+      this.options  = arguments[7] || {};
+
+      this.sizeAndPosition();
+   },
+
+   sizeAndPosition: function() {
+      if (this.isFinished()) {
+         if(this.options.complete) this.options.complete(this);
+         return;
+      }
+
+      if (this.timer)
+         clearTimeout(this.timer);
+
+      var stepDuration = Math.round(this.duration/this.steps) ;
+
+      // Get original values: x,y = top left corner;  w,h = width height
+      var currentX = this.element.offsetLeft;
+      var currentY = this.element.offsetTop;
+      var currentW = this.element.offsetWidth;
+      var currentH = this.element.offsetHeight;
+
+      // If values not set, or zero, we do not modify them, and take original as final as well
+      this.x = (this.x) ? this.x : currentX;
+      this.y = (this.y) ? this.y : currentY;
+      this.w = (this.w) ? this.w : currentW;
+      this.h = (this.h) ? this.h : currentH;
+
+      // how much do we need to modify our values for each step?
+      var difX = this.steps >  0 ? (this.x - currentX)/this.steps : 0;
+      var difY = this.steps >  0 ? (this.y - currentY)/this.steps : 0;
+      var difW = this.steps >  0 ? (this.w - currentW)/this.steps : 0;
+      var difH = this.steps >  0 ? (this.h - currentH)/this.steps : 0;
+
+      this.moveBy(difX, difY);
+      this.resizeBy(difW, difH);
+
+      this.duration -= stepDuration;
+      this.steps--;
+
+      this.timer = setTimeout(this.sizeAndPosition.bind(this), stepDuration);
+   },
+
+   isFinished: function() {
+      return this.steps <= 0;
+   },
+
+   moveBy: function( difX, difY ) {
+      var currentLeft = this.element.offsetLeft;
+      var currentTop  = this.element.offsetTop;
+      var intDifX     = parseInt(difX);
+      var intDifY     = parseInt(difY);
+
+      var style = this.element.style;
+      if ( intDifX != 0 )
+         style.left = (currentLeft + intDifX) + "px";
+      if ( intDifY != 0 )
+         style.top  = (currentTop + intDifY) + "px";
+   },
+
+   resizeBy: function( difW, difH ) {
+      var currentWidth  = this.element.offsetWidth;
+      var currentHeight = this.element.offsetHeight;
+      var intDifW       = parseInt(difW);
+      var intDifH       = parseInt(difH);
+
+      var style = this.element.style;
+      if ( intDifW != 0 )
+         style.width   = (currentWidth  + intDifW) + "px";
+      if ( intDifH != 0 )
+         style.height  = (currentHeight + intDifH) + "px";
+   }
+}
+
+Rico.Effect.Size = Class.create();
+Rico.Effect.Size.prototype = {
+
+   initialize: function(element, w, h, duration, steps, options) {
+      new Rico.Effect.SizeAndPosition(element, null, null, w, h, duration, steps, options);
+  }
+}
+
+Rico.Effect.Position = Class.create();
+Rico.Effect.Position.prototype = {
+
+   initialize: function(element, x, y, duration, steps, options) {
+      new Rico.Effect.SizeAndPosition(element, x, y, null, null, duration, steps, options);
+  }
+}
+
+Rico.Effect.Round = Class.create();
+Rico.Effect.Round.prototype = {
+
+   initialize: function(tagName, className, options) {
+      var elements = document.getElementsByTagAndClassName(tagName,className);
+      for ( var i = 0 ; i < elements.length ; i++ )
+         Rico.Corner.round( elements[i], options );
+   }
+};
+
+Rico.Effect.FadeTo = Class.create();
+Rico.Effect.FadeTo.prototype = {
+
+   initialize: function( element, opacity, duration, steps, options) {
+      this.element  = $(element);
+      this.opacity  = opacity;
+      this.duration = duration;
+      this.steps    = steps;
+      this.options  = arguments[4] || {};
+      this.fadeTo();
+   },
+
+   fadeTo: function() {
+      if (this.isFinished()) {
+         if(this.options.complete) this.options.complete(this);
+         return;
+      }
+
+      if (this.timer)
+         clearTimeout(this.timer);
+
+      var stepDuration = Math.round(this.duration/this.steps) ;
+      var currentOpacity = this.getElementOpacity();
+      var delta = this.steps > 0 ? (this.opacity - currentOpacity)/this.steps : 0;
+
+      this.changeOpacityBy(delta);
+      this.duration -= stepDuration;
+      this.steps--;
+
+      this.timer = setTimeout(this.fadeTo.bind(this), stepDuration);
+   },
+
+   changeOpacityBy: function(v) {
+      var currentOpacity = this.getElementOpacity();
+      var newOpacity = Math.max(0, Math.min(currentOpacity+v, 1));
+      this.element.ricoOpacity = newOpacity;
+
+      this.element.style.filter = "alpha(opacity:"+Math.round(newOpacity*100)+")";
+      this.element.style.opacity = newOpacity; /*//*/;
+   },
+
+   isFinished: function() {
+      return this.steps <= 0;
+   },
+
+   getElementOpacity: function() {
+      if ( this.element.ricoOpacity == undefined ) {
+         var opacity = RicoUtil.getElementsComputedStyle(this.element, 'opacity');
+         this.element.ricoOpacity = opacity != undefined ? opacity : 1.0;
+      }
+      return parseFloat(this.element.ricoOpacity);
+   }
+}
+
+Rico.Effect.AccordionSize = Class.create();
+
+Rico.Effect.AccordionSize.prototype = {
+
+   initialize: function(e1, e2, start, end, duration, steps, options) {
+      this.e1       = $(e1);
+      this.e2       = $(e2);
+      this.start    = start;
+      this.end      = end;
+      this.duration = duration;
+      this.steps    = steps;
+      this.options  = arguments[6] || {};
+
+      this.accordionSize();
+   },
+
+   accordionSize: function() {
+
+      if (this.isFinished()) {
+         // just in case there are round errors or such...
+         this.e1.style.height = this.start + "px";
+         this.e2.style.height = this.end + "px";
+
+         if(this.options.complete)
+            this.options.complete(this);
+         return;
+      }
+
+      if (this.timer)
+         clearTimeout(this.timer);
+
+      var stepDuration = Math.round(this.duration/this.steps) ;
+
+      var diff = this.steps > 0 ? (parseInt(this.e1.offsetHeight) - this.start)/this.steps : 0;
+      this.resizeBy(diff);
+
+      this.duration -= stepDuration;
+      this.steps--;
+
+      this.timer = setTimeout(this.accordionSize.bind(this), stepDuration);
+   },
+
+   isFinished: function() {
+      return this.steps <= 0;
+   },
+
+   resizeBy: function(diff) {
+      var h1Height = this.e1.offsetHeight;
+      var h2Height = this.e2.offsetHeight;
+      var intDiff = parseInt(diff);
+      if ( diff != 0 ) {
+         this.e1.style.height = (h1Height - intDiff) + "px";
+         this.e2.style.height = (h2Height + intDiff) + "px";
+      }
+   }
+
+};
+
+
+//-------------------- ricoLiveGrid.js
+// Rico.LiveGridMetaData -----------------------------------------------------
+
+Rico.LiveGridMetaData = Class.create();
+
+Rico.LiveGridMetaData.prototype = {
+
+   initialize: function( pageSize, totalRows, columnCount, options ) {
+      this.pageSize  = pageSize;
+      this.totalRows = totalRows;
+      this.setOptions(options);
+      this.ArrowHeight = 16;
+      this.columnCount = columnCount;
+   },
+
+   setOptions: function(options) {
+      this.options = {
+         largeBufferSize    : 7.0,   // 7 pages
+         nearLimitFactor    : 0.2    // 20% of buffer
+      };
+      Object.extend(this.options, options || {});
+   },
+
+   getPageSize: function() {
+      return this.pageSize;
+   },
+
+   getTotalRows: function() {
+      return this.totalRows;
+   },
+
+   setTotalRows: function(n) {
+      this.totalRows = n;
+   },
+
+   getLargeBufferSize: function() {
+      return parseInt(this.options.largeBufferSize * this.pageSize);
+   },
+
+   getLimitTolerance: function() {
+      return parseInt(this.getLargeBufferSize() * this.options.nearLimitFactor);
+   }
+};
+
+// Rico.LiveGridScroller -----------------------------------------------------
+
+Rico.LiveGridScroller = Class.create();
+
+Rico.LiveGridScroller.prototype = {
+
+   initialize: function(liveGrid, viewPort) {
+      this.isIE = navigator.userAgent.toLowerCase().indexOf("msie") >= 0;
+      this.liveGrid = liveGrid;
+      this.metaData = liveGrid.metaData;
+      this.createScrollBar();
+      this.scrollTimeout = null;
+      this.lastScrollPos = 0;
+      this.viewPort = viewPort;
+      this.rows = new Array();
+   },
+
+   isUnPlugged: function() {
+      return this.scrollerDiv.onscroll == null;
+   },
+
+   plugin: function() {
+      this.scrollerDiv.onscroll = this.handleScroll.bindAsEventListener(this);
+   },
+
+   unplug: function() {
+      this.scrollerDiv.onscroll = null;
+   },
+
+   sizeIEHeaderHack: function() {
+      if ( !this.isIE ) return;
+      var headerTable = $(this.liveGrid.tableId + "_header");
+      if ( headerTable )
+         headerTable.rows[0].cells[0].style.width =
+            (headerTable.rows[0].cells[0].offsetWidth + 1) + "px";
+   },
+
+   createScrollBar: function() {
+      var visibleHeight = this.liveGrid.viewPort.visibleHeight();
+      // create the outer div...
+      this.scrollerDiv  = document.createElement("div");
+      var scrollerStyle = this.scrollerDiv.style;
+      scrollerStyle.borderRight = this.liveGrid.options.scrollerBorderRight;
+      scrollerStyle.position    = "relative";
+      scrollerStyle.left        = this.isIE ? "-6px" : "-3px";
+      scrollerStyle.width       = "19px";
+      scrollerStyle.height      = visibleHeight + "px";
+      scrollerStyle.overflow    = "auto";
+
+      // create the inner div...
+      this.heightDiv = document.createElement("div");
+      this.heightDiv.style.width  = "1px";
+
+      this.heightDiv.style.height = parseInt(visibleHeight *
+                        this.metaData.getTotalRows()/this.metaData.getPageSize()) + "px" ;
+      this.scrollerDiv.appendChild(this.heightDiv);
+      this.scrollerDiv.onscroll = this.handleScroll.bindAsEventListener(this);
+
+     var table = this.liveGrid.table;
+     table.parentNode.parentNode.insertBefore( this.scrollerDiv, table.parentNode.nextSibling );
+  	  var eventName = this.isIE ? "mousewheel" : "DOMMouseScroll";
+	  Event.observe(table, eventName, 
+	                function(evt) {
+	                   if (evt.wheelDelta>=0 || evt.detail < 0) //wheel-up
+	                      this.scrollerDiv.scrollTop -= (2*this.viewPort.rowHeight);
+	                   else
+	                      this.scrollerDiv.scrollTop += (2*this.viewPort.rowHeight);
+	                   this.handleScroll(false);
+	                }.bindAsEventListener(this), 
+	                false);
+     },
+
+   updateSize: function() {
+      var table = this.liveGrid.table;
+      var visibleHeight = this.viewPort.visibleHeight();
+      this.heightDiv.style.height = parseInt(visibleHeight *
+                                  this.metaData.getTotalRows()/this.metaData.getPageSize()) + "px";
+   },
+
+   rowToPixel: function(rowOffset) {
+      return (rowOffset / this.metaData.getTotalRows()) * this.heightDiv.offsetHeight
+   },
+   
+   moveScroll: function(rowOffset) {
+      this.scrollerDiv.scrollTop = this.rowToPixel(rowOffset);
+      if ( this.metaData.options.onscroll )
+         this.metaData.options.onscroll( this.liveGrid, rowOffset );
+   },
+
+   handleScroll: function() {
+     if ( this.scrollTimeout )
+         clearTimeout( this.scrollTimeout );
+
+    var scrollDiff = this.lastScrollPos-this.scrollerDiv.scrollTop;
+    if (scrollDiff != 0.00) {
+       var r = this.scrollerDiv.scrollTop % this.viewPort.rowHeight;
+       if (r != 0) {
+          this.unplug();
+          if ( scrollDiff < 0 ) {
+             this.scrollerDiv.scrollTop += (this.viewPort.rowHeight-r);
+          } else {
+             this.scrollerDiv.scrollTop -= r;
+          }
+          this.plugin();
+       }
+    }
+    var contentOffset = parseInt(this.scrollerDiv.scrollTop / this.viewPort.rowHeight);
+    this.liveGrid.requestContentRefresh(contentOffset);
+    this.viewPort.scrollTo(this.scrollerDiv.scrollTop);
+
+    if ( this.metaData.options.onscroll )
+       this.metaData.options.onscroll( this.liveGrid, contentOffset );
+
+    this.scrollTimeout = setTimeout(this.scrollIdle.bind(this), 1200 );
+    this.lastScrollPos = this.scrollerDiv.scrollTop;
+
+   },
+
+   scrollIdle: function() {
+      if ( this.metaData.options.onscrollidle )
+         this.metaData.options.onscrollidle();
+   }
+};
+
+// Rico.LiveGridBuffer -----------------------------------------------------
+
+Rico.LiveGridBuffer = Class.create();
+
+Rico.LiveGridBuffer.prototype = {
+
+   initialize: function(metaData, viewPort) {
+      this.startPos = 0;
+      this.size     = 0;
+      this.metaData = metaData;
+      this.rows     = new Array();
+      this.updateInProgress = false;
+      this.viewPort = viewPort;
+      this.maxBufferSize = metaData.getLargeBufferSize() * 2;
+      this.maxFetchSize = metaData.getLargeBufferSize();
+      this.lastOffset = 0;
+   },
+
+   getBlankRow: function() {
+      if (!this.blankRow ) {
+         this.blankRow = new Array();
+         for ( var i=0; i < this.metaData.columnCount ; i++ ) 
+            this.blankRow[i] = "&nbsp;";
+     }
+     return this.blankRow;
+   },
+
+   loadRows: function(ajaxResponse) {
+      var rowsElement = ajaxResponse.getElementsByTagName('rows')[0];
+      this.updateUI = rowsElement.getAttribute("update_ui") == "true"
+      var newRows = new Array()
+      var trs = rowsElement.getElementsByTagName("tr");
+      for ( var i=0 ; i < trs.length; i++ ) {
+         var row = newRows[i] = new Array(); 
+         var cells = trs[i].getElementsByTagName("td");
+         for ( var j=0; j < cells.length ; j++ ) {
+            var cell = cells[j];
+            var convertSpaces = cell.getAttribute("convert_spaces") == "true";
+            var cellContent = RicoUtil.getContentAsString(cell);
+            row[j] = convertSpaces ? this.convertSpaces(cellContent) : cellContent;
+            if (!row[j]) 
+               row[j] = '&nbsp;';
+         }
+      }
+      return newRows;
+   },
+      
+   update: function(ajaxResponse, start) {
+     var newRows = this.loadRows(ajaxResponse);
+      if (this.rows.length == 0) { // initial load
+         this.rows = newRows;
+         this.size = this.rows.length;
+         this.startPos = start;
+         return;
+      }
+      if (start > this.startPos) { //appending
+         if (this.startPos + this.rows.length < start) {
+            this.rows =  newRows;
+            this.startPos = start;//
+         } else {
+              this.rows = this.rows.concat( newRows.slice(0, newRows.length));
+            if (this.rows.length > this.maxBufferSize) {
+               var fullSize = this.rows.length;
+               this.rows = this.rows.slice(this.rows.length - this.maxBufferSize, this.rows.length)
+               this.startPos = this.startPos +  (fullSize - this.rows.length);
+            }
+         }
+      } else { //prepending
+         if (start + newRows.length < this.startPos) {
+            this.rows =  newRows;
+         } else {
+            this.rows = newRows.slice(0, this.startPos).concat(this.rows);
+            if (this.rows.length > this.maxBufferSize) 
+               this.rows = this.rows.slice(0, this.maxBufferSize)
+         }
+         this.startPos =  start;
+      }
+      this.size = this.rows.length;
+   },
+   
+   clear: function() {
+      this.rows = new Array();
+      this.startPos = 0;
+      this.size = 0;
+   },
+
+   isOverlapping: function(start, size) {
+      return ((start < this.endPos()) && (this.startPos < start + size)) || (this.endPos() == 0)
+   },
+
+   isInRange: function(position) {
+      return (position >= this.startPos) && (position + this.metaData.getPageSize() <= this.endPos()); 
+             //&& this.size()  != 0;
+   },
+
+   isNearingTopLimit: function(position) {
+      return position - this.startPos < this.metaData.getLimitTolerance();
+   },
+
+   endPos: function() {
+      return this.startPos + this.rows.length;
+   },
+   
+   isNearingBottomLimit: function(position) {
+      return this.endPos() - (position + this.metaData.getPageSize()) < this.metaData.getLimitTolerance();
+   },
+
+   isAtTop: function() {
+      return this.startPos == 0;
+   },
+
+   isAtBottom: function() {
+      return this.endPos() == this.metaData.getTotalRows();
+   },
+
+   isNearingLimit: function(position) {
+      return ( !this.isAtTop()    && this.isNearingTopLimit(position)) ||
+             ( !this.isAtBottom() && this.isNearingBottomLimit(position) )
+   },
+
+   getFetchSize: function(offset) {
+      var adjustedOffset = this.getFetchOffset(offset);
+      var adjustedSize = 0;
+      if (adjustedOffset >= this.startPos) { //apending
+         var endFetchOffset = this.maxFetchSize  + adjustedOffset;
+         if (endFetchOffset > this.metaData.totalRows)
+            endFetchOffset = this.metaData.totalRows;
+         adjustedSize = endFetchOffset - adjustedOffset;  
+			if(adjustedOffset == 0 && adjustedSize < this.maxFetchSize){
+			   adjustedSize = this.maxFetchSize;
+			}
+      } else {//prepending
+         var adjustedSize = this.startPos - adjustedOffset;
+         if (adjustedSize > this.maxFetchSize)
+            adjustedSize = this.maxFetchSize;
+      }
+      return adjustedSize;
+   }, 
+
+   getFetchOffset: function(offset) {
+      var adjustedOffset = offset;
+      if (offset > this.startPos)  //apending
+         adjustedOffset = (offset > this.endPos()) ? offset :  this.endPos(); 
+      else { //prepending
+         if (offset + this.maxFetchSize >= this.startPos) {
+            var adjustedOffset = this.startPos - this.maxFetchSize;
+            if (adjustedOffset < 0)
+               adjustedOffset = 0;
+         }
+      }
+      this.lastOffset = adjustedOffset;
+      return adjustedOffset;
+   },
+
+   getRows: function(start, count) {
+      var begPos = start - this.startPos
+      var endPos = begPos + count
+
+      // er? need more data...
+      if ( endPos > this.size )
+         endPos = this.size
+
+      var results = new Array()
+      var index = 0;
+      for ( var i=begPos ; i < endPos; i++ ) {
+         results[index++] = this.rows[i]
+      }
+      return results
+   },
+
+   convertSpaces: function(s) {
+      return s.split(" ").join("&nbsp;");
+   }
+
+};
+
+
+//Rico.GridViewPort --------------------------------------------------
+Rico.GridViewPort = Class.create();
+
+Rico.GridViewPort.prototype = {
+
+   initialize: function(table, rowHeight, visibleRows, buffer, liveGrid) {
+      this.lastDisplayedStartPos = 0;
+      this.div = table.parentNode;
+      this.table = table
+      this.rowHeight = rowHeight;
+      this.div.style.height = (this.rowHeight * visibleRows) + "px";
+      this.div.style.overflow = "hidden";
+      this.buffer = buffer;
+      this.liveGrid = liveGrid;
+      this.visibleRows = visibleRows + 1;
+      this.lastPixelOffset = 0;
+      this.startPos = 0;
+   },
+
+   populateRow: function(htmlRow, row) {
+      for (var j=0; j < row.length; j++) {
+         htmlRow.cells[j].innerHTML = row[j]
+      }
+   },
+   
+   bufferChanged: function() {
+      this.refreshContents( parseInt(this.lastPixelOffset / this.rowHeight));
+   },
+   
+   clearRows: function() {
+      if (!this.isBlank) {
+         this.liveGrid.table.className = this.liveGrid.options.loadingClass;
+         for (var i=0; i < this.visibleRows; i++)
+            this.populateRow(this.table.rows[i], this.buffer.getBlankRow());
+         this.isBlank = true;
+      }
+   },
+   
+   clearContents: function() {   
+      this.clearRows();
+      this.scrollTo(0);
+      this.startPos = 0;
+      this.lastStartPos = -1;   
+   },
+   
+   refreshContents: function(startPos) {
+      if (startPos == this.lastRowPos && !this.isPartialBlank && !this.isBlank) {
+         return;
+      }
+      if ((startPos + this.visibleRows < this.buffer.startPos)  
+          || (this.buffer.startPos + this.buffer.size < startPos) 
+          || (this.buffer.size == 0)) {
+         this.clearRows();
+         return;
+      }
+      this.isBlank = false;
+      var viewPrecedesBuffer = this.buffer.startPos > startPos
+      var contentStartPos = viewPrecedesBuffer ? this.buffer.startPos: startPos; 
+      var contentEndPos = (this.buffer.startPos + this.buffer.size < startPos + this.visibleRows) 
+                                 ? this.buffer.startPos + this.buffer.size
+                                 : startPos + this.visibleRows;
+      var rowSize = contentEndPos - contentStartPos;
+      var rows = this.buffer.getRows(contentStartPos, rowSize ); 
+      var blankSize = this.visibleRows - rowSize;
+      var blankOffset = viewPrecedesBuffer ? 0: rowSize;
+      var contentOffset = viewPrecedesBuffer ? blankSize: 0;
+
+      for (var i=0; i < rows.length; i++) {//initialize what we have
+        this.populateRow(this.table.rows[i + contentOffset], rows[i]);
+      }
+      for (var i=0; i < blankSize; i++) {// blank out the rest 
+        this.populateRow(this.table.rows[i + blankOffset], this.buffer.getBlankRow());
+      }
+      this.isPartialBlank = blankSize > 0;
+      this.lastRowPos = startPos;
+
+       this.liveGrid.table.className = this.liveGrid.options.tableClass;
+       // Check if user has set a onRefreshComplete function
+       var onRefreshComplete = this.liveGrid.options.onRefreshComplete;
+       if (onRefreshComplete != null)
+           onRefreshComplete();
+   },
+
+   scrollTo: function(pixelOffset) {      
+      if (this.lastPixelOffset == pixelOffset)
+         return;
+
+      this.refreshContents(parseInt(pixelOffset / this.rowHeight))
+      this.div.scrollTop = pixelOffset % this.rowHeight        
+      
+      this.lastPixelOffset = pixelOffset;
+   },
+   
+   visibleHeight: function() {
+      return parseInt(RicoUtil.getElementsComputedStyle(this.div, 'height'));
+   }
+
+};
+
+
+Rico.LiveGridRequest = Class.create();
+Rico.LiveGridRequest.prototype = {
+   initialize: function( requestOffset, options ) {
+      this.requestOffset = requestOffset;
+   }
+};
+
+// Rico.LiveGrid -----------------------------------------------------
+
+Rico.LiveGrid = Class.create();
+
+Rico.LiveGrid.prototype = {
+
+   initialize: function( tableId, visibleRows, totalRows, url, options, ajaxOptions ) {
+
+     this.options = {
+                tableClass:           $(tableId).className,
+                loadingClass:         $(tableId).className,
+                scrollerBorderRight: '1px solid #ababab',
+                bufferTimeout:        20000,
+                sortAscendImg:        'images/sort_asc.gif',
+                sortDescendImg:       'images/sort_desc.gif',
+                sortImageWidth:       9,
+                sortImageHeight:      5,
+                ajaxSortURLParms:     [],
+                onRefreshComplete:    null,
+                requestParameters:    null,
+                inlineStyles:         true
+                };
+      Object.extend(this.options, options || {});
+
+      this.ajaxOptions = {parameters: null};
+      Object.extend(this.ajaxOptions, ajaxOptions || {});
+
+      this.tableId     = tableId; 
+      this.table       = $(tableId);
+
+      this.addLiveGridHtml();
+
+      var columnCount  = this.table.rows[0].cells.length;
+      this.metaData    = new Rico.LiveGridMetaData(visibleRows, totalRows, columnCount, options);
+      this.buffer      = new Rico.LiveGridBuffer(this.metaData);
+
+      var rowCount = this.table.rows.length;
+      this.viewPort =  new Rico.GridViewPort(this.table, 
+                                            this.table.offsetHeight/rowCount,
+                                            visibleRows,
+                                            this.buffer, this);
+      this.scroller    = new Rico.LiveGridScroller(this,this.viewPort);
+      this.options.sortHandler = this.sortHandler.bind(this);
+
+      if ( $(tableId + '_header') )
+         this.sort = new Rico.LiveGridSort(tableId + '_header', this.options)
+
+      this.processingRequest = null;
+      this.unprocessedRequest = null;
+
+      this.initAjax(url);
+      if ( this.options.prefetchBuffer || this.options.prefetchOffset > 0) {
+         var offset = 0;
+         if (this.options.offset ) {
+            offset = this.options.offset;            
+            this.scroller.moveScroll(offset);
+            this.viewPort.scrollTo(this.scroller.rowToPixel(offset));            
+         }
+         if (this.options.sortCol) {
+             this.sortCol = options.sortCol;
+             this.sortDir = options.sortDir;
+         }
+         this.requestContentRefresh(offset);
+      }
+   },
+
+   addLiveGridHtml: function() {
+     // Check to see if need to create a header table.
+     if (this.table.getElementsByTagName("thead").length > 0){
+       // Create Table this.tableId+'_header'
+       var tableHeader = this.table.cloneNode(true);
+       tableHeader.setAttribute('id', this.tableId+'_header');
+       tableHeader.setAttribute('class', this.table.className+'_header');
+
+       // Clean up and insert
+       for( var i = 0; i < tableHeader.tBodies.length; i++ ) 
+       tableHeader.removeChild(tableHeader.tBodies[i]);
+       this.table.deleteTHead();
+       this.table.parentNode.insertBefore(tableHeader,this.table);
+     }
+
+    new Insertion.Before(this.table, "<div id='"+this.tableId+"_container'></div>");
+    this.table.previousSibling.appendChild(this.table);
+    new Insertion.Before(this.table,"<div id='"+this.tableId+"_viewport' style='float:left;'></div>");
+    this.table.previousSibling.appendChild(this.table);
+   },
+
+
+   resetContents: function() {
+      this.scroller.moveScroll(0);
+      this.buffer.clear();
+      this.viewPort.clearContents();
+   },
+   
+   sortHandler: function(column) {
+	   if(!column) return ;
+      this.sortCol = column.name;
+      this.sortDir = column.currentSort;
+
+      this.resetContents();
+      this.requestContentRefresh(0) 
+   },
+
+   adjustRowSize: function() {
+	  
+	},
+	
+   setTotalRows: function( newTotalRows ) {
+      this.resetContents();
+      this.metaData.setTotalRows(newTotalRows);
+      this.scroller.updateSize();
+   },
+
+   initAjax: function(url) {
+      ajaxEngine.registerRequest( this.tableId + '_request', url );
+      ajaxEngine.registerAjaxObject( this.tableId + '_updater', this );
+   },
+
+   invokeAjax: function() {
+   },
+
+   handleTimedOut: function() {
+      //server did not respond in 4 seconds... assume that there could have been
+      //an error or something, and allow requests to be processed again...
+      this.processingRequest = null;
+      this.processQueuedRequest();
+   },
+
+   fetchBuffer: function(offset) {
+      if ( this.buffer.isInRange(offset) &&
+         !this.buffer.isNearingLimit(offset)) {
+         return;
+         }
+      if (this.processingRequest) {
+          this.unprocessedRequest = new Rico.LiveGridRequest(offset);
+         return;
+      }
+      var bufferStartPos = this.buffer.getFetchOffset(offset);
+      this.processingRequest = new Rico.LiveGridRequest(offset);
+      this.processingRequest.bufferOffset = bufferStartPos;   
+      var fetchSize = this.buffer.getFetchSize(offset);
+      var partialLoaded = false;
+      
+      var queryString
+      if (this.options.requestParameters)
+         queryString = this._createQueryString(this.options.requestParameters, 0);
+
+        queryString = (queryString == null) ? '' : queryString+'&';
+        queryString  = queryString+'id='+this.tableId+'&page_size='+fetchSize+'&offset='+bufferStartPos;
+        if (this.sortCol)
+            queryString = queryString+'&sort_col='+escape(this.sortCol)+'&sort_dir='+this.sortDir;
+
+        this.ajaxOptions.parameters = queryString;
+
+       ajaxEngine.sendRequest( this.tableId + '_request', this.ajaxOptions );
+
+       this.timeoutHandler = setTimeout( this.handleTimedOut.bind(this), this.options.bufferTimeout);
+
+   },
+
+   setRequestParams: function() {
+      this.options.requestParameters = [];
+      for ( var i=0 ; i < arguments.length ; i++ )
+         this.options.requestParameters[i] = arguments[i];
+   },
+
+   requestContentRefresh: function(contentOffset) {
+      this.fetchBuffer(contentOffset);
+   },
+
+   ajaxUpdate: function(ajaxResponse) {
+      try {
+         clearTimeout( this.timeoutHandler );
+         this.buffer.update(ajaxResponse,this.processingRequest.bufferOffset);
+         this.viewPort.bufferChanged();
+      }
+      catch(err) {}
+      finally {this.processingRequest = null; }
+      this.processQueuedRequest();
+   },
+
+   _createQueryString: function( theArgs, offset ) {
+      var queryString = ""
+      if (!theArgs)
+          return queryString;
+
+      for ( var i = offset ; i < theArgs.length ; i++ ) {
+          if ( i != offset )
+            queryString += "&";
+
+          var anArg = theArgs[i];
+
+          if ( anArg.name != undefined && anArg.value != undefined ) {
+            queryString += anArg.name +  "=" + escape(anArg.value);
+          }
+          else {
+             var ePos  = anArg.indexOf('=');
+             var argName  = anArg.substring( 0, ePos );
+             var argValue = anArg.substring( ePos + 1 );
+             queryString += argName + "=" + escape(argValue);
+          }
+      }
+      return queryString;
+   },
+
+   processQueuedRequest: function() {
+      if (this.unprocessedRequest != null) {
+         this.requestContentRefresh(this.unprocessedRequest.requestOffset);
+         this.unprocessedRequest = null
+      }
+   }
+};
+
+
+//-------------------- ricoLiveGridSort.js
+Rico.LiveGridSort = Class.create();
+
+Rico.LiveGridSort.prototype = {
+
+   initialize: function(headerTableId, options) {
+      this.headerTableId = headerTableId;
+      this.headerTable   = $(headerTableId);
+      this.options = options;
+      this.setOptions();
+      this.applySortBehavior();
+
+      if ( this.options.sortCol ) {
+         this.setSortUI( this.options.sortCol, this.options.sortDir );
+      }
+   },
+
+   setSortUI: function( columnName, sortDirection ) {
+      var cols = this.options.columns;
+      for ( var i = 0 ; i < cols.length ; i++ ) {
+         if ( cols[i].name == columnName ) {
+            this.setColumnSort(i, sortDirection);
+            break;
+         }
+      }
+   },
+
+   setOptions: function() {
+      // preload the images...
+      new Image().src = this.options.sortAscendImg;
+      new Image().src = this.options.sortDescendImg;
+
+      this.sort = this.options.sortHandler;
+      if ( !this.options.columns )
+         this.options.columns = this.introspectForColumnInfo();
+      else {
+         // allow client to pass { columns: [ ["a", true], ["b", false] ] }
+         // and convert to an array of Rico.TableColumn objs...
+         this.options.columns = this.convertToTableColumns(this.options.columns);
+      }
+   },
+
+   applySortBehavior: function() {
+      var headerRow   = this.headerTable.rows[0];
+      var headerCells = headerRow.cells;
+      for ( var i = 0 ; i < headerCells.length ; i++ ) {
+         this.addSortBehaviorToColumn( i, headerCells[i] );
+      }
+   },
+
+   addSortBehaviorToColumn: function( n, cell ) {
+      if ( this.options.columns[n].isSortable() ) {
+         cell.id            = this.headerTableId + '_' + n;
+         cell.style.cursor  = 'pointer';
+         cell.onclick       = this.headerCellClicked.bindAsEventListener(this);
+         cell.innerHTML     = cell.innerHTML + '<span id="' + this.headerTableId + '_img_' + n + '">'
+                           + '&nbsp;&nbsp;&nbsp;</span>';
+      }
+   },
+
+   // event handler....
+   headerCellClicked: function(evt) {
+      var eventTarget = evt.target ? evt.target : evt.srcElement;
+      var cellId = eventTarget.id;
+      var columnNumber = parseInt(cellId.substring( cellId.lastIndexOf('_') + 1 ));
+      var sortedColumnIndex = this.getSortedColumnIndex();
+      if ( sortedColumnIndex != -1 ) {
+         if ( sortedColumnIndex != columnNumber ) {
+            this.removeColumnSort(sortedColumnIndex);
+            this.setColumnSort(columnNumber, Rico.TableColumn.SORT_ASC);
+         }
+         else
+            this.toggleColumnSort(sortedColumnIndex);
+      }
+      else
+         this.setColumnSort(columnNumber, Rico.TableColumn.SORT_ASC);
+
+      if (this.options.sortHandler) {
+         this.options.sortHandler(this.options.columns[columnNumber]);
+      }
+   },
+
+   removeColumnSort: function(n) {
+      this.options.columns[n].setUnsorted();
+      this.setSortImage(n);
+   },
+
+   setColumnSort: function(n, direction) {
+   	if(isNaN(n)) return ;
+      this.options.columns[n].setSorted(direction);
+      this.setSortImage(n);
+   },
+
+   toggleColumnSort: function(n) {
+      this.options.columns[n].toggleSort();
+      this.setSortImage(n);
+   },
+
+   setSortImage: function(n) {
+      var sortDirection = this.options.columns[n].getSortDirection();
+
+      var sortImageSpan = $( this.headerTableId + '_img_' + n );
+      if ( sortDirection == Rico.TableColumn.UNSORTED )
+         sortImageSpan.innerHTML = '&nbsp;&nbsp;';
+      else if ( sortDirection == Rico.TableColumn.SORT_ASC )
+         sortImageSpan.innerHTML = '&nbsp;&nbsp;<img width="'  + this.options.sortImageWidth    + '" ' +
+                                                     'height="'+ this.options.sortImageHeight   + '" ' +
+                                                     'src="'   + this.options.sortAscendImg + '"/>';
+      else if ( sortDirection == Rico.TableColumn.SORT_DESC )
+         sortImageSpan.innerHTML = '&nbsp;&nbsp;<img width="'  + this.options.sortImageWidth    + '" ' +
+                                                     'height="'+ this.options.sortImageHeight   + '" ' +
+                                                     'src="'   + this.options.sortDescendImg + '"/>';
+   },
+
+   getSortedColumnIndex: function() {
+      var cols = this.options.columns;
+      for ( var i = 0 ; i < cols.length ; i++ ) {
+         if ( cols[i].isSorted() )
+            return i;
+      }
+
+      return -1;
+   },
+
+   introspectForColumnInfo: function() {
+      var columns = new Array();
+      var headerRow   = this.headerTable.rows[0];
+      var headerCells = headerRow.cells;
+      for ( var i = 0 ; i < headerCells.length ; i++ )
+         columns.push( new Rico.TableColumn( this.deriveColumnNameFromCell(headerCells[i],i), true ) );
+      return columns;
+   },
+
+   convertToTableColumns: function(cols) {
+      var columns = new Array();
+      for ( var i = 0 ; i < cols.length ; i++ )
+         columns.push( new Rico.TableColumn( cols[i][0], cols[i][1] ) );
+      return columns;
+   },
+
+   deriveColumnNameFromCell: function(cell,columnNumber) {
+      var cellContent = cell.innerText != undefined ? cell.innerText : cell.textContent;
+      return cellContent ? cellContent.toLowerCase().split(' ').join('_') : "col_" + columnNumber;
+   }
+};
+
+Rico.TableColumn = Class.create();
+
+Rico.TableColumn.UNSORTED  = 0;
+Rico.TableColumn.SORT_ASC  = "ASC";
+Rico.TableColumn.SORT_DESC = "DESC";
+
+Rico.TableColumn.prototype = {
+   initialize: function(name, sortable) {
+      this.name        = name;
+      this.sortable    = sortable;
+      this.currentSort = Rico.TableColumn.UNSORTED;
+   },
+
+   isSortable: function() {
+      return this.sortable;
+   },
+
+   isSorted: function() {
+      return this.currentSort != Rico.TableColumn.UNSORTED;
+   },
+
+   getSortDirection: function() {
+      return this.currentSort;
+   },
+
+   toggleSort: function() {
+      if ( this.currentSort == Rico.TableColumn.UNSORTED || this.currentSort == Rico.TableColumn.SORT_DESC )
+         this.currentSort = Rico.TableColumn.SORT_ASC;
+      else if ( this.currentSort == Rico.TableColumn.SORT_ASC )
+         this.currentSort = Rico.TableColumn.SORT_DESC;
+   },
+
+   setUnsorted: function(direction) {
+      this.setSorted(Rico.TableColumn.UNSORTED);
+   },
+
+   setSorted: function(direction) {
+      // direction must by one of Rico.TableColumn.UNSORTED, .SORT_ASC, or .SORT_DESC...
+      this.currentSort = direction;
+   }
+
+};
+
+
+//-------------------- ricoUtil.js
+var RicoUtil = {
+
+   getElementsComputedStyle: function ( htmlElement, cssProperty, mozillaEquivalentCSS) {
+      if ( arguments.length == 2 )
+         mozillaEquivalentCSS = cssProperty;
+
+      var el = $(htmlElement);
+      if ( el.currentStyle )
+         return el.currentStyle[cssProperty];
+      else
+         return document.defaultView.getComputedStyle(el, null).getPropertyValue(mozillaEquivalentCSS);
+   },
+
+   createXmlDocument : function() {
+      if (document.implementation && document.implementation.createDocument) {
+         var doc = document.implementation.createDocument("", "", null);
+
+         if (doc.readyState == null) {
+            doc.readyState = 1;
+            doc.addEventListener("load", function () {
+               doc.readyState = 4;
+               if (typeof doc.onreadystatechange == "function")
+                  doc.onreadystatechange();
+            }, false);
+         }
+
+         return doc;
+      }
+
+      if (window.ActiveXObject)
+          return Try.these(
+            function() { return new ActiveXObject('MSXML2.DomDocument')   },
+            function() { return new ActiveXObject('Microsoft.DomDocument')},
+            function() { return new ActiveXObject('MSXML.DomDocument')    },
+            function() { return new ActiveXObject('MSXML3.DomDocument')   }
+          ) || false;
+
+      return null;
+   },
+
+   getContentAsString: function( parentNode ) {
+      return parentNode.xml != undefined ? 
+         this._getContentAsStringIE(parentNode) :
+         this._getContentAsStringMozilla(parentNode);
+   },
+
+  _getContentAsStringIE: function(parentNode) {
+     var contentStr = "";
+     for ( var i = 0 ; i < parentNode.childNodes.length ; i++ ) {
+         var n = parentNode.childNodes[i];
+         if (n.nodeType == 4) {
+             contentStr += n.nodeValue;
+         }
+         else {
+           contentStr += n.xml;
+       }
+     }
+     return contentStr;
+  },
+
+  _getContentAsStringMozilla: function(parentNode) {
+     var xmlSerializer = new XMLSerializer();
+     var contentStr = "";
+     for ( var i = 0 ; i < parentNode.childNodes.length ; i++ ) {
+          var n = parentNode.childNodes[i];
+          if (n.nodeType == 4) { // CDATA node
+              contentStr += n.nodeValue;
+          }
+          else {
+            contentStr += xmlSerializer.serializeToString(n);
+        }
+     }
+     return contentStr;
+  },
+
+   toViewportPosition: function(element) {
+      return this._toAbsolute(element,true);
+   },
+
+   toDocumentPosition: function(element) {
+      return this._toAbsolute(element,false);
+   },
+
+   /**
+    *  Compute the elements position in terms of the window viewport
+    *  so that it can be compared to the position of the mouse (dnd)
+    *  This is additions of all the offsetTop,offsetLeft values up the
+    *  offsetParent hierarchy, ...taking into account any scrollTop,
+    *  scrollLeft values along the way...
+    *
+    * IE has a bug reporting a correct offsetLeft of elements within a
+    * a relatively positioned parent!!!
+    **/
+   _toAbsolute: function(element,accountForDocScroll) {
+
+      if ( navigator.userAgent.toLowerCase().indexOf("msie") == -1 )
+         return this._toAbsoluteMozilla(element,accountForDocScroll);
+
+      var x = 0;
+      var y = 0;
+      var parent = element;
+      while ( parent ) {
+
+         var borderXOffset = 0;
+         var borderYOffset = 0;
+         if ( parent != element ) {
+            var borderXOffset = parseInt(this.getElementsComputedStyle(parent, "borderLeftWidth" ));
+            var borderYOffset = parseInt(this.getElementsComputedStyle(parent, "borderTopWidth" ));
+            borderXOffset = isNaN(borderXOffset) ? 0 : borderXOffset;
+            borderYOffset = isNaN(borderYOffset) ? 0 : borderYOffset;
+         }
+
+         x += parent.offsetLeft - parent.scrollLeft + borderXOffset;
+         y += parent.offsetTop - parent.scrollTop + borderYOffset;
+         parent = parent.offsetParent;
+      }
+
+      if ( accountForDocScroll ) {
+         x -= this.docScrollLeft();
+         y -= this.docScrollTop();
+      }
+
+      return { x:x, y:y };
+   },
+
+   /**
+    *  Mozilla did not report all of the parents up the hierarchy via the
+    *  offsetParent property that IE did.  So for the calculation of the
+    *  offsets we use the offsetParent property, but for the calculation of
+    *  the scrollTop/scrollLeft adjustments we navigate up via the parentNode
+    *  property instead so as to get the scroll offsets...
+    *
+    **/
+   _toAbsoluteMozilla: function(element,accountForDocScroll) {
+      var x = 0;
+      var y = 0;
+      var parent = element;
+      while ( parent ) {
+         x += parent.offsetLeft;
+         y += parent.offsetTop;
+         parent = parent.offsetParent;
+      }
+
+      parent = element;
+      while ( parent &&
+              parent != document.body &&
+              parent != document.documentElement ) {
+         if ( parent.scrollLeft  )
+            x -= parent.scrollLeft;
+         if ( parent.scrollTop )
+            y -= parent.scrollTop;
+         parent = parent.parentNode;
+      }
+
+      if ( accountForDocScroll ) {
+         x -= this.docScrollLeft();
+         y -= this.docScrollTop();
+      }
+
+      return { x:x, y:y };
+   },
+
+   docScrollLeft: function() {
+      if ( window.pageXOffset )
+         return window.pageXOffset;
+      else if ( document.documentElement && document.documentElement.scrollLeft )
+         return document.documentElement.scrollLeft;
+      else if ( document.body )
+         return document.body.scrollLeft;
+      else
+         return 0;
+   },
+
+   docScrollTop: function() {
+      if ( window.pageYOffset )
+         return window.pageYOffset;
+      else if ( document.documentElement && document.documentElement.scrollTop )
+         return document.documentElement.scrollTop;
+      else if ( document.body )
+         return document.body.scrollTop;
+      else
+         return 0;
+   }
+
+};

Added: jifty/branches/schema-plugins/share/web/static/js/scriptaculous/builder.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/scriptaculous/builder.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,101 @@
+// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//
+// See scriptaculous.js for full license.
+
+var Builder = {
+  NODEMAP: {
+    AREA: 'map',
+    CAPTION: 'table',
+    COL: 'table',
+    COLGROUP: 'table',
+    LEGEND: 'fieldset',
+    OPTGROUP: 'select',
+    OPTION: 'select',
+    PARAM: 'object',
+    TBODY: 'table',
+    TD: 'table',
+    TFOOT: 'table',
+    TH: 'table',
+    THEAD: 'table',
+    TR: 'table'
+  },
+  // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
+  //       due to a Firefox bug
+  node: function(elementName) {
+    elementName = elementName.toUpperCase();
+    
+    // try innerHTML approach
+    var parentTag = this.NODEMAP[elementName] || 'div';
+    var parentElement = document.createElement(parentTag);
+    try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
+      parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
+    } catch(e) {}
+    var element = parentElement.firstChild || null;
+      
+    // see if browser added wrapping tags
+    if(element && (element.tagName != elementName))
+      element = element.getElementsByTagName(elementName)[0];
+    
+    // fallback to createElement approach
+    if(!element) element = document.createElement(elementName);
+    
+    // abort if nothing could be created
+    if(!element) return;
+
+    // attributes (or text)
+    if(arguments[1])
+      if(this._isStringOrNumber(arguments[1]) ||
+        (arguments[1] instanceof Array)) {
+          this._children(element, arguments[1]);
+        } else {
+          var attrs = this._attributes(arguments[1]);
+          if(attrs.length) {
+            try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
+              parentElement.innerHTML = "<" +elementName + " " +
+                attrs + "></" + elementName + ">";
+            } catch(e) {}
+            element = parentElement.firstChild || null;
+            // workaround firefox 1.0.X bug
+            if(!element) {
+              element = document.createElement(elementName);
+              for(attr in arguments[1]) 
+                element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
+            }
+            if(element.tagName != elementName)
+              element = parentElement.getElementsByTagName(elementName)[0];
+            }
+        } 
+
+    // text, or array of children
+    if(arguments[2])
+      this._children(element, arguments[2]);
+
+     return element;
+  },
+  _text: function(text) {
+     return document.createTextNode(text);
+  },
+  _attributes: function(attributes) {
+    var attrs = [];
+    for(attribute in attributes)
+      attrs.push((attribute=='className' ? 'class' : attribute) +
+          '="' + attributes[attribute].toString().escapeHTML() + '"');
+    return attrs.join(" ");
+  },
+  _children: function(element, children) {
+    if(typeof children=='object') { // array can hold nodes and text
+      children.flatten().each( function(e) {
+        if(typeof e=='object')
+          element.appendChild(e)
+        else
+          if(Builder._isStringOrNumber(e))
+            element.appendChild(Builder._text(e));
+      });
+    } else
+      if(Builder._isStringOrNumber(children)) 
+         element.appendChild(Builder._text(children));
+  },
+  _isStringOrNumber: function(param) {
+    return(typeof param=='string' || typeof param=='number');
+  }
+}
\ No newline at end of file

Added: jifty/branches/schema-plugins/share/web/static/js/scriptaculous/controls.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/scriptaculous/controls.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,776 @@
+// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//           (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
+//           (c) 2005 Jon Tirsen (http://www.tirsen.com)
+// Contributors:
+//  Richard Livsey
+//  Rahul Bhargava
+//  Rob Wills
+// 
+// See scriptaculous.js for full license.
+
+// Autocompleter.Base handles all the autocompletion functionality 
+// that's independent of the data source for autocompletion. This
+// includes drawing the autocompletion menu, observing keyboard
+// and mouse events, and similar.
+//
+// Specific autocompleters need to provide, at the very least, 
+// a getUpdatedChoices function that will be invoked every time
+// the text inside the monitored textbox changes. This method 
+// should get the text for which to provide autocompletion by
+// invoking this.getToken(), NOT by directly accessing
+// this.element.value. This is to allow incremental tokenized
+// autocompletion. Specific auto-completion logic (AJAX, etc)
+// belongs in getUpdatedChoices.
+//
+// Tokenized incremental autocompletion is enabled automatically
+// when an autocompleter is instantiated with the 'tokens' option
+// in the options parameter, e.g.:
+// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
+// will incrementally autocomplete with a comma as the token.
+// Additionally, ',' in the above example can be replaced with
+// a token array, e.g. { tokens: [',', '\n'] } which
+// enables autocompletion on multiple tokens. This is most 
+// useful when one of the tokens is \n (a newline), as it 
+// allows smart autocompletion after linebreaks.
+
+var Autocompleter = {}
+Autocompleter.Base = function() {};
+Autocompleter.Base.prototype = {
+  baseInitialize: function(element, update, options) {
+    this.element     = $(element); 
+    this.update      = $(update);  
+    this.hasFocus    = false; 
+    this.changed     = false; 
+    this.active      = false; 
+    this.index       = 0;     
+    this.entryCount  = 0;
+
+    if (this.setOptions)
+      this.setOptions(options);
+    else
+      this.options = options || {};
+
+    this.options.paramName    = this.options.paramName || this.element.name;
+    this.options.tokens       = this.options.tokens || [];
+    this.options.frequency    = this.options.frequency || 0.4;
+    this.options.minChars     = this.options.minChars || 1;
+    this.options.onShow       = this.options.onShow || 
+    function(element, update){ 
+      if(!update.style.position || update.style.position=='absolute') {
+        update.style.position = 'absolute';
+        Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight});
+      }
+      Effect.Appear(update,{duration:0.15});
+    };
+    this.options.onHide = this.options.onHide || 
+    function(element, update){ new Effect.Fade(update,{duration:0.15}) };
+
+    if (typeof(this.options.tokens) == 'string') 
+      this.options.tokens = new Array(this.options.tokens);
+
+    this.observer = null;
+    
+    this.element.setAttribute('autocomplete','off');
+
+    Element.hide(this.update);
+
+    Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
+    Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
+  },
+
+  show: function() {
+    /* Next line added by TRS, 07 July 2006 */
+    if ( this.options.beforeShow ) this.options.beforeShow(this);
+    
+    if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
+    if(!this.iefix && 
+      (navigator.appVersion.indexOf('MSIE')>0) &&
+      (navigator.userAgent.indexOf('Opera')<0) &&
+      (Element.getStyle(this.update, 'position')=='absolute')) {
+      new Insertion.After(this.update, 
+       '<iframe id="' + this.update.id + '_iefix" '+
+       'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
+       'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
+      this.iefix = $(this.update.id+'_iefix');
+    }
+    if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
+  },
+  
+  fixIEOverlapping: function() {
+    Position.clone(this.update, this.iefix);
+    this.iefix.style.zIndex = 1;
+    this.update.style.zIndex = 2;
+    Element.show(this.iefix);
+  },
+
+  hide: function() {
+    /* Next line added by TRS, 07 July 2006 */
+    if ( this.options.beforeHide ) this.options.beforeHide(this);
+    
+    this.stopIndicator();
+    if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
+    if(this.iefix) Element.hide(this.iefix);
+  },
+
+  startIndicator: function() {
+    if(this.options.indicator) Element.show(this.options.indicator);
+  },
+
+  stopIndicator: function() {
+    if(this.options.indicator) Element.hide(this.options.indicator);
+  },
+
+  onKeyPress: function(event) {
+    if(this.active)
+      switch(event.keyCode) {
+       case Event.KEY_TAB:
+       case Event.KEY_RETURN:
+         this.selectEntry();
+         Event.stop(event);
+       case Event.KEY_ESC:
+         this.hide();
+         this.active = false;
+         Event.stop(event);
+         return;
+       case Event.KEY_LEFT:
+       case Event.KEY_RIGHT:
+         return;
+       case Event.KEY_UP:
+         this.markPrevious();
+         this.render();
+         if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
+         return;
+       case Event.KEY_DOWN:
+         this.markNext();
+         this.render();
+         if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
+         return;
+      }
+     else 
+      if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN) 
+        return;
+
+    this.changed = true;
+    this.hasFocus = true;
+
+    if(this.observer) clearTimeout(this.observer);
+      this.observer = 
+        setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
+  },
+
+  onHover: function(event) {
+    var element = Event.findElement(event, 'LI');
+    if(this.index != element.autocompleteIndex) 
+    {
+        this.index = element.autocompleteIndex;
+        this.render();
+    }
+    Event.stop(event);
+  },
+  
+  onClick: function(event) {
+    var element = Event.findElement(event, 'LI');
+    this.index = element.autocompleteIndex;
+    this.selectEntry();
+    this.hide();
+  },
+  
+  onBlur: function(event) {
+    // needed to make click events working
+    setTimeout(this.hide.bind(this), 250);
+    this.hasFocus = false;
+    this.active = false;     
+  },
+
+  render: function() {
+    if(this.entryCount > 0) {
+      for (var i = 0; i < this.entryCount; i++)
+        this.index==i ? 
+          Element.addClassName(this.getEntry(i),"selected") : 
+          Element.removeClassName(this.getEntry(i),"selected");
+        
+      if(this.hasFocus) { 
+        this.show();
+        this.active = true;
+      }
+    } else {
+      this.active = false;
+      this.hide();
+    }
+  },
+  
+  markPrevious: function() {
+    if(this.index > 0) this.index--
+      else this.index = this.entryCount-1;
+  },
+  
+  markNext: function() {
+    if(this.index < this.entryCount-1) this.index++
+      else this.index = 0;
+  },
+  
+  getEntry: function(index) {
+    return this.update.firstChild.childNodes[index];
+  },
+  
+  getCurrentEntry: function() {
+    return this.getEntry(this.index);
+  },
+  
+  selectEntry: function() {
+    this.active = false;
+    this.updateElement(this.getCurrentEntry());
+  },
+
+  updateElement: function(selectedElement) {
+    if (this.options.updateElement) {
+      this.options.updateElement(selectedElement);
+      return;
+    }
+    var value = '';
+    if (this.options.select) {
+      var nodes = document.getElementsByClassName(this.options.select, selectedElement) || [];
+      if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
+    } else
+      value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
+    
+    var lastTokenPos = this.findLastToken();
+    if (lastTokenPos != -1) {
+      var newValue = this.element.value.substr(0, lastTokenPos + 1);
+      var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
+      if (whitespace)
+        newValue += whitespace[0];
+      this.element.value = newValue + value;
+    } else {
+      this.element.value = value;
+    }
+    this.element.focus();
+    
+    if (this.options.afterUpdateElement)
+      this.options.afterUpdateElement(this.element, selectedElement);
+  },
+
+  updateChoices: function(choices) {
+    if(!this.changed && this.hasFocus) {
+      this.update.innerHTML = choices;
+      Element.cleanWhitespace(this.update);
+      Element.cleanWhitespace(this.update.firstChild);
+
+      if(this.update.firstChild && this.update.firstChild.childNodes) {
+        this.entryCount = 
+          this.update.firstChild.childNodes.length;
+        for (var i = 0; i < this.entryCount; i++) {
+          var entry = this.getEntry(i);
+          entry.autocompleteIndex = i;
+          this.addObservers(entry);
+        }
+      } else { 
+        this.entryCount = 0;
+      }
+
+      this.stopIndicator();
+
+      this.index = 0;
+      this.render();
+    }
+  },
+
+  addObservers: function(element) {
+    Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
+    Event.observe(element, "click", this.onClick.bindAsEventListener(this));
+  },
+
+  onObserverEvent: function() {
+    this.changed = false;   
+    if(this.getToken().length>=this.options.minChars) {
+      this.startIndicator();
+      this.getUpdatedChoices();
+    } else {
+      this.active = false;
+      this.hide();
+    }
+  },
+
+  getToken: function() {
+    var tokenPos = this.findLastToken();
+    if (tokenPos != -1)
+      var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
+    else
+      var ret = this.element.value;
+
+    return /\n/.test(ret) ? '' : ret;
+  },
+
+  findLastToken: function() {
+    var lastTokenPos = -1;
+
+    for (var i=0; i<this.options.tokens.length; i++) {
+      var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
+      if (thisTokenPos > lastTokenPos)
+        lastTokenPos = thisTokenPos;
+    }
+    return lastTokenPos;
+  }
+}
+
+Ajax.Autocompleter = Class.create();
+Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
+  initialize: function(element, update, url, options) {
+	  this.baseInitialize(element, update, options);
+    this.options.asynchronous  = true;
+    this.options.onComplete    = this.onComplete.bind(this);
+    this.options.defaultParams = this.options.parameters || null;
+    this.url                   = url;
+  },
+
+  getUpdatedChoices: function() {
+    entry = encodeURIComponent(this.options.paramName) + '=' + 
+      encodeURIComponent(this.getToken());
+
+    this.options.parameters = this.options.callback ?
+      this.options.callback(this.element, entry) : entry;
+
+    if(this.options.defaultParams) 
+      this.options.parameters += '&' + this.options.defaultParams;
+
+    new Ajax.Request(this.url, this.options);
+  },
+
+  onComplete: function(request) {
+    this.updateChoices(request.responseText);
+  }
+
+});
+
+// The local array autocompleter. Used when you'd prefer to
+// inject an array of autocompletion options into the page, rather
+// than sending out Ajax queries, which can be quite slow sometimes.
+//
+// The constructor takes four parameters. The first two are, as usual,
+// the id of the monitored textbox, and id of the autocompletion menu.
+// The third is the array you want to autocomplete from, and the fourth
+// is the options block.
+//
+// Extra local autocompletion options:
+// - choices - How many autocompletion choices to offer
+//
+// - partialSearch - If false, the autocompleter will match entered
+//                    text only at the beginning of strings in the 
+//                    autocomplete array. Defaults to true, which will
+//                    match text at the beginning of any *word* in the
+//                    strings in the autocomplete array. If you want to
+//                    search anywhere in the string, additionally set
+//                    the option fullSearch to true (default: off).
+//
+// - fullSsearch - Search anywhere in autocomplete array strings.
+//
+// - partialChars - How many characters to enter before triggering
+//                   a partial match (unlike minChars, which defines
+//                   how many characters are required to do any match
+//                   at all). Defaults to 2.
+//
+// - ignoreCase - Whether to ignore case when autocompleting.
+//                 Defaults to true.
+//
+// It's possible to pass in a custom function as the 'selector' 
+// option, if you prefer to write your own autocompletion logic.
+// In that case, the other options above will not apply unless
+// you support them.
+
+Autocompleter.Local = Class.create();
+Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
+  initialize: function(element, update, array, options) {
+    this.baseInitialize(element, update, options);
+    this.options.array = array;
+  },
+
+  getUpdatedChoices: function() {
+    this.updateChoices(this.options.selector(this));
+  },
+
+  setOptions: function(options) {
+    this.options = Object.extend({
+      choices: 10,
+      partialSearch: true,
+      partialChars: 2,
+      ignoreCase: true,
+      fullSearch: false,
+      selector: function(instance) {
+        var ret       = []; // Beginning matches
+        var partial   = []; // Inside matches
+        var entry     = instance.getToken();
+        var count     = 0;
+
+        for (var i = 0; i < instance.options.array.length &&  
+          ret.length < instance.options.choices ; i++) { 
+
+          var elem = instance.options.array[i];
+          var foundPos = instance.options.ignoreCase ? 
+            elem.toLowerCase().indexOf(entry.toLowerCase()) : 
+            elem.indexOf(entry);
+
+          while (foundPos != -1) {
+            if (foundPos == 0 && elem.length != entry.length) { 
+              ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" + 
+                elem.substr(entry.length) + "</li>");
+              break;
+            } else if (entry.length >= instance.options.partialChars && 
+              instance.options.partialSearch && foundPos != -1) {
+              if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
+                partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
+                  elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
+                  foundPos + entry.length) + "</li>");
+                break;
+              }
+            }
+
+            foundPos = instance.options.ignoreCase ? 
+              elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : 
+              elem.indexOf(entry, foundPos + 1);
+
+          }
+        }
+        if (partial.length)
+          ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
+        return "<ul>" + ret.join('') + "</ul>";
+      }
+    }, options || {});
+  }
+});
+
+// AJAX in-place editor
+//
+// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor
+
+// Use this if you notice weird scrolling problems on some browsers,
+// the DOM might be a bit confused when this gets called so do this
+// waits 1 ms (with setTimeout) until it does the activation
+Field.scrollFreeActivate = function(field) {
+  setTimeout(function() {
+    Field.activate(field);
+  }, 1);
+}
+
+Ajax.InPlaceEditor = Class.create();
+Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99";
+Ajax.InPlaceEditor.prototype = {
+  initialize: function(element, url, options) {
+    this.url = url;
+    this.element = $(element);
+
+    this.options = Object.extend({
+      okButton: true,
+      okText: "ok",
+      cancelLink: true,
+      cancelText: "cancel",
+      savingText: "Saving...",
+      clickToEditText: "Click to edit",
+      okText: "ok",
+      rows: 1,
+      onComplete: function(transport, element) {
+        new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
+      },
+      onFailure: function(transport) {
+        alert("Error communicating with the server: " + transport.responseText.stripTags());
+      },
+      callback: function(form) {
+        return Form.serialize(form);
+      },
+      handleLineBreaks: true,
+      loadingText: 'Loading...',
+      savingClassName: 'inplaceeditor-saving',
+      loadingClassName: 'inplaceeditor-loading',
+      formClassName: 'inplaceeditor-form',
+      highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
+      highlightendcolor: "#FFFFFF",
+      externalControl:	null,
+      submitOnBlur: false,
+      ajaxOptions: {}
+    }, options || {});
+
+    if(!this.options.formId && this.element.id) {
+      this.options.formId = this.element.id + "-inplaceeditor";
+      if ($(this.options.formId)) {
+        // there's already a form with that name, don't specify an id
+        this.options.formId = null;
+      }
+    }
+    
+    if (this.options.externalControl) {
+      this.options.externalControl = $(this.options.externalControl);
+    }
+    
+    this.originalBackground = Element.getStyle(this.element, 'background-color');
+    if (!this.originalBackground) {
+      this.originalBackground = "transparent";
+    }
+    
+    this.element.title = this.options.clickToEditText;
+    
+    this.onclickListener = this.enterEditMode.bindAsEventListener(this);
+    this.mouseoverListener = this.enterHover.bindAsEventListener(this);
+    this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
+    Event.observe(this.element, 'click', this.onclickListener);
+    Event.observe(this.element, 'mouseover', this.mouseoverListener);
+    Event.observe(this.element, 'mouseout', this.mouseoutListener);
+    if (this.options.externalControl) {
+      Event.observe(this.options.externalControl, 'click', this.onclickListener);
+      Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
+      Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
+    }
+  },
+  enterEditMode: function(evt) {
+    if (this.saving) return;
+    if (this.editing) return;
+    this.editing = true;
+    this.onEnterEditMode();
+    if (this.options.externalControl) {
+      Element.hide(this.options.externalControl);
+    }
+    Element.hide(this.element);
+    this.createForm();
+    this.element.parentNode.insertBefore(this.form, this.element);
+    Field.scrollFreeActivate(this.editField);
+    // stop the event to avoid a page refresh in Safari
+    if (evt) {
+      Event.stop(evt);
+    }
+    return false;
+  },
+  createForm: function() {
+    this.form = document.createElement("form");
+    this.form.id = this.options.formId;
+    Element.addClassName(this.form, this.options.formClassName)
+    this.form.onsubmit = this.onSubmit.bind(this);
+
+    this.createEditField();
+
+    if (this.options.textarea) {
+      var br = document.createElement("br");
+      this.form.appendChild(br);
+    }
+
+    if (this.options.okButton) {
+      okButton = document.createElement("input");
+      okButton.type = "submit";
+      okButton.value = this.options.okText;
+      this.form.appendChild(okButton);
+    }
+
+    if (this.options.cancelLink) {
+      cancelLink = document.createElement("a");
+      cancelLink.href = "#";
+      cancelLink.appendChild(document.createTextNode(this.options.cancelText));
+      cancelLink.onclick = this.onclickCancel.bind(this);
+      this.form.appendChild(cancelLink);
+    }
+  },
+  hasHTMLLineBreaks: function(string) {
+    if (!this.options.handleLineBreaks) return false;
+    return string.match(/<br/i) || string.match(/<p>/i);
+  },
+  convertHTMLLineBreaks: function(string) {
+    return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, "");
+  },
+  createEditField: function() {
+    var text;
+    if(this.options.loadTextURL) {
+      text = this.options.loadingText;
+    } else {
+      text = this.getText();
+    }
+
+    var obj = this;
+    
+    if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) {
+      this.options.textarea = false;
+      var textField = document.createElement("input");
+      textField.obj = this;
+      textField.type = "text";
+      textField.name = "value";
+      textField.value = text;
+      textField.style.backgroundColor = this.options.highlightcolor;
+      var size = this.options.size || this.options.cols || 0;
+      if (size != 0) textField.size = size;
+      if (this.options.submitOnBlur)
+        textField.onblur = this.onSubmit.bind(this);
+      this.editField = textField;
+    } else {
+      this.options.textarea = true;
+      var textArea = document.createElement("textarea");
+      textArea.obj = this;
+      textArea.name = "value";
+      textArea.value = this.convertHTMLLineBreaks(text);
+      textArea.rows = this.options.rows;
+      textArea.cols = this.options.cols || 40;
+      if (this.options.submitOnBlur)
+        textArea.onblur = this.onSubmit.bind(this);
+      this.editField = textArea;
+    }
+    
+    if(this.options.loadTextURL) {
+      this.loadExternalText();
+    }
+    this.form.appendChild(this.editField);
+  },
+  getText: function() {
+    return this.element.innerHTML;
+  },
+  loadExternalText: function() {
+    Element.addClassName(this.form, this.options.loadingClassName);
+    this.editField.disabled = true;
+    new Ajax.Request(
+      this.options.loadTextURL,
+      Object.extend({
+        asynchronous: true,
+        onComplete: this.onLoadedExternalText.bind(this)
+      }, this.options.ajaxOptions)
+    );
+  },
+  onLoadedExternalText: function(transport) {
+    Element.removeClassName(this.form, this.options.loadingClassName);
+    this.editField.disabled = false;
+    this.editField.value = transport.responseText.stripTags();
+  },
+  onclickCancel: function() {
+    this.onComplete();
+    this.leaveEditMode();
+    return false;
+  },
+  onFailure: function(transport) {
+    this.options.onFailure(transport);
+    if (this.oldInnerHTML) {
+      this.element.innerHTML = this.oldInnerHTML;
+      this.oldInnerHTML = null;
+    }
+    return false;
+  },
+  onSubmit: function() {
+    // onLoading resets these so we need to save them away for the Ajax call
+    var form = this.form;
+    var value = this.editField.value;
+    
+    // do this first, sometimes the ajax call returns before we get a chance to switch on Saving...
+    // which means this will actually switch on Saving... *after* we've left edit mode causing Saving...
+    // to be displayed indefinitely
+    this.onLoading();
+    
+    new Ajax.Updater(
+      { 
+        success: this.element,
+         // don't update on failure (this could be an option)
+        failure: null
+      },
+      this.url,
+      Object.extend({
+        parameters: this.options.callback(form, value),
+        onComplete: this.onComplete.bind(this),
+        onFailure: this.onFailure.bind(this)
+      }, this.options.ajaxOptions)
+    );
+    // stop the event to avoid a page refresh in Safari
+    if (arguments.length > 1) {
+      Event.stop(arguments[0]);
+    }
+    return false;
+  },
+  onLoading: function() {
+    this.saving = true;
+    this.removeForm();
+    this.leaveHover();
+    this.showSaving();
+  },
+  showSaving: function() {
+    this.oldInnerHTML = this.element.innerHTML;
+    this.element.innerHTML = this.options.savingText;
+    Element.addClassName(this.element, this.options.savingClassName);
+    this.element.style.backgroundColor = this.originalBackground;
+    Element.show(this.element);
+  },
+  removeForm: function() {
+    if(this.form) {
+      if (this.form.parentNode) Element.remove(this.form);
+      this.form = null;
+    }
+  },
+  enterHover: function() {
+    if (this.saving) return;
+    this.element.style.backgroundColor = this.options.highlightcolor;
+    if (this.effect) {
+      this.effect.cancel();
+    }
+    Element.addClassName(this.element, this.options.hoverClassName)
+  },
+  leaveHover: function() {
+    if (this.options.backgroundColor) {
+      this.element.style.backgroundColor = this.oldBackground;
+    }
+    Element.removeClassName(this.element, this.options.hoverClassName)
+    if (this.saving) return;
+    this.effect = new Effect.Highlight(this.element, {
+      startcolor: this.options.highlightcolor,
+      endcolor: this.options.highlightendcolor,
+      restorecolor: this.originalBackground
+    });
+  },
+  leaveEditMode: function() {
+    Element.removeClassName(this.element, this.options.savingClassName);
+    this.removeForm();
+    this.leaveHover();
+    this.element.style.backgroundColor = this.originalBackground;
+    Element.show(this.element);
+    if (this.options.externalControl) {
+      Element.show(this.options.externalControl);
+    }
+    this.editing = false;
+    this.saving = false;
+    this.oldInnerHTML = null;
+    this.onLeaveEditMode();
+  },
+  onComplete: function(transport) {
+    this.leaveEditMode();
+    this.options.onComplete.bind(this)(transport, this.element);
+  },
+  onEnterEditMode: function() {},
+  onLeaveEditMode: function() {},
+  dispose: function() {
+    if (this.oldInnerHTML) {
+      this.element.innerHTML = this.oldInnerHTML;
+    }
+    this.leaveEditMode();
+    Event.stopObserving(this.element, 'click', this.onclickListener);
+    Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
+    Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
+    if (this.options.externalControl) {
+      Event.stopObserving(this.options.externalControl, 'click', this.onclickListener);
+      Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener);
+      Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
+    }
+  }
+};
+
+// Delayed observer, like Form.Element.Observer, 
+// but waits for delay after last key input
+// Ideal for live-search fields
+
+Form.Element.DelayedObserver = Class.create();
+Form.Element.DelayedObserver.prototype = {
+  initialize: function(element, delay, callback) {
+    this.delay     = delay || 0.5;
+    this.element   = $(element);
+    this.callback  = callback;
+    this.timer     = null;
+    this.lastValue = $F(this.element); 
+    Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
+  },
+  delayedListener: function(event) {
+    if(this.lastValue == $F(this.element)) return;
+    if(this.timer) clearTimeout(this.timer);
+    this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
+    this.lastValue = $F(this.element);
+  },
+  onTimerEvent: function() {
+    this.timer = null;
+    this.callback(this.element, $F(this.element));
+  }
+};

Added: jifty/branches/schema-plugins/share/web/static/js/scriptaculous/dragdrop.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/scriptaculous/dragdrop.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,585 @@
+// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// 
+// See scriptaculous.js for full license.
+
+/*--------------------------------------------------------------------------*/
+
+var Droppables = {
+  drops: [],
+
+  remove: function(element) {
+    this.drops = this.drops.reject(function(d) { return d.element==$(element) });
+  },
+
+  add: function(element) {
+    element = $(element);
+    var options = Object.extend({
+      greedy:     true,
+      hoverclass: null  
+    }, arguments[1] || {});
+
+    // cache containers
+    if(options.containment) {
+      options._containers = [];
+      var containment = options.containment;
+      if((typeof containment == 'object') && 
+        (containment.constructor == Array)) {
+        containment.each( function(c) { options._containers.push($(c)) });
+      } else {
+        options._containers.push($(containment));
+      }
+    }
+    
+    if(options.accept) options.accept = [options.accept].flatten();
+
+    Element.makePositioned(element); // fix IE
+    options.element = element;
+
+    this.drops.push(options);
+  },
+
+  isContained: function(element, drop) {
+    var parentNode = element.parentNode;
+    return drop._containers.detect(function(c) { return parentNode == c });
+  },
+
+  isAffected: function(point, element, drop) {
+    return (
+      (drop.element!=element) &&
+      ((!drop._containers) ||
+        this.isContained(element, drop)) &&
+      ((!drop.accept) ||
+        (Element.classNames(element).detect( 
+          function(v) { return drop.accept.include(v) } ) )) &&
+      Position.within(drop.element, point[0], point[1]) );
+  },
+
+  deactivate: function(drop) {
+    if(drop.hoverclass)
+      Element.removeClassName(drop.element, drop.hoverclass);
+    this.last_active = null;
+  },
+
+  activate: function(drop) {
+    if(drop.hoverclass)
+      Element.addClassName(drop.element, drop.hoverclass);
+    this.last_active = drop;
+  },
+
+  show: function(point, element) {
+    if(!this.drops.length) return;
+    
+    if(this.last_active) this.deactivate(this.last_active);
+    this.drops.each( function(drop) {
+      if(Droppables.isAffected(point, element, drop)) {
+        if(drop.onHover)
+           drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
+        if(drop.greedy) { 
+          Droppables.activate(drop);
+          throw $break;
+        }
+      }
+    });
+  },
+
+  fire: function(event, element) {
+    if(!this.last_active) return;
+    Position.prepare();
+
+    if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
+      if (this.last_active.onDrop) 
+        this.last_active.onDrop(element, this.last_active.element, event);
+  },
+
+  reset: function() {
+    if(this.last_active)
+      this.deactivate(this.last_active);
+  }
+}
+
+var Draggables = {
+  drags: [],
+  observers: [],
+  
+  register: function(draggable) {
+    if(this.drags.length == 0) {
+      this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
+      this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
+      this.eventKeypress  = this.keyPress.bindAsEventListener(this);
+      
+      Event.observe(document, "mouseup", this.eventMouseUp);
+      Event.observe(document, "mousemove", this.eventMouseMove);
+      Event.observe(document, "keypress", this.eventKeypress);
+    }
+    this.drags.push(draggable);
+  },
+  
+  unregister: function(draggable) {
+    this.drags = this.drags.reject(function(d) { return d==draggable });
+    if(this.drags.length == 0) {
+      Event.stopObserving(document, "mouseup", this.eventMouseUp);
+      Event.stopObserving(document, "mousemove", this.eventMouseMove);
+      Event.stopObserving(document, "keypress", this.eventKeypress);
+    }
+  },
+  
+  activate: function(draggable) {
+    window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
+    this.activeDraggable = draggable;
+  },
+  
+  deactivate: function(draggbale) {
+    this.activeDraggable = null;
+  },
+  
+  updateDrag: function(event) {
+    if(!this.activeDraggable) return;
+    var pointer = [Event.pointerX(event), Event.pointerY(event)];
+    // Mozilla-based browsers fire successive mousemove events with
+    // the same coordinates, prevent needless redrawing (moz bug?)
+    if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
+    this._lastPointer = pointer;
+    this.activeDraggable.updateDrag(event, pointer);
+  },
+  
+  endDrag: function(event) {
+    if(!this.activeDraggable) return;
+    this._lastPointer = null;
+    this.activeDraggable.endDrag(event);
+    this.activeDraggable = null;
+  },
+  
+  keyPress: function(event) {
+    if(this.activeDraggable)
+      this.activeDraggable.keyPress(event);
+  },
+  
+  addObserver: function(observer) {
+    this.observers.push(observer);
+    this._cacheObserverCallbacks();
+  },
+  
+  removeObserver: function(element) {  // element instead of observer fixes mem leaks
+    this.observers = this.observers.reject( function(o) { return o.element==element });
+    this._cacheObserverCallbacks();
+  },
+  
+  notify: function(eventName, draggable, event) {  // 'onStart', 'onEnd', 'onDrag'
+    if(this[eventName+'Count'] > 0)
+      this.observers.each( function(o) {
+        if(o[eventName]) o[eventName](eventName, draggable, event);
+      });
+  },
+  
+  _cacheObserverCallbacks: function() {
+    ['onStart','onEnd','onDrag'].each( function(eventName) {
+      Draggables[eventName+'Count'] = Draggables.observers.select(
+        function(o) { return o[eventName]; }
+      ).length;
+    });
+  }
+}
+
+/*--------------------------------------------------------------------------*/
+
+var Draggable = Class.create();
+Draggable.prototype = {
+  initialize: function(element) {
+    var options = Object.extend({
+      handle: false,
+      starteffect: function(element) { 
+        new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7}); 
+      },
+      reverteffect: function(element, top_offset, left_offset) {
+        var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
+        element._revert = new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur});
+      },
+      endeffect: function(element) { 
+        new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0}); 
+      },
+      zindex: 1000,
+      revert: false,
+      snap: false   // false, or xy or [x,y] or function(x,y){ return [x,y] }
+    }, arguments[1] || {});
+
+    this.element = $(element);
+    
+    if(options.handle && (typeof options.handle == 'string'))
+      this.handle = Element.childrenWithClassName(this.element, options.handle)[0];  
+    if(!this.handle) this.handle = $(options.handle);
+    if(!this.handle) this.handle = this.element;
+
+    Element.makePositioned(this.element); // fix IE    
+
+    this.delta    = this.currentDelta();
+    this.options  = options;
+    this.dragging = false;   
+
+    this.eventMouseDown = this.initDrag.bindAsEventListener(this);
+    Event.observe(this.handle, "mousedown", this.eventMouseDown);
+    
+    Draggables.register(this);
+  },
+  
+  destroy: function() {
+    Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
+    Draggables.unregister(this);
+  },
+  
+  currentDelta: function() {
+    return([
+      parseInt(Element.getStyle(this.element,'left') || '0'),
+      parseInt(Element.getStyle(this.element,'top') || '0')]);
+  },
+  
+  initDrag: function(event) {
+    if(Event.isLeftClick(event)) {    
+      // abort on form elements, fixes a Firefox issue
+      var src = Event.element(event);
+      if(src.tagName && (
+        src.tagName=='INPUT' ||
+        src.tagName=='SELECT' ||
+        src.tagName=='BUTTON' ||
+        src.tagName=='TEXTAREA')) return;
+        
+      if(this.element._revert) {
+        this.element._revert.cancel();
+        this.element._revert = null;
+      }
+      
+      var pointer = [Event.pointerX(event), Event.pointerY(event)];
+      var pos     = Position.cumulativeOffset(this.element);
+      this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
+      
+      Draggables.activate(this);
+      Event.stop(event);
+    }
+  },
+  
+  startDrag: function(event) {
+    this.dragging = true;
+    
+    if(this.options.zindex) {
+      this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
+      this.element.style.zIndex = this.options.zindex;
+    }
+    
+    if(this.options.ghosting) {
+      this._clone = this.element.cloneNode(true);
+      Position.absolutize(this.element);
+      this.element.parentNode.insertBefore(this._clone, this.element);
+    }
+    
+    Draggables.notify('onStart', this, event);
+    if(this.options.starteffect) this.options.starteffect(this.element);
+  },
+  
+  updateDrag: function(event, pointer) {
+    if(!this.dragging) this.startDrag(event);
+    Position.prepare();
+    Droppables.show(pointer, this.element);
+    Draggables.notify('onDrag', this, event);
+    this.draw(pointer);
+    if(this.options.change) this.options.change(this);
+    
+    // fix AppleWebKit rendering
+    if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
+    Event.stop(event);
+  },
+  
+  finishDrag: function(event, success) {
+    this.dragging = false;
+
+    if(this.options.ghosting) {
+      Position.relativize(this.element);
+      Element.remove(this._clone);
+      this._clone = null;
+    }
+
+    if(success) Droppables.fire(event, this.element);
+    Draggables.notify('onEnd', this, event);
+
+    var revert = this.options.revert;
+    if(revert && typeof revert == 'function') revert = revert(this.element);
+    
+    var d = this.currentDelta();
+    if(revert && this.options.reverteffect) {
+      this.options.reverteffect(this.element, 
+        d[1]-this.delta[1], d[0]-this.delta[0]);
+    } else {
+      this.delta = d;
+    }
+
+    if(this.options.zindex)
+      this.element.style.zIndex = this.originalZ;
+
+    if(this.options.endeffect) 
+      this.options.endeffect(this.element);
+
+    Draggables.deactivate(this);
+    Droppables.reset();
+  },
+  
+  keyPress: function(event) {
+    if(!event.keyCode==Event.KEY_ESC) return;
+    this.finishDrag(event, false);
+    Event.stop(event);
+  },
+  
+  endDrag: function(event) {
+    if(!this.dragging) return;
+    this.finishDrag(event, true);
+    Event.stop(event);
+  },
+  
+  draw: function(point) {
+    var pos = Position.cumulativeOffset(this.element);
+    var d = this.currentDelta();
+    pos[0] -= d[0]; pos[1] -= d[1];
+    
+    var p = [0,1].map(function(i){ return (point[i]-pos[i]-this.offset[i]) }.bind(this));
+    
+    if(this.options.snap) {
+      if(typeof this.options.snap == 'function') {
+        p = this.options.snap(p[0],p[1]);
+      } else {
+      if(this.options.snap instanceof Array) {
+        p = p.map( function(v, i) {
+          return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this))
+      } else {
+        p = p.map( function(v) {
+          return Math.round(v/this.options.snap)*this.options.snap }.bind(this))
+      }
+    }}
+    
+    var style = this.element.style;
+    if((!this.options.constraint) || (this.options.constraint=='horizontal'))
+      style.left = p[0] + "px";
+    if((!this.options.constraint) || (this.options.constraint=='vertical'))
+      style.top  = p[1] + "px";
+    if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
+  }
+}
+
+/*--------------------------------------------------------------------------*/
+
+var SortableObserver = Class.create();
+SortableObserver.prototype = {
+  initialize: function(element, observer) {
+    this.element   = $(element);
+    this.observer  = observer;
+    this.lastValue = Sortable.serialize(this.element);
+  },
+  
+  onStart: function() {
+    this.lastValue = Sortable.serialize(this.element);
+  },
+  
+  onEnd: function() {
+    Sortable.unmark();
+    if(this.lastValue != Sortable.serialize(this.element))
+      this.observer(this.element)
+  }
+}
+
+var Sortable = {
+  sortables: new Array(),
+  
+  options: function(element){
+    element = $(element);
+    return this.sortables.detect(function(s) { return s.element == element });
+  },
+  
+  destroy: function(element){
+    element = $(element);
+    this.sortables.findAll(function(s) { return s.element == element }).each(function(s){
+      Draggables.removeObserver(s.element);
+      s.droppables.each(function(d){ Droppables.remove(d) });
+      s.draggables.invoke('destroy');
+    });
+    this.sortables = this.sortables.reject(function(s) { return s.element == element });
+  },
+  
+  create: function(element) {
+    element = $(element);
+    var options = Object.extend({ 
+      element:     element,
+      tag:         'li',       // assumes li children, override with tag: 'tagname'
+      dropOnEmpty: false,
+      tree:        false,      // fixme: unimplemented
+      overlap:     'vertical', // one of 'vertical', 'horizontal'
+      constraint:  'vertical', // one of 'vertical', 'horizontal', false
+      containment: element,    // also takes array of elements (or id's); or false
+      handle:      false,      // or a CSS class
+      only:        false,
+      hoverclass:  null,
+      ghosting:    false,
+      format:      null,
+      onChange:    Prototype.emptyFunction,
+      onUpdate:    Prototype.emptyFunction
+    }, arguments[1] || {});
+
+    // clear any old sortable with same element
+    this.destroy(element);
+
+    // build options for the draggables
+    var options_for_draggable = {
+      revert:      true,
+      ghosting:    options.ghosting,
+      constraint:  options.constraint,
+      handle:      options.handle };
+
+    if(options.starteffect)
+      options_for_draggable.starteffect = options.starteffect;
+
+    if(options.reverteffect)
+      options_for_draggable.reverteffect = options.reverteffect;
+    else
+      if(options.ghosting) options_for_draggable.reverteffect = function(element) {
+        element.style.top  = 0;
+        element.style.left = 0;
+      };
+
+    if(options.endeffect)
+      options_for_draggable.endeffect = options.endeffect;
+
+    if(options.zindex)
+      options_for_draggable.zindex = options.zindex;
+
+    // build options for the droppables  
+    var options_for_droppable = {
+      overlap:     options.overlap,
+      containment: options.containment,
+      hoverclass:  options.hoverclass,
+      onHover:     Sortable.onHover,
+      greedy:      !options.dropOnEmpty
+    }
+
+    // fix for gecko engine
+    Element.cleanWhitespace(element); 
+
+    options.draggables = [];
+    options.droppables = [];
+
+    // make it so
+
+    // drop on empty handling
+    if(options.dropOnEmpty) {
+      Droppables.add(element,
+        {containment: options.containment, onHover: Sortable.onEmptyHover, greedy: false});
+      options.droppables.push(element);
+    }
+
+    (this.findElements(element, options) || []).each( function(e) {
+      // handles are per-draggable
+      var handle = options.handle ? 
+        Element.childrenWithClassName(e, options.handle)[0] : e;    
+      options.draggables.push(
+        new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
+      Droppables.add(e, options_for_droppable);
+      options.droppables.push(e);      
+    });
+
+    // keep reference
+    this.sortables.push(options);
+
+    // for onupdate
+    Draggables.addObserver(new SortableObserver(element, options.onUpdate));
+
+  },
+
+  // return all suitable-for-sortable elements in a guaranteed order
+  findElements: function(element, options) {
+    if(!element.hasChildNodes()) return null;
+    var elements = [];
+    $A(element.childNodes).each( function(e) {
+      if(e.tagName && e.tagName.toUpperCase()==options.tag.toUpperCase() &&
+        (!options.only || (Element.hasClassName(e, options.only))))
+          elements.push(e);
+      if(options.tree) {
+        var grandchildren = this.findElements(e, options);
+        if(grandchildren) elements.push(grandchildren);
+      }
+    });
+
+    return (elements.length>0 ? elements.flatten() : null);
+  },
+
+  onHover: function(element, dropon, overlap) {
+    if(overlap>0.5) {
+      Sortable.mark(dropon, 'before');
+      if(dropon.previousSibling != element) {
+        var oldParentNode = element.parentNode;
+        element.style.visibility = "hidden"; // fix gecko rendering
+        dropon.parentNode.insertBefore(element, dropon);
+        if(dropon.parentNode!=oldParentNode) 
+          Sortable.options(oldParentNode).onChange(element);
+        Sortable.options(dropon.parentNode).onChange(element);
+      }
+    } else {
+      Sortable.mark(dropon, 'after');
+      var nextElement = dropon.nextSibling || null;
+      if(nextElement != element) {
+        var oldParentNode = element.parentNode;
+        element.style.visibility = "hidden"; // fix gecko rendering
+        dropon.parentNode.insertBefore(element, nextElement);
+        if(dropon.parentNode!=oldParentNode) 
+          Sortable.options(oldParentNode).onChange(element);
+        Sortable.options(dropon.parentNode).onChange(element);
+      }
+    }
+  },
+
+  onEmptyHover: function(element, dropon) {
+    if(element.parentNode!=dropon) {
+      var oldParentNode = element.parentNode;
+      dropon.appendChild(element);
+      Sortable.options(oldParentNode).onChange(element);
+      Sortable.options(dropon).onChange(element);
+    }
+  },
+
+  unmark: function() {
+    if(Sortable._marker) Element.hide(Sortable._marker);
+  },
+
+  mark: function(dropon, position) {
+    // mark on ghosting only
+    var sortable = Sortable.options(dropon.parentNode);
+    if(sortable && !sortable.ghosting) return; 
+
+    if(!Sortable._marker) {
+      Sortable._marker = $('dropmarker') || document.createElement('DIV');
+      Element.hide(Sortable._marker);
+      Element.addClassName(Sortable._marker, 'dropmarker');
+      Sortable._marker.style.position = 'absolute';
+      document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
+    }    
+    var offsets = Position.cumulativeOffset(dropon);
+    Sortable._marker.style.left = offsets[0] + 'px';
+    Sortable._marker.style.top = offsets[1] + 'px';
+    
+    if(position=='after')
+      if(sortable.overlap == 'horizontal') 
+        Sortable._marker.style.left = (offsets[0]+dropon.clientWidth) + 'px';
+      else
+        Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px';
+    
+    Element.show(Sortable._marker);
+  },
+
+  serialize: function(element) {
+    element = $(element);
+    var sortableOptions = this.options(element);
+    var options = Object.extend({
+      tag:  sortableOptions.tag,
+      only: sortableOptions.only,
+      name: element.id,
+      format: sortableOptions.format || /^[^_]*_(.*)$/
+    }, arguments[1] || {});
+    return $(this.findElements(element, options) || []).map( function(item) {
+      return (encodeURIComponent(options.name) + "[]=" + 
+              encodeURIComponent(item.id.match(options.format) ? item.id.match(options.format)[1] : ''));
+    }).join("&");
+  }
+}
\ No newline at end of file

Added: jifty/branches/schema-plugins/share/web/static/js/scriptaculous/effects.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/scriptaculous/effects.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,903 @@
+// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// Contributors:
+//  Justin Palmer (http://encytemedia.com/)
+//  Mark Pilgrim (http://diveintomark.org/)
+//  Martin Bialasinki
+// 
+// See scriptaculous.js for full license.  
+
+/* ------------- element ext -------------- */  
+ 
+// converts rgb() and #xxx to #xxxxxx format,  
+// returns self (or first argument) if not convertable  
+String.prototype.parseColor = function() {  
+  var color = '#';  
+  if(this.slice(0,4) == 'rgb(') {  
+    var cols = this.slice(4,this.length-1).split(',');  
+    var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);  
+  } else {  
+    if(this.slice(0,1) == '#') {  
+      if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();  
+      if(this.length==7) color = this.toLowerCase();  
+    }  
+  }  
+  return(color.length==7 ? color : (arguments[0] || this));  
+}
+
+Element.collectTextNodes = function(element) {  
+  return $A($(element).childNodes).collect( function(node) {
+    return (node.nodeType==3 ? node.nodeValue : 
+      (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
+  }).flatten().join('');
+}
+
+Element.collectTextNodesIgnoreClass = function(element, className) {  
+  return $A($(element).childNodes).collect( function(node) {
+    return (node.nodeType==3 ? node.nodeValue : 
+      ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? 
+        Element.collectTextNodes(node) : ''));
+  }).flatten().join('');
+}
+
+Element.setStyle = function(element, style) {
+  element = $(element);
+  for(k in style) element.style[k.camelize()] = style[k];
+}
+
+Element.setContentZoom = function(element, percent) {  
+  Element.setStyle(element, {fontSize: (percent/100) + 'em'});   
+  if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);  
+}
+
+Element.getOpacity = function(element){  
+  var opacity;
+  if (opacity = Element.getStyle(element, 'opacity'))  
+    return parseFloat(opacity);  
+  if (opacity = (Element.getStyle(element, 'filter') || '').match(/alpha\(opacity=(.*)\)/))  
+    if(opacity[1]) return parseFloat(opacity[1]) / 100;  
+  return 1.0;  
+}
+
+Element.setOpacity = function(element, value){  
+  element= $(element);  
+  if (value == 1){
+    Element.setStyle(element, { opacity: 
+      (/Gecko/.test(navigator.userAgent) && !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? 
+      0.999999 : null });
+    if(/MSIE/.test(navigator.userAgent))  
+      Element.setStyle(element, {filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')});  
+  } else {  
+    if(value < 0.00001) value = 0;  
+    Element.setStyle(element, {opacity: value});
+    if(/MSIE/.test(navigator.userAgent))  
+     Element.setStyle(element, 
+       { filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') +
+                 'alpha(opacity='+value*100+')' });  
+  }   
+}  
+ 
+Element.getInlineOpacity = function(element){  
+  return $(element).style.opacity || '';
+}  
+
+Element.childrenWithClassName = function(element, className) {  
+  return $A($(element).getElementsByTagName('*')).select(
+    function(c) { return Element.hasClassName(c, className) });
+}
+
+Array.prototype.call = function() {
+  var args = arguments;
+  this.each(function(f){ f.apply(this, args) });
+}
+
+/*--------------------------------------------------------------------------*/
+
+var Effect = {
+  tagifyText: function(element) {
+    var tagifyStyle = 'position:relative';
+    if(/MSIE/.test(navigator.userAgent)) tagifyStyle += ';zoom:1';
+    element = $(element);
+    $A(element.childNodes).each( function(child) {
+      if(child.nodeType==3) {
+        child.nodeValue.toArray().each( function(character) {
+          element.insertBefore(
+            Builder.node('span',{style: tagifyStyle},
+              character == ' ' ? String.fromCharCode(160) : character), 
+              child);
+        });
+        Element.remove(child);
+      }
+    });
+  },
+  multiple: function(element, effect) {
+    var elements;
+    if(((typeof element == 'object') || 
+        (typeof element == 'function')) && 
+       (element.length))
+      elements = element;
+    else
+      elements = $(element).childNodes;
+      
+    var options = Object.extend({
+      speed: 0.1,
+      delay: 0.0
+    }, arguments[2] || {});
+    var masterDelay = options.delay;
+
+    $A(elements).each( function(element, index) {
+      new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
+    });
+  },
+  PAIRS: {
+    'slide':  ['SlideDown','SlideUp'],
+    'blind':  ['BlindDown','BlindUp'],
+    'appear': ['Appear','Fade']
+  },
+  toggle: function(element, effect) {
+    element = $(element);
+    effect = (effect || 'appear').toLowerCase();
+    var options = Object.extend({
+      queue: { position:'end', scope:(element.id || 'global') }
+    }, arguments[2] || {});
+    Effect[Element.visible(element) ? 
+      Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
+  }
+};
+
+var Effect2 = Effect; // deprecated
+
+/* ------------- transitions ------------- */
+
+Effect.Transitions = {}
+
+Effect.Transitions.linear = function(pos) {
+  return pos;
+}
+Effect.Transitions.sinoidal = function(pos) {
+  return (-Math.cos(pos*Math.PI)/2) + 0.5;
+}
+Effect.Transitions.reverse  = function(pos) {
+  return 1-pos;
+}
+Effect.Transitions.flicker = function(pos) {
+  return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
+}
+Effect.Transitions.wobble = function(pos) {
+  return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
+}
+Effect.Transitions.pulse = function(pos) {
+  return (Math.floor(pos*10) % 2 == 0 ? 
+    (pos*10-Math.floor(pos*10)) : 1-(pos*10-Math.floor(pos*10)));
+}
+Effect.Transitions.none = function(pos) {
+  return 0;
+}
+Effect.Transitions.full = function(pos) {
+  return 1;
+}
+
+/* ------------- core effects ------------- */
+
+Effect.ScopedQueue = Class.create();
+Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), {
+  initialize: function() {
+    this.effects  = [];
+    this.interval = null;
+  },
+  _each: function(iterator) {
+    this.effects._each(iterator);
+  },
+  add: function(effect) {
+    var timestamp = new Date().getTime();
+    
+    var position = (typeof effect.options.queue == 'string') ? 
+      effect.options.queue : effect.options.queue.position;
+    
+    switch(position) {
+      case 'front':
+        // move unstarted effects after this effect  
+        this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
+            e.startOn  += effect.finishOn;
+            e.finishOn += effect.finishOn;
+          });
+        break;
+      case 'end':
+        // start effect after last queued effect has finished
+        timestamp = this.effects.pluck('finishOn').max() || timestamp;
+        break;
+    }
+    
+    effect.startOn  += timestamp;
+    effect.finishOn += timestamp;
+    this.effects.push(effect);
+    if(!this.interval) 
+      this.interval = setInterval(this.loop.bind(this), 40);
+  },
+  remove: function(effect) {
+    this.effects = this.effects.reject(function(e) { return e==effect });
+    if(this.effects.length == 0) {
+      clearInterval(this.interval);
+      this.interval = null;
+    }
+  },
+  loop: function() {
+    var timePos = new Date().getTime();
+    this.effects.invoke('loop', timePos);
+  }
+});
+
+Effect.Queues = {
+  instances: $H(),
+  get: function(queueName) {
+    if(typeof queueName != 'string') return queueName;
+    
+    if(!this.instances[queueName])
+      this.instances[queueName] = new Effect.ScopedQueue();
+      
+    return this.instances[queueName];
+  }
+}
+Effect.Queue = Effect.Queues.get('global');
+
+Effect.DefaultOptions = {
+  transition: Effect.Transitions.sinoidal,
+  duration:   1.0,   // seconds
+  fps:        25.0,  // max. 25fps due to Effect.Queue implementation
+  sync:       false, // true for combining
+  from:       0.0,
+  to:         1.0,
+  delay:      0.0,
+  queue:      'parallel'
+}
+
+Effect.Base = function() {};
+Effect.Base.prototype = {
+  position: null,
+  start: function(options) {
+    this.options      = Object.extend(Object.extend({},Effect.DefaultOptions), options || {});
+    this.currentFrame = 0;
+    this.state        = 'idle';
+    this.startOn      = this.options.delay*1000;
+    this.finishOn     = this.startOn + (this.options.duration*1000);
+    this.event('beforeStart');
+    if(!this.options.sync)
+      Effect.Queues.get(typeof this.options.queue == 'string' ? 
+        'global' : this.options.queue.scope).add(this);
+  },
+  loop: function(timePos) {
+    if(timePos >= this.startOn) {
+      if(timePos >= this.finishOn) {
+        this.render(1.0);
+        this.cancel();
+        this.event('beforeFinish');
+        if(this.finish) this.finish(); 
+        this.event('afterFinish');
+        return;  
+      }
+      var pos   = (timePos - this.startOn) / (this.finishOn - this.startOn);
+      var frame = Math.round(pos * this.options.fps * this.options.duration);
+      if(frame > this.currentFrame) {
+        this.render(pos);
+        this.currentFrame = frame;
+      }
+    }
+  },
+  render: function(pos) {
+    if(this.state == 'idle') {
+      this.state = 'running';
+      this.event('beforeSetup');
+      if(this.setup) this.setup();
+      this.event('afterSetup');
+    }
+    if(this.state == 'running') {
+      if(this.options.transition) pos = this.options.transition(pos);
+      pos *= (this.options.to-this.options.from);
+      pos += this.options.from;
+      this.position = pos;
+      this.event('beforeUpdate');
+      if(this.update) this.update(pos);
+      this.event('afterUpdate');
+    }
+  },
+  cancel: function() {
+    if(!this.options.sync)
+      Effect.Queues.get(typeof this.options.queue == 'string' ? 
+        'global' : this.options.queue.scope).remove(this);
+    this.state = 'finished';
+  },
+  event: function(eventName) {
+    if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
+    if(this.options[eventName]) this.options[eventName](this);
+  },
+  inspect: function() {
+    return '#<Effect:' + $H(this).inspect() + ',options:' + $H(this.options).inspect() + '>';
+  }
+}
+
+Effect.Parallel = Class.create();
+Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {
+  initialize: function(effects) {
+    this.effects = effects || [];
+    this.start(arguments[1]);
+  },
+  update: function(position) {
+    this.effects.invoke('render', position);
+  },
+  finish: function(position) {
+    this.effects.each( function(effect) {
+      effect.render(1.0);
+      effect.cancel();
+      effect.event('beforeFinish');
+      if(effect.finish) effect.finish(position);
+      effect.event('afterFinish');
+    });
+  }
+});
+
+Effect.Opacity = Class.create();
+Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
+  initialize: function(element) {
+    this.element = $(element);
+    // make this work on IE on elements without 'layout'
+    if(/MSIE/.test(navigator.userAgent) && (!this.element.hasLayout))
+      Element.setStyle(this.element, {zoom: 1});
+    var options = Object.extend({
+      from: Element.getOpacity(this.element) || 0.0,
+      to:   1.0
+    }, arguments[1] || {});
+    this.start(options);
+  },
+  update: function(position) {
+    Element.setOpacity(this.element, position);
+  }
+});
+
+Effect.Move = Class.create();
+Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), {
+  initialize: function(element) {
+    this.element = $(element);
+    var options = Object.extend({
+      x:    0,
+      y:    0,
+      mode: 'relative'
+    }, arguments[1] || {});
+    this.start(options);
+  },
+  setup: function() {
+    // Bug in Opera: Opera returns the "real" position of a static element or
+    // relative element that does not have top/left explicitly set.
+    // ==> Always set top and left for position relative elements in your stylesheets 
+    // (to 0 if you do not need them) 
+    Element.makePositioned(this.element);
+    this.originalLeft = parseFloat(Element.getStyle(this.element,'left') || '0');
+    this.originalTop  = parseFloat(Element.getStyle(this.element,'top')  || '0');
+    if(this.options.mode == 'absolute') {
+      // absolute movement, so we need to calc deltaX and deltaY
+      this.options.x = this.options.x - this.originalLeft;
+      this.options.y = this.options.y - this.originalTop;
+    }
+  },
+  update: function(position) {
+    Element.setStyle(this.element, {
+      left: this.options.x  * position + this.originalLeft + 'px',
+      top:  this.options.y  * position + this.originalTop  + 'px'
+    });
+  }
+});
+
+// for backwards compatibility
+Effect.MoveBy = function(element, toTop, toLeft) {
+  return new Effect.Move(element, 
+    Object.extend({ x: toLeft, y: toTop }, arguments[3] || {}));
+};
+
+Effect.Scale = Class.create();
+Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
+  initialize: function(element, percent) {
+    this.element = $(element)
+    var options = Object.extend({
+      scaleX: true,
+      scaleY: true,
+      scaleContent: true,
+      scaleFromCenter: false,
+      scaleMode: 'box',        // 'box' or 'contents' or {} with provided values
+      scaleFrom: 100.0,
+      scaleTo:   percent
+    }, arguments[2] || {});
+    this.start(options);
+  },
+  setup: function() {
+    this.restoreAfterFinish = this.options.restoreAfterFinish || false;
+    this.elementPositioning = Element.getStyle(this.element,'position');
+    
+    this.originalStyle = {};
+    ['top','left','width','height','fontSize'].each( function(k) {
+      this.originalStyle[k] = this.element.style[k];
+    }.bind(this));
+      
+    this.originalTop  = this.element.offsetTop;
+    this.originalLeft = this.element.offsetLeft;
+    
+    var fontSize = Element.getStyle(this.element,'font-size') || '100%';
+    ['em','px','%'].each( function(fontSizeType) {
+      if(fontSize.indexOf(fontSizeType)>0) {
+        this.fontSize     = parseFloat(fontSize);
+        this.fontSizeType = fontSizeType;
+      }
+    }.bind(this));
+    
+    this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
+    
+    this.dims = null;
+    if(this.options.scaleMode=='box')
+      this.dims = [this.element.offsetHeight, this.element.offsetWidth];
+    if(/^content/.test(this.options.scaleMode))
+      this.dims = [this.element.scrollHeight, this.element.scrollWidth];
+    if(!this.dims)
+      this.dims = [this.options.scaleMode.originalHeight,
+                   this.options.scaleMode.originalWidth];
+  },
+  update: function(position) {
+    var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
+    if(this.options.scaleContent && this.fontSize)
+      Element.setStyle(this.element, {fontSize: this.fontSize * currentScale + this.fontSizeType });
+    this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
+  },
+  finish: function(position) {
+    if (this.restoreAfterFinish) Element.setStyle(this.element, this.originalStyle);
+  },
+  setDimensions: function(height, width) {
+    var d = {};
+    if(this.options.scaleX) d.width = width + 'px';
+    if(this.options.scaleY) d.height = height + 'px';
+    if(this.options.scaleFromCenter) {
+      var topd  = (height - this.dims[0])/2;
+      var leftd = (width  - this.dims[1])/2;
+      if(this.elementPositioning == 'absolute') {
+        if(this.options.scaleY) d.top = this.originalTop-topd + 'px';
+        if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
+      } else {
+        if(this.options.scaleY) d.top = -topd + 'px';
+        if(this.options.scaleX) d.left = -leftd + 'px';
+      }
+    }
+    Element.setStyle(this.element, d);
+  }
+});
+
+Effect.Highlight = Class.create();
+Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), {
+  initialize: function(element) {
+    this.element = $(element);
+    var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {});
+    this.start(options);
+  },
+  setup: function() {
+    // Prevent executing on elements not in the layout flow
+    if(Element.getStyle(this.element, 'display')=='none') { this.cancel(); return; }
+    // Disable background image during the effect
+    this.oldStyle = {
+      backgroundImage: Element.getStyle(this.element, 'background-image') };
+    Element.setStyle(this.element, {backgroundImage: 'none'});
+    if(!this.options.endcolor)
+      this.options.endcolor = Element.getStyle(this.element, 'background-color').parseColor('#ffffff');
+    if(!this.options.restorecolor)
+      this.options.restorecolor = Element.getStyle(this.element, 'background-color');
+    // init color calculations
+    this._base  = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
+    this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
+  },
+  update: function(position) {
+    Element.setStyle(this.element,{backgroundColor: $R(0,2).inject('#',function(m,v,i){
+      return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) });
+  },
+  finish: function() {
+    Element.setStyle(this.element, Object.extend(this.oldStyle, {
+      backgroundColor: this.options.restorecolor
+    }));
+  }
+});
+
+Effect.ScrollTo = Class.create();
+Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
+  initialize: function(element) {
+    this.element = $(element);
+    this.start(arguments[1] || {});
+  },
+  setup: function() {
+    Position.prepare();
+    var offsets = Position.cumulativeOffset(this.element);
+    if(this.options.offset) offsets[1] += this.options.offset;
+    var max = window.innerHeight ? 
+      window.height - window.innerHeight :
+      document.body.scrollHeight - 
+        (document.documentElement.clientHeight ? 
+          document.documentElement.clientHeight : document.body.clientHeight);
+    this.scrollStart = Position.deltaY;
+    this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart;
+  },
+  update: function(position) {
+    Position.prepare();
+    window.scrollTo(Position.deltaX, 
+      this.scrollStart + (position*this.delta));
+  }
+});
+
+/* ------------- combination effects ------------- */
+
+Effect.Fade = function(element) {
+  var oldOpacity = Element.getInlineOpacity(element);
+  var options = Object.extend({
+  from: Element.getOpacity(element) || 1.0,
+  to:   0.0,
+  afterFinishInternal: function(effect) { with(Element) { 
+    if(effect.options.to!=0) return;
+    hide(effect.element);
+    setStyle(effect.element, {opacity: oldOpacity}); }}
+  }, arguments[1] || {});
+  return new Effect.Opacity(element,options);
+}
+
+Effect.Appear = function(element) {
+  var options = Object.extend({
+  from: (Element.getStyle(element, 'display') == 'none' ? 0.0 : Element.getOpacity(element) || 0.0),
+  to:   1.0,
+  beforeSetup: function(effect) { with(Element) {
+    setOpacity(effect.element, effect.options.from);
+    show(effect.element); }}
+  }, arguments[1] || {});
+  return new Effect.Opacity(element,options);
+}
+
+Effect.Puff = function(element) {
+  element = $(element);
+  var oldStyle = { opacity: Element.getInlineOpacity(element), position: Element.getStyle(element, 'position') };
+  return new Effect.Parallel(
+   [ new Effect.Scale(element, 200, 
+      { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), 
+     new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], 
+     Object.extend({ duration: 1.0, 
+      beforeSetupInternal: function(effect) { with(Element) {
+        setStyle(effect.effects[0].element, {position: 'absolute'}); }},
+      afterFinishInternal: function(effect) { with(Element) {
+         hide(effect.effects[0].element);
+         setStyle(effect.effects[0].element, oldStyle); }}
+     }, arguments[1] || {})
+   );
+}
+
+Effect.BlindUp = function(element) {
+  element = $(element);
+  Element.makeClipping(element);
+  return new Effect.Scale(element, 0, 
+    Object.extend({ scaleContent: false, 
+      scaleX: false, 
+      restoreAfterFinish: true,
+      afterFinishInternal: function(effect) { with(Element) {
+        [hide, undoClipping].call(effect.element); }} 
+    }, arguments[1] || {})
+  );
+}
+
+Effect.BlindDown = function(element) {
+  element = $(element);
+  var oldHeight = Element.getStyle(element, 'height');
+  var elementDimensions = Element.getDimensions(element);
+  return new Effect.Scale(element, 100, 
+    Object.extend({ scaleContent: false, 
+      scaleX: false,
+      scaleFrom: 0,
+      scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
+      restoreAfterFinish: true,
+      afterSetup: function(effect) { with(Element) {
+        makeClipping(effect.element);
+        setStyle(effect.element, {height: '0px'});
+        show(effect.element); 
+      }},  
+      afterFinishInternal: function(effect) { with(Element) {
+        undoClipping(effect.element);
+        setStyle(effect.element, {height: oldHeight});
+      }}
+    }, arguments[1] || {})
+  );
+}
+
+Effect.SwitchOff = function(element) {
+  element = $(element);
+  var oldOpacity = Element.getInlineOpacity(element);
+  return new Effect.Appear(element, { 
+    duration: 0.4,
+    from: 0,
+    transition: Effect.Transitions.flicker,
+    afterFinishInternal: function(effect) {
+      new Effect.Scale(effect.element, 1, { 
+        duration: 0.3, scaleFromCenter: true,
+        scaleX: false, scaleContent: false, restoreAfterFinish: true,
+        beforeSetup: function(effect) { with(Element) {
+          [makePositioned,makeClipping].call(effect.element);
+        }},
+        afterFinishInternal: function(effect) { with(Element) {
+          [hide,undoClipping,undoPositioned].call(effect.element);
+          setStyle(effect.element, {opacity: oldOpacity});
+        }}
+      })
+    }
+  });
+}
+
+Effect.DropOut = function(element) {
+  element = $(element);
+  var oldStyle = {
+    top: Element.getStyle(element, 'top'),
+    left: Element.getStyle(element, 'left'),
+    opacity: Element.getInlineOpacity(element) };
+  return new Effect.Parallel(
+    [ new Effect.Move(element, {x: 0, y: 100, sync: true }), 
+      new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
+    Object.extend(
+      { duration: 0.5,
+        beforeSetup: function(effect) { with(Element) {
+          makePositioned(effect.effects[0].element); }},
+        afterFinishInternal: function(effect) { with(Element) {
+          [hide, undoPositioned].call(effect.effects[0].element);
+          setStyle(effect.effects[0].element, oldStyle); }} 
+      }, arguments[1] || {}));
+}
+
+Effect.Shake = function(element) {
+  element = $(element);
+  var oldStyle = {
+    top: Element.getStyle(element, 'top'),
+    left: Element.getStyle(element, 'left') };
+	  return new Effect.Move(element, 
+	    { x:  20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
+	  new Effect.Move(effect.element,
+	    { x: -40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
+	  new Effect.Move(effect.element,
+	    { x:  40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
+	  new Effect.Move(effect.element,
+	    { x: -40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
+	  new Effect.Move(effect.element,
+	    { x:  40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
+	  new Effect.Move(effect.element,
+	    { x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) { with(Element) {
+        undoPositioned(effect.element);
+        setStyle(effect.element, oldStyle);
+  }}}) }}) }}) }}) }}) }});
+}
+
+Effect.SlideDown = function(element) {
+  element = $(element);
+  Element.cleanWhitespace(element);
+  // SlideDown need to have the content of the element wrapped in a container element with fixed height!
+  var oldInnerBottom = Element.getStyle(element.firstChild, 'bottom');
+  var elementDimensions = Element.getDimensions(element);
+  return new Effect.Scale(element, 100, Object.extend({ 
+    scaleContent: false, 
+    scaleX: false, 
+    scaleFrom: 0,
+    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
+    restoreAfterFinish: true,
+    afterSetup: function(effect) { with(Element) {
+      makePositioned(effect.element);
+      makePositioned(effect.element.firstChild);
+      if(window.opera) setStyle(effect.element, {top: ''});
+      makeClipping(effect.element);
+      setStyle(effect.element, {height: '0px'});
+      show(element); }},
+    afterUpdateInternal: function(effect) { with(Element) {
+      setStyle(effect.element.firstChild, {bottom:
+        (effect.dims[0] - effect.element.clientHeight) + 'px' }); }},
+    afterFinishInternal: function(effect) { with(Element) {
+      undoClipping(effect.element); 
+      undoPositioned(effect.element.firstChild);
+      undoPositioned(effect.element);
+      setStyle(effect.element.firstChild, {bottom: oldInnerBottom}); }}
+    }, arguments[1] || {})
+  );
+}
+  
+Effect.SlideUp = function(element) {
+  element = $(element);
+  Element.cleanWhitespace(element);
+  var oldInnerBottom = Element.getStyle(element.firstChild, 'bottom');
+  return new Effect.Scale(element, 0, 
+   Object.extend({ scaleContent: false, 
+    scaleX: false, 
+    scaleMode: 'box',
+    scaleFrom: 100,
+    restoreAfterFinish: true,
+    beforeStartInternal: function(effect) { with(Element) {
+      makePositioned(effect.element);
+      makePositioned(effect.element.firstChild);
+      if(window.opera) setStyle(effect.element, {top: ''});
+      makeClipping(effect.element);
+      show(element); }},  
+    afterUpdateInternal: function(effect) { with(Element) {
+      setStyle(effect.element.firstChild, {bottom:
+        (effect.dims[0] - effect.element.clientHeight) + 'px' }); }},
+    afterFinishInternal: function(effect) { with(Element) {
+        [hide, undoClipping].call(effect.element); 
+        undoPositioned(effect.element.firstChild);
+        undoPositioned(effect.element);
+        setStyle(effect.element.firstChild, {bottom: oldInnerBottom}); }}
+   }, arguments[1] || {})
+  );
+}
+
+// Bug in opera makes the TD containing this element expand for a instance after finish 
+Effect.Squish = function(element) {
+  return new Effect.Scale(element, window.opera ? 1 : 0, 
+    { restoreAfterFinish: true,
+      beforeSetup: function(effect) { with(Element) {
+        makeClipping(effect.element); }},  
+      afterFinishInternal: function(effect) { with(Element) {
+        hide(effect.element); 
+        undoClipping(effect.element); }}
+  });
+}
+
+Effect.Grow = function(element) {
+  element = $(element);
+  var options = Object.extend({
+    direction: 'center',
+    moveTransistion: Effect.Transitions.sinoidal,
+    scaleTransition: Effect.Transitions.sinoidal,
+    opacityTransition: Effect.Transitions.full
+  }, arguments[1] || {});
+  var oldStyle = {
+    top: element.style.top,
+    left: element.style.left,
+    height: element.style.height,
+    width: element.style.width,
+    opacity: Element.getInlineOpacity(element) };
+
+  var dims = Element.getDimensions(element);    
+  var initialMoveX, initialMoveY;
+  var moveX, moveY;
+  
+  switch (options.direction) {
+    case 'top-left':
+      initialMoveX = initialMoveY = moveX = moveY = 0; 
+      break;
+    case 'top-right':
+      initialMoveX = dims.width;
+      initialMoveY = moveY = 0;
+      moveX = -dims.width;
+      break;
+    case 'bottom-left':
+      initialMoveX = moveX = 0;
+      initialMoveY = dims.height;
+      moveY = -dims.height;
+      break;
+    case 'bottom-right':
+      initialMoveX = dims.width;
+      initialMoveY = dims.height;
+      moveX = -dims.width;
+      moveY = -dims.height;
+      break;
+    case 'center':
+      initialMoveX = dims.width / 2;
+      initialMoveY = dims.height / 2;
+      moveX = -dims.width / 2;
+      moveY = -dims.height / 2;
+      break;
+  }
+  
+  return new Effect.Move(element, {
+    x: initialMoveX,
+    y: initialMoveY,
+    duration: 0.01, 
+    beforeSetup: function(effect) { with(Element) {
+      hide(effect.element);
+      makeClipping(effect.element);
+      makePositioned(effect.element);
+    }},
+    afterFinishInternal: function(effect) {
+      new Effect.Parallel(
+        [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
+          new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
+          new Effect.Scale(effect.element, 100, {
+            scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, 
+            sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
+        ], Object.extend({
+             beforeSetup: function(effect) { with(Element) {
+               setStyle(effect.effects[0].element, {height: '0px'});
+               show(effect.effects[0].element); }},
+             afterFinishInternal: function(effect) { with(Element) {
+               [undoClipping, undoPositioned].call(effect.effects[0].element); 
+               setStyle(effect.effects[0].element, oldStyle); }}
+           }, options)
+      )
+    }
+  });
+}
+
+Effect.Shrink = function(element) {
+  element = $(element);
+  var options = Object.extend({
+    direction: 'center',
+    moveTransistion: Effect.Transitions.sinoidal,
+    scaleTransition: Effect.Transitions.sinoidal,
+    opacityTransition: Effect.Transitions.none
+  }, arguments[1] || {});
+  var oldStyle = {
+    top: element.style.top,
+    left: element.style.left,
+    height: element.style.height,
+    width: element.style.width,
+    opacity: Element.getInlineOpacity(element) };
+
+  var dims = Element.getDimensions(element);
+  var moveX, moveY;
+  
+  switch (options.direction) {
+    case 'top-left':
+      moveX = moveY = 0;
+      break;
+    case 'top-right':
+      moveX = dims.width;
+      moveY = 0;
+      break;
+    case 'bottom-left':
+      moveX = 0;
+      moveY = dims.height;
+      break;
+    case 'bottom-right':
+      moveX = dims.width;
+      moveY = dims.height;
+      break;
+    case 'center':  
+      moveX = dims.width / 2;
+      moveY = dims.height / 2;
+      break;
+  }
+  
+  return new Effect.Parallel(
+    [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
+      new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
+      new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
+    ], Object.extend({            
+         beforeStartInternal: function(effect) { with(Element) {
+           [makePositioned, makeClipping].call(effect.effects[0].element) }},
+         afterFinishInternal: function(effect) { with(Element) {
+           [hide, undoClipping, undoPositioned].call(effect.effects[0].element);
+           setStyle(effect.effects[0].element, oldStyle); }}
+       }, options)
+  );
+}
+
+Effect.Pulsate = function(element) {
+  element = $(element);
+  var options    = arguments[1] || {};
+  var oldOpacity = Element.getInlineOpacity(element);
+  var transition = options.transition || Effect.Transitions.sinoidal;
+  var reverser   = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) };
+  reverser.bind(transition);
+  return new Effect.Opacity(element, 
+    Object.extend(Object.extend({  duration: 3.0, from: 0,
+      afterFinishInternal: function(effect) { Element.setStyle(effect.element, {opacity: oldOpacity}); }
+    }, options), {transition: reverser}));
+}
+
+Effect.Fold = function(element) {
+  element = $(element);
+  var oldStyle = {
+    top: element.style.top,
+    left: element.style.left,
+    width: element.style.width,
+    height: element.style.height };
+  Element.makeClipping(element);
+  return new Effect.Scale(element, 5, Object.extend({   
+    scaleContent: false,
+    scaleX: false,
+    afterFinishInternal: function(effect) {
+    new Effect.Scale(element, 1, { 
+      scaleContent: false, 
+      scaleY: false,
+      afterFinishInternal: function(effect) { with(Element) {
+        [hide, undoClipping].call(effect.element); 
+        setStyle(effect.element, oldStyle);
+      }} });
+  }}, arguments[1] || {}));
+}

Added: jifty/branches/schema-plugins/share/web/static/js/scriptaculous/scriptaculous.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/scriptaculous/scriptaculous.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,45 @@
+// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// 
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+var Scriptaculous = {
+  Version: '1.5.1',
+  require: function(libraryName) {
+    // inserting via DOM fails in Safari 2.0, so brute force approach
+    document.write('<script type="text/javascript" src="'+libraryName+'"></script>');
+  },
+  load: function() {
+    if((typeof Prototype=='undefined') ||
+      parseFloat(Prototype.Version.split(".")[0] + "." +
+                 Prototype.Version.split(".")[1]) < 1.4)
+      throw("script.aculo.us requires the Prototype JavaScript framework >= 1.4.0");
+    
+    $A(document.getElementsByTagName("script")).findAll( function(s) {
+      return (s.src && s.src.match(/scriptaculous\.js(\?.*)?$/))
+    }).each( function(s) {
+      var path = s.src.replace(/scriptaculous\.js(\?.*)?$/,'');
+      var includes = s.src.match(/\?.*load=([a-z,]*)/);
+      (includes ? includes[1] : 'builder,effects,dragdrop,controls,slider').split(',').each(
+       function(include) { Scriptaculous.require(path+include+'.js') });
+    });
+  }
+}
+
+Scriptaculous.load();
\ No newline at end of file

Added: jifty/branches/schema-plugins/share/web/static/js/scriptaculous/slider.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/scriptaculous/slider.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,283 @@
+// Copyright (c) 2005 Marty Haught, Thomas Fuchs 
+//
+// See http://script.aculo.us for more info
+// 
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+if(!Control) var Control = {};
+Control.Slider = Class.create();
+
+// options:
+//  axis: 'vertical', or 'horizontal' (default)
+//
+// callbacks:
+//  onChange(value)
+//  onSlide(value)
+Control.Slider.prototype = {
+  initialize: function(handle, track, options) {
+    var slider = this;
+    
+    if(handle instanceof Array) {
+      this.handles = handle.collect( function(e) { return $(e) });
+    } else {
+      this.handles = [$(handle)];
+    }
+    
+    this.track   = $(track);
+    this.options = options || {};
+
+    this.axis      = this.options.axis || 'horizontal';
+    this.increment = this.options.increment || 1;
+    this.step      = parseInt(this.options.step || '1');
+    this.range     = this.options.range || $R(0,1);
+    
+    this.value     = 0; // assure backwards compat
+    this.values    = this.handles.map( function() { return 0 });
+    this.spans     = this.options.spans ? this.options.spans.map(function(s){ return $(s) }) : false;
+    this.options.startSpan = $(this.options.startSpan || null);
+    this.options.endSpan   = $(this.options.endSpan || null);
+
+    this.restricted = this.options.restricted || false;
+
+    this.maximum   = this.options.maximum || this.range.end;
+    this.minimum   = this.options.minimum || this.range.start;
+
+    // Will be used to align the handle onto the track, if necessary
+    this.alignX = parseInt(this.options.alignX || '0');
+    this.alignY = parseInt(this.options.alignY || '0');
+    
+    this.trackLength = this.maximumOffset() - this.minimumOffset();
+    this.handleLength = this.isVertical() ? this.handles[0].offsetHeight : this.handles[0].offsetWidth;
+
+    this.active   = false;
+    this.dragging = false;
+    this.disabled = false;
+
+    if(this.options.disabled) this.setDisabled();
+
+    // Allowed values array
+    this.allowedValues = this.options.values ? this.options.values.sortBy(Prototype.K) : false;
+    if(this.allowedValues) {
+      this.minimum = this.allowedValues.min();
+      this.maximum = this.allowedValues.max();
+    }
+
+    this.eventMouseDown = this.startDrag.bindAsEventListener(this);
+    this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
+    this.eventMouseMove = this.update.bindAsEventListener(this);
+
+    // Initialize handles in reverse (make sure first handle is active)
+    this.handles.each( function(h,i) {
+      i = slider.handles.length-1-i;
+      slider.setValue(parseFloat(
+        (slider.options.sliderValue instanceof Array ? 
+          slider.options.sliderValue[i] : slider.options.sliderValue) || 
+         slider.range.start), i);
+      Element.makePositioned(h); // fix IE
+      Event.observe(h, "mousedown", slider.eventMouseDown);
+    });
+    
+    Event.observe(this.track, "mousedown", this.eventMouseDown);
+    Event.observe(document, "mouseup", this.eventMouseUp);
+    Event.observe(document, "mousemove", this.eventMouseMove);
+    
+    this.initialized = true;
+  },
+  dispose: function() {
+    var slider = this;    
+    Event.stopObserving(this.track, "mousedown", this.eventMouseDown);
+    Event.stopObserving(document, "mouseup", this.eventMouseUp);
+    Event.stopObserving(document, "mousemove", this.eventMouseMove);
+    this.handles.each( function(h) {
+      Event.stopObserving(h, "mousedown", slider.eventMouseDown);
+    });
+  },
+  setDisabled: function(){
+    this.disabled = true;
+  },
+  setEnabled: function(){
+    this.disabled = false;
+  },  
+  getNearestValue: function(value){
+    if(this.allowedValues){
+      if(value >= this.allowedValues.max()) return(this.allowedValues.max());
+      if(value <= this.allowedValues.min()) return(this.allowedValues.min());
+      
+      var offset = Math.abs(this.allowedValues[0] - value);
+      var newValue = this.allowedValues[0];
+      this.allowedValues.each( function(v) {
+        var currentOffset = Math.abs(v - value);
+        if(currentOffset <= offset){
+          newValue = v;
+          offset = currentOffset;
+        } 
+      });
+      return newValue;
+    }
+    if(value > this.range.end) return this.range.end;
+    if(value < this.range.start) return this.range.start;
+    return value;
+  },
+  setValue: function(sliderValue, handleIdx){
+    if(!this.active) {
+      this.activeHandle    = this.handles[handleIdx];
+      this.activeHandleIdx = handleIdx;
+      this.updateStyles();
+    }
+    handleIdx = handleIdx || this.activeHandleIdx || 0;
+    if(this.initialized && this.restricted) {
+      if((handleIdx>0) && (sliderValue<this.values[handleIdx-1]))
+        sliderValue = this.values[handleIdx-1];
+      if((handleIdx < (this.handles.length-1)) && (sliderValue>this.values[handleIdx+1]))
+        sliderValue = this.values[handleIdx+1];
+    }
+    sliderValue = this.getNearestValue(sliderValue);
+    this.values[handleIdx] = sliderValue;
+    this.value = this.values[0]; // assure backwards compat
+    
+    this.handles[handleIdx].style[this.isVertical() ? 'top' : 'left'] = 
+      this.translateToPx(sliderValue);
+    
+    this.drawSpans();
+    if(!this.dragging || !this.event) this.updateFinished();
+  },
+  setValueBy: function(delta, handleIdx) {
+    this.setValue(this.values[handleIdx || this.activeHandleIdx || 0] + delta, 
+      handleIdx || this.activeHandleIdx || 0);
+  },
+  translateToPx: function(value) {
+    return Math.round(
+      ((this.trackLength-this.handleLength)/(this.range.end-this.range.start)) * 
+      (value - this.range.start)) + "px";
+  },
+  translateToValue: function(offset) {
+    return ((offset/(this.trackLength-this.handleLength) * 
+      (this.range.end-this.range.start)) + this.range.start);
+  },
+  getRange: function(range) {
+    var v = this.values.sortBy(Prototype.K); 
+    range = range || 0;
+    return $R(v[range],v[range+1]);
+  },
+  minimumOffset: function(){
+    return(this.isVertical() ? this.alignY : this.alignX);
+  },
+  maximumOffset: function(){
+    return(this.isVertical() ?
+      this.track.offsetHeight - this.alignY : this.track.offsetWidth - this.alignX);
+  },  
+  isVertical:  function(){
+    return (this.axis == 'vertical');
+  },
+  drawSpans: function() {
+    var slider = this;
+    if(this.spans)
+      $R(0, this.spans.length-1).each(function(r) { slider.setSpan(slider.spans[r], slider.getRange(r)) });
+    if(this.options.startSpan)
+      this.setSpan(this.options.startSpan,
+        $R(0, this.values.length>1 ? this.getRange(0).min() : this.value ));
+    if(this.options.endSpan)
+      this.setSpan(this.options.endSpan, 
+        $R(this.values.length>1 ? this.getRange(this.spans.length-1).max() : this.value, this.maximum));
+  },
+  setSpan: function(span, range) {
+    if(this.isVertical()) {
+      span.style.top = this.translateToPx(range.start);
+      span.style.height = this.translateToPx(range.end - range.start);
+    } else {
+      span.style.left = this.translateToPx(range.start);
+      span.style.width = this.translateToPx(range.end - range.start);
+    }
+  },
+  updateStyles: function() {
+    this.handles.each( function(h){ Element.removeClassName(h, 'selected') });
+    Element.addClassName(this.activeHandle, 'selected');
+  },
+  startDrag: function(event) {
+    if(Event.isLeftClick(event)) {
+      if(!this.disabled){
+        this.active = true;
+        
+        var handle = Event.element(event);
+        var pointer  = [Event.pointerX(event), Event.pointerY(event)];
+        if(handle==this.track) {
+          var offsets  = Position.cumulativeOffset(this.track); 
+          this.event = event;
+          this.setValue(this.translateToValue( 
+           (this.isVertical() ? pointer[1]-offsets[1] : pointer[0]-offsets[0])-(this.handleLength/2)
+          ));
+          var offsets  = Position.cumulativeOffset(this.activeHandle);
+          this.offsetX = (pointer[0] - offsets[0]);
+          this.offsetY = (pointer[1] - offsets[1]);
+        } else {
+          // find the handle (prevents issues with Safari)
+          while((this.handles.indexOf(handle) == -1) && handle.parentNode) 
+            handle = handle.parentNode;
+        
+          this.activeHandle    = handle;
+          this.activeHandleIdx = this.handles.indexOf(this.activeHandle);
+          this.updateStyles();
+        
+          var offsets  = Position.cumulativeOffset(this.activeHandle);
+          this.offsetX = (pointer[0] - offsets[0]);
+          this.offsetY = (pointer[1] - offsets[1]);
+        }
+      }
+      Event.stop(event);
+    }
+  },
+  update: function(event) {
+   if(this.active) {
+      if(!this.dragging) this.dragging = true;
+      this.draw(event);
+      // fix AppleWebKit rendering
+      if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
+      Event.stop(event);
+   }
+  },
+  draw: function(event) {
+    var pointer = [Event.pointerX(event), Event.pointerY(event)];
+    var offsets = Position.cumulativeOffset(this.track);
+    pointer[0] -= this.offsetX + offsets[0];
+    pointer[1] -= this.offsetY + offsets[1];
+    this.event = event;
+    this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] ));
+    if(this.initialized && this.options.onSlide)
+      this.options.onSlide(this.values.length>1 ? this.values : this.value, this);
+  },
+  endDrag: function(event) {
+    if(this.active && this.dragging) {
+      this.finishDrag(event, true);
+      Event.stop(event);
+    }
+    this.active = false;
+    this.dragging = false;
+  },  
+  finishDrag: function(event, success) {
+    this.active = false;
+    this.dragging = false;
+    this.updateFinished();
+  },
+  updateFinished: function() {
+    if(this.initialized && this.options.onChange) 
+      this.options.onChange(this.values.length>1 ? this.values : this.value, this);
+    this.event = null;
+  }
+}
\ No newline at end of file

Added: jifty/branches/schema-plugins/share/web/static/js/scriptaculous/unittest.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/scriptaculous/unittest.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,363 @@
+// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//           (c) 2005 Jon Tirsen (http://www.tirsen.com)
+//           (c) 2005 Michael Schuerig (http://www.schuerig.de/michael/)
+//
+// See scriptaculous.js for full license.
+
+// experimental, Firefox-only
+Event.simulateMouse = function(element, eventName) {
+  var options = Object.extend({
+    pointerX: 0,
+    pointerY: 0,
+    buttons: 0
+  }, arguments[2] || {});
+  var oEvent = document.createEvent("MouseEvents");
+  oEvent.initMouseEvent(eventName, true, true, document.defaultView, 
+    options.buttons, options.pointerX, options.pointerY, options.pointerX, options.pointerY, 
+    false, false, false, false, 0, $(element));
+  
+  if(this.mark) Element.remove(this.mark);
+  this.mark = document.createElement('div');
+  this.mark.appendChild(document.createTextNode(" "));
+  document.body.appendChild(this.mark);
+  this.mark.style.position = 'absolute';
+  this.mark.style.top = options.pointerY + "px";
+  this.mark.style.left = options.pointerX + "px";
+  this.mark.style.width = "5px";
+  this.mark.style.height = "5px;";
+  this.mark.style.borderTop = "1px solid red;"
+  this.mark.style.borderLeft = "1px solid red;"
+  
+  if(this.step)
+    alert('['+new Date().getTime().toString()+'] '+eventName+'/'+Test.Unit.inspect(options));
+  
+  $(element).dispatchEvent(oEvent);
+};
+
+// Note: Due to a fix in Firefox 1.0.5/6 that probably fixed "too much", this doesn't work in 1.0.6 or DP2.
+// You need to downgrade to 1.0.4 for now to get this working
+// See https://bugzilla.mozilla.org/show_bug.cgi?id=289940 for the fix that fixed too much
+Event.simulateKey = function(element, eventName) {
+  var options = Object.extend({
+    ctrlKey: false,
+    altKey: false,
+    shiftKey: false,
+    metaKey: false,
+    keyCode: 0,
+    charCode: 0
+  }, arguments[2] || {});
+
+  var oEvent = document.createEvent("KeyEvents");
+  oEvent.initKeyEvent(eventName, true, true, window, 
+    options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
+    options.keyCode, options.charCode );
+  $(element).dispatchEvent(oEvent);
+};
+
+Event.simulateKeys = function(element, command) {
+  for(var i=0; i<command.length; i++) {
+    Event.simulateKey(element,'keypress',{charCode:command.charCodeAt(i)});
+  }
+};
+
+var Test = {}
+Test.Unit = {};
+
+// security exception workaround
+Test.Unit.inspect = function(obj) {
+  var info = [];
+
+  if(typeof obj=="string" || 
+     typeof obj=="number") {
+    return obj;
+  } else {
+    for(property in obj)
+      if(typeof obj[property]!="function")
+        info.push(property + ' => ' + 
+          (typeof obj[property] == "string" ?
+            '"' + obj[property] + '"' :
+            obj[property]));
+  }
+
+  return ("'" + obj + "' #" + typeof obj + 
+    ": {" + info.join(", ") + "}");
+}
+
+Test.Unit.Logger = Class.create();
+Test.Unit.Logger.prototype = {
+  initialize: function(log) {
+    this.log = $(log);
+    if (this.log) {
+      this._createLogTable();
+    }
+  },
+  start: function(testName) {
+    if (!this.log) return;
+    this.testName = testName;
+    this.lastLogLine = document.createElement('tr');
+    this.statusCell = document.createElement('td');
+    this.nameCell = document.createElement('td');
+    this.nameCell.appendChild(document.createTextNode(testName));
+    this.messageCell = document.createElement('td');
+    this.lastLogLine.appendChild(this.statusCell);
+    this.lastLogLine.appendChild(this.nameCell);
+    this.lastLogLine.appendChild(this.messageCell);
+    this.loglines.appendChild(this.lastLogLine);
+  },
+  finish: function(status, summary) {
+    if (!this.log) return;
+    this.lastLogLine.className = status;
+    this.statusCell.innerHTML = status;
+    this.messageCell.innerHTML = this._toHTML(summary);
+  },
+  message: function(message) {
+    if (!this.log) return;
+    this.messageCell.innerHTML = this._toHTML(message);
+  },
+  summary: function(summary) {
+    if (!this.log) return;
+    this.logsummary.innerHTML = this._toHTML(summary);
+  },
+  _createLogTable: function() {
+    this.log.innerHTML =
+    '<div id="logsummary"></div>' +
+    '<table id="logtable">' +
+    '<thead><tr><th>Status</th><th>Test</th><th>Message</th></tr></thead>' +
+    '<tbody id="loglines"></tbody>' +
+    '</table>';
+    this.logsummary = $('logsummary')
+    this.loglines = $('loglines');
+  },
+  _toHTML: function(txt) {
+    return txt.escapeHTML().replace(/\n/g,"<br/>");
+  }
+}
+
+Test.Unit.Runner = Class.create();
+Test.Unit.Runner.prototype = {
+  initialize: function(testcases) {
+    this.options = Object.extend({
+      testLog: 'testlog'
+    }, arguments[1] || {});
+    this.options.resultsURL = this.parseResultsURLQueryParameter();
+    if (this.options.testLog) {
+      this.options.testLog = $(this.options.testLog) || null;
+    }
+    if(this.options.tests) {
+      this.tests = [];
+      for(var i = 0; i < this.options.tests.length; i++) {
+        if(/^test/.test(this.options.tests[i])) {
+          this.tests.push(new Test.Unit.Testcase(this.options.tests[i], testcases[this.options.tests[i]], testcases["setup"], testcases["teardown"]));
+        }
+      }
+    } else {
+      if (this.options.test) {
+        this.tests = [new Test.Unit.Testcase(this.options.test, testcases[this.options.test], testcases["setup"], testcases["teardown"])];
+      } else {
+        this.tests = [];
+        for(var testcase in testcases) {
+          if(/^test/.test(testcase)) {
+            this.tests.push(new Test.Unit.Testcase(testcase, testcases[testcase], testcases["setup"], testcases["teardown"]));
+          }
+        }
+      }
+    }
+    this.currentTest = 0;
+    this.logger = new Test.Unit.Logger(this.options.testLog);
+    setTimeout(this.runTests.bind(this), 1000);
+  },
+  parseResultsURLQueryParameter: function() {
+    return window.location.search.parseQuery()["resultsURL"];
+  },
+  // Returns:
+  //  "ERROR" if there was an error,
+  //  "FAILURE" if there was a failure, or
+  //  "SUCCESS" if there was neither
+  getResult: function() {
+    var hasFailure = false;
+    for(var i=0;i<this.tests.length;i++) {
+      if (this.tests[i].errors > 0) {
+        return "ERROR";
+      }
+      if (this.tests[i].failures > 0) {
+        hasFailure = true;
+      }
+    }
+    if (hasFailure) {
+      return "FAILURE";
+    } else {
+      return "SUCCESS";
+    }
+  },
+  postResults: function() {
+    if (this.options.resultsURL) {
+      new Ajax.Request(this.options.resultsURL, 
+        { method: 'get', parameters: 'result=' + this.getResult(), asynchronous: false });
+    }
+  },
+  runTests: function() {
+    var test = this.tests[this.currentTest];
+    if (!test) {
+      // finished!
+      this.postResults();
+      this.logger.summary(this.summary());
+      return;
+    }
+    if(!test.isWaiting) {
+      this.logger.start(test.name);
+    }
+    test.run();
+    if(test.isWaiting) {
+      this.logger.message("Waiting for " + test.timeToWait + "ms");
+      setTimeout(this.runTests.bind(this), test.timeToWait || 1000);
+    } else {
+      this.logger.finish(test.status(), test.summary());
+      this.currentTest++;
+      // tail recursive, hopefully the browser will skip the stackframe
+      this.runTests();
+    }
+  },
+  summary: function() {
+    var assertions = 0;
+    var failures = 0;
+    var errors = 0;
+    var messages = [];
+    for(var i=0;i<this.tests.length;i++) {
+      assertions +=   this.tests[i].assertions;
+      failures   +=   this.tests[i].failures;
+      errors     +=   this.tests[i].errors;
+    }
+    return (
+      this.tests.length + " tests, " + 
+      assertions + " assertions, " + 
+      failures   + " failures, " +
+      errors     + " errors");
+  }
+}
+
+Test.Unit.Assertions = Class.create();
+Test.Unit.Assertions.prototype = {
+  initialize: function() {
+    this.assertions = 0;
+    this.failures   = 0;
+    this.errors     = 0;
+    this.messages   = [];
+  },
+  summary: function() {
+    return (
+      this.assertions + " assertions, " + 
+      this.failures   + " failures, " +
+      this.errors     + " errors" + "\n" +
+      this.messages.join("\n"));
+  },
+  pass: function() {
+    this.assertions++;
+  },
+  fail: function(message) {
+    this.failures++;
+    this.messages.push("Failure: " + message);
+  },
+  error: function(error) {
+    this.errors++;
+    this.messages.push(error.name + ": "+ error.message + "(" + Test.Unit.inspect(error) +")");
+  },
+  status: function() {
+    if (this.failures > 0) return 'failed';
+    if (this.errors > 0) return 'error';
+    return 'passed';
+  },
+  assert: function(expression) {
+    var message = arguments[1] || 'assert: got "' + Test.Unit.inspect(expression) + '"';
+    try { expression ? this.pass() : 
+      this.fail(message); }
+    catch(e) { this.error(e); }
+  },
+  assertEqual: function(expected, actual) {
+    var message = arguments[2] || "assertEqual";
+    try { (expected == actual) ? this.pass() :
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) + 
+        '", actual "' + Test.Unit.inspect(actual) + '"'); }
+    catch(e) { this.error(e); }
+  },
+  assertNotEqual: function(expected, actual) {
+    var message = arguments[2] || "assertNotEqual";
+    try { (expected != actual) ? this.pass() : 
+      this.fail(message + ': got "' + Test.Unit.inspect(actual) + '"'); }
+    catch(e) { this.error(e); }
+  },
+  assertNull: function(obj) {
+    var message = arguments[1] || 'assertNull'
+    try { (obj==null) ? this.pass() : 
+      this.fail(message + ': got "' + Test.Unit.inspect(obj) + '"'); }
+    catch(e) { this.error(e); }
+  },
+  assertHidden: function(element) {
+    var message = arguments[1] || 'assertHidden';
+    this.assertEqual("none", element.style.display, message);
+  },
+  assertNotNull: function(object) {
+    var message = arguments[1] || 'assertNotNull';
+    this.assert(object != null, message);
+  },
+  assertInstanceOf: function(expected, actual) {
+    var message = arguments[2] || 'assertInstanceOf';
+    try { 
+      (actual instanceof expected) ? this.pass() : 
+      this.fail(message + ": object was not an instance of the expected type"); }
+    catch(e) { this.error(e); } 
+  },
+  assertNotInstanceOf: function(expected, actual) {
+    var message = arguments[2] || 'assertNotInstanceOf';
+    try { 
+      !(actual instanceof expected) ? this.pass() : 
+      this.fail(message + ": object was an instance of the not expected type"); }
+    catch(e) { this.error(e); } 
+  },
+  _isVisible: function(element) {
+    element = $(element);
+    if(!element.parentNode) return true;
+    this.assertNotNull(element);
+    if(element.style && Element.getStyle(element, 'display') == 'none')
+      return false;
+    
+    return this._isVisible(element.parentNode);
+  },
+  assertNotVisible: function(element) {
+    this.assert(!this._isVisible(element), Test.Unit.inspect(element) + " was not hidden and didn't have a hidden parent either. " + ("" || arguments[1]));
+  },
+  assertVisible: function(element) {
+    this.assert(this._isVisible(element), Test.Unit.inspect(element) + " was not visible. " + ("" || arguments[1]));
+  }
+}
+
+Test.Unit.Testcase = Class.create();
+Object.extend(Object.extend(Test.Unit.Testcase.prototype, Test.Unit.Assertions.prototype), {
+  initialize: function(name, test, setup, teardown) {
+    Test.Unit.Assertions.prototype.initialize.bind(this)();
+    this.name           = name;
+    this.test           = test || function() {};
+    this.setup          = setup || function() {};
+    this.teardown       = teardown || function() {};
+    this.isWaiting      = false;
+    this.timeToWait     = 1000;
+  },
+  wait: function(time, nextPart) {
+    this.isWaiting = true;
+    this.test = nextPart;
+    this.timeToWait = time;
+  },
+  run: function() {
+    try {
+      try {
+        if (!this.isWaiting) this.setup.bind(this)();
+        this.isWaiting = false;
+        this.test.bind(this)();
+      } finally {
+        if(!this.isWaiting) {
+          this.teardown.bind(this)();
+        }
+      }
+    }
+    catch(e) { this.error(e); }
+  }
+});
\ No newline at end of file

Added: jifty/branches/schema-plugins/share/web/static/js/setup_jsan.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/setup_jsan.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,13 @@
+/* Setup JSAN for Jifty defaults */
+JSAN.includePath = [ "/static/js/jsan" ];
+JSAN.errorLevel  = "none";
+
+/*
+ * Stub out JSAN.use to avoid Ajax loading of JSAN libs if they've
+ * already been loaded by a <script> tag
+ */
+JSAN._use = JSAN.use;
+JSAN.use  = function() {
+    if ( !arguments[0] ) JSAN._use(arguments);
+};
+

Added: jifty/branches/schema-plugins/share/web/static/js/yui/calendar.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/yui/calendar.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,4255 @@
+/*
+Copyright (c) 2006, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.net/yui/license.txt
+version 0.12.0
+*/
+
+/**
+* Config is a utility used within an Object to allow the implementer to maintain a list of local configuration properties and listen for changes to those properties dynamically using CustomEvent. The initial values are also maintained so that the configuration can be reset at any given point to its initial state.
+* @class YAHOO.util.Config
+* @constructor
+* @param {Object}	owner	The owner Object to which this Config Object belongs
+*/
+YAHOO.util.Config = function(owner) {
+	if (owner) {
+		this.init(owner);
+	}
+};
+
+YAHOO.util.Config.prototype = {
+
+	/**
+	* Object reference to the owner of this Config Object
+	* @property owner
+	* @type Object
+	*/
+	owner : null,
+
+	/**
+	* Boolean flag that specifies whether a queue is currently being executed
+	* @property queueInProgress
+	* @type Boolean
+	*/
+	queueInProgress : false,
+
+
+	/**
+	* Validates that the value passed in is a Boolean.
+	* @method checkBoolean
+	* @param	{Object}	val	The value to validate
+	* @return	{Boolean}	true, if the value is valid
+	*/
+	checkBoolean: function(val) {
+		if (typeof val == 'boolean') {
+			return true;
+		} else {
+			return false;
+		}
+	},
+
+	/**
+	* Validates that the value passed in is a number.
+	* @method checkNumber
+	* @param	{Object}	val	The value to validate
+	* @return	{Boolean}	true, if the value is valid
+	*/
+	checkNumber: function(val) {
+		if (isNaN(val)) {
+			return false;
+		} else {
+			return true;
+		}
+	}
+};
+
+
+/**
+* Initializes the configuration Object and all of its local members.
+* @method init
+* @param {Object}	owner	The owner Object to which this Config Object belongs
+*/
+YAHOO.util.Config.prototype.init = function(owner) {
+
+	this.owner = owner;
+
+	/**
+	* Object reference to the owner of this Config Object
+	* @event configChangedEvent
+	*/
+	this.configChangedEvent = new YAHOO.util.CustomEvent("configChanged");
+
+	this.queueInProgress = false;
+
+	/* Private Members */
+
+	/**
+	* Maintains the local collection of configuration property objects and their specified values
+	* @property config
+	* @private
+	* @type Object
+	*/
+	var config = {};
+
+	/**
+	* Maintains the local collection of configuration property objects as they were initially applied.
+	* This object is used when resetting a property.
+	* @property initialConfig
+	* @private
+	* @type Object
+	*/
+	var initialConfig = {};
+
+	/**
+	* Maintains the local, normalized CustomEvent queue
+	* @property eventQueue
+	* @private
+	* @type Object
+	*/
+	var eventQueue = [];
+
+	/**
+	* Fires a configuration property event using the specified value.
+	* @method fireEvent
+	* @private
+	* @param {String}	key			The configuration property's name
+	* @param {value}	Object		The value of the correct type for the property
+	*/
+	var fireEvent = function( key, value ) {
+		key = key.toLowerCase();
+
+		var property = config[key];
+
+		if (typeof property != 'undefined' && property.event) {
+			property.event.fire(value);
+		}
+	};
+	/* End Private Members */
+
+	/**
+	* Adds a property to the Config Object's private config hash.
+	* @method addProperty
+	* @param {String}	key	The configuration property's name
+	* @param {Object}	propertyObject	The Object containing all of this property's arguments
+	*/
+	this.addProperty = function( key, propertyObject ) {
+		key = key.toLowerCase();
+
+		config[key] = propertyObject;
+
+		propertyObject.event = new YAHOO.util.CustomEvent(key);
+		propertyObject.key = key;
+
+		if (propertyObject.handler) {
+			propertyObject.event.subscribe(propertyObject.handler, this.owner, true);
+		}
+
+		this.setProperty(key, propertyObject.value, true);
+
+		if (! propertyObject.suppressEvent) {
+			this.queueProperty(key, propertyObject.value);
+		}
+	};
+
+	/**
+	* Returns a key-value configuration map of the values currently set in the Config Object.
+	* @method getConfig
+	* @return {Object} The current config, represented in a key-value map
+	*/
+	this.getConfig = function() {
+		var cfg = {};
+
+		for (var prop in config) {
+			var property = config[prop];
+			if (typeof property != 'undefined' && property.event) {
+				cfg[prop] = property.value;
+			}
+		}
+
+		return cfg;
+	};
+
+	/**
+	* Returns the value of specified property.
+	* @method getProperty
+	* @param {String} key	The name of the property
+	* @return {Object}		The value of the specified property
+	*/
+	this.getProperty = function(key) {
+		key = key.toLowerCase();
+
+		var property = config[key];
+		if (typeof property != 'undefined' && property.event) {
+			return property.value;
+		} else {
+			return undefined;
+		}
+	};
+
+	/**
+	* Resets the specified property's value to its initial value.
+	* @method resetProperty
+	* @param {String} key	The name of the property
+	* @return {Boolean} True is the property was reset, false if not
+	*/
+	this.resetProperty = function(key) {
+		key = key.toLowerCase();
+
+		var property = config[key];
+		if (typeof property != 'undefined' && property.event) {
+			if (initialConfig[key] && initialConfig[key] != 'undefined')	{
+				this.setProperty(key, initialConfig[key]);
+			}
+			return true;
+		} else {
+			return false;
+		}
+	};
+
+	/**
+	* Sets the value of a property. If the silent property is passed as true, the property's event will not be fired.
+	* @method setProperty
+	* @param {String} key		The name of the property
+	* @param {String} value		The value to set the property to
+	* @param {Boolean} silent	Whether the value should be set silently, without firing the property event.
+	* @return {Boolean}			True, if the set was successful, false if it failed.
+	*/
+	this.setProperty = function(key, value, silent) {
+		key = key.toLowerCase();
+
+		if (this.queueInProgress && ! silent) {
+			this.queueProperty(key,value); // Currently running through a queue...
+			return true;
+		} else {
+			var property = config[key];
+			if (typeof property != 'undefined' && property.event) {
+				if (property.validator && ! property.validator(value)) { // validator
+					return false;
+				} else {
+					property.value = value;
+					if (! silent) {
+						fireEvent(key, value);
+						this.configChangedEvent.fire([key, value]);
+					}
+					return true;
+				}
+			} else {
+				return false;
+			}
+		}
+	};
+
+	/**
+	* Sets the value of a property and queues its event to execute. If the event is already scheduled to execute, it is
+	* moved from its current position to the end of the queue.
+	* @method queueProperty
+	* @param {String} key	The name of the property
+	* @param {String} value	The value to set the property to
+	* @return {Boolean}		true, if the set was successful, false if it failed.
+	*/
+	this.queueProperty = function(key, value) {
+		key = key.toLowerCase();
+
+		var property = config[key];
+
+		if (typeof property != 'undefined' && property.event) {
+			if (typeof value != 'undefined' && property.validator && ! property.validator(value)) { // validator
+				return false;
+			} else {
+
+				if (typeof value != 'undefined') {
+					property.value = value;
+				} else {
+					value = property.value;
+				}
+
+				var foundDuplicate = false;
+
+				for (var i=0;i<eventQueue.length;i++) {
+					var queueItem = eventQueue[i];
+
+					if (queueItem) {
+						var queueItemKey = queueItem[0];
+						var queueItemValue = queueItem[1];
+
+						if (queueItemKey.toLowerCase() == key) {
+							// found a dupe... push to end of queue, null current item, and break
+							eventQueue[i] = null;
+							eventQueue.push([key, (typeof value != 'undefined' ? value : queueItemValue)]);
+							foundDuplicate = true;
+							break;
+						}
+					}
+				}
+
+				if (! foundDuplicate && typeof value != 'undefined') { // this is a refire, or a new property in the queue
+					eventQueue.push([key, value]);
+				}
+			}
+
+			if (property.supercedes) {
+				for (var s=0;s<property.supercedes.length;s++) {
+					var supercedesCheck = property.supercedes[s];
+
+					for (var q=0;q<eventQueue.length;q++) {
+						var queueItemCheck = eventQueue[q];
+
+						if (queueItemCheck) {
+							var queueItemCheckKey = queueItemCheck[0];
+							var queueItemCheckValue = queueItemCheck[1];
+
+							if ( queueItemCheckKey.toLowerCase() == supercedesCheck.toLowerCase() ) {
+								eventQueue.push([queueItemCheckKey, queueItemCheckValue]);
+								eventQueue[q] = null;
+								break;
+							}
+						}
+					}
+				}
+			}
+
+			return true;
+		} else {
+			return false;
+		}
+	};
+
+	/**
+	* Fires the event for a property using the property's current value.
+	* @method refireEvent
+	* @param {String} key	The name of the property
+	*/
+	this.refireEvent = function(key) {
+		key = key.toLowerCase();
+
+		var property = config[key];
+		if (typeof property != 'undefined' && property.event && typeof property.value != 'undefined') {
+			if (this.queueInProgress) {
+				this.queueProperty(key);
+			} else {
+				fireEvent(key, property.value);
+			}
+		}
+	};
+
+	/**
+	* Applies a key-value Object literal to the configuration, replacing any existing values, and queueing the property events.
+	* Although the values will be set, fireQueue() must be called for their associated events to execute.
+	* @method applyConfig
+	* @param {Object}	userConfig	The configuration Object literal
+	* @param {Boolean}	init		When set to true, the initialConfig will be set to the userConfig passed in, so that calling a reset will reset the properties to the passed values.
+	*/
+	this.applyConfig = function(userConfig, init) {
+		if (init) {
+			initialConfig = userConfig;
+		}
+		for (var prop in userConfig) {
+			this.queueProperty(prop, userConfig[prop]);
+		}
+	};
+
+	/**
+	* Refires the events for all configuration properties using their current values.
+	* @method refresh
+	*/
+	this.refresh = function() {
+		for (var prop in config) {
+			this.refireEvent(prop);
+		}
+	};
+
+	/**
+	* Fires the normalized list of queued property change events
+	* @method fireQueue
+	*/
+	this.fireQueue = function() {
+		this.queueInProgress = true;
+		for (var i=0;i<eventQueue.length;i++) {
+			var queueItem = eventQueue[i];
+			if (queueItem) {
+				var key = queueItem[0];
+				var value = queueItem[1];
+
+				var property = config[key];
+				property.value = value;
+
+				fireEvent(key,value);
+			}
+		}
+
+		this.queueInProgress = false;
+		eventQueue = [];
+	};
+
+	/**
+	* Subscribes an external handler to the change event for any given property.
+	* @method subscribeToConfigEvent
+	* @param {String}	key			The property name
+	* @param {Function}	handler		The handler function to use subscribe to the property's event
+	* @param {Object}	obj			The Object to use for scoping the event handler (see CustomEvent documentation)
+	* @param {Boolean}	override	Optional. If true, will override "this" within the handler to map to the scope Object passed into the method.
+	* @return {Boolean}				True, if the subscription was successful, otherwise false.
+	*/
+	this.subscribeToConfigEvent = function(key, handler, obj, override) {
+		key = key.toLowerCase();
+
+		var property = config[key];
+		if (typeof property != 'undefined' && property.event) {
+			if (! YAHOO.util.Config.alreadySubscribed(property.event, handler, obj)) {
+				property.event.subscribe(handler, obj, override);
+			}
+			return true;
+		} else {
+			return false;
+		}
+	};
+
+	/**
+	* Unsubscribes an external handler from the change event for any given property.
+	* @method unsubscribeFromConfigEvent
+	* @param {String}	key			The property name
+	* @param {Function}	handler		The handler function to use subscribe to the property's event
+	* @param {Object}	obj			The Object to use for scoping the event handler (see CustomEvent documentation)
+	* @return {Boolean}				True, if the unsubscription was successful, otherwise false.
+	*/
+	this.unsubscribeFromConfigEvent = function(key, handler, obj) {
+		key = key.toLowerCase();
+
+		var property = config[key];
+		if (typeof property != 'undefined' && property.event) {
+			return property.event.unsubscribe(handler, obj);
+		} else {
+			return false;
+		}
+	};
+
+	/**
+	* Returns a string representation of the Config object
+	* @method toString
+	* @return {String}	The Config object in string format.
+	*/
+	this.toString = function() {
+		var output = "Config";
+		if (this.owner) {
+			output += " [" + this.owner.toString() + "]";
+		}
+		return output;
+	};
+
+	/**
+	* Returns a string representation of the Config object's current CustomEvent queue
+	* @method outputEventQueue
+	* @return {String}	The string list of CustomEvents currently queued for execution
+	*/
+	this.outputEventQueue = function() {
+		var output = "";
+		for (var q=0;q<eventQueue.length;q++) {
+			var queueItem = eventQueue[q];
+			if (queueItem) {
+				output += queueItem[0] + "=" + queueItem[1] + ", ";
+			}
+		}
+		return output;
+	};
+};
+
+/**
+* Checks to determine if a particular function/Object pair are already subscribed to the specified CustomEvent
+* @method YAHOO.util.Config.alreadySubscribed
+* @static
+* @param {YAHOO.util.CustomEvent} evt	The CustomEvent for which to check the subscriptions
+* @param {Function}	fn	The function to look for in the subscribers list
+* @param {Object}	obj	The execution scope Object for the subscription
+* @return {Boolean}	true, if the function/Object pair is already subscribed to the CustomEvent passed in
+*/
+YAHOO.util.Config.alreadySubscribed = function(evt, fn, obj) {
+	for (var e=0;e<evt.subscribers.length;e++) {
+		var subsc = evt.subscribers[e];
+		if (subsc && subsc.obj == obj && subsc.fn == fn) {
+			return true;
+		}
+	}
+	return false;
+};
+
+/**
+* YAHOO.widget.DateMath is used for simple date manipulation. The class is a static utility
+* used for adding, subtracting, and comparing dates.
+* @class YAHOO.widget.DateMath
+*/
+YAHOO.widget.DateMath = {
+	/**
+	* Constant field representing Day
+	* @property DAY
+	* @static
+	* @final
+	* @type String
+	*/
+	DAY : "D",
+
+	/**
+	* Constant field representing Week
+	* @property WEEK
+	* @static
+	* @final
+	* @type String
+	*/
+	WEEK : "W",
+
+	/**
+	* Constant field representing Year
+	* @property YEAR
+	* @static
+	* @final
+	* @type String
+	*/
+	YEAR : "Y",
+
+	/**
+	* Constant field representing Month
+	* @property MONTH
+	* @static
+	* @final
+	* @type String
+	*/
+	MONTH : "M",
+
+	/**
+	* Constant field representing one day, in milliseconds
+	* @property ONE_DAY_MS
+	* @static
+	* @final
+	* @type Number
+	*/
+	ONE_DAY_MS : 1000*60*60*24,
+
+	/**
+	* Adds the specified amount of time to the this instance.
+	* @method add
+	* @param {Date} date	The JavaScript Date object to perform addition on
+	* @param {String} field	The field constant to be used for performing addition.
+	* @param {Number} amount	The number of units (measured in the field constant) to add to the date.
+	* @return {Date} The resulting Date object
+	*/
+	add : function(date, field, amount) {
+		var d = new Date(date.getTime());
+		switch (field) {
+			case this.MONTH:
+				var newMonth = date.getMonth() + amount;
+				var years = 0;
+
+
+				if (newMonth < 0) {
+					while (newMonth < 0) {
+						newMonth += 12;
+						years -= 1;
+					}
+				} else if (newMonth > 11) {
+					while (newMonth > 11) {
+						newMonth -= 12;
+						years += 1;
+					}
+				}
+
+				d.setMonth(newMonth);
+				d.setFullYear(date.getFullYear() + years);
+				break;
+			case this.DAY:
+				d.setDate(date.getDate() + amount);
+				break;
+			case this.YEAR:
+				d.setFullYear(date.getFullYear() + amount);
+				break;
+			case this.WEEK:
+				d.setDate(date.getDate() + (amount * 7));
+				break;
+		}
+		return d;
+	},
+
+	/**
+	* Subtracts the specified amount of time from the this instance.
+	* @method subtract
+	* @param {Date} date	The JavaScript Date object to perform subtraction on
+	* @param {Number} field	The this field constant to be used for performing subtraction.
+	* @param {Number} amount	The number of units (measured in the field constant) to subtract from the date.
+	* @return {Date} The resulting Date object
+	*/
+	subtract : function(date, field, amount) {
+		return this.add(date, field, (amount*-1));
+	},
+
+	/**
+	* Determines whether a given date is before another date on the calendar.
+	* @method before
+	* @param {Date} date		The Date object to compare with the compare argument
+	* @param {Date} compareTo	The Date object to use for the comparison
+	* @return {Boolean} true if the date occurs before the compared date; false if not.
+	*/
+	before : function(date, compareTo) {
+		var ms = compareTo.getTime();
+		if (date.getTime() < ms) {
+			return true;
+		} else {
+			return false;
+		}
+	},
+
+	/**
+	* Determines whether a given date is after another date on the calendar.
+	* @method after
+	* @param {Date} date		The Date object to compare with the compare argument
+	* @param {Date} compareTo	The Date object to use for the comparison
+	* @return {Boolean} true if the date occurs after the compared date; false if not.
+	*/
+	after : function(date, compareTo) {
+		var ms = compareTo.getTime();
+		if (date.getTime() > ms) {
+			return true;
+		} else {
+			return false;
+		}
+	},
+
+	/**
+	* Determines whether a given date is between two other dates on the calendar.
+	* @method between
+	* @param {Date} date		The date to check for
+	* @param {Date} dateBegin	The start of the range
+	* @param {Date} dateEnd		The end of the range
+	* @return {Boolean} true if the date occurs between the compared dates; false if not.
+	*/
+	between : function(date, dateBegin, dateEnd) {
+		if (this.after(date, dateBegin) && this.before(date, dateEnd)) {
+			return true;
+		} else {
+			return false;
+		}
+	},
+
+	/**
+	* Retrieves a JavaScript Date object representing January 1 of any given year.
+	* @method getJan1
+	* @param {Number} calendarYear		The calendar year for which to retrieve January 1
+	* @return {Date}	January 1 of the calendar year specified.
+	*/
+	getJan1 : function(calendarYear) {
+		return new Date(calendarYear,0,1);
+	},
+
+	/**
+	* Calculates the number of days the specified date is from January 1 of the specified calendar year.
+	* Passing January 1 to this function would return an offset value of zero.
+	* @method getDayOffset
+	* @param {Date}	date	The JavaScript date for which to find the offset
+	* @param {Number} calendarYear	The calendar year to use for determining the offset
+	* @return {Number}	The number of days since January 1 of the given year
+	*/
+	getDayOffset : function(date, calendarYear) {
+		var beginYear = this.getJan1(calendarYear); // Find the start of the year. This will be in week 1.
+
+		// Find the number of days the passed in date is away from the calendar year start
+		var dayOffset = Math.ceil((date.getTime()-beginYear.getTime()) / this.ONE_DAY_MS);
+		return dayOffset;
+	},
+
+	/**
+	* Calculates the week number for the given date. This function assumes that week 1 is the
+	* week in which January 1 appears, regardless of whether the week consists of a full 7 days.
+	* The calendar year can be specified to help find what a the week number would be for a given
+	* date if the date overlaps years. For instance, a week may be considered week 1 of 2005, or
+	* week 53 of 2004. Specifying the optional calendarYear allows one to make this distinction
+	* easily.
+	* @method getWeekNumber
+	* @param {Date}	date	The JavaScript date for which to find the week number
+	* @param {Number} calendarYear	OPTIONAL - The calendar year to use for determining the week number. Default is
+	*											the calendar year of parameter "date".
+	* @param {Number} weekStartsOn	OPTIONAL - The integer (0-6) representing which day a week begins on. Default is 0 (for Sunday).
+	* @return {Number}	The week number of the given date.
+	*/
+	getWeekNumber : function(date, calendarYear) {
+		date = this.clearTime(date);
+		var nearestThurs = new Date(date.getTime() + (4 * this.ONE_DAY_MS) - ((date.getDay()) * this.ONE_DAY_MS));
+
+		var jan1 = new Date(nearestThurs.getFullYear(),0,1);
+		var dayOfYear = ((nearestThurs.getTime() - jan1.getTime()) / this.ONE_DAY_MS) - 1;
+
+		var weekNum = Math.ceil((dayOfYear)/ 7);
+		return weekNum;
+	},
+
+	/**
+	* Determines if a given week overlaps two different years.
+	* @method isYearOverlapWeek
+	* @param {Date}	weekBeginDate	The JavaScript Date representing the first day of the week.
+	* @return {Boolean}	true if the date overlaps two different years.
+	*/
+	isYearOverlapWeek : function(weekBeginDate) {
+		var overlaps = false;
+		var nextWeek = this.add(weekBeginDate, this.DAY, 6);
+		if (nextWeek.getFullYear() != weekBeginDate.getFullYear()) {
+			overlaps = true;
+		}
+		return overlaps;
+	},
+
+	/**
+	* Determines if a given week overlaps two different months.
+	* @method isMonthOverlapWeek
+	* @param {Date}	weekBeginDate	The JavaScript Date representing the first day of the week.
+	* @return {Boolean}	true if the date overlaps two different months.
+	*/
+	isMonthOverlapWeek : function(weekBeginDate) {
+		var overlaps = false;
+		var nextWeek = this.add(weekBeginDate, this.DAY, 6);
+		if (nextWeek.getMonth() != weekBeginDate.getMonth()) {
+			overlaps = true;
+		}
+		return overlaps;
+	},
+
+	/**
+	* Gets the first day of a month containing a given date.
+	* @method findMonthStart
+	* @param {Date}	date	The JavaScript Date used to calculate the month start
+	* @return {Date}		The JavaScript Date representing the first day of the month
+	*/
+	findMonthStart : function(date) {
+		var start = new Date(date.getFullYear(), date.getMonth(), 1);
+		return start;
+	},
+
+	/**
+	* Gets the last day of a month containing a given date.
+	* @method findMonthEnd
+	* @param {Date}	date	The JavaScript Date used to calculate the month end
+	* @return {Date}		The JavaScript Date representing the last day of the month
+	*/
+	findMonthEnd : function(date) {
+		var start = this.findMonthStart(date);
+		var nextMonth = this.add(start, this.MONTH, 1);
+		var end = this.subtract(nextMonth, this.DAY, 1);
+		return end;
+	},
+
+	/**
+	* Clears the time fields from a given date, effectively setting the time to midnight.
+	* @method clearTime
+	* @param {Date}	date	The JavaScript Date for which the time fields will be cleared
+	* @return {Date}		The JavaScript Date cleared of all time fields
+	*/
+	clearTime : function(date) {
+		date.setHours(12,0,0,0);
+		return date;
+	}
+};
+
+/**
+* The Calendar component is a UI control that enables users to choose one or more dates from a graphical calendar presented in a one-month ("one-up") or two-month ("two-up") interface. Calendars are generated entirely via script and can be navigated without any page refreshes.
+* @module    Calendar
+* @title     Calendar Widget
+* @namespace YAHOO.widget
+* @requires  yahoo,dom,event
+*/
+
+/**
+* Calendar is the base class for the Calendar widget. In its most basic
+* implementation, it has the ability to render a calendar widget on the page
+* that can be manipulated to select a single date, move back and forth between
+* months and years.
+* <p>To construct the placeholder for the calendar widget, the code is as
+* follows:
+*	<xmp>
+*		<div id="cal1Container"></div>
+*	</xmp>
+* Note that the table can be replaced with any kind of element.
+* </p>
+* @namespace YAHOO.widget
+* @class Calendar
+* @constructor
+* @param {String}	id			The id of the table element that will represent the calendar widget
+* @param {String}	containerId	The id of the container div element that will wrap the calendar table
+* @param {Object}	config		The configuration object containing the Calendar's arguments
+*/
+YAHOO.widget.Calendar = function(id, containerId, config) {
+	this.init(id, containerId, config);
+};
+
+/**
+* The path to be used for images loaded for the Calendar
+* @property YAHOO.widget.Calendar.IMG_ROOT
+* @static
+* @type String
+*/
+YAHOO.widget.Calendar.IMG_ROOT = (window.location.href.toLowerCase().indexOf("https") === 0 ? "https://a248.e.akamai.net/sec.yimg.com/i/" : "http://us.i1.yimg.com/us.yimg.com/i/");
+
+/**
+* Type constant used for renderers to represent an individual date (M/D/Y)
+* @property YAHOO.widget.Calendar.DATE
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Calendar.DATE = "D";
+
+/**
+* Type constant used for renderers to represent an individual date across any year (M/D)
+* @property YAHOO.widget.Calendar.MONTH_DAY
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Calendar.MONTH_DAY = "MD";
+
+/**
+* Type constant used for renderers to represent a weekday
+* @property YAHOO.widget.Calendar.WEEKDAY
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Calendar.WEEKDAY = "WD";
+
+/**
+* Type constant used for renderers to represent a range of individual dates (M/D/Y-M/D/Y)
+* @property YAHOO.widget.Calendar.RANGE
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Calendar.RANGE = "R";
+
+/**
+* Type constant used for renderers to represent a month across any year
+* @property YAHOO.widget.Calendar.MONTH
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Calendar.MONTH = "M";
+
+/**
+* Constant that represents the total number of date cells that are displayed in a given month
+* @property YAHOO.widget.Calendar.DISPLAY_DAYS
+* @static
+* @final
+* @type Number
+*/
+YAHOO.widget.Calendar.DISPLAY_DAYS = 42;
+
+/**
+* Constant used for halting the execution of the remainder of the render stack
+* @property YAHOO.widget.Calendar.STOP_RENDER
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Calendar.STOP_RENDER = "S";
+
+YAHOO.widget.Calendar.prototype = {
+
+	/**
+	* The configuration object used to set up the calendars various locale and style options.
+	* @property Config
+	* @private
+	* @deprecated Configuration properties should be set by calling Calendar.cfg.setProperty.
+	* @type Object
+	*/
+	Config : null,
+
+	/**
+	* The parent CalendarGroup, only to be set explicitly by the parent group
+	* @property parent
+	* @type CalendarGroup
+	*/
+	parent : null,
+
+	/**
+	* The index of this item in the parent group
+	* @property index
+	* @type Number
+	*/
+	index : -1,
+
+	/**
+	* The collection of calendar table cells
+	* @property cells
+	* @type HTMLTableCellElement[]
+	*/
+	cells : null,
+
+	/**
+	* The collection of calendar cell dates that is parallel to the cells collection. The array contains dates field arrays in the format of [YYYY, M, D].
+	* @property cellDates
+	* @type Array[](Number[])
+	*/
+	cellDates : null,
+
+	/**
+	* The id that uniquely identifies this calendar. This id should match the id of the placeholder element on the page.
+	* @property id
+	* @type String
+	*/
+	id : null,
+
+	/**
+	* The DOM element reference that points to this calendar's container element. The calendar will be inserted into this element when the shell is rendered.
+	* @property oDomContainer
+	* @type HTMLElement
+	*/
+	oDomContainer : null,
+
+	/**
+	* A Date object representing today's date.
+	* @property today
+	* @type Date
+	*/
+	today : null,
+
+	/**
+	* The list of render functions, along with required parameters, used to render cells.
+	* @property renderStack
+	* @type Array[]
+	*/
+	renderStack : null,
+
+	/**
+	* A copy of the initial render functions created before rendering.
+	* @property _renderStack
+	* @private
+	* @type Array
+	*/
+	_renderStack : null,
+
+	/**
+	* A Date object representing the month/year that the calendar is initially set to
+	* @property _pageDate
+	* @private
+	* @type Date
+	*/
+	_pageDate : null,
+
+	/**
+	* The private list of initially selected dates.
+	* @property _selectedDates
+	* @private
+	* @type Array
+	*/
+	_selectedDates : null,
+
+	/**
+	* A map of DOM event handlers to attach to cells associated with specific CSS class names
+	* @property domEventMap
+	* @type Object
+	*/
+	domEventMap : null
+};
+
+
+
+/**
+* Initializes the Calendar widget.
+* @method init
+* @param {String}	id			The id of the table element that will represent the calendar widget
+* @param {String}	containerId	The id of the container div element that will wrap the calendar table
+* @param {Object}	config		The configuration object containing the Calendar's arguments
+*/
+YAHOO.widget.Calendar.prototype.init = function(id, containerId, config) {
+	this.initEvents();
+	this.today = new Date();
+	YAHOO.widget.DateMath.clearTime(this.today);
+
+	this.id = id;
+	this.oDomContainer = document.getElementById(containerId);
+
+	/**
+	* The Config object used to hold the configuration variables for the Calendar
+	* @property cfg
+	* @type YAHOO.util.Config
+	*/
+	this.cfg = new YAHOO.util.Config(this);
+
+	/**
+	* The local object which contains the Calendar's options
+	* @property Options
+	* @type Object
+	*/
+	this.Options = {};
+
+	/**
+	* The local object which contains the Calendar's locale settings
+	* @property Locale
+	* @type Object
+	*/
+	this.Locale = {};
+
+	this.initStyles();
+
+	YAHOO.util.Dom.addClass(this.oDomContainer, this.Style.CSS_CONTAINER);
+	YAHOO.util.Dom.addClass(this.oDomContainer, this.Style.CSS_SINGLE);
+
+	this.cellDates = [];
+	this.cells = [];
+	this.renderStack = [];
+	this._renderStack = [];
+
+	this.setupConfig();
+
+	if (config) {
+		this.cfg.applyConfig(config, true);
+	}
+
+	this.cfg.fireQueue();
+};
+
+/**
+* Renders the built-in IFRAME shim for the IE6 and below
+* @method configIframe
+*/
+YAHOO.widget.Calendar.prototype.configIframe = function(type, args, obj) {
+	var useIframe = args[0];
+
+	if (YAHOO.util.Dom.inDocument(this.oDomContainer)) {
+		if (useIframe) {
+			var pos = YAHOO.util.Dom.getStyle(this.oDomContainer, "position");
+
+			if (this.browser == "ie" && (pos == "absolute" || pos == "relative")) {
+				if (! YAHOO.util.Dom.inDocument(this.iframe)) {
+					this.iframe = document.createElement("iframe");
+					this.iframe.src = "javascript:false;";
+					YAHOO.util.Dom.setStyle(this.iframe, "opacity", "0");
+					this.oDomContainer.insertBefore(this.iframe, this.oDomContainer.firstChild);
+				}
+			}
+		} else {
+			if (this.iframe) {
+				if (this.iframe.parentNode) {
+					this.iframe.parentNode.removeChild(this.iframe);
+				}
+				this.iframe = null;
+			}
+		}
+	}
+};
+
+/**
+* Default handler for the "title" property
+* @method configTitle
+*/
+YAHOO.widget.Calendar.prototype.configTitle = function(type, args, obj) {
+	var title = args[0];
+	var close = this.cfg.getProperty("close");
+
+	var titleDiv;
+
+	if (title && title !== "") {
+		titleDiv = YAHOO.util.Dom.getElementsByClassName(YAHOO.widget.CalendarGroup.CSS_2UPTITLE, "div", this.oDomContainer)[0] || document.createElement("div");
+		titleDiv.className = YAHOO.widget.CalendarGroup.CSS_2UPTITLE;
+		titleDiv.innerHTML = title;
+		this.oDomContainer.insertBefore(titleDiv, this.oDomContainer.firstChild);
+		YAHOO.util.Dom.addClass(this.oDomContainer, "withtitle");
+	} else {
+		titleDiv = YAHOO.util.Dom.getElementsByClassName(YAHOO.widget.CalendarGroup.CSS_2UPTITLE, "div", this.oDomContainer)[0] || null;
+
+		if (titleDiv) {
+			YAHOO.util.Event.purgeElement(titleDiv);
+			this.oDomContainer.removeChild(titleDiv);
+		}
+		if (! close) {
+			YAHOO.util.Dom.removeClass(this.oDomContainer, "withtitle");
+		}
+	}
+};
+
+/**
+* Default handler for the "close" property
+* @method configClose
+*/
+YAHOO.widget.Calendar.prototype.configClose = function(type, args, obj) {
+	var close = args[0];
+	var title = this.cfg.getProperty("title");
+
+	var linkClose;
+
+	if (close === true) {
+		linkClose = YAHOO.util.Dom.getElementsByClassName("link-close", "a", this.oDomContainer)[0] || document.createElement("a");
+		linkClose.href = "javascript:void(null);";
+		linkClose.className = "link-close";
+		YAHOO.util.Event.addListener(linkClose, "click", this.hide, this, true);
+		var imgClose = document.createElement("img");
+		imgClose.src = YAHOO.widget.Calendar.IMG_ROOT + "us/my/bn/x_d.gif";
+		imgClose.className = YAHOO.widget.CalendarGroup.CSS_2UPCLOSE;
+		linkClose.appendChild(imgClose);
+		this.oDomContainer.appendChild(linkClose);
+		YAHOO.util.Dom.addClass(this.oDomContainer, "withtitle");
+	} else {
+		linkClose = YAHOO.util.Dom.getElementsByClassName("link-close", "a", this.oDomContainer)[0] || null;
+
+		if (linkClose) {
+			YAHOO.util.Event.purgeElement(linkClose);
+			this.oDomContainer.removeChild(linkClose);
+		}
+		if (! title || title === "") {
+			YAHOO.util.Dom.removeClass(this.oDomContainer, "withtitle");
+		}
+	}
+};
+
+/**
+* Initializes Calendar's built-in CustomEvents
+* @method initEvents
+*/
+YAHOO.widget.Calendar.prototype.initEvents = function() {
+
+	/**
+	* Fired before a selection is made
+	* @event beforeSelectEvent
+	*/
+	this.beforeSelectEvent = new YAHOO.util.CustomEvent("beforeSelect");
+
+	/**
+	* Fired when a selection is made
+	* @event selectEvent
+	* @param {Array}	Array of Date field arrays in the format [YYYY, MM, DD].
+	*/
+	this.selectEvent = new YAHOO.util.CustomEvent("select");
+
+	/**
+	* Fired before a selection is made
+	* @event beforeDeselectEvent
+	*/
+	this.beforeDeselectEvent = new YAHOO.util.CustomEvent("beforeDeselect");
+
+	/**
+	* Fired when a selection is made
+	* @event deselectEvent
+	* @param {Array}	Array of Date field arrays in the format [YYYY, MM, DD].
+	*/
+	this.deselectEvent = new YAHOO.util.CustomEvent("deselect");
+
+	/**
+	* Fired when the Calendar page is changed
+	* @event changePageEvent
+	*/
+	this.changePageEvent = new YAHOO.util.CustomEvent("changePage");
+
+	/**
+	* Fired before the Calendar is rendered
+	* @event beforeRenderEvent
+	*/
+	this.beforeRenderEvent = new YAHOO.util.CustomEvent("beforeRender");
+
+	/**
+	* Fired when the Calendar is rendered
+	* @event renderEvent
+	*/
+	this.renderEvent = new YAHOO.util.CustomEvent("render");
+
+	/**
+	* Fired when the Calendar is reset
+	* @event resetEvent
+	*/
+	this.resetEvent = new YAHOO.util.CustomEvent("reset");
+
+	/**
+	* Fired when the Calendar is cleared
+	* @event clearEvent
+	*/
+	this.clearEvent = new YAHOO.util.CustomEvent("clear");
+
+	this.beforeSelectEvent.subscribe(this.onBeforeSelect, this, true);
+	this.selectEvent.subscribe(this.onSelect, this, true);
+	this.beforeDeselectEvent.subscribe(this.onBeforeDeselect, this, true);
+	this.deselectEvent.subscribe(this.onDeselect, this, true);
+	this.changePageEvent.subscribe(this.onChangePage, this, true);
+	this.renderEvent.subscribe(this.onRender, this, true);
+	this.resetEvent.subscribe(this.onReset, this, true);
+	this.clearEvent.subscribe(this.onClear, this, true);
+};
+
+
+/**
+* The default event function that is attached to a date link within a calendar cell
+* when the calendar is rendered.
+* @method doSelectCell
+* @param {DOMEvent} e	The event
+* @param {Calendar} cal	A reference to the calendar passed by the Event utility
+*/
+YAHOO.widget.Calendar.prototype.doSelectCell = function(e, cal) {
+	var target = YAHOO.util.Event.getTarget(e);
+
+	var cell,index,d,date;
+
+	while (target.tagName.toLowerCase() != "td" && ! YAHOO.util.Dom.hasClass(target, cal.Style.CSS_CELL_SELECTABLE)) {
+		target = target.parentNode;
+		if (target.tagName.toLowerCase() == "html") {
+			return;
+		}
+	}
+
+	cell = target;
+
+	if (YAHOO.util.Dom.hasClass(cell, cal.Style.CSS_CELL_SELECTABLE)) {
+		index = cell.id.split("cell")[1];
+		d = cal.cellDates[index];
+		date = new Date(d[0],d[1]-1,d[2]);
+
+		var link;
+
+		if (cal.Options.MULTI_SELECT) {
+			link = cell.getElementsByTagName("a")[0];
+			if (link) {
+				link.blur();
+			}
+
+			var cellDate = cal.cellDates[index];
+			var cellDateIndex = cal._indexOfSelectedFieldArray(cellDate);
+
+			if (cellDateIndex > -1) {
+				cal.deselectCell(index);
+			} else {
+				cal.selectCell(index);
+			}
+
+		} else {
+			link = cell.getElementsByTagName("a")[0];
+			if (link) {
+				link.blur();
+			}
+			cal.selectCell(index);
+		}
+	}
+};
+
+/**
+* The event that is executed when the user hovers over a cell
+* @method doCellMouseOver
+* @param {DOMEvent} e	The event
+* @param {Calendar} cal	A reference to the calendar passed by the Event utility
+*/
+YAHOO.widget.Calendar.prototype.doCellMouseOver = function(e, cal) {
+	var target;
+	if (e) {
+		target = YAHOO.util.Event.getTarget(e);
+	} else {
+		target = this;
+	}
+
+	while (target.tagName.toLowerCase() != "td") {
+		target = target.parentNode;
+		if (target.tagName.toLowerCase() == "html") {
+			return;
+		}
+	}
+
+	if (YAHOO.util.Dom.hasClass(target, cal.Style.CSS_CELL_SELECTABLE)) {
+		YAHOO.util.Dom.addClass(target, cal.Style.CSS_CELL_HOVER);
+	}
+};
+
+/**
+* The event that is executed when the user moves the mouse out of a cell
+* @method doCellMouseOut
+* @param {DOMEvent} e	The event
+* @param {Calendar} cal	A reference to the calendar passed by the Event utility
+*/
+YAHOO.widget.Calendar.prototype.doCellMouseOut = function(e, cal) {
+	var target;
+	if (e) {
+		target = YAHOO.util.Event.getTarget(e);
+	} else {
+		target = this;
+	}
+
+	while (target.tagName.toLowerCase() != "td") {
+		target = target.parentNode;
+		if (target.tagName.toLowerCase() == "html") {
+			return;
+		}
+	}
+
+	if (YAHOO.util.Dom.hasClass(target, cal.Style.CSS_CELL_SELECTABLE)) {
+		YAHOO.util.Dom.removeClass(target, cal.Style.CSS_CELL_HOVER);
+	}
+};
+
+YAHOO.widget.Calendar.prototype.setupConfig = function() {
+
+	/**
+	* The month/year representing the current visible Calendar date (mm/yyyy)
+	* @config pagedate
+	* @type String
+	* @default today's date
+	*/
+	this.cfg.addProperty("pagedate", { value:new Date(), handler:this.configPageDate } );
+
+	/**
+	* The date or range of dates representing the current Calendar selection
+	* @config selected
+	* @type String
+	* @default []
+	*/
+	this.cfg.addProperty("selected", { value:[], handler:this.configSelected } );
+
+	/**
+	* The title to display above the Calendar's month header
+	* @config title
+	* @type String
+	* @default ""
+	*/
+	this.cfg.addProperty("title", { value:"", handler:this.configTitle } );
+
+	/**
+	* Whether or not a close button should be displayed for this Calendar
+	* @config close
+	* @type Boolean
+	* @default false
+	*/
+	this.cfg.addProperty("close", { value:false, handler:this.configClose } );
+
+	/**
+	* Whether or not an iframe shim should be placed under the Calendar to prevent select boxes from bleeding through in Internet Explorer 6 and below.
+	* @config iframe
+	* @type Boolean
+	* @default true
+	*/
+	this.cfg.addProperty("iframe", { value:true, handler:this.configIframe, validator:this.cfg.checkBoolean } );
+
+	/**
+	* The minimum selectable date in the current Calendar (mm/dd/yyyy)
+	* @config mindate
+	* @type String
+	* @default null
+	*/
+	this.cfg.addProperty("mindate", { value:null, handler:this.configMinDate } );
+
+	/**
+	* The maximum selectable date in the current Calendar (mm/dd/yyyy)
+	* @config maxdate
+	* @type String
+	* @default null
+	*/
+	this.cfg.addProperty("maxdate", { value:null, handler:this.configMaxDate } );
+
+
+	// Options properties
+
+	/**
+	* True if the Calendar should allow multiple selections. False by default.
+	* @config MULTI_SELECT
+	* @type Boolean
+	* @default false
+	*/
+	this.cfg.addProperty("MULTI_SELECT",	{ value:false, handler:this.configOptions, validator:this.cfg.checkBoolean } );
+
+    /**
+    * True if the Calendar should allow selection of out-of-month dates. False by default.
+    * @config OOM_SELECT
+    * @type Boolean
+    * @default false
+    */
+    this.cfg.addProperty("OOM_SELECT",      { value:false, handler:this.configOptions, validator:this.cfg.checkBoolean } );
+
+	/**
+	* The weekday the week begins on. Default is 0 (Sunday).
+	* @config START_WEEKDAY
+	* @type number
+	* @default 0
+	*/
+	this.cfg.addProperty("START_WEEKDAY",	{ value:0, handler:this.configOptions, validator:this.cfg.checkNumber  } );
+
+	/**
+	* True if the Calendar should show weekday labels. True by default.
+	* @config SHOW_WEEKDAYS
+	* @type Boolean
+	* @default true
+	*/
+	this.cfg.addProperty("SHOW_WEEKDAYS",	{ value:true, handler:this.configOptions, validator:this.cfg.checkBoolean  } );
+
+	/**
+	* True if the Calendar should show week row headers. False by default.
+	* @config SHOW_WEEK_HEADER
+	* @type Boolean
+	* @default false
+	*/
+	this.cfg.addProperty("SHOW_WEEK_HEADER",{ value:false, handler:this.configOptions, validator:this.cfg.checkBoolean } );
+
+	/**
+	* True if the Calendar should show week row footers. False by default.
+	* @config SHOW_WEEK_FOOTER
+	* @type Boolean
+	* @default false
+	*/
+	this.cfg.addProperty("SHOW_WEEK_FOOTER",{ value:false, handler:this.configOptions, validator:this.cfg.checkBoolean } );
+
+	/**
+	* True if the Calendar should suppress weeks that are not a part of the current month. False by default.
+	* @config HIDE_BLANK_WEEKS
+	* @type Boolean
+	* @default false
+	*/
+	this.cfg.addProperty("HIDE_BLANK_WEEKS",{ value:false, handler:this.configOptions, validator:this.cfg.checkBoolean } );
+
+	/**
+	* The image that should be used for the left navigation arrow.
+	* @config NAV_ARROW_LEFT
+	* @type String
+	* @default YAHOO.widget.Calendar.IMG_ROOT + "us/tr/callt.gif"
+	*/
+	this.cfg.addProperty("NAV_ARROW_LEFT",	{ value:YAHOO.widget.Calendar.IMG_ROOT + "us/tr/callt.gif", handler:this.configOptions } );
+
+	/**
+	* The image that should be used for the left navigation arrow.
+	* @config NAV_ARROW_RIGHT
+	* @type String
+	* @default YAHOO.widget.Calendar.IMG_ROOT + "us/tr/calrt.gif"
+	*/
+	this.cfg.addProperty("NAV_ARROW_RIGHT",	{ value:YAHOO.widget.Calendar.IMG_ROOT + "us/tr/calrt.gif", handler:this.configOptions } );
+
+	// Locale properties
+
+	/**
+	* The short month labels for the current locale.
+	* @config MONTHS_SHORT
+	* @type String[]
+	* @default ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
+	*/
+	this.cfg.addProperty("MONTHS_SHORT",	{ value:["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], handler:this.configLocale } );
+
+	/**
+	* The long month labels for the current locale.
+	* @config MONTHS_LONG
+	* @type String[]
+	* @default ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
+	*/
+	this.cfg.addProperty("MONTHS_LONG",		{ value:["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], handler:this.configLocale } );
+
+	/**
+	* The 1-character weekday labels for the current locale.
+	* @config WEEKDAYS_1CHAR
+	* @type String[]
+	* @default ["S", "M", "T", "W", "T", "F", "S"]
+	*/
+	this.cfg.addProperty("WEEKDAYS_1CHAR",	{ value:["S", "M", "T", "W", "T", "F", "S"], handler:this.configLocale } );
+
+	/**
+	* The short weekday labels for the current locale.
+	* @config WEEKDAYS_SHORT
+	* @type String[]
+	* @default ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]
+	*/
+	this.cfg.addProperty("WEEKDAYS_SHORT",	{ value:["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"], handler:this.configLocale } );
+
+	/**
+	* The medium weekday labels for the current locale.
+	* @config WEEKDAYS_MEDIUM
+	* @type String[]
+	* @default ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
+	*/
+	this.cfg.addProperty("WEEKDAYS_MEDIUM",	{ value:["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], handler:this.configLocale } );
+
+	/**
+	* The long weekday labels for the current locale.
+	* @config WEEKDAYS_LONG
+	* @type String[]
+	* @default ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
+	*/
+	this.cfg.addProperty("WEEKDAYS_LONG",	{ value:["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], handler:this.configLocale } );
+
+	/**
+	* Refreshes the locale values used to build the Calendar.
+	* @method refreshLocale
+	* @private
+	*/
+	var refreshLocale = function() {
+		this.cfg.refireEvent("LOCALE_MONTHS");
+		this.cfg.refireEvent("LOCALE_WEEKDAYS");
+	};
+
+	this.cfg.subscribeToConfigEvent("START_WEEKDAY", refreshLocale, this, true);
+	this.cfg.subscribeToConfigEvent("MONTHS_SHORT", refreshLocale, this, true);
+	this.cfg.subscribeToConfigEvent("MONTHS_LONG", refreshLocale, this, true);
+	this.cfg.subscribeToConfigEvent("WEEKDAYS_1CHAR", refreshLocale, this, true);
+	this.cfg.subscribeToConfigEvent("WEEKDAYS_SHORT", refreshLocale, this, true);
+	this.cfg.subscribeToConfigEvent("WEEKDAYS_MEDIUM", refreshLocale, this, true);
+	this.cfg.subscribeToConfigEvent("WEEKDAYS_LONG", refreshLocale, this, true);
+
+	/**
+	* The setting that determines which length of month labels should be used. Possible values are "short" and "long".
+	* @config LOCALE_MONTHS
+	* @type String
+	* @default "long"
+	*/
+	this.cfg.addProperty("LOCALE_MONTHS",	{ value:"long", handler:this.configLocaleValues } );
+
+	/**
+	* The setting that determines which length of weekday labels should be used. Possible values are "1char", "short", "medium", and "long".
+	* @config LOCALE_WEEKDAYS
+	* @type String
+	* @default "short"
+	*/
+	this.cfg.addProperty("LOCALE_WEEKDAYS",	{ value:"short", handler:this.configLocaleValues } );
+
+	/**
+	* The value used to delimit individual dates in a date string passed to various Calendar functions.
+	* @config DATE_DELIMITER
+	* @type String
+	* @default ","
+	*/
+	this.cfg.addProperty("DATE_DELIMITER",		{ value:",", handler:this.configLocale } );
+
+	/**
+	* The value used to delimit date fields in a date string passed to various Calendar functions.
+	* @config DATE_FIELD_DELIMITER
+	* @type String
+	* @default "/"
+	*/
+	this.cfg.addProperty("DATE_FIELD_DELIMITER",{ value:"/", handler:this.configLocale } );
+
+	/**
+	* The value used to delimit date ranges in a date string passed to various Calendar functions.
+	* @config DATE_RANGE_DELIMITER
+	* @type String
+	* @default "-"
+	*/
+	this.cfg.addProperty("DATE_RANGE_DELIMITER",{ value:"-", handler:this.configLocale } );
+
+	/**
+	* The position of the month in a month/year date string
+	* @config MY_MONTH_POSITION
+	* @type Number
+	* @default 1
+	*/
+	this.cfg.addProperty("MY_MONTH_POSITION",	{ value:1, handler:this.configLocale, validator:this.cfg.checkNumber } );
+
+	/**
+	* The position of the year in a month/year date string
+	* @config MY_YEAR_POSITION
+	* @type Number
+	* @default 2
+	*/
+	this.cfg.addProperty("MY_YEAR_POSITION",	{ value:2, handler:this.configLocale, validator:this.cfg.checkNumber } );
+
+	/**
+	* The position of the month in a month/day date string
+	* @config MD_MONTH_POSITION
+	* @type Number
+	* @default 1
+	*/
+	this.cfg.addProperty("MD_MONTH_POSITION",	{ value:1, handler:this.configLocale, validator:this.cfg.checkNumber } );
+
+	/**
+	* The position of the day in a month/year date string
+	* @config MD_DAY_POSITION
+	* @type Number
+	* @default 2
+	*/
+	this.cfg.addProperty("MD_DAY_POSITION",		{ value:2, handler:this.configLocale, validator:this.cfg.checkNumber } );
+
+	/**
+	* The position of the month in a month/day/year date string
+	* @config MDY_MONTH_POSITION
+	* @type Number
+	* @default 1
+	*/
+	this.cfg.addProperty("MDY_MONTH_POSITION",	{ value:1, handler:this.configLocale, validator:this.cfg.checkNumber } );
+
+	/**
+	* The position of the day in a month/day/year date string
+	* @config MDY_DAY_POSITION
+	* @type Number
+	* @default 2
+	*/
+	this.cfg.addProperty("MDY_DAY_POSITION",	{ value:2, handler:this.configLocale, validator:this.cfg.checkNumber } );
+
+	/**
+	* The position of the year in a month/day/year date string
+	* @config MDY_YEAR_POSITION
+	* @type Number
+	* @default 3
+	*/
+	this.cfg.addProperty("MDY_YEAR_POSITION",	{ value:3, handler:this.configLocale, validator:this.cfg.checkNumber } );
+};
+
+/**
+* The default handler for the "pagedate" property
+* @method configPageDate
+*/
+YAHOO.widget.Calendar.prototype.configPageDate = function(type, args, obj) {
+	var val = args[0];
+	var month, year, aMonthYear;
+
+	if (val) {
+		if (val instanceof Date) {
+			val = YAHOO.widget.DateMath.findMonthStart(val);
+			this.cfg.setProperty("pagedate", val, true);
+			if (! this._pageDate) {
+				this._pageDate = this.cfg.getProperty("pagedate");
+			}
+			return;
+		} else {
+			aMonthYear = val.split(this.cfg.getProperty("DATE_FIELD_DELIMITER"));
+			month = parseInt(aMonthYear[this.cfg.getProperty("MY_MONTH_POSITION")-1], 10)-1;
+			year = parseInt(aMonthYear[this.cfg.getProperty("MY_YEAR_POSITION")-1], 10);
+		}
+	} else {
+		month = this.today.getMonth();
+		year = this.today.getFullYear();
+	}
+
+	this.cfg.setProperty("pagedate", new Date(year, month, 1), true);
+	if (! this._pageDate) {
+		this._pageDate = this.cfg.getProperty("pagedate");
+	}
+};
+
+/**
+* The default handler for the "mindate" property
+* @method configMinDate
+*/
+YAHOO.widget.Calendar.prototype.configMinDate = function(type, args, obj) {
+	var val = args[0];
+	if (typeof val == 'string') {
+		val = this._parseDate(val);
+		this.cfg.setProperty("mindate", new Date(val[0],(val[1]-1),val[2]));
+	}
+};
+
+/**
+* The default handler for the "maxdate" property
+* @method configMaxDate
+*/
+YAHOO.widget.Calendar.prototype.configMaxDate = function(type, args, obj) {
+	var val = args[0];
+	if (typeof val == 'string') {
+		val = this._parseDate(val);
+		this.cfg.setProperty("maxdate", new Date(val[0],(val[1]-1),val[2]));
+	}
+};
+
+/**
+* The default handler for the "selected" property
+* @method configSelected
+*/
+YAHOO.widget.Calendar.prototype.configSelected = function(type, args, obj) {
+	var selected = args[0];
+
+	if (selected) {
+		if (typeof selected == 'string') {
+			this.cfg.setProperty("selected", this._parseDates(selected), true);
+		}
+	}
+	if (! this._selectedDates) {
+		this._selectedDates = this.cfg.getProperty("selected");
+	}
+};
+
+/**
+* The default handler for all configuration options properties
+* @method configOptions
+*/
+YAHOO.widget.Calendar.prototype.configOptions = function(type, args, obj) {
+	type = type.toUpperCase();
+	var val = args[0];
+	this.Options[type] = val;
+};
+
+/**
+* The default handler for all configuration locale properties
+* @method configLocale
+*/
+YAHOO.widget.Calendar.prototype.configLocale = function(type, args, obj) {
+	type = type.toUpperCase();
+	var val = args[0];
+	this.Locale[type] = val;
+
+	this.cfg.refireEvent("LOCALE_MONTHS");
+	this.cfg.refireEvent("LOCALE_WEEKDAYS");
+
+};
+
+/**
+* The default handler for all configuration locale field length properties
+* @method configLocaleValues
+*/
+YAHOO.widget.Calendar.prototype.configLocaleValues = function(type, args, obj) {
+	type = type.toUpperCase();
+	var val = args[0];
+
+	switch (type) {
+		case "LOCALE_MONTHS":
+			switch (val) {
+				case "short":
+					this.Locale.LOCALE_MONTHS = this.cfg.getProperty("MONTHS_SHORT").concat();
+					break;
+				case "long":
+					this.Locale.LOCALE_MONTHS = this.cfg.getProperty("MONTHS_LONG").concat();
+					break;
+			}
+			break;
+		case "LOCALE_WEEKDAYS":
+			switch (val) {
+				case "1char":
+					this.Locale.LOCALE_WEEKDAYS = this.cfg.getProperty("WEEKDAYS_1CHAR").concat();
+					break;
+				case "short":
+					this.Locale.LOCALE_WEEKDAYS = this.cfg.getProperty("WEEKDAYS_SHORT").concat();
+					break;
+				case "medium":
+					this.Locale.LOCALE_WEEKDAYS = this.cfg.getProperty("WEEKDAYS_MEDIUM").concat();
+					break;
+				case "long":
+					this.Locale.LOCALE_WEEKDAYS = this.cfg.getProperty("WEEKDAYS_LONG").concat();
+					break;
+			}
+
+			var START_WEEKDAY = this.cfg.getProperty("START_WEEKDAY");
+
+			if (START_WEEKDAY > 0) {
+				for (var w=0;w<START_WEEKDAY;++w) {
+					this.Locale.LOCALE_WEEKDAYS.push(this.Locale.LOCALE_WEEKDAYS.shift());
+				}
+			}
+			break;
+	}
+};
+
+/**
+* Defines the style constants for the Calendar
+* @method initStyles
+*/
+YAHOO.widget.Calendar.prototype.initStyles = function() {
+
+	/**
+	* Collection of Style constants for the Calendar
+	* @property Style
+	*/
+	this.Style = {
+		/**
+		* @property Style.CSS_ROW_HEADER
+		*/
+		CSS_ROW_HEADER: "calrowhead",
+		/**
+		* @property Style.CSS_ROW_FOOTER
+		*/
+		CSS_ROW_FOOTER: "calrowfoot",
+		/**
+		* @property Style.CSS_CELL
+		*/
+		CSS_CELL : "calcell",
+		/**
+		* @property Style.CSS_CELL_SELECTED
+		*/
+		CSS_CELL_SELECTED : "selected",
+		/**
+		* @property Style.CSS_CELL_SELECTABLE
+		*/
+		CSS_CELL_SELECTABLE : "selectable",
+		/**
+		* @property Style.CSS_CELL_RESTRICTED
+		*/
+		CSS_CELL_RESTRICTED : "restricted",
+		/**
+		* @property Style.CSS_CELL_TODAY
+		*/
+		CSS_CELL_TODAY : "today",
+		/**
+		* @property Style.CSS_CELL_OOM
+		*/
+		CSS_CELL_OOM : "oom",
+		/**
+		* @property Style.CSS_CELL_OOB
+		*/
+		CSS_CELL_OOB : "previous",
+		/**
+		* @property Style.CSS_HEADER
+		*/
+		CSS_HEADER : "calheader",
+		/**
+		* @property Style.CSS_HEADER_TEXT
+		*/
+		CSS_HEADER_TEXT : "calhead",
+		/**
+		* @property Style.CSS_WEEKDAY_CELL
+		*/
+		CSS_WEEKDAY_CELL : "calweekdaycell",
+		/**
+		* @property Style.CSS_WEEKDAY_ROW
+		*/
+		CSS_WEEKDAY_ROW : "calweekdayrow",
+		/**
+		* @property Style.CSS_FOOTER
+		*/
+		CSS_FOOTER : "calfoot",
+		/**
+		* @property Style.CSS_CALENDAR
+		*/
+		CSS_CALENDAR : "yui-calendar",
+		/**
+		* @property Style.CSS_SINGLE
+		*/
+		CSS_SINGLE : "single",
+		/**
+		* @property Style.CSS_CONTAINER
+		*/
+		CSS_CONTAINER : "yui-calcontainer",
+		/**
+		* @property Style.CSS_NAV_LEFT
+		*/
+		CSS_NAV_LEFT : "calnavleft",
+		/**
+		* @property Style.CSS_NAV_RIGHT
+		*/
+		CSS_NAV_RIGHT : "calnavright",
+		/**
+		* @property Style.CSS_CELL_TOP
+		*/
+		CSS_CELL_TOP : "calcelltop",
+		/**
+		* @property Style.CSS_CELL_LEFT
+		*/
+		CSS_CELL_LEFT : "calcellleft",
+		/**
+		* @property Style.CSS_CELL_RIGHT
+		*/
+		CSS_CELL_RIGHT : "calcellright",
+		/**
+		* @property Style.CSS_CELL_BOTTOM
+		*/
+		CSS_CELL_BOTTOM : "calcellbottom",
+		/**
+		* @property Style.CSS_CELL_HOVER
+		*/
+		CSS_CELL_HOVER : "calcellhover",
+		/**
+		* @property Style.CSS_CELL_HIGHLIGHT1
+		*/
+		CSS_CELL_HIGHLIGHT1 : "highlight1",
+		/**
+		* @property Style.CSS_CELL_HIGHLIGHT2
+		*/
+		CSS_CELL_HIGHLIGHT2 : "highlight2",
+		/**
+		* @property Style.CSS_CELL_HIGHLIGHT3
+		*/
+		CSS_CELL_HIGHLIGHT3 : "highlight3",
+		/**
+		* @property Style.CSS_CELL_HIGHLIGHT4
+		*/
+		CSS_CELL_HIGHLIGHT4 : "highlight4"
+	};
+};
+
+/**
+* Builds the date label that will be displayed in the calendar header or
+* footer, depending on configuration.
+* @method buildMonthLabel
+* @return	{String}	The formatted calendar month label
+*/
+YAHOO.widget.Calendar.prototype.buildMonthLabel = function() {
+	var text = this.Locale.LOCALE_MONTHS[this.cfg.getProperty("pagedate").getMonth()] + " " + this.cfg.getProperty("pagedate").getFullYear();
+	return text;
+};
+
+/**
+* Builds the date digit that will be displayed in calendar cells
+* @method buildDayLabel
+* @param {Date}	workingDate	The current working date
+* @return	{String}	The formatted day label
+*/
+YAHOO.widget.Calendar.prototype.buildDayLabel = function(workingDate) {
+	var day = workingDate.getDate();
+	return day;
+};
+
+/**
+* Renders the calendar header.
+* @method renderHeader
+* @param {Array}	html	The current working HTML array
+* @return {Array} The current working HTML array
+*/
+YAHOO.widget.Calendar.prototype.renderHeader = function(html) {
+	var colSpan = 7;
+
+	if (this.cfg.getProperty("SHOW_WEEK_HEADER")) {
+		colSpan += 1;
+	}
+
+	if (this.cfg.getProperty("SHOW_WEEK_FOOTER")) {
+		colSpan += 1;
+	}
+
+	html[html.length] = "<thead>";
+	html[html.length] =		"<tr>";
+	html[html.length] =			'<th colspan="' + colSpan + '" class="' + this.Style.CSS_HEADER_TEXT + '">';
+	html[html.length] =				'<div class="' + this.Style.CSS_HEADER + '">';
+
+		var renderLeft, renderRight = false;
+
+		if (this.parent) {
+			if (this.index === 0) {
+				renderLeft = true;
+			}
+			if (this.index == (this.parent.cfg.getProperty("pages") -1)) {
+				renderRight = true;
+			}
+		} else {
+			renderLeft = true;
+			renderRight = true;
+		}
+
+		var cal = this.parent || this;
+
+		if (renderLeft) {
+			html[html.length] = '<a class="' + this.Style.CSS_NAV_LEFT + '" style="background-image:url(' + this.cfg.getProperty("NAV_ARROW_LEFT") + ')">&#160;</a>';
+		}
+
+		html[html.length] = this.buildMonthLabel();
+
+		if (renderRight) {
+			html[html.length] = '<a class="' + this.Style.CSS_NAV_RIGHT + '" style="background-image:url(' + this.cfg.getProperty("NAV_ARROW_RIGHT") + ')">&#160;</a>';
+		}
+
+
+	html[html.length] =				'</div>';
+	html[html.length] =			'</th>';
+	html[html.length] =		'</tr>';
+
+	if (this.cfg.getProperty("SHOW_WEEKDAYS")) {
+		html = this.buildWeekdays(html);
+	}
+
+	html[html.length] = '</thead>';
+
+	return html;
+};
+
+/**
+* Renders the Calendar's weekday headers.
+* @method buildWeekdays
+* @param {Array}	html	The current working HTML array
+* @return {Array} The current working HTML array
+*/
+YAHOO.widget.Calendar.prototype.buildWeekdays = function(html) {
+
+	html[html.length] = '<tr class="' + this.Style.CSS_WEEKDAY_ROW + '">';
+
+	if (this.cfg.getProperty("SHOW_WEEK_HEADER")) {
+		html[html.length] = '<th>&#160;</th>';
+	}
+
+	for(var i=0;i<this.Locale.LOCALE_WEEKDAYS.length;++i) {
+		html[html.length] = '<th class="calweekdaycell">' + this.Locale.LOCALE_WEEKDAYS[i] + '</th>';
+	}
+
+	if (this.cfg.getProperty("SHOW_WEEK_FOOTER")) {
+		html[html.length] = '<th>&#160;</th>';
+	}
+
+	html[html.length] = '</tr>';
+
+	return html;
+};
+
+/**
+* Renders the calendar body.
+* @method renderBody
+* @param {Date}	workingDate	The current working Date being used for the render process
+* @param {Array}	html	The current working HTML array
+* @return {Array} The current working HTML array
+*/
+YAHOO.widget.Calendar.prototype.renderBody = function(workingDate, html) {
+
+	var startDay = this.cfg.getProperty("START_WEEKDAY");
+
+	this.preMonthDays = workingDate.getDay();
+	if (startDay > 0) {
+		this.preMonthDays -= startDay;
+	}
+	if (this.preMonthDays < 0) {
+		this.preMonthDays += 7;
+	}
+
+	this.monthDays = YAHOO.widget.DateMath.findMonthEnd(workingDate).getDate();
+	this.postMonthDays = YAHOO.widget.Calendar.DISPLAY_DAYS-this.preMonthDays-this.monthDays;
+
+	workingDate = YAHOO.widget.DateMath.subtract(workingDate, YAHOO.widget.DateMath.DAY, this.preMonthDays);
+
+	var useDate,weekNum,weekClass;
+	useDate = this.cfg.getProperty("pagedate");
+
+	html[html.length] = '<tbody class="m' + (useDate.getMonth()+1) + '">';
+
+	var i = 0;
+
+	var tempDiv = document.createElement("div");
+	var cell = document.createElement("td");
+	tempDiv.appendChild(cell);
+
+	var jan1 = new Date(useDate.getFullYear(),0,1);
+
+	var cal = this.parent || this;
+
+	for (var r=0;r<6;r++) {
+
+		weekNum = YAHOO.widget.DateMath.getWeekNumber(workingDate, useDate.getFullYear(), startDay);
+
+		weekClass = "w" + weekNum;
+
+		if (r !== 0 && (this.isDateOOM(workingDate) && !this.cfg.getProperty("OOM_SELECT")) && this.cfg.getProperty("HIDE_BLANK_WEEKS") === true) {
+			break;
+		} else {
+
+			html[html.length] = '<tr class="' + weekClass + '">';
+
+			if (this.cfg.getProperty("SHOW_WEEK_HEADER")) { html = this.renderRowHeader(weekNum, html); }
+
+			for (var d=0;d<7;d++){ // Render actual days
+
+				var cellRenderers = [];
+
+				this.clearElement(cell);
+
+				YAHOO.util.Dom.addClass(cell, "calcell");
+
+				cell.id = this.id + "_cell" + i;
+
+				cell.innerHTML = i;
+
+				var renderer = null;
+
+				if (workingDate.getFullYear()	== this.today.getFullYear() &&
+					workingDate.getMonth()		== this.today.getMonth() &&
+					workingDate.getDate()		== this.today.getDate()) {
+					cellRenderers[cellRenderers.length]=cal.renderCellStyleToday;
+				}
+
+				this.cellDates[this.cellDates.length]=[workingDate.getFullYear(),workingDate.getMonth()+1,workingDate.getDate()]; // Add this date to cellDates
+
+				if (this.isDateOOM(workingDate) && !this.cfg.getProperty("OOM_SELECT")) {
+					cellRenderers[cellRenderers.length]=cal.renderCellNotThisMonth;
+				} else {
+
+					YAHOO.util.Dom.addClass(cell, "wd" + workingDate.getDay());
+					YAHOO.util.Dom.addClass(cell, "d" + workingDate.getDate());
+
+					for (var s=0;s<this.renderStack.length;++s) {
+
+						var rArray = this.renderStack[s];
+						var type = rArray[0];
+
+						var month;
+						var day;
+						var year;
+
+						switch (type) {
+							case YAHOO.widget.Calendar.DATE:
+								month = rArray[1][1];
+								day = rArray[1][2];
+								year = rArray[1][0];
+
+								if (workingDate.getMonth()+1 == month && workingDate.getDate() == day && workingDate.getFullYear() == year) {
+									renderer = rArray[2];
+									this.renderStack.splice(s,1);
+								}
+								break;
+							case YAHOO.widget.Calendar.MONTH_DAY:
+								month = rArray[1][0];
+								day = rArray[1][1];
+
+								if (workingDate.getMonth()+1 == month && workingDate.getDate() == day) {
+									renderer = rArray[2];
+									this.renderStack.splice(s,1);
+								}
+								break;
+							case YAHOO.widget.Calendar.RANGE:
+								var date1 = rArray[1][0];
+								var date2 = rArray[1][1];
+
+								var d1month = date1[1];
+								var d1day = date1[2];
+								var d1year = date1[0];
+
+								var d1 = new Date(d1year, d1month-1, d1day);
+
+								var d2month = date2[1];
+								var d2day = date2[2];
+								var d2year = date2[0];
+
+								var d2 = new Date(d2year, d2month-1, d2day);
+
+								if (workingDate.getTime() >= d1.getTime() && workingDate.getTime() <= d2.getTime()) {
+									renderer = rArray[2];
+
+									if (workingDate.getTime()==d2.getTime()) {
+										this.renderStack.splice(s,1);
+									}
+								}
+								break;
+							case YAHOO.widget.Calendar.WEEKDAY:
+
+								var weekday = rArray[1][0];
+								if (workingDate.getDay()+1 == weekday) {
+									renderer = rArray[2];
+								}
+								break;
+							case YAHOO.widget.Calendar.MONTH:
+
+								month = rArray[1][0];
+								if (workingDate.getMonth()+1 == month) {
+									renderer = rArray[2];
+								}
+								break;
+						}
+
+						if (renderer) {
+							cellRenderers[cellRenderers.length]=renderer;
+						}
+					}
+
+				}
+
+				if (this._indexOfSelectedFieldArray([workingDate.getFullYear(),workingDate.getMonth()+1,workingDate.getDate()]) > -1) {
+					cellRenderers[cellRenderers.length]=cal.renderCellStyleSelected;
+				}
+
+				var mindate = this.cfg.getProperty("mindate");
+				var maxdate = this.cfg.getProperty("maxdate");
+
+				if (mindate) {
+					mindate = YAHOO.widget.DateMath.clearTime(mindate);
+				}
+				if (maxdate) {
+					maxdate = YAHOO.widget.DateMath.clearTime(maxdate);
+				}
+
+				if (
+					(mindate && (workingDate.getTime() < mindate.getTime())) ||
+					(maxdate && (workingDate.getTime() > maxdate.getTime()))
+				) {
+					cellRenderers[cellRenderers.length]=cal.renderOutOfBoundsDate;
+				} else {
+					cellRenderers[cellRenderers.length]=cal.styleCellDefault;
+					cellRenderers[cellRenderers.length]=cal.renderCellDefault;
+				}
+
+
+
+				for (var x=0;x<cellRenderers.length;++x) {
+					var ren = cellRenderers[x];
+					if (ren.call((this.parent || this),workingDate,cell) == YAHOO.widget.Calendar.STOP_RENDER) {
+						break;
+					}
+				}
+
+				workingDate.setTime(workingDate.getTime() + YAHOO.widget.DateMath.ONE_DAY_MS);
+
+				if (i >= 0 && i <= 6) {
+					YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_TOP);
+				}
+				if ((i % 7) === 0) {
+					YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_LEFT);
+				}
+				if (((i+1) % 7) === 0) {
+					YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_RIGHT);
+				}
+
+				var postDays = this.postMonthDays;
+				if (postDays >= 7 && this.cfg.getProperty("HIDE_BLANK_WEEKS")) {
+					var blankWeeks = Math.floor(postDays/7);
+					for (var p=0;p<blankWeeks;++p) {
+						postDays -= 7;
+					}
+				}
+
+				if (i >= ((this.preMonthDays+postDays+this.monthDays)-7)) {
+					YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_BOTTOM);
+				}
+
+				html[html.length] = tempDiv.innerHTML;
+
+				i++;
+			}
+
+			if (this.cfg.getProperty("SHOW_WEEK_FOOTER")) { html = this.renderRowFooter(weekNum, html); }
+
+			html[html.length] = '</tr>';
+		}
+	}
+
+	html[html.length] = '</tbody>';
+
+	return html;
+};
+
+/**
+* Renders the calendar footer. In the default implementation, there is
+* no footer.
+* @method renderFooter
+* @param {Array}	html	The current working HTML array
+* @return {Array} The current working HTML array
+*/
+YAHOO.widget.Calendar.prototype.renderFooter = function(html) { return html; };
+
+/**
+* Renders the calendar after it has been configured. The render() method has a specific call chain that will execute
+* when the method is called: renderHeader, renderBody, renderFooter.
+* Refer to the documentation for those methods for information on
+* individual render tasks.
+* @method render
+*/
+YAHOO.widget.Calendar.prototype.render = function() {
+	this.beforeRenderEvent.fire();
+
+	// Find starting day of the current month
+	var workingDate = YAHOO.widget.DateMath.findMonthStart(this.cfg.getProperty("pagedate"));
+
+	this.resetRenderers();
+	this.cellDates.length = 0;
+
+	YAHOO.util.Event.purgeElement(this.oDomContainer, true);
+
+	var html = [];
+
+	html[html.length] = '<table cellSpacing="0" class="' + this.Style.CSS_CALENDAR + ' y' + workingDate.getFullYear() + '" id="' + this.id + '">';
+	html = this.renderHeader(html);
+	html = this.renderBody(workingDate, html);
+	html = this.renderFooter(html);
+	html[html.length] = '</table>';
+
+	this.oDomContainer.innerHTML = html.join("\n");
+
+	this.applyListeners();
+	this.cells = this.oDomContainer.getElementsByTagName("td");
+
+	this.cfg.refireEvent("title");
+	this.cfg.refireEvent("close");
+	this.cfg.refireEvent("iframe");
+
+	this.renderEvent.fire();
+};
+
+/**
+* Applies the Calendar's DOM listeners to applicable elements.
+* @method applyListeners
+*/
+YAHOO.widget.Calendar.prototype.applyListeners = function() {
+
+	var root = this.oDomContainer;
+	var cal = this.parent || this;
+
+	var linkLeft, linkRight;
+
+	linkLeft = YAHOO.util.Dom.getElementsByClassName(this.Style.CSS_NAV_LEFT, "a", root);
+	linkRight = YAHOO.util.Dom.getElementsByClassName(this.Style.CSS_NAV_RIGHT, "a", root);
+
+	if (linkLeft) {
+		this.linkLeft = linkLeft[0];
+		YAHOO.util.Event.addListener(this.linkLeft, "mousedown", cal.previousMonth, cal, true);
+	}
+
+	if (linkRight) {
+		this.linkRight = linkRight[0];
+		YAHOO.util.Event.addListener(this.linkRight, "mousedown", cal.nextMonth, cal, true);
+	}
+
+	if (this.domEventMap) {
+		var el,elements;
+		for (var cls in this.domEventMap) {
+			if (this.domEventMap.hasOwnProperty(cls)) {
+				var items = this.domEventMap[cls];
+
+				if (! (items instanceof Array)) {
+					items = [items];
+				}
+
+				for (var i=0;i<items.length;i++)	{
+					var item = items[i];
+					elements = YAHOO.util.Dom.getElementsByClassName(cls, item.tag, this.oDomContainer);
+
+					for (var c=0;c<elements.length;c++) {
+						el = elements[c];
+						 YAHOO.util.Event.addListener(el, item.event, item.handler, item.scope, item.correct );
+					}
+				}
+			}
+		}
+	}
+
+	YAHOO.util.Event.addListener(this.oDomContainer, "click", this.doSelectCell, this);
+	YAHOO.util.Event.addListener(this.oDomContainer, "mouseover", this.doCellMouseOver, this);
+	YAHOO.util.Event.addListener(this.oDomContainer, "mouseout", this.doCellMouseOut, this);
+};
+
+/**
+* Retrieves the Date object for the specified Calendar cell
+* @method getDateByCellId
+* @param {String}	id	The id of the cell
+* @return {Date} The Date object for the specified Calendar cell
+*/
+YAHOO.widget.Calendar.prototype.getDateByCellId = function(id) {
+	var date = this.getDateFieldsByCellId(id);
+	return new Date(date[0],date[1]-1,date[2]);
+};
+
+/**
+* Retrieves the Date object for the specified Calendar cell
+* @method getDateFieldsByCellId
+* @param {String}	id	The id of the cell
+* @return {Array}	The array of Date fields for the specified Calendar cell
+*/
+YAHOO.widget.Calendar.prototype.getDateFieldsByCellId = function(id) {
+	id = id.toLowerCase().split("_cell")[1];
+	id = parseInt(id, 10);
+	return this.cellDates[id];
+};
+
+// BEGIN BUILT-IN TABLE CELL RENDERERS
+
+/**
+* Renders a cell that falls before the minimum date or after the maximum date.
+* widget class.
+* @method renderOutOfBoundsDate
+* @param {Date}					workingDate		The current working Date object being used to generate the calendar
+* @param {HTMLTableCellElement}	cell			The current working cell in the calendar
+* @return {String} YAHOO.widget.Calendar.STOP_RENDER if rendering should stop with this style, null or nothing if rendering
+*			should not be terminated
+*/
+YAHOO.widget.Calendar.prototype.renderOutOfBoundsDate = function(workingDate, cell) {
+	YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_OOB);
+	cell.innerHTML = workingDate.getDate();
+	return YAHOO.widget.Calendar.STOP_RENDER;
+};
+
+/**
+* Renders the row header for a week.
+* @method renderRowHeader
+* @param {Number}	weekNum	The week number of the current row
+* @param {Array}	cell	The current working HTML array
+*/
+YAHOO.widget.Calendar.prototype.renderRowHeader = function(weekNum, html) {
+	html[html.length] = '<th class="calrowhead">' + weekNum + '</th>';
+	return html;
+};
+
+/**
+* Renders the row footer for a week.
+* @method renderRowFooter
+* @param {Number}	weekNum	The week number of the current row
+* @param {Array}	cell	The current working HTML array
+*/
+YAHOO.widget.Calendar.prototype.renderRowFooter = function(weekNum, html) {
+	html[html.length] = '<th class="calrowfoot">' + weekNum + '</th>';
+	return html;
+};
+
+/**
+* Renders a single standard calendar cell in the calendar widget table.
+* All logic for determining how a standard default cell will be rendered is
+* encapsulated in this method, and must be accounted for when extending the
+* widget class.
+* @method renderCellDefault
+* @param {Date}					workingDate		The current working Date object being used to generate the calendar
+* @param {HTMLTableCellElement}	cell			The current working cell in the calendar
+*/
+YAHOO.widget.Calendar.prototype.renderCellDefault = function(workingDate, cell) {
+	cell.innerHTML = '<a href="javascript:void(null);" >' + this.buildDayLabel(workingDate) + "</a>";
+};
+
+/**
+* Styles a selectable cell.
+* @method styleCellDefault
+* @param {Date}					workingDate		The current working Date object being used to generate the calendar
+* @param {HTMLTableCellElement}	cell			The current working cell in the calendar
+*/
+YAHOO.widget.Calendar.prototype.styleCellDefault = function(workingDate, cell) {
+	YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_SELECTABLE);
+};
+
+
+/**
+* Renders a single standard calendar cell using the CSS hightlight1 style
+* @method renderCellStyleHighlight1
+* @param {Date}					workingDate		The current working Date object being used to generate the calendar
+* @param {HTMLTableCellElement}	cell			The current working cell in the calendar
+*/
+YAHOO.widget.Calendar.prototype.renderCellStyleHighlight1 = function(workingDate, cell) {
+	YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_HIGHLIGHT1);
+};
+
+/**
+* Renders a single standard calendar cell using the CSS hightlight2 style
+* @method renderCellStyleHighlight2
+* @param {Date}					workingDate		The current working Date object being used to generate the calendar
+* @param {HTMLTableCellElement}	cell			The current working cell in the calendar
+*/
+YAHOO.widget.Calendar.prototype.renderCellStyleHighlight2 = function(workingDate, cell) {
+	YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_HIGHLIGHT2);
+};
+
+/**
+* Renders a single standard calendar cell using the CSS hightlight3 style
+* @method renderCellStyleHighlight3
+* @param {Date}					workingDate		The current working Date object being used to generate the calendar
+* @param {HTMLTableCellElement}	cell			The current working cell in the calendar
+*/
+YAHOO.widget.Calendar.prototype.renderCellStyleHighlight3 = function(workingDate, cell) {
+	YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_HIGHLIGHT3);
+};
+
+/**
+* Renders a single standard calendar cell using the CSS hightlight4 style
+* @method renderCellStyleHighlight4
+* @param {Date}					workingDate		The current working Date object being used to generate the calendar
+* @param {HTMLTableCellElement}	cell			The current working cell in the calendar
+*/
+YAHOO.widget.Calendar.prototype.renderCellStyleHighlight4 = function(workingDate, cell) {
+	YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_HIGHLIGHT4);
+};
+
+/**
+* Applies the default style used for rendering today's date to the current calendar cell
+* @method renderCellStyleToday
+* @param {Date}					workingDate		The current working Date object being used to generate the calendar
+* @param {HTMLTableCellElement}	cell			The current working cell in the calendar
+*/
+YAHOO.widget.Calendar.prototype.renderCellStyleToday = function(workingDate, cell) {
+	YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_TODAY);
+};
+
+/**
+* Applies the default style used for rendering selected dates to the current calendar cell
+* @method renderCellStyleSelected
+* @param {Date}					workingDate		The current working Date object being used to generate the calendar
+* @param {HTMLTableCellElement}	cell			The current working cell in the calendar
+* @return {String} YAHOO.widget.Calendar.STOP_RENDER if rendering should stop with this style, null or nothing if rendering
+*			should not be terminated
+*/
+YAHOO.widget.Calendar.prototype.renderCellStyleSelected = function(workingDate, cell) {
+	YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_SELECTED);
+};
+
+/**
+* Applies the default style used for rendering dates that are not a part of the current
+* month (preceding or trailing the cells for the current month)
+* @method renderCellNotThisMonth
+* @param {Date}					workingDate		The current working Date object being used to generate the calendar
+* @param {HTMLTableCellElement}	cell			The current working cell in the calendar
+* @return {String} YAHOO.widget.Calendar.STOP_RENDER if rendering should stop with this style, null or nothing if rendering
+*			should not be terminated
+*/
+YAHOO.widget.Calendar.prototype.renderCellNotThisMonth = function(workingDate, cell) {
+	YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_OOM);
+	cell.innerHTML=workingDate.getDate();
+	return YAHOO.widget.Calendar.STOP_RENDER;
+};
+
+/**
+* Renders the current calendar cell as a non-selectable "black-out" date using the default
+* restricted style.
+* @method renderBodyCellRestricted
+* @param {Date}					workingDate		The current working Date object being used to generate the calendar
+* @param {HTMLTableCellElement}	cell			The current working cell in the calendar
+* @return {String} YAHOO.widget.Calendar.STOP_RENDER if rendering should stop with this style, null or nothing if rendering
+*			should not be terminated
+*/
+YAHOO.widget.Calendar.prototype.renderBodyCellRestricted = function(workingDate, cell) {
+	YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL);
+	YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_RESTRICTED);
+	cell.innerHTML=workingDate.getDate();
+	return YAHOO.widget.Calendar.STOP_RENDER;
+};
+
+// END BUILT-IN TABLE CELL RENDERERS
+
+// BEGIN MONTH NAVIGATION METHODS
+
+/**
+* Adds the designated number of months to the current calendar month, and sets the current
+* calendar page date to the new month.
+* @method addMonths
+* @param {Number}	count	The number of months to add to the current calendar
+*/
+YAHOO.widget.Calendar.prototype.addMonths = function(count) {
+	this.cfg.setProperty("pagedate", YAHOO.widget.DateMath.add(this.cfg.getProperty("pagedate"), YAHOO.widget.DateMath.MONTH, count));
+	this.resetRenderers();
+	this.changePageEvent.fire();
+};
+
+/**
+* Subtracts the designated number of months from the current calendar month, and sets the current
+* calendar page date to the new month.
+* @method subtractMonths
+* @param {Number}	count	The number of months to subtract from the current calendar
+*/
+YAHOO.widget.Calendar.prototype.subtractMonths = function(count) {
+	this.cfg.setProperty("pagedate", YAHOO.widget.DateMath.subtract(this.cfg.getProperty("pagedate"), YAHOO.widget.DateMath.MONTH, count));
+	this.resetRenderers();
+	this.changePageEvent.fire();
+};
+
+/**
+* Adds the designated number of years to the current calendar, and sets the current
+* calendar page date to the new month.
+* @method addYears
+* @param {Number}	count	The number of years to add to the current calendar
+*/
+YAHOO.widget.Calendar.prototype.addYears = function(count) {
+	this.cfg.setProperty("pagedate", YAHOO.widget.DateMath.add(this.cfg.getProperty("pagedate"), YAHOO.widget.DateMath.YEAR, count));
+	this.resetRenderers();
+	this.changePageEvent.fire();
+};
+
+/**
+* Subtcats the designated number of years from the current calendar, and sets the current
+* calendar page date to the new month.
+* @method subtractYears
+* @param {Number}	count	The number of years to subtract from the current calendar
+*/
+YAHOO.widget.Calendar.prototype.subtractYears = function(count) {
+	this.cfg.setProperty("pagedate", YAHOO.widget.DateMath.subtract(this.cfg.getProperty("pagedate"), YAHOO.widget.DateMath.YEAR, count));
+	this.resetRenderers();
+	this.changePageEvent.fire();
+};
+
+/**
+* Navigates to the next month page in the calendar widget.
+* @method nextMonth
+*/
+YAHOO.widget.Calendar.prototype.nextMonth = function() {
+	this.addMonths(1);
+};
+
+/**
+* Navigates to the previous month page in the calendar widget.
+* @method previousMonth
+*/
+YAHOO.widget.Calendar.prototype.previousMonth = function() {
+	this.subtractMonths(1);
+};
+
+/**
+* Navigates to the next year in the currently selected month in the calendar widget.
+* @method nextYear
+*/
+YAHOO.widget.Calendar.prototype.nextYear = function() {
+	this.addYears(1);
+};
+
+/**
+* Navigates to the previous year in the currently selected month in the calendar widget.
+* @method previousYear
+*/
+YAHOO.widget.Calendar.prototype.previousYear = function() {
+	this.subtractYears(1);
+};
+
+// END MONTH NAVIGATION METHODS
+
+// BEGIN SELECTION METHODS
+
+/**
+* Resets the calendar widget to the originally selected month and year, and
+* sets the calendar to the initial selection(s).
+* @method reset
+*/
+YAHOO.widget.Calendar.prototype.reset = function() {
+	this.cfg.resetProperty("selected");
+	this.cfg.resetProperty("pagedate");
+	this.resetEvent.fire();
+};
+
+/**
+* Clears the selected dates in the current calendar widget and sets the calendar
+* to the current month and year.
+* @method clear
+*/
+YAHOO.widget.Calendar.prototype.clear = function() {
+	this.cfg.setProperty("selected", []);
+	this.cfg.setProperty("pagedate", new Date(this.today.getTime()));
+	this.clearEvent.fire();
+};
+
+/**
+* Selects a date or a collection of dates on the current calendar. This method, by default,
+* does not call the render method explicitly. Once selection has completed, render must be
+* called for the changes to be reflected visually.
+* @method select
+* @param	{String/Date/Date[]}	date	The date string of dates to select in the current calendar. Valid formats are
+*								individual date(s) (12/24/2005,12/26/2005) or date range(s) (12/24/2005-1/1/2006).
+*								Multiple comma-delimited dates can also be passed to this method (12/24/2005,12/11/2005-12/13/2005).
+*								This method can also take a JavaScript Date object or an array of Date objects.
+* @return	{Date[]}			Array of JavaScript Date objects representing all individual dates that are currently selected.
+*/
+YAHOO.widget.Calendar.prototype.select = function(date) {
+	this.beforeSelectEvent.fire();
+
+	var selected = this.cfg.getProperty("selected");
+	var aToBeSelected = this._toFieldArray(date);
+
+	for (var a=0;a<aToBeSelected.length;++a) {
+		var toSelect = aToBeSelected[a]; // For each date item in the list of dates we're trying to select
+		if (this._indexOfSelectedFieldArray(toSelect) == -1) { // not already selected?
+			selected[selected.length]=toSelect;
+		}
+	}
+
+	if (this.parent) {
+		this.parent.cfg.setProperty("selected", selected);
+	} else {
+		this.cfg.setProperty("selected", selected);
+	}
+
+	this.selectEvent.fire(aToBeSelected);
+
+	return this.getSelectedDates();
+};
+
+/**
+* Selects a date on the current calendar by referencing the index of the cell that should be selected.
+* This method is used to easily select a single cell (usually with a mouse click) without having to do
+* a full render. The selected style is applied to the cell directly.
+* @method selectCell
+* @param	{Number}	cellIndex	The index of the cell to select in the current calendar.
+* @return	{Date[]}	Array of JavaScript Date objects representing all individual dates that are currently selected.
+*/
+YAHOO.widget.Calendar.prototype.selectCell = function(cellIndex) {
+	this.beforeSelectEvent.fire();
+
+	var selected = this.cfg.getProperty("selected");
+
+	var cell = this.cells[cellIndex];
+	var cellDate = this.cellDates[cellIndex];
+
+	var dCellDate = this._toDate(cellDate);
+
+	var selectDate = cellDate.concat();
+
+	selected[selected.length] = selectDate;
+
+	if (this.parent) {
+		this.parent.cfg.setProperty("selected", selected);
+	} else {
+		this.cfg.setProperty("selected", selected);
+	}
+
+	this.renderCellStyleSelected(dCellDate,cell);
+
+	this.selectEvent.fire([selectDate]);
+
+	this.doCellMouseOut.call(cell, null, this);
+
+	return this.getSelectedDates();
+};
+
+/**
+* Deselects a date or a collection of dates on the current calendar. This method, by default,
+* does not call the render method explicitly. Once deselection has completed, render must be
+* called for the changes to be reflected visually.
+* @method deselect
+* @param	{String/Date/Date[]}	date	The date string of dates to deselect in the current calendar. Valid formats are
+*								individual date(s) (12/24/2005,12/26/2005) or date range(s) (12/24/2005-1/1/2006).
+*								Multiple comma-delimited dates can also be passed to this method (12/24/2005,12/11/2005-12/13/2005).
+*								This method can also take a JavaScript Date object or an array of Date objects.
+* @return	{Date[]}			Array of JavaScript Date objects representing all individual dates that are currently selected.
+*/
+YAHOO.widget.Calendar.prototype.deselect = function(date) {
+	this.beforeDeselectEvent.fire();
+
+	var selected = this.cfg.getProperty("selected");
+
+	var aToBeSelected = this._toFieldArray(date);
+
+	for (var a=0;a<aToBeSelected.length;++a) {
+		var toSelect = aToBeSelected[a]; // For each date item in the list of dates we're trying to select
+		var index = this._indexOfSelectedFieldArray(toSelect);
+
+		if (index != -1) {
+			selected.splice(index,1);
+		}
+	}
+
+	if (this.parent) {
+		this.parent.cfg.setProperty("selected", selected);
+	} else {
+		this.cfg.setProperty("selected", selected);
+	}
+
+	this.deselectEvent.fire(aToBeSelected);
+
+	return this.getSelectedDates();
+};
+
+/**
+* Deselects a date on the current calendar by referencing the index of the cell that should be deselected.
+* This method is used to easily deselect a single cell (usually with a mouse click) without having to do
+* a full render. The selected style is removed from the cell directly.
+* @method deselectCell
+* @param	{Number}	cellIndex	The index of the cell to deselect in the current calendar.
+* @return	{Date[]}	Array of JavaScript Date objects representing all individual dates that are currently selected.
+*/
+YAHOO.widget.Calendar.prototype.deselectCell = function(i) {
+	this.beforeDeselectEvent.fire();
+
+	var selected = this.cfg.getProperty("selected");
+
+	var cell = this.cells[i];
+	var cellDate = this.cellDates[i];
+	var cellDateIndex = this._indexOfSelectedFieldArray(cellDate);
+
+	var dCellDate = this._toDate(cellDate);
+
+	var selectDate = cellDate.concat();
+
+	if (cellDateIndex > -1) {
+		if (this.cfg.getProperty("pagedate").getMonth() == dCellDate.getMonth() &&
+			this.cfg.getProperty("pagedate").getFullYear() == dCellDate.getFullYear()) {
+			YAHOO.util.Dom.removeClass(cell, this.Style.CSS_CELL_SELECTED);
+		}
+
+		selected.splice(cellDateIndex, 1);
+	}
+
+
+	if (this.parent) {
+		this.parent.cfg.setProperty("selected", selected);
+	} else {
+		this.cfg.setProperty("selected", selected);
+	}
+
+	this.deselectEvent.fire(selectDate);
+	return this.getSelectedDates();
+};
+
+/**
+* Deselects all dates on the current calendar.
+* @method deselectAll
+* @return {Date[]}		Array of JavaScript Date objects representing all individual dates that are currently selected.
+*						Assuming that this function executes properly, the return value should be an empty array.
+*						However, the empty array is returned for the sake of being able to check the selection status
+*						of the calendar.
+*/
+YAHOO.widget.Calendar.prototype.deselectAll = function() {
+	this.beforeDeselectEvent.fire();
+
+	var selected = this.cfg.getProperty("selected");
+	var count = selected.length;
+	var sel = selected.concat();
+
+	if (this.parent) {
+		this.parent.cfg.setProperty("selected", []);
+	} else {
+		this.cfg.setProperty("selected", []);
+	}
+
+	if (count > 0) {
+		this.deselectEvent.fire(sel);
+	}
+
+	return this.getSelectedDates();
+};
+
+// END SELECTION METHODS
+
+// BEGIN TYPE CONVERSION METHODS
+
+/**
+* Converts a date (either a JavaScript Date object, or a date string) to the internal data structure
+* used to represent dates: [[yyyy,mm,dd],[yyyy,mm,dd]].
+* @method _toFieldArray
+* @private
+* @param	{String/Date/Date[]}	date	The date string of dates to deselect in the current calendar. Valid formats are
+*								individual date(s) (12/24/2005,12/26/2005) or date range(s) (12/24/2005-1/1/2006).
+*								Multiple comma-delimited dates can also be passed to this method (12/24/2005,12/11/2005-12/13/2005).
+*								This method can also take a JavaScript Date object or an array of Date objects.
+* @return {Array[](Number[])}	Array of date field arrays
+*/
+YAHOO.widget.Calendar.prototype._toFieldArray = function(date) {
+	var returnDate = [];
+
+	if (date instanceof Date) {
+		returnDate = [[date.getFullYear(), date.getMonth()+1, date.getDate()]];
+	} else if (typeof date == 'string') {
+		returnDate = this._parseDates(date);
+	} else if (date instanceof Array) {
+		for (var i=0;i<date.length;++i) {
+			var d = date[i];
+			returnDate[returnDate.length] = [d.getFullYear(),d.getMonth()+1,d.getDate()];
+		}
+	}
+
+	return returnDate;
+};
+
+/**
+* Converts a date field array [yyyy,mm,dd] to a JavaScript Date object.
+* @method _toDate
+* @private
+* @param	{Number[]}		dateFieldArray	The date field array to convert to a JavaScript Date.
+* @return	{Date}	JavaScript Date object representing the date field array
+*/
+YAHOO.widget.Calendar.prototype._toDate = function(dateFieldArray) {
+	if (dateFieldArray instanceof Date) {
+		return dateFieldArray;
+	} else {
+		return new Date(dateFieldArray[0],dateFieldArray[1]-1,dateFieldArray[2]);
+	}
+};
+
+// END TYPE CONVERSION METHODS
+
+// BEGIN UTILITY METHODS
+
+/**
+* Converts a date field array [yyyy,mm,dd] to a JavaScript Date object.
+* @method _fieldArraysAreEqual
+* @private
+* @param	{Number[]}	array1	The first date field array to compare
+* @param	{Number[]}	array2	The first date field array to compare
+* @return	{Boolean}	The boolean that represents the equality of the two arrays
+*/
+YAHOO.widget.Calendar.prototype._fieldArraysAreEqual = function(array1, array2) {
+	var match = false;
+
+	if (array1[0]==array2[0]&&array1[1]==array2[1]&&array1[2]==array2[2]) {
+		match=true;
+	}
+
+	return match;
+};
+
+/**
+* Gets the index of a date field array [yyyy,mm,dd] in the current list of selected dates.
+* @method	_indexOfSelectedFieldArray
+* @private
+* @param	{Number[]}		find	The date field array to search for
+* @return	{Number}			The index of the date field array within the collection of selected dates.
+*								-1 will be returned if the date is not found.
+*/
+YAHOO.widget.Calendar.prototype._indexOfSelectedFieldArray = function(find) {
+	var selected = -1;
+	var seldates = this.cfg.getProperty("selected");
+
+	for (var s=0;s<seldates.length;++s) {
+		var sArray = seldates[s];
+		if (find[0]==sArray[0]&&find[1]==sArray[1]&&find[2]==sArray[2]) {
+			selected = s;
+			break;
+		}
+	}
+
+	return selected;
+};
+
+/**
+* Determines whether a given date is OOM (out of month).
+* @method	isDateOOM
+* @param	{Date}	date	The JavaScript Date object for which to check the OOM status
+* @return	{Boolean}	true if the date is OOM
+*/
+YAHOO.widget.Calendar.prototype.isDateOOM = function(date) {
+	var isOOM = false;
+	if (date.getMonth() != this.cfg.getProperty("pagedate").getMonth()) {
+		isOOM = true;
+	}
+	return isOOM;
+};
+
+// END UTILITY METHODS
+
+// BEGIN EVENT HANDLERS
+
+/**
+* Event executed before a date is selected in the calendar widget.
+* @deprecated Event handlers for this event should be susbcribed to beforeSelectEvent.
+*/
+YAHOO.widget.Calendar.prototype.onBeforeSelect = function() {
+	if (this.cfg.getProperty("MULTI_SELECT") === false) {
+		if (this.parent) {
+			this.parent.callChildFunction("clearAllBodyCellStyles", this.Style.CSS_CELL_SELECTED);
+			this.parent.deselectAll();
+		} else {
+			this.clearAllBodyCellStyles(this.Style.CSS_CELL_SELECTED);
+			this.deselectAll();
+		}
+	}
+};
+
+/**
+* Event executed when a date is selected in the calendar widget.
+* @param	{Array}	selected	An array of date field arrays representing which date or dates were selected. Example: [ [2006,8,6],[2006,8,7],[2006,8,8] ]
+* @deprecated Event handlers for this event should be susbcribed to selectEvent.
+*/
+YAHOO.widget.Calendar.prototype.onSelect = function(selected) { };
+
+/**
+* Event executed before a date is deselected in the calendar widget.
+* @deprecated Event handlers for this event should be susbcribed to beforeDeselectEvent.
+*/
+YAHOO.widget.Calendar.prototype.onBeforeDeselect = function() { };
+
+/**
+* Event executed when a date is deselected in the calendar widget.
+* @param	{Array}	selected	An array of date field arrays representing which date or dates were deselected. Example: [ [2006,8,6],[2006,8,7],[2006,8,8] ]
+* @deprecated Event handlers for this event should be susbcribed to deselectEvent.
+*/
+YAHOO.widget.Calendar.prototype.onDeselect = function(deselected) { };
+
+/**
+* Event executed when the user navigates to a different calendar page.
+* @deprecated Event handlers for this event should be susbcribed to changePageEvent.
+*/
+YAHOO.widget.Calendar.prototype.onChangePage = function() {
+	this.render();
+};
+
+/**
+* Event executed when the calendar widget is rendered.
+* @deprecated Event handlers for this event should be susbcribed to renderEvent.
+*/
+YAHOO.widget.Calendar.prototype.onRender = function() { };
+
+/**
+* Event executed when the calendar widget is reset to its original state.
+* @deprecated Event handlers for this event should be susbcribed to resetEvemt.
+*/
+YAHOO.widget.Calendar.prototype.onReset = function() { this.render(); };
+
+/**
+* Event executed when the calendar widget is completely cleared to the current month with no selections.
+* @deprecated Event handlers for this event should be susbcribed to clearEvent.
+*/
+YAHOO.widget.Calendar.prototype.onClear = function() { this.render(); };
+
+/**
+* Validates the calendar widget. This method has no default implementation
+* and must be extended by subclassing the widget.
+* @return	Should return true if the widget validates, and false if
+* it doesn't.
+* @type Boolean
+*/
+YAHOO.widget.Calendar.prototype.validate = function() { return true; };
+
+// END EVENT HANDLERS
+
+// BEGIN DATE PARSE METHODS
+
+/**
+* Converts a date string to a date field array
+* @private
+* @param	{String}	sDate			Date string. Valid formats are mm/dd and mm/dd/yyyy.
+* @return				A date field array representing the string passed to the method
+* @type Array[](Number[])
+*/
+YAHOO.widget.Calendar.prototype._parseDate = function(sDate) {
+	var aDate = sDate.split(this.Locale.DATE_FIELD_DELIMITER);
+	var rArray;
+
+	if (aDate.length == 2) {
+		rArray = [aDate[this.Locale.MD_MONTH_POSITION-1],aDate[this.Locale.MD_DAY_POSITION-1]];
+		rArray.type = YAHOO.widget.Calendar.MONTH_DAY;
+	} else {
+		rArray = [aDate[this.Locale.MDY_YEAR_POSITION-1],aDate[this.Locale.MDY_MONTH_POSITION-1],aDate[this.Locale.MDY_DAY_POSITION-1]];
+		rArray.type = YAHOO.widget.Calendar.DATE;
+	}
+
+	for (var i=0;i<rArray.length;i++) {
+		rArray[i] = parseInt(rArray[i], 10);
+	}
+
+	return rArray;
+};
+
+/**
+* Converts a multi or single-date string to an array of date field arrays
+* @private
+* @param	{String}	sDates		Date string with one or more comma-delimited dates. Valid formats are mm/dd, mm/dd/yyyy, mm/dd/yyyy-mm/dd/yyyy
+* @return							An array of date field arrays
+* @type Array[](Number[])
+*/
+YAHOO.widget.Calendar.prototype._parseDates = function(sDates) {
+	var aReturn = [];
+
+	var aDates = sDates.split(this.Locale.DATE_DELIMITER);
+
+	for (var d=0;d<aDates.length;++d) {
+		var sDate = aDates[d];
+
+		if (sDate.indexOf(this.Locale.DATE_RANGE_DELIMITER) != -1) {
+			// This is a range
+			var aRange = sDate.split(this.Locale.DATE_RANGE_DELIMITER);
+
+			var dateStart = this._parseDate(aRange[0]);
+			var dateEnd = this._parseDate(aRange[1]);
+
+			var fullRange = this._parseRange(dateStart, dateEnd);
+			aReturn = aReturn.concat(fullRange);
+		} else {
+			// This is not a range
+			var aDate = this._parseDate(sDate);
+			aReturn.push(aDate);
+		}
+	}
+	return aReturn;
+};
+
+/**
+* Converts a date range to the full list of included dates
+* @private
+* @param	{Number[]}	startDate	Date field array representing the first date in the range
+* @param	{Number[]}	endDate		Date field array representing the last date in the range
+* @return							An array of date field arrays
+* @type Array[](Number[])
+*/
+YAHOO.widget.Calendar.prototype._parseRange = function(startDate, endDate) {
+	var dStart   = new Date(startDate[0],startDate[1]-1,startDate[2]);
+	var dCurrent = YAHOO.widget.DateMath.add(new Date(startDate[0],startDate[1]-1,startDate[2]),YAHOO.widget.DateMath.DAY,1);
+	var dEnd     = new Date(endDate[0],  endDate[1]-1,  endDate[2]);
+
+	var results = [];
+	results.push(startDate);
+	while (dCurrent.getTime() <= dEnd.getTime()) {
+		results.push([dCurrent.getFullYear(),dCurrent.getMonth()+1,dCurrent.getDate()]);
+		dCurrent = YAHOO.widget.DateMath.add(dCurrent,YAHOO.widget.DateMath.DAY,1);
+	}
+	return results;
+};
+
+// END DATE PARSE METHODS
+
+// BEGIN RENDERER METHODS
+
+/**
+* Resets the render stack of the current calendar to its original pre-render value.
+*/
+YAHOO.widget.Calendar.prototype.resetRenderers = function() {
+	this.renderStack = this._renderStack.concat();
+};
+
+/**
+* Clears the inner HTML, CSS class and style information from the specified cell.
+* @method clearElement
+* @param	{HTMLTableCellElement}	The cell to clear
+*/
+YAHOO.widget.Calendar.prototype.clearElement = function(cell) {
+	cell.innerHTML = "&#160;";
+	cell.className="";
+};
+
+/**
+* Adds a renderer to the render stack. The function reference passed to this method will be executed
+* when a date cell matches the conditions specified in the date string for this renderer.
+* @method addRenderer
+* @param	{String}	sDates		A date string to associate with the specified renderer. Valid formats
+*									include date (12/24/2005), month/day (12/24), and range (12/1/2004-1/1/2005)
+* @param	{Function}	fnRender	The function executed to render cells that match the render rules for this renderer.
+*/
+YAHOO.widget.Calendar.prototype.addRenderer = function(sDates, fnRender) {
+	var aDates = this._parseDates(sDates);
+	for (var i=0;i<aDates.length;++i) {
+		var aDate = aDates[i];
+
+		if (aDate.length == 2) { // this is either a range or a month/day combo
+			if (aDate[0] instanceof Array) { // this is a range
+				this._addRenderer(YAHOO.widget.Calendar.RANGE,aDate,fnRender);
+			} else { // this is a month/day combo
+				this._addRenderer(YAHOO.widget.Calendar.MONTH_DAY,aDate,fnRender);
+			}
+		} else if (aDate.length == 3) {
+			this._addRenderer(YAHOO.widget.Calendar.DATE,aDate,fnRender);
+		}
+	}
+};
+
+/**
+* The private method used for adding cell renderers to the local render stack.
+* This method is called by other methods that set the renderer type prior to the method call.
+* @method _addRenderer
+* @private
+* @param	{String}	type		The type string that indicates the type of date renderer being added.
+*									Values are YAHOO.widget.Calendar.DATE, YAHOO.widget.Calendar.MONTH_DAY, YAHOO.widget.Calendar.WEEKDAY,
+*									YAHOO.widget.Calendar.RANGE, YAHOO.widget.Calendar.MONTH
+* @param	{Array}		aDates		An array of dates used to construct the renderer. The format varies based
+*									on the renderer type
+* @param	{Function}	fnRender	The function executed to render cells that match the render rules for this renderer.
+*/
+YAHOO.widget.Calendar.prototype._addRenderer = function(type, aDates, fnRender) {
+	var add = [type,aDates,fnRender];
+	this.renderStack.unshift(add);
+	this._renderStack = this.renderStack.concat();
+};
+
+/**
+* Adds a month to the render stack. The function reference passed to this method will be executed
+* when a date cell matches the month passed to this method.
+* @method addMonthRenderer
+* @param	{Number}	month		The month (1-12) to associate with this renderer
+* @param	{Function}	fnRender	The function executed to render cells that match the render rules for this renderer.
+*/
+YAHOO.widget.Calendar.prototype.addMonthRenderer = function(month, fnRender) {
+	this._addRenderer(YAHOO.widget.Calendar.MONTH,[month],fnRender);
+};
+
+/**
+* Adds a weekday to the render stack. The function reference passed to this method will be executed
+* when a date cell matches the weekday passed to this method.
+* @method addWeekdayRenderer
+* @param	{Number}	weekday		The weekday (0-6) to associate with this renderer
+* @param	{Function}	fnRender	The function executed to render cells that match the render rules for this renderer.
+*/
+YAHOO.widget.Calendar.prototype.addWeekdayRenderer = function(weekday, fnRender) {
+	this._addRenderer(YAHOO.widget.Calendar.WEEKDAY,[weekday],fnRender);
+};
+
+// END RENDERER METHODS
+
+// BEGIN CSS METHODS
+
+/**
+* Removes all styles from all body cells in the current calendar table.
+* @method clearAllBodyCellStyles
+* @param	{style}		The CSS class name to remove from all calendar body cells
+*/
+YAHOO.widget.Calendar.prototype.clearAllBodyCellStyles = function(style) {
+	for (var c=0;c<this.cells.length;++c) {
+		YAHOO.util.Dom.removeClass(this.cells[c],style);
+	}
+};
+
+// END CSS METHODS
+
+// BEGIN GETTER/SETTER METHODS
+/**
+* Sets the calendar's month explicitly
+* @method setMonth
+* @param {Number}	month		The numeric month, from 0 (January) to 11 (December)
+*/
+YAHOO.widget.Calendar.prototype.setMonth = function(month) {
+	var current = this.cfg.getProperty("pagedate");
+	current.setMonth(month);
+	this.cfg.setProperty("pagedate", current);
+};
+
+/**
+* Sets the calendar's year explicitly.
+* @method setYear
+* @param {Number}	year		The numeric 4-digit year
+*/
+YAHOO.widget.Calendar.prototype.setYear = function(year) {
+	var current = this.cfg.getProperty("pagedate");
+	current.setFullYear(year);
+	this.cfg.setProperty("pagedate", current);
+};
+
+/**
+* Gets the list of currently selected dates from the calendar.
+* @method getSelectedDates
+* @return {Date[]} An array of currently selected JavaScript Date objects.
+*/
+YAHOO.widget.Calendar.prototype.getSelectedDates = function() {
+	var returnDates = [];
+	var selected = this.cfg.getProperty("selected");
+
+	for (var d=0;d<selected.length;++d) {
+		var dateArray = selected[d];
+
+		var date = new Date(dateArray[0],dateArray[1]-1,dateArray[2]);
+		returnDates.push(date);
+	}
+
+	returnDates.sort( function(a,b) { return a-b; } );
+	return returnDates;
+};
+
+/// END GETTER/SETTER METHODS ///
+
+/**
+* Hides the Calendar's outer container from view.
+* @method hide
+*/
+YAHOO.widget.Calendar.prototype.hide = function() {
+	this.oDomContainer.style.display = "none";
+};
+
+/**
+* Shows the Calendar's outer container.
+* @method show
+*/
+YAHOO.widget.Calendar.prototype.show = function() {
+	this.oDomContainer.style.display = "block";
+};
+
+/**
+* Returns a string representing the current browser.
+* @property browser
+* @type String
+*/
+YAHOO.widget.Calendar.prototype.browser = function() {
+			var ua = navigator.userAgent.toLowerCase();
+				  if (ua.indexOf('opera')!=-1) { // Opera (check first in case of spoof)
+					 return 'opera';
+				  } else if (ua.indexOf('msie 7')!=-1) { // IE7
+					 return 'ie7';
+				  } else if (ua.indexOf('msie') !=-1) { // IE
+					 return 'ie';
+				  } else if (ua.indexOf('safari')!=-1) { // Safari (check before Gecko because it includes "like Gecko")
+					 return 'safari';
+				  } else if (ua.indexOf('gecko') != -1) { // Gecko
+					 return 'gecko';
+				  } else {
+					 return false;
+				  }
+			}();
+/**
+* Returns a string representation of the object.
+* @method toString
+* @return {String}	A string representation of the Calendar object.
+*/
+YAHOO.widget.Calendar.prototype.toString = function() {
+	return "Calendar " + this.id;
+};
+
+/**
+* @namespace YAHOO.widget
+* @class Calendar_Core
+* @extends YAHOO.widget.Calendar
+* @deprecated The old Calendar_Core class is no longer necessary.
+*/
+YAHOO.widget.Calendar_Core = YAHOO.widget.Calendar;
+
+YAHOO.widget.Cal_Core = YAHOO.widget.Calendar;
+
+/**
+* YAHOO.widget.CalendarGroup is a special container class for YAHOO.widget.Calendar. This class facilitates
+* the ability to have multi-page calendar views that share a single dataset and are
+* dependent on each other.
+*
+* The calendar group instance will refer to each of its elements using a 0-based index.
+* For example, to construct the placeholder for a calendar group widget with id "cal1" and
+* containerId of "cal1Container", the markup would be as follows:
+*	<xmp>
+*		<div id="cal1Container_0"></div>
+*		<div id="cal1Container_1"></div>
+*	</xmp>
+* The tables for the calendars ("cal1_0" and "cal1_1") will be inserted into those containers.
+* @namespace YAHOO.widget
+* @class CalendarGroup
+* @constructor
+* @param {String}	id			The id of the table element that will represent the calendar widget
+* @param {String}	containerId	The id of the container div element that will wrap the calendar table
+* @param {Object}	config		The configuration object containing the Calendar's arguments
+*/
+YAHOO.widget.CalendarGroup = function(id, containerId, config) {
+	if (arguments.length > 0) {
+		this.init(id, containerId, config);
+	}
+};
+
+/**
+* Initializes the calendar group. All subclasses must call this method in order for the
+* group to be initialized properly.
+* @method init
+* @param {String}	id			The id of the table element that will represent the calendar widget
+* @param {String}	containerId	The id of the container div element that will wrap the calendar table
+* @param {Object}	config		The configuration object containing the Calendar's arguments
+*/
+YAHOO.widget.CalendarGroup.prototype.init = function(id, containerId, config) {
+	this.initEvents();
+	this.initStyles();
+
+	/**
+	* The collection of Calendar pages contained within the CalendarGroup
+	* @property pages
+	* @type YAHOO.widget.Calendar[]
+	*/
+	this.pages = [];
+
+	/**
+	* The unique id associated with the CalendarGroup
+	* @property id
+	* @type String
+	*/
+	this.id = id;
+
+	/**
+	* The unique id associated with the CalendarGroup container
+	* @property containerId
+	* @type String
+	*/
+	this.containerId = containerId;
+
+	/**
+	* The outer containing element for the CalendarGroup
+	* @property oDomContainer
+	* @type HTMLElement
+	*/
+	this.oDomContainer = document.getElementById(containerId);
+
+	YAHOO.util.Dom.addClass(this.oDomContainer, YAHOO.widget.CalendarGroup.CSS_CONTAINER);
+	YAHOO.util.Dom.addClass(this.oDomContainer, YAHOO.widget.CalendarGroup.CSS_MULTI_UP);
+
+	/**
+	* The Config object used to hold the configuration variables for the CalendarGroup
+	* @property cfg
+	* @type YAHOO.util.Config
+	*/
+	this.cfg = new YAHOO.util.Config(this);
+
+	/**
+	* The local object which contains the CalendarGroup's options
+	* @property Options
+	* @type Object
+	*/
+	this.Options = {};
+
+	/**
+	* The local object which contains the CalendarGroup's locale settings
+	* @property Locale
+	* @type Object
+	*/
+	this.Locale = {};
+
+	this.setupConfig();
+
+	if (config) {
+		this.cfg.applyConfig(config, true);
+	}
+
+	this.cfg.fireQueue();
+
+	// OPERA HACK FOR MISWRAPPED FLOATS
+	if (this.browser == "opera"){
+		var fixWidth = function() {
+			var startW = this.oDomContainer.offsetWidth;
+			var w = 0;
+			for (var p=0;p<this.pages.length;++p) {
+				var cal = this.pages[p];
+				w += cal.oDomContainer.offsetWidth;
+			}
+			if (w > 0) {
+				this.oDomContainer.style.width = w + "px";
+			}
+		};
+		this.renderEvent.subscribe(fixWidth,this,true);
+	}
+};
+
+
+YAHOO.widget.CalendarGroup.prototype.setupConfig = function() {
+	/**
+	* The number of pages to include in the CalendarGroup. This value can only be set once, in the CalendarGroup's constructor arguments.
+	* @config pages
+	* @type Number
+	* @default 2
+	*/
+	this.cfg.addProperty("pages", { value:2, validator:this.cfg.checkNumber, handler:this.configPages } );
+
+	/**
+	* The month/year representing the current visible Calendar date (mm/yyyy)
+	* @config pagedate
+	* @type String
+	* @default today's date
+	*/
+	this.cfg.addProperty("pagedate", { value:new Date(), handler:this.configPageDate } );
+
+	/**
+	* The date or range of dates representing the current Calendar selection
+	* @config selected
+	* @type String
+	* @default []
+	*/
+	this.cfg.addProperty("selected", { value:[], handler:this.delegateConfig } );
+
+	/**
+	* The title to display above the CalendarGroup's month header
+	* @config title
+	* @type String
+	* @default ""
+	*/
+	this.cfg.addProperty("title", { value:"", handler:this.configTitle } );
+
+	/**
+	* Whether or not a close button should be displayed for this CalendarGroup
+	* @config close
+	* @type Boolean
+	* @default false
+	*/
+	this.cfg.addProperty("close", { value:false, handler:this.configClose } );
+
+	/**
+	* Whether or not an iframe shim should be placed under the Calendar to prevent select boxes from bleeding through in Internet Explorer 6 and below.
+	* @config iframe
+	* @type Boolean
+	* @default true
+	*/
+	this.cfg.addProperty("iframe", { value:true, handler:this.delegateConfig, validator:this.cfg.checkBoolean } );
+
+	/**
+	* The minimum selectable date in the current Calendar (mm/dd/yyyy)
+	* @config mindate
+	* @type String
+	* @default null
+	*/
+	this.cfg.addProperty("mindate", { value:null, handler:this.delegateConfig } );
+
+	/**
+	* The maximum selectable date in the current Calendar (mm/dd/yyyy)
+	* @config maxdate
+	* @type String
+	* @default null
+	*/
+	this.cfg.addProperty("maxdate", { value:null, handler:this.delegateConfig  } );
+
+	// Options properties
+
+	/**
+	* True if the Calendar should allow multiple selections. False by default.
+	* @config MULTI_SELECT
+	* @type Boolean
+	* @default false
+	*/
+	this.cfg.addProperty("MULTI_SELECT",	{ value:false, handler:this.delegateConfig, validator:this.cfg.checkBoolean } );
+
+    /**
+    * True if the Calendar should allow selection of out-of-month dates. False by default.
+    * @config OOM_SELECT
+    * @type Boolean
+    * @default false
+    */
+    this.cfg.addProperty("OOM_SELECT",      { value:false, handler:this.delegateConfig, validator:this.cfg.checkBoolean } );
+
+	/**
+	* The weekday the week begins on. Default is 0 (Sunday).
+	* @config START_WEEKDAY
+	* @type number
+	* @default 0
+	*/
+	this.cfg.addProperty("START_WEEKDAY",	{ value:0, handler:this.delegateConfig, validator:this.cfg.checkNumber  } );
+
+	/**
+	* True if the Calendar should show weekday labels. True by default.
+	* @config SHOW_WEEKDAYS
+	* @type Boolean
+	* @default true
+	*/
+	this.cfg.addProperty("SHOW_WEEKDAYS",	{ value:true, handler:this.delegateConfig, validator:this.cfg.checkBoolean } );
+
+	/**
+	* True if the Calendar should show week row headers. False by default.
+	* @config SHOW_WEEK_HEADER
+	* @type Boolean
+	* @default false
+	*/
+	this.cfg.addProperty("SHOW_WEEK_HEADER",{ value:false, handler:this.delegateConfig, validator:this.cfg.checkBoolean } );
+
+	/**
+	* True if the Calendar should show week row footers. False by default.
+	* @config SHOW_WEEK_FOOTER
+	* @type Boolean
+	* @default false
+	*/
+	this.cfg.addProperty("SHOW_WEEK_FOOTER",{ value:false, handler:this.delegateConfig, validator:this.cfg.checkBoolean } );
+
+	/**
+	* True if the Calendar should suppress weeks that are not a part of the current month. False by default.
+	* @config HIDE_BLANK_WEEKS
+	* @type Boolean
+	* @default false
+	*/
+	this.cfg.addProperty("HIDE_BLANK_WEEKS",{ value:false, handler:this.delegateConfig, validator:this.cfg.checkBoolean } );
+
+	/**
+	* The image that should be used for the left navigation arrow.
+	* @config NAV_ARROW_LEFT
+	* @type String
+	* @default YAHOO.widget.Calendar.IMG_ROOT + "us/tr/callt.gif"
+	*/
+	this.cfg.addProperty("NAV_ARROW_LEFT",	{ value:YAHOO.widget.Calendar.IMG_ROOT + "us/tr/callt.gif", handler:this.delegateConfig } );
+
+	/**
+	* The image that should be used for the left navigation arrow.
+	* @config NAV_ARROW_RIGHT
+	* @type String
+	* @default YAHOO.widget.Calendar.IMG_ROOT + "us/tr/calrt.gif"
+	*/
+	this.cfg.addProperty("NAV_ARROW_RIGHT",	{ value:YAHOO.widget.Calendar.IMG_ROOT + "us/tr/calrt.gif", handler:this.delegateConfig } );
+
+	// Locale properties
+
+	/**
+	* The short month labels for the current locale.
+	* @config MONTHS_SHORT
+	* @type String[]
+	* @default ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
+	*/
+	this.cfg.addProperty("MONTHS_SHORT",	{ value:["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], handler:this.delegateConfig } );
+
+	/**
+	* The long month labels for the current locale.
+	* @config MONTHS_LONG
+	* @type String[]
+	* @default ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
+	*/
+	this.cfg.addProperty("MONTHS_LONG",		{ value:["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], handler:this.delegateConfig } );
+
+	/**
+	* The 1-character weekday labels for the current locale.
+	* @config WEEKDAYS_1CHAR
+	* @type String[]
+	* @default ["S", "M", "T", "W", "T", "F", "S"]
+	*/
+	this.cfg.addProperty("WEEKDAYS_1CHAR",	{ value:["S", "M", "T", "W", "T", "F", "S"], handler:this.delegateConfig } );
+
+	/**
+	* The short weekday labels for the current locale.
+	* @config WEEKDAYS_SHORT
+	* @type String[]
+	* @default ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]
+	*/
+	this.cfg.addProperty("WEEKDAYS_SHORT",	{ value:["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"], handler:this.delegateConfig } );
+
+	/**
+	* The medium weekday labels for the current locale.
+	* @config WEEKDAYS_MEDIUM
+	* @type String[]
+	* @default ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
+	*/
+	this.cfg.addProperty("WEEKDAYS_MEDIUM",	{ value:["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], handler:this.delegateConfig } );
+
+	/**
+	* The long weekday labels for the current locale.
+	* @config WEEKDAYS_LONG
+	* @type String[]
+	* @default ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
+	*/
+	this.cfg.addProperty("WEEKDAYS_LONG",	{ value:["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], handler:this.delegateConfig } );
+
+	/**
+	* The setting that determines which length of month labels should be used. Possible values are "short" and "long".
+	* @config LOCALE_MONTHS
+	* @type String
+	* @default "long"
+	*/
+	this.cfg.addProperty("LOCALE_MONTHS",	{ value:"long", handler:this.delegateConfig } );
+
+	/**
+	* The setting that determines which length of weekday labels should be used. Possible values are "1char", "short", "medium", and "long".
+	* @config LOCALE_WEEKDAYS
+	* @type String
+	* @default "short"
+	*/
+	this.cfg.addProperty("LOCALE_WEEKDAYS",	{ value:"short", handler:this.delegateConfig } );
+
+	/**
+	* The value used to delimit individual dates in a date string passed to various Calendar functions.
+	* @config DATE_DELIMITER
+	* @type String
+	* @default ","
+	*/
+	this.cfg.addProperty("DATE_DELIMITER",		{ value:",", handler:this.delegateConfig } );
+
+	/**
+	* The value used to delimit date fields in a date string passed to various Calendar functions.
+	* @config DATE_FIELD_DELIMITER
+	* @type String
+	* @default "/"
+	*/
+	this.cfg.addProperty("DATE_FIELD_DELIMITER",{ value:"/", handler:this.delegateConfig } );
+
+	/**
+	* The value used to delimit date ranges in a date string passed to various Calendar functions.
+	* @config DATE_RANGE_DELIMITER
+	* @type String
+	* @default "-"
+	*/
+	this.cfg.addProperty("DATE_RANGE_DELIMITER",{ value:"-", handler:this.delegateConfig } );
+
+	/**
+	* The position of the month in a month/year date string
+	* @config MY_MONTH_POSITION
+	* @type Number
+	* @default 1
+	*/
+	this.cfg.addProperty("MY_MONTH_POSITION",	{ value:1, handler:this.delegateConfig, validator:this.cfg.checkNumber } );
+
+	/**
+	* The position of the year in a month/year date string
+	* @config MY_YEAR_POSITION
+	* @type Number
+	* @default 2
+	*/
+	this.cfg.addProperty("MY_YEAR_POSITION",	{ value:2, handler:this.delegateConfig, validator:this.cfg.checkNumber } );
+
+	/**
+	* The position of the month in a month/day date string
+	* @config MD_MONTH_POSITION
+	* @type Number
+	* @default 1
+	*/
+	this.cfg.addProperty("MD_MONTH_POSITION",	{ value:1, handler:this.delegateConfig, validator:this.cfg.checkNumber } );
+
+	/**
+	* The position of the day in a month/year date string
+	* @config MD_DAY_POSITION
+	* @type Number
+	* @default 2
+	*/
+	this.cfg.addProperty("MD_DAY_POSITION",		{ value:2, handler:this.delegateConfig, validator:this.cfg.checkNumber } );
+
+	/**
+	* The position of the month in a month/day/year date string
+	* @config MDY_MONTH_POSITION
+	* @type Number
+	* @default 1
+	*/
+	this.cfg.addProperty("MDY_MONTH_POSITION",	{ value:1, handler:this.delegateConfig, validator:this.cfg.checkNumber } );
+
+	/**
+	* The position of the day in a month/day/year date string
+	* @config MDY_DAY_POSITION
+	* @type Number
+	* @default 2
+	*/
+	this.cfg.addProperty("MDY_DAY_POSITION",	{ value:2, handler:this.delegateConfig, validator:this.cfg.checkNumber } );
+
+	/**
+	* The position of the year in a month/day/year date string
+	* @config MDY_YEAR_POSITION
+	* @type Number
+	* @default 3
+	*/
+	this.cfg.addProperty("MDY_YEAR_POSITION",	{ value:3, handler:this.delegateConfig, validator:this.cfg.checkNumber } );
+
+};
+
+/**
+* Initializes CalendarGroup's built-in CustomEvents
+* @method initEvents
+*/
+YAHOO.widget.CalendarGroup.prototype.initEvents = function() {
+	var me = this;
+
+	/**
+	* Proxy subscriber to subscribe to the CalendarGroup's child Calendars' CustomEvents
+	* @method sub
+	* @private
+	* @param {Function} fn	The function to subscribe to this CustomEvent
+	* @param {Object}	obj	The CustomEvent's scope object
+	* @param {Boolean}	bOverride	Whether or not to apply scope correction
+	*/
+	var sub = function(fn, obj, bOverride) {
+		for (var p=0;p<me.pages.length;++p) {
+			var cal = me.pages[p];
+			cal[this.type + "Event"].subscribe(fn, obj, bOverride);
+		}
+	};
+
+	/**
+	* Proxy unsubscriber to unsubscribe from the CalendarGroup's child Calendars' CustomEvents
+	* @method unsub
+	* @private
+	* @param {Function} fn	The function to subscribe to this CustomEvent
+	* @param {Object}	obj	The CustomEvent's scope object
+	*/
+	var unsub = function(fn, obj) {
+		for (var p=0;p<me.pages.length;++p) {
+			var cal = me.pages[p];
+			cal[this.type + "Event"].unsubscribe(fn, obj);
+		}
+	};
+
+	/**
+	* Fired before a selection is made
+	* @event beforeSelectEvent
+	*/
+	this.beforeSelectEvent = new YAHOO.util.CustomEvent("beforeSelect");
+	this.beforeSelectEvent.subscribe = sub; this.beforeSelectEvent.unsubscribe = unsub;
+
+	/**
+	* Fired when a selection is made
+	* @event selectEvent
+	* @param {Array}	Array of Date field arrays in the format [YYYY, MM, DD].
+	*/
+	this.selectEvent = new YAHOO.util.CustomEvent("select");
+	this.selectEvent.subscribe = sub; this.selectEvent.unsubscribe = unsub;
+
+	/**
+	* Fired before a selection is made
+	* @event beforeDeselectEvent
+	*/
+	this.beforeDeselectEvent = new YAHOO.util.CustomEvent("beforeDeselect");
+	this.beforeDeselectEvent.subscribe = sub; this.beforeDeselectEvent.unsubscribe = unsub;
+
+	/**
+	* Fired when a selection is made
+	* @event deselectEvent
+	* @param {Array}	Array of Date field arrays in the format [YYYY, MM, DD].
+	*/
+	this.deselectEvent = new YAHOO.util.CustomEvent("deselect");
+	this.deselectEvent.subscribe = sub; this.deselectEvent.unsubscribe = unsub;
+
+	/**
+	* Fired when the Calendar page is changed
+	* @event changePageEvent
+	*/
+	this.changePageEvent = new YAHOO.util.CustomEvent("changePage");
+	this.changePageEvent.subscribe = sub; this.changePageEvent.unsubscribe = unsub;
+
+	/**
+	* Fired before the Calendar is rendered
+	* @event beforeRenderEvent
+	*/
+	this.beforeRenderEvent = new YAHOO.util.CustomEvent("beforeRender");
+	this.beforeRenderEvent.subscribe = sub; this.beforeRenderEvent.unsubscribe = unsub;
+
+	/**
+	* Fired when the Calendar is rendered
+	* @event renderEvent
+	*/
+	this.renderEvent = new YAHOO.util.CustomEvent("render");
+	this.renderEvent.subscribe = sub; this.renderEvent.unsubscribe = unsub;
+
+	/**
+	* Fired when the Calendar is reset
+	* @event resetEvent
+	*/
+	this.resetEvent = new YAHOO.util.CustomEvent("reset");
+	this.resetEvent.subscribe = sub; this.resetEvent.unsubscribe = unsub;
+
+	/**
+	* Fired when the Calendar is cleared
+	* @event clearEvent
+	*/
+	this.clearEvent = new YAHOO.util.CustomEvent("clear");
+	this.clearEvent.subscribe = sub; this.clearEvent.unsubscribe = unsub;
+
+};
+
+/**
+* The default Config handler for the "pages" property
+* @method configPages
+* @param {String} type	The CustomEvent type (usually the property name)
+* @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
+* @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
+*/
+YAHOO.widget.CalendarGroup.prototype.configPages = function(type, args, obj) {
+	var pageCount = args[0];
+
+	for (var p=0;p<pageCount;++p) {
+		var calId = this.id + "_" + p;
+		var calContainerId = this.containerId + "_" + p;
+
+		var childConfig = this.cfg.getConfig();
+		childConfig.close = false;
+		childConfig.title = false;
+
+		var cal = this.constructChild(calId, calContainerId, childConfig);
+		var caldate = cal.cfg.getProperty("pagedate");
+		caldate.setMonth(caldate.getMonth()+p);
+		cal.cfg.setProperty("pagedate", caldate);
+
+		YAHOO.util.Dom.removeClass(cal.oDomContainer, this.Style.CSS_SINGLE);
+		YAHOO.util.Dom.addClass(cal.oDomContainer, "groupcal");
+
+		if (p===0) {
+			YAHOO.util.Dom.addClass(cal.oDomContainer, "first");
+		}
+
+		if (p==(pageCount-1)) {
+			YAHOO.util.Dom.addClass(cal.oDomContainer, "last");
+		}
+
+		cal.parent = this;
+		cal.index = p;
+
+		this.pages[this.pages.length] = cal;
+	}
+};
+
+/**
+* The default Config handler for the "pagedate" property
+* @method configPageDate
+* @param {String} type	The CustomEvent type (usually the property name)
+* @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
+* @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
+*/
+YAHOO.widget.CalendarGroup.prototype.configPageDate = function(type, args, obj) {
+	var val = args[0];
+
+	for (var p=0;p<this.pages.length;++p) {
+		var cal = this.pages[p];
+		cal.cfg.setProperty("pagedate", val);
+		var calDate = cal.cfg.getProperty("pagedate");
+		calDate.setMonth(calDate.getMonth()+p);
+	}
+};
+
+/**
+* Delegates a configuration property to the CustomEvents associated with the CalendarGroup's children
+* @method delegateConfig
+* @param {String} type	The CustomEvent type (usually the property name)
+* @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
+* @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
+*/
+YAHOO.widget.CalendarGroup.prototype.delegateConfig = function(type, args, obj) {
+	var val = args[0];
+	var cal;
+
+	for (var p=0;p<this.pages.length;p++) {
+		cal = this.pages[p];
+		cal.cfg.setProperty(type, val);
+	}
+};
+
+
+/**
+* Adds a function to all child Calendars within this CalendarGroup.
+* @method setChildFunction
+* @param {String}		fnName		The name of the function
+* @param {Function}		fn			The function to apply to each Calendar page object
+*/
+YAHOO.widget.CalendarGroup.prototype.setChildFunction = function(fnName, fn) {
+	var pageCount = this.cfg.getProperty("pages");
+
+	for (var p=0;p<pageCount;++p) {
+		this.pages[p][fnName] = fn;
+	}
+};
+
+/**
+* Calls a function within all child Calendars within this CalendarGroup.
+* @method callChildFunction
+* @param {String}		fnName		The name of the function
+* @param {Array}		args		The arguments to pass to the function
+*/
+YAHOO.widget.CalendarGroup.prototype.callChildFunction = function(fnName, args) {
+	var pageCount = this.cfg.getProperty("pages");
+
+	for (var p=0;p<pageCount;++p) {
+		var page = this.pages[p];
+		if (page[fnName]) {
+			var fn = page[fnName];
+			fn.call(page, args);
+		}
+	}
+};
+
+/**
+* Constructs a child calendar. This method can be overridden if a subclassed version of the default
+* calendar is to be used.
+* @method constructChild
+* @param {String}	id			The id of the table element that will represent the calendar widget
+* @param {String}	containerId	The id of the container div element that will wrap the calendar table
+* @param {Object}	config		The configuration object containing the Calendar's arguments
+* @return {YAHOO.widget.Calendar}	The YAHOO.widget.Calendar instance that is constructed
+*/
+YAHOO.widget.CalendarGroup.prototype.constructChild = function(id,containerId,config) {
+	var container = document.getElementById(containerId);
+	if (! container) {
+		container = document.createElement("div");
+		container.id = containerId;
+		this.oDomContainer.appendChild(container);
+	}
+	return new YAHOO.widget.Calendar(id,containerId,config);
+};
+
+
+/**
+* Sets the calendar group's month explicitly. This month will be set into the first
+* @method setMonth
+* page of the multi-page calendar, and all other months will be iterated appropriately.
+* @param {Number}	month		The numeric month, from 0 (January) to 11 (December)
+*/
+YAHOO.widget.CalendarGroup.prototype.setMonth = function(month) {
+	for (var p=0;p<this.pages.length;++p) {
+		var cal = this.pages[p];
+		cal.setMonth(month+p);
+	}
+};
+
+/**
+* Sets the calendar group's year explicitly. This year will be set into the first
+* @method setYear
+* page of the multi-page calendar, and all other months will be iterated appropriately.
+* @param {Number}	year		The numeric 4-digit year
+*/
+YAHOO.widget.CalendarGroup.prototype.setYear = function(year) {
+	for (var p=0;p<this.pages.length;++p) {
+		var cal = this.pages[p];
+		var pageDate = cal.cfg.getProperty("pageDate");
+
+		if ((pageDate.getMonth()+1) == 1 && p>0) {
+			year+=1;
+		}
+		cal.setYear(year);
+	}
+};
+/**
+* Calls the render function of all child calendars within the group.
+* @method render
+*/
+YAHOO.widget.CalendarGroup.prototype.render = function() {
+	this.renderHeader();
+	for (var p=0;p<this.pages.length;++p) {
+		var cal = this.pages[p];
+		cal.render();
+	}
+	this.renderFooter();
+};
+
+/**
+* Selects a date or a collection of dates on the current calendar. This method, by default,
+* does not call the render method explicitly. Once selection has completed, render must be
+* called for the changes to be reflected visually.
+* @method select
+* @param	{String/Date/Date[]}	date	The date string of dates to select in the current calendar. Valid formats are
+*								individual date(s) (12/24/2005,12/26/2005) or date range(s) (12/24/2005-1/1/2006).
+*								Multiple comma-delimited dates can also be passed to this method (12/24/2005,12/11/2005-12/13/2005).
+*								This method can also take a JavaScript Date object or an array of Date objects.
+* @return	{Date[]}			Array of JavaScript Date objects representing all individual dates that are currently selected.
+*/
+YAHOO.widget.CalendarGroup.prototype.select = function(date) {
+	for (var p=0;p<this.pages.length;++p) {
+		var cal = this.pages[p];
+		cal.select(date);
+	}
+	return this.getSelectedDates();
+};
+
+/**
+* Selects a date on the current calendar by referencing the index of the cell that should be selected.
+* This method is used to easily select a single cell (usually with a mouse click) without having to do
+* a full render. The selected style is applied to the cell directly.
+* @method selectCell
+* @param	{Number}	cellIndex	The index of the cell to select in the current calendar.
+* @return	{Date[]}	Array of JavaScript Date objects representing all individual dates that are currently selected.
+*/
+YAHOO.widget.CalendarGroup.prototype.selectCell = function(cellIndex) {
+	for (var p=0;p<this.pages.length;++p) {
+		var cal = this.pages[p];
+		cal.selectCell(cellIndex);
+	}
+	return this.getSelectedDates();
+};
+
+/**
+* Deselects a date or a collection of dates on the current calendar. This method, by default,
+* does not call the render method explicitly. Once deselection has completed, render must be
+* called for the changes to be reflected visually.
+* @method deselect
+* @param	{String/Date/Date[]}	date	The date string of dates to deselect in the current calendar. Valid formats are
+*								individual date(s) (12/24/2005,12/26/2005) or date range(s) (12/24/2005-1/1/2006).
+*								Multiple comma-delimited dates can also be passed to this method (12/24/2005,12/11/2005-12/13/2005).
+*								This method can also take a JavaScript Date object or an array of Date objects.
+* @return	{Date[]}			Array of JavaScript Date objects representing all individual dates that are currently selected.
+*/
+YAHOO.widget.CalendarGroup.prototype.deselect = function(date) {
+	for (var p=0;p<this.pages.length;++p) {
+		var cal = this.pages[p];
+		cal.deselect(date);
+	}
+	return this.getSelectedDates();
+};
+
+/**
+* Deselects all dates on the current calendar.
+* @method deselectAll
+* @return {Date[]}		Array of JavaScript Date objects representing all individual dates that are currently selected.
+*						Assuming that this function executes properly, the return value should be an empty array.
+*						However, the empty array is returned for the sake of being able to check the selection status
+*						of the calendar.
+*/
+YAHOO.widget.CalendarGroup.prototype.deselectAll = function() {
+	for (var p=0;p<this.pages.length;++p) {
+		var cal = this.pages[p];
+		cal.deselectAll();
+	}
+	return this.getSelectedDates();
+};
+
+/**
+* Deselects a date on the current calendar by referencing the index of the cell that should be deselected.
+* This method is used to easily deselect a single cell (usually with a mouse click) without having to do
+* a full render. The selected style is removed from the cell directly.
+* @method deselectCell
+* @param	{Number}	cellIndex	The index of the cell to deselect in the current calendar.
+* @return	{Date[]}	Array of JavaScript Date objects representing all individual dates that are currently selected.
+*/
+YAHOO.widget.CalendarGroup.prototype.deselectCell = function(cellIndex) {
+	for (var p=0;p<this.pages.length;++p) {
+		var cal = this.pages[p];
+		cal.deselectCell(cellIndex);
+	}
+	return this.getSelectedDates();
+};
+
+/**
+* Resets the calendar widget to the originally selected month and year, and
+* sets the calendar to the initial selection(s).
+* @method reset
+*/
+YAHOO.widget.CalendarGroup.prototype.reset = function() {
+	for (var p=0;p<this.pages.length;++p) {
+		var cal = this.pages[p];
+		cal.reset();
+	}
+};
+
+/**
+* Clears the selected dates in the current calendar widget and sets the calendar
+* to the current month and year.
+* @method clear
+*/
+YAHOO.widget.CalendarGroup.prototype.clear = function() {
+	for (var p=0;p<this.pages.length;++p) {
+		var cal = this.pages[p];
+		cal.clear();
+	}
+};
+
+/**
+* Navigates to the next month page in the calendar widget.
+* @method nextMonth
+*/
+YAHOO.widget.CalendarGroup.prototype.nextMonth = function() {
+	for (var p=0;p<this.pages.length;++p) {
+		var cal = this.pages[p];
+		cal.nextMonth();
+	}
+};
+
+/**
+* Navigates to the previous month page in the calendar widget.
+* @method previousMonth
+*/
+YAHOO.widget.CalendarGroup.prototype.previousMonth = function() {
+	for (var p=this.pages.length-1;p>=0;--p) {
+		var cal = this.pages[p];
+		cal.previousMonth();
+	}
+};
+
+/**
+* Navigates to the next year in the currently selected month in the calendar widget.
+* @method nextYear
+*/
+YAHOO.widget.CalendarGroup.prototype.nextYear = function() {
+	for (var p=0;p<this.pages.length;++p) {
+		var cal = this.pages[p];
+		cal.nextYear();
+	}
+};
+
+/**
+* Navigates to the previous year in the currently selected month in the calendar widget.
+* @method previousYear
+*/
+YAHOO.widget.CalendarGroup.prototype.previousYear = function() {
+	for (var p=0;p<this.pages.length;++p) {
+		var cal = this.pages[p];
+		cal.previousYear();
+	}
+};
+
+
+/**
+* Gets the list of currently selected dates from the calendar.
+* @return			An array of currently selected JavaScript Date objects.
+* @type Date[]
+*/
+YAHOO.widget.CalendarGroup.prototype.getSelectedDates = function() {
+	var returnDates = [];
+	var selected = this.cfg.getProperty("selected");
+
+	for (var d=0;d<selected.length;++d) {
+		var dateArray = selected[d];
+
+		var date = new Date(dateArray[0],dateArray[1]-1,dateArray[2]);
+		returnDates.push(date);
+	}
+
+	returnDates.sort( function(a,b) { return a-b; } );
+	return returnDates;
+};
+
+/**
+* Adds a renderer to the render stack. The function reference passed to this method will be executed
+* when a date cell matches the conditions specified in the date string for this renderer.
+* @method addRenderer
+* @param	{String}	sDates		A date string to associate with the specified renderer. Valid formats
+*									include date (12/24/2005), month/day (12/24), and range (12/1/2004-1/1/2005)
+* @param	{Function}	fnRender	The function executed to render cells that match the render rules for this renderer.
+*/
+YAHOO.widget.CalendarGroup.prototype.addRenderer = function(sDates, fnRender) {
+	for (var p=0;p<this.pages.length;++p) {
+		var cal = this.pages[p];
+		cal.addRenderer(sDates, fnRender);
+	}
+};
+
+/**
+* Adds a month to the render stack. The function reference passed to this method will be executed
+* when a date cell matches the month passed to this method.
+* @method addMonthRenderer
+* @param	{Number}	month		The month (1-12) to associate with this renderer
+* @param	{Function}	fnRender	The function executed to render cells that match the render rules for this renderer.
+*/
+YAHOO.widget.CalendarGroup.prototype.addMonthRenderer = function(month, fnRender) {
+	for (var p=0;p<this.pages.length;++p) {
+		var cal = this.pages[p];
+		cal.addMonthRenderer(month, fnRender);
+	}
+};
+
+/**
+* Adds a weekday to the render stack. The function reference passed to this method will be executed
+* when a date cell matches the weekday passed to this method.
+* @method addWeekdayRenderer
+* @param	{Number}	weekday		The weekday (0-6) to associate with this renderer
+* @param	{Function}	fnRender	The function executed to render cells that match the render rules for this renderer.
+*/
+YAHOO.widget.CalendarGroup.prototype.addWeekdayRenderer = function(weekday, fnRender) {
+	for (var p=0;p<this.pages.length;++p) {
+		var cal = this.pages[p];
+		cal.addWeekdayRenderer(weekday, fnRender);
+	}
+};
+
+/**
+* Renders the header for the CalendarGroup.
+* @method renderHeader
+*/
+YAHOO.widget.CalendarGroup.prototype.renderHeader = function() {};
+
+/**
+* Renders a footer for the 2-up calendar container. By default, this method is
+* unimplemented.
+* @method renderFooter
+*/
+YAHOO.widget.CalendarGroup.prototype.renderFooter = function() {};
+
+/**
+* Adds the designated number of months to the current calendar month, and sets the current
+* calendar page date to the new month.
+* @method addMonths
+* @param {Number}	count	The number of months to add to the current calendar
+*/
+YAHOO.widget.CalendarGroup.prototype.addMonths = function(count) {
+	this.callChildFunction("addMonths", count);
+};
+
+
+/**
+* Subtracts the designated number of months from the current calendar month, and sets the current
+* calendar page date to the new month.
+* @method subtractMonths
+* @param {Number}	count	The number of months to subtract from the current calendar
+*/
+YAHOO.widget.CalendarGroup.prototype.subtractMonths = function(count) {
+	this.callChildFunction("subtractMonths", count);
+};
+
+/**
+* Adds the designated number of years to the current calendar, and sets the current
+* calendar page date to the new month.
+* @method addYears
+* @param {Number}	count	The number of years to add to the current calendar
+*/
+YAHOO.widget.CalendarGroup.prototype.addYears = function(count) {
+	this.callChildFunction("addYears", count);
+};
+
+/**
+* Subtcats the designated number of years from the current calendar, and sets the current
+* calendar page date to the new month.
+* @method subtractYears
+* @param {Number}	count	The number of years to subtract from the current calendar
+*/
+YAHOO.widget.CalendarGroup.prototype.subtractYears = function(count) {
+	this.callChildFunction("subtractYears", count);
+};
+
+/**
+* CSS class representing the container for the calendar
+* @property YAHOO.widget.CalendarGroup.CSS_CONTAINER
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.CalendarGroup.CSS_CONTAINER = "yui-calcontainer";
+
+/**
+* CSS class representing the container for the calendar
+* @property YAHOO.widget.CalendarGroup.CSS_MULTI_UP
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.CalendarGroup.CSS_MULTI_UP = "multi";
+
+/**
+* CSS class representing the title for the 2-up calendar
+* @property YAHOO.widget.CalendarGroup.CSS_2UPTITLE
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.CalendarGroup.CSS_2UPTITLE = "title";
+
+/**
+* CSS class representing the close icon for the 2-up calendar
+* @property YAHOO.widget.CalendarGroup.CSS_2UPCLOSE
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.CalendarGroup.CSS_2UPCLOSE = "close-icon";
+
+YAHOO.augment(YAHOO.widget.CalendarGroup, YAHOO.widget.Calendar, "buildDayLabel",
+																 "buildMonthLabel",
+																 "renderOutOfBoundsDate",
+																 "renderRowHeader",
+																 "renderRowFooter",
+																 "renderCellDefault",
+																 "styleCellDefault",
+																 "renderCellStyleHighlight1",
+																 "renderCellStyleHighlight2",
+																 "renderCellStyleHighlight3",
+																 "renderCellStyleHighlight4",
+																 "renderCellStyleToday",
+																 "renderCellStyleSelected",
+																 "renderCellNotThisMonth",
+																 "renderBodyCellRestricted",
+																 "initStyles",
+																 "configTitle",
+																 "configClose",
+																 "hide",
+																 "show",
+																 "browser");
+
+/**
+* Returns a string representation of the object.
+* @method toString
+* @return {String}	A string representation of the CalendarGroup object.
+*/
+YAHOO.widget.CalendarGroup.prototype.toString = function() {
+	return "CalendarGroup " + this.id;
+};
+
+YAHOO.widget.CalGrp = YAHOO.widget.CalendarGroup;
+
+/**
+* @class YAHOO.widget.Calendar2up
+* @extends YAHOO.widget.CalendarGroup
+* @deprecated The old Calendar2up class is no longer necessary, since CalendarGroup renders in a 2up view by default.
+*/
+YAHOO.widget.Calendar2up = function(id, containerId, config) {
+	this.init(id, containerId, config);
+};
+
+YAHOO.extend(YAHOO.widget.Calendar2up, YAHOO.widget.CalendarGroup);
+
+/**
+* @deprecated The old Calendar2up class is no longer necessary, since CalendarGroup renders in a 2up view by default.
+*/
+YAHOO.widget.Cal2up = YAHOO.widget.Calendar2up;

Added: jifty/branches/schema-plugins/share/web/static/js/yui/container.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/yui/container.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,4614 @@
+/*
+Copyright (c) 2006, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.net/yui/license.txt
+version 0.12.1
+*/
+
+/**
+* Config is a utility used within an Object to allow the implementer to maintain a list of local configuration properties and listen for changes to those properties dynamically using CustomEvent. The initial values are also maintained so that the configuration can be reset at any given point to its initial state.
+* @namespace YAHOO.util
+* @class Config
+* @constructor
+* @param {Object}	owner	The owner Object to which this Config Object belongs
+*/
+YAHOO.util.Config = function(owner) {
+	if (owner) {
+		this.init(owner);
+	}
+};
+
+YAHOO.util.Config.prototype = {
+
+	/**
+	* Object reference to the owner of this Config Object
+	* @property owner
+	* @type Object
+	*/
+	owner : null,
+
+	/**
+	* Boolean flag that specifies whether a queue is currently being executed
+	* @property queueInProgress
+	* @type Boolean
+	*/
+	queueInProgress : false,
+
+
+	/**
+	* Validates that the value passed in is a Boolean.
+	* @method checkBoolean
+	* @param	{Object}	val	The value to validate
+	* @return	{Boolean}	true, if the value is valid
+	*/
+	checkBoolean: function(val) {
+		if (typeof val == 'boolean') {
+			return true;
+		} else {
+			return false;
+		}
+	},
+
+	/**
+	* Validates that the value passed in is a number.
+	* @method checkNumber
+	* @param	{Object}	val	The value to validate
+	* @return	{Boolean}	true, if the value is valid
+	*/
+	checkNumber: function(val) {
+		if (isNaN(val)) {
+			return false;
+		} else {
+			return true;
+		}
+	}
+};
+
+
+/**
+* Initializes the configuration Object and all of its local members.
+* @method init
+* @param {Object}	owner	The owner Object to which this Config Object belongs
+*/
+YAHOO.util.Config.prototype.init = function(owner) {
+
+	this.owner = owner;
+
+	/**
+	* Object reference to the owner of this Config Object
+	* @event configChangedEvent
+	*/
+	this.configChangedEvent = new YAHOO.util.CustomEvent("configChanged");
+
+	this.queueInProgress = false;
+
+	/* Private Members */
+
+	/**
+	* Maintains the local collection of configuration property objects and their specified values
+	* @property config
+	* @private
+	* @type Object
+	*/
+	var config = {};
+
+	/**
+	* Maintains the local collection of configuration property objects as they were initially applied.
+	* This object is used when resetting a property.
+	* @property initialConfig
+	* @private
+	* @type Object
+	*/
+	var initialConfig = {};
+
+	/**
+	* Maintains the local, normalized CustomEvent queue
+	* @property eventQueue
+	* @private
+	* @type Object
+	*/
+	var eventQueue = [];
+
+	/**
+	* Fires a configuration property event using the specified value.
+	* @method fireEvent
+	* @private
+	* @param {String}	key			The configuration property's name
+	* @param {value}	Object		The value of the correct type for the property
+	*/
+	var fireEvent = function( key, value ) {
+		key = key.toLowerCase();
+
+		var property = config[key];
+
+		if (typeof property != 'undefined' && property.event) {
+			property.event.fire(value);
+		}
+	};
+	/* End Private Members */
+
+	/**
+	* Adds a property to the Config Object's private config hash.
+	* @method addProperty
+	* @param {String}	key	The configuration property's name
+	* @param {Object}	propertyObject	The Object containing all of this property's arguments
+	*/
+	this.addProperty = function( key, propertyObject ) {
+		key = key.toLowerCase();
+
+		config[key] = propertyObject;
+
+		propertyObject.event = new YAHOO.util.CustomEvent(key);
+		propertyObject.key = key;
+
+		if (propertyObject.handler) {
+			propertyObject.event.subscribe(propertyObject.handler, this.owner, true);
+		}
+
+		this.setProperty(key, propertyObject.value, true);
+
+		if (! propertyObject.suppressEvent) {
+			this.queueProperty(key, propertyObject.value);
+		}
+	};
+
+	/**
+	* Returns a key-value configuration map of the values currently set in the Config Object.
+	* @method getConfig
+	* @return {Object} The current config, represented in a key-value map
+	*/
+	this.getConfig = function() {
+		var cfg = {};
+
+		for (var prop in config) {
+			var property = config[prop];
+			if (typeof property != 'undefined' && property.event) {
+				cfg[prop] = property.value;
+			}
+		}
+
+		return cfg;
+	};
+
+	/**
+	* Returns the value of specified property.
+	* @method getProperty
+	* @param {String} key	The name of the property
+	* @return {Object}		The value of the specified property
+	*/
+	this.getProperty = function(key) {
+		key = key.toLowerCase();
+
+		var property = config[key];
+		if (typeof property != 'undefined' && property.event) {
+			return property.value;
+		} else {
+			return undefined;
+		}
+	};
+
+	/**
+	* Resets the specified property's value to its initial value.
+	* @method resetProperty
+	* @param {String} key	The name of the property
+	* @return {Boolean} True is the property was reset, false if not
+	*/
+	this.resetProperty = function(key) {
+		key = key.toLowerCase();
+
+		var property = config[key];
+		if (typeof property != 'undefined' && property.event) {
+			if (initialConfig[key] && initialConfig[key] != 'undefined')	{
+				this.setProperty(key, initialConfig[key]);
+			}
+			return true;
+		} else {
+			return false;
+		}
+	};
+
+	/**
+	* Sets the value of a property. If the silent property is passed as true, the property's event will not be fired.
+	* @method setProperty
+	* @param {String} key		The name of the property
+	* @param {String} value		The value to set the property to
+	* @param {Boolean} silent	Whether the value should be set silently, without firing the property event.
+	* @return {Boolean}			True, if the set was successful, false if it failed.
+	*/
+	this.setProperty = function(key, value, silent) {
+		key = key.toLowerCase();
+
+		if (this.queueInProgress && ! silent) {
+			this.queueProperty(key,value); // Currently running through a queue...
+			return true;
+		} else {
+			var property = config[key];
+			if (typeof property != 'undefined' && property.event) {
+				if (property.validator && ! property.validator(value)) { // validator
+					return false;
+				} else {
+					property.value = value;
+					if (! silent) {
+						fireEvent(key, value);
+						this.configChangedEvent.fire([key, value]);
+					}
+					return true;
+				}
+			} else {
+				return false;
+			}
+		}
+	};
+
+	/**
+	* Sets the value of a property and queues its event to execute. If the event is already scheduled to execute, it is
+	* moved from its current position to the end of the queue.
+	* @method queueProperty
+	* @param {String} key	The name of the property
+	* @param {String} value	The value to set the property to
+	* @return {Boolean}		true, if the set was successful, false if it failed.
+	*/
+	this.queueProperty = function(key, value) {
+		key = key.toLowerCase();
+
+		var property = config[key];
+
+		if (typeof property != 'undefined' && property.event) {
+			if (typeof value != 'undefined' && property.validator && ! property.validator(value)) { // validator
+				return false;
+			} else {
+
+				if (typeof value != 'undefined') {
+					property.value = value;
+				} else {
+					value = property.value;
+				}
+
+				var foundDuplicate = false;
+
+				for (var i=0;i<eventQueue.length;i++) {
+					var queueItem = eventQueue[i];
+
+					if (queueItem) {
+						var queueItemKey = queueItem[0];
+						var queueItemValue = queueItem[1];
+
+						if (queueItemKey.toLowerCase() == key) {
+							// found a dupe... push to end of queue, null current item, and break
+							eventQueue[i] = null;
+							eventQueue.push([key, (typeof value != 'undefined' ? value : queueItemValue)]);
+							foundDuplicate = true;
+							break;
+						}
+					}
+				}
+
+				if (! foundDuplicate && typeof value != 'undefined') { // this is a refire, or a new property in the queue
+					eventQueue.push([key, value]);
+				}
+			}
+
+			if (property.supercedes) {
+				for (var s=0;s<property.supercedes.length;s++) {
+					var supercedesCheck = property.supercedes[s];
+
+					for (var q=0;q<eventQueue.length;q++) {
+						var queueItemCheck = eventQueue[q];
+
+						if (queueItemCheck) {
+							var queueItemCheckKey = queueItemCheck[0];
+							var queueItemCheckValue = queueItemCheck[1];
+
+							if ( queueItemCheckKey.toLowerCase() == supercedesCheck.toLowerCase() ) {
+								eventQueue.push([queueItemCheckKey, queueItemCheckValue]);
+								eventQueue[q] = null;
+								break;
+							}
+						}
+					}
+				}
+			}
+
+			return true;
+		} else {
+			return false;
+		}
+	};
+
+	/**
+	* Fires the event for a property using the property's current value.
+	* @method refireEvent
+	* @param {String} key	The name of the property
+	*/
+	this.refireEvent = function(key) {
+		key = key.toLowerCase();
+
+		var property = config[key];
+		if (typeof property != 'undefined' && property.event && typeof property.value != 'undefined') {
+			if (this.queueInProgress) {
+				this.queueProperty(key);
+			} else {
+				fireEvent(key, property.value);
+			}
+		}
+	};
+
+	/**
+	* Applies a key-value Object literal to the configuration, replacing any existing values, and queueing the property events.
+	* Although the values will be set, fireQueue() must be called for their associated events to execute.
+	* @method applyConfig
+	* @param {Object}	userConfig	The configuration Object literal
+	* @param {Boolean}	init		When set to true, the initialConfig will be set to the userConfig passed in, so that calling a reset will reset the properties to the passed values.
+	*/
+	this.applyConfig = function(userConfig, init) {
+		if (init) {
+			initialConfig = userConfig;
+		}
+		for (var prop in userConfig) {
+			this.queueProperty(prop, userConfig[prop]);
+		}
+	};
+
+	/**
+	* Refires the events for all configuration properties using their current values.
+	* @method refresh
+	*/
+	this.refresh = function() {
+		for (var prop in config) {
+			this.refireEvent(prop);
+		}
+	};
+
+	/**
+	* Fires the normalized list of queued property change events
+	* @method fireQueue
+	*/
+	this.fireQueue = function() {
+		this.queueInProgress = true;
+		for (var i=0;i<eventQueue.length;i++) {
+			var queueItem = eventQueue[i];
+			if (queueItem) {
+				var key = queueItem[0];
+				var value = queueItem[1];
+
+				var property = config[key];
+				property.value = value;
+
+				fireEvent(key,value);
+			}
+		}
+
+		this.queueInProgress = false;
+		eventQueue = [];
+	};
+
+	/**
+	* Subscribes an external handler to the change event for any given property.
+	* @method subscribeToConfigEvent
+	* @param {String}	key			The property name
+	* @param {Function}	handler		The handler function to use subscribe to the property's event
+	* @param {Object}	obj			The Object to use for scoping the event handler (see CustomEvent documentation)
+	* @param {Boolean}	override	Optional. If true, will override "this" within the handler to map to the scope Object passed into the method.
+	* @return {Boolean}				True, if the subscription was successful, otherwise false.
+	*/
+	this.subscribeToConfigEvent = function(key, handler, obj, override) {
+		key = key.toLowerCase();
+
+		var property = config[key];
+		if (typeof property != 'undefined' && property.event) {
+			if (! YAHOO.util.Config.alreadySubscribed(property.event, handler, obj)) {
+				property.event.subscribe(handler, obj, override);
+			}
+			return true;
+		} else {
+			return false;
+		}
+	};
+
+	/**
+	* Unsubscribes an external handler from the change event for any given property.
+	* @method unsubscribeFromConfigEvent
+	* @param {String}	key			The property name
+	* @param {Function}	handler		The handler function to use subscribe to the property's event
+	* @param {Object}	obj			The Object to use for scoping the event handler (see CustomEvent documentation)
+	* @return {Boolean}				True, if the unsubscription was successful, otherwise false.
+	*/
+	this.unsubscribeFromConfigEvent = function(key, handler, obj) {
+		key = key.toLowerCase();
+
+		var property = config[key];
+		if (typeof property != 'undefined' && property.event) {
+			return property.event.unsubscribe(handler, obj);
+		} else {
+			return false;
+		}
+	};
+
+	/**
+	* Returns a string representation of the Config object
+	* @method toString
+	* @return {String}	The Config object in string format.
+	*/
+	this.toString = function() {
+		var output = "Config";
+		if (this.owner) {
+			output += " [" + this.owner.toString() + "]";
+		}
+		return output;
+	};
+
+	/**
+	* Returns a string representation of the Config object's current CustomEvent queue
+	* @method outputEventQueue
+	* @return {String}	The string list of CustomEvents currently queued for execution
+	*/
+	this.outputEventQueue = function() {
+		var output = "";
+		for (var q=0;q<eventQueue.length;q++) {
+			var queueItem = eventQueue[q];
+			if (queueItem) {
+				output += queueItem[0] + "=" + queueItem[1] + ", ";
+			}
+		}
+		return output;
+	};
+};
+
+/**
+* Checks to determine if a particular function/Object pair are already subscribed to the specified CustomEvent
+* @method YAHOO.util.Config.alreadySubscribed
+* @static
+* @param {YAHOO.util.CustomEvent} evt	The CustomEvent for which to check the subscriptions
+* @param {Function}	fn	The function to look for in the subscribers list
+* @param {Object}	obj	The execution scope Object for the subscription
+* @return {Boolean}	true, if the function/Object pair is already subscribed to the CustomEvent passed in
+*/
+YAHOO.util.Config.alreadySubscribed = function(evt, fn, obj) {
+	for (var e=0;e<evt.subscribers.length;e++) {
+		var subsc = evt.subscribers[e];
+		if (subsc && subsc.obj == obj && subsc.fn == fn) {
+			return true;
+		}
+	}
+	return false;
+};
+
+/**
+*  The Container family of components is designed to enable developers to create different kinds of content-containing modules on the web. Module and Overlay are the most basic containers, and they can be used directly or extended to build custom containers. Also part of the Container family are four UI controls that extend Module and Overlay: Tooltip, Panel, Dialog, and SimpleDialog.
+* @module container
+* @title Container
+* @requires yahoo,dom,event,dragdrop,animation
+*/
+
+/**
+* Module is a JavaScript representation of the Standard Module Format. Standard Module Format is a simple standard for markup containers where child nodes representing the header, body, and footer of the content are denoted using the CSS classes "hd", "bd", and "ft" respectively. Module is the base class for all other classes in the YUI Container package.
+* @namespace YAHOO.widget
+* @class Module
+* @constructor
+* @param {String} el			The element ID representing the Module <em>OR</em>
+* @param {HTMLElement} el		The element representing the Module
+* @param {Object} userConfig	The configuration Object literal containing the configuration that should be set for this module. See configuration documentation for more details.
+*/
+YAHOO.widget.Module = function(el, userConfig) {
+	if (el) {
+		this.init(el, userConfig);
+	}
+};
+
+/**
+* Constant representing the prefix path to use for non-secure images
+* @property YAHOO.widget.Module.IMG_ROOT
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Module.IMG_ROOT = "http://us.i1.yimg.com/us.yimg.com/i/";
+
+/**
+* Constant representing the prefix path to use for securely served images
+* @property YAHOO.widget.Module.IMG_ROOT_SSL
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Module.IMG_ROOT_SSL = "https://a248.e.akamai.net/sec.yimg.com/i/";
+
+/**
+* Constant for the default CSS class name that represents a Module
+* @property YAHOO.widget.Module.CSS_MODULE
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Module.CSS_MODULE = "module";
+
+/**
+* Constant representing the module header
+* @property YAHOO.widget.Module.CSS_HEADER
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Module.CSS_HEADER = "hd";
+
+/**
+* Constant representing the module body
+* @property YAHOO.widget.Module.CSS_BODY
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Module.CSS_BODY = "bd";
+
+/**
+* Constant representing the module footer
+* @property YAHOO.widget.Module.CSS_FOOTER
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Module.CSS_FOOTER = "ft";
+
+/**
+* Constant representing the url for the "src" attribute of the iframe used to monitor changes to the browser's base font size
+* @property YAHOO.widget.Module.RESIZE_MONITOR_SECURE_URL
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Module.RESIZE_MONITOR_SECURE_URL = "javascript:false;";
+
+/**
+* Singleton CustomEvent fired when the font size is changed in the browser.
+* Opera's "zoom" functionality currently does not support text size detection.
+* @event YAHOO.widget.Module.textResizeEvent
+*/
+YAHOO.widget.Module.textResizeEvent = new YAHOO.util.CustomEvent("textResize");
+
+YAHOO.widget.Module.prototype = {
+	/**
+	* The class's constructor function
+	* @property contructor
+	* @type Function
+	*/
+	constructor : YAHOO.widget.Module,
+
+	/**
+	* The main module element that contains the header, body, and footer
+	* @property element
+	* @type HTMLElement
+	*/
+	element : null,
+
+	/**
+	* The header element, denoted with CSS class "hd"
+	* @property header
+	* @type HTMLElement
+	*/
+	header : null,
+
+	/**
+	* The body element, denoted with CSS class "bd"
+	* @property body
+	* @type HTMLElement
+	*/
+	body : null,
+
+	/**
+	* The footer element, denoted with CSS class "ft"
+	* @property footer
+	* @type HTMLElement
+	*/
+	footer : null,
+
+	/**
+	* The id of the element
+	* @property id
+	* @type String
+	*/
+	id : null,
+
+	/**
+	* The String representing the image root
+	* @property imageRoot
+	* @type String
+	*/
+	imageRoot : YAHOO.widget.Module.IMG_ROOT,
+
+	/**
+	* Initializes the custom events for Module which are fired automatically at appropriate times by the Module class.
+	* @method initEvents
+	*/
+	initEvents : function() {
+
+		/**
+		* CustomEvent fired prior to class initalization.
+		* @event beforeInitEvent
+		* @param {class} classRef	class reference of the initializing class, such as this.beforeInitEvent.fire(YAHOO.widget.Module)
+		*/
+		this.beforeInitEvent = new YAHOO.util.CustomEvent("beforeInit");
+
+		/**
+		* CustomEvent fired after class initalization.
+		* @event initEvent
+		* @param {class} classRef	class reference of the initializing class, such as this.beforeInitEvent.fire(YAHOO.widget.Module)
+		*/
+		this.initEvent = new YAHOO.util.CustomEvent("init");
+
+		/**
+		* CustomEvent fired when the Module is appended to the DOM
+		* @event appendEvent
+		*/
+		this.appendEvent = new YAHOO.util.CustomEvent("append");
+
+		/**
+		* CustomEvent fired before the Module is rendered
+		* @event beforeRenderEvent
+		*/
+		this.beforeRenderEvent = new YAHOO.util.CustomEvent("beforeRender");
+
+		/**
+		* CustomEvent fired after the Module is rendered
+		* @event renderEvent
+		*/
+		this.renderEvent = new YAHOO.util.CustomEvent("render");
+
+		/**
+		* CustomEvent fired when the header content of the Module is modified
+		* @event changeHeaderEvent
+		* @param {String/HTMLElement} content	String/element representing the new header content
+		*/
+		this.changeHeaderEvent = new YAHOO.util.CustomEvent("changeHeader");
+
+		/**
+		* CustomEvent fired when the body content of the Module is modified
+		* @event changeBodyEvent
+		* @param {String/HTMLElement} content	String/element representing the new body content
+		*/
+		this.changeBodyEvent = new YAHOO.util.CustomEvent("changeBody");
+
+		/**
+		* CustomEvent fired when the footer content of the Module is modified
+		* @event changeFooterEvent
+		* @param {String/HTMLElement} content	String/element representing the new footer content
+		*/
+		this.changeFooterEvent = new YAHOO.util.CustomEvent("changeFooter");
+
+		/**
+		* CustomEvent fired when the content of the Module is modified
+		* @event changeContentEvent
+		*/
+		this.changeContentEvent = new YAHOO.util.CustomEvent("changeContent");
+
+		/**
+		* CustomEvent fired when the Module is destroyed
+		* @event destroyEvent
+		*/
+		this.destroyEvent = new YAHOO.util.CustomEvent("destroy");
+
+		/**
+		* CustomEvent fired before the Module is shown
+		* @event beforeShowEvent
+		*/
+		this.beforeShowEvent = new YAHOO.util.CustomEvent("beforeShow");
+
+		/**
+		* CustomEvent fired after the Module is shown
+		* @event showEvent
+		*/
+		this.showEvent = new YAHOO.util.CustomEvent("show");
+
+		/**
+		* CustomEvent fired before the Module is hidden
+		* @event beforeHideEvent
+		*/
+		this.beforeHideEvent = new YAHOO.util.CustomEvent("beforeHide");
+
+		/**
+		* CustomEvent fired after the Module is hidden
+		* @event hideEvent
+		*/
+		this.hideEvent = new YAHOO.util.CustomEvent("hide");
+	},
+
+	/**
+	* String representing the current user-agent platform
+	* @property platform
+	* @type String
+	*/
+	platform : function() {
+					var ua = navigator.userAgent.toLowerCase();
+					if (ua.indexOf("windows") != -1 || ua.indexOf("win32") != -1) {
+						return "windows";
+					} else if (ua.indexOf("macintosh") != -1) {
+						return "mac";
+					} else {
+						return false;
+					}
+				}(),
+
+	/**
+	* String representing the current user-agent browser
+	* @property browser
+	* @type String
+	*/
+	browser : function() {
+			var ua = navigator.userAgent.toLowerCase();
+				  if (ua.indexOf('opera')!=-1) { // Opera (check first in case of spoof)
+					 return 'opera';
+				  } else if (ua.indexOf('msie 7')!=-1) { // IE7
+					 return 'ie7';
+				  } else if (ua.indexOf('msie') !=-1) { // IE
+					 return 'ie';
+				  } else if (ua.indexOf('safari')!=-1) { // Safari (check before Gecko because it includes "like Gecko")
+					 return 'safari';
+				  } else if (ua.indexOf('gecko') != -1) { // Gecko
+					 return 'gecko';
+				  } else {
+					 return false;
+				  }
+			}(),
+
+	/**
+	* Boolean representing whether or not the current browsing context is secure (https)
+	* @property isSecure
+	* @type Boolean
+	*/
+	isSecure : function() {
+		if (window.location.href.toLowerCase().indexOf("https") === 0) {
+			return true;
+		} else {
+			return false;
+		}
+	}(),
+
+	/**
+	* Initializes the custom events for Module which are fired automatically at appropriate times by the Module class.
+	*/
+	initDefaultConfig : function() {
+		// Add properties //
+
+		/**
+		* Specifies whether the Module is visible on the page.
+		* @config visible
+		* @type Boolean
+		* @default true
+		*/
+		this.cfg.addProperty("visible", { value:true, handler:this.configVisible, validator:this.cfg.checkBoolean } );
+
+		/**
+		* Object or array of objects representing the ContainerEffect classes that are active for animating the container.
+		* @config effect
+		* @type Object
+		* @default null
+		*/
+		this.cfg.addProperty("effect", { suppressEvent:true, supercedes:["visible"] } );
+
+		/**
+		* Specifies whether to create a special proxy iframe to monitor for user font resizing in the document
+		* @config monitorresize
+		* @type Boolean
+		* @default true
+		*/
+		this.cfg.addProperty("monitorresize", { value:true, handler:this.configMonitorResize } );
+	},
+
+	/**
+	* The Module class's initialization method, which is executed for Module and all of its subclasses. This method is automatically called by the constructor, and  sets up all DOM references for pre-existing markup, and creates required markup if it is not already present.
+	* @method init
+	* @param {String}	el	The element ID representing the Module <em>OR</em>
+	* @param {HTMLElement}	el	The element representing the Module
+	* @param {Object}	userConfig	The configuration Object literal containing the configuration that should be set for this module. See configuration documentation for more details.
+	*/
+	init : function(el, userConfig) {
+
+		this.initEvents();
+
+		this.beforeInitEvent.fire(YAHOO.widget.Module);
+
+		/**
+		* The Module's Config object used for monitoring configuration properties.
+		* @property cfg
+		* @type YAHOO.util.Config
+		*/
+		this.cfg = new YAHOO.util.Config(this);
+
+		if (this.isSecure) {
+			this.imageRoot = YAHOO.widget.Module.IMG_ROOT_SSL;
+		}
+
+		if (typeof el == "string") {
+			var elId = el;
+
+			el = document.getElementById(el);
+			if (! el) {
+				el = document.createElement("DIV");
+				el.id = elId;
+			}
+		}
+
+		this.element = el;
+
+		if (el.id) {
+			this.id = el.id;
+		}
+
+		var childNodes = this.element.childNodes;
+
+		if (childNodes) {
+			for (var i=0;i<childNodes.length;i++) {
+				var child = childNodes[i];
+				switch (child.className) {
+					case YAHOO.widget.Module.CSS_HEADER:
+						this.header = child;
+						break;
+					case YAHOO.widget.Module.CSS_BODY:
+						this.body = child;
+						break;
+					case YAHOO.widget.Module.CSS_FOOTER:
+						this.footer = child;
+						break;
+				}
+			}
+		}
+
+		this.initDefaultConfig();
+
+		YAHOO.util.Dom.addClass(this.element, YAHOO.widget.Module.CSS_MODULE);
+
+		if (userConfig) {
+			this.cfg.applyConfig(userConfig, true);
+		}
+
+		// Subscribe to the fireQueue() method of Config so that any queued configuration changes are
+		// excecuted upon render of the Module
+		if (! YAHOO.util.Config.alreadySubscribed(this.renderEvent, this.cfg.fireQueue, this.cfg)) {
+			this.renderEvent.subscribe(this.cfg.fireQueue, this.cfg, true);
+		}
+
+		this.initEvent.fire(YAHOO.widget.Module);
+	},
+
+	/**
+	* Initialized an empty IFRAME that is placed out of the visible area that can be used to detect text resize.
+	* @method initResizeMonitor
+	*/
+	initResizeMonitor : function() {
+
+        if(this.browser != "opera") {
+
+            var resizeMonitor = document.getElementById("_yuiResizeMonitor");
+
+            if (! resizeMonitor) {
+
+                resizeMonitor = document.createElement("iframe");
+
+                var bIE = (this.browser.indexOf("ie") === 0);
+
+                if(this.isSecure &&
+                   YAHOO.widget.Module.RESIZE_MONITOR_SECURE_URL &&
+                   bIE) {
+
+                  resizeMonitor.src =
+                       YAHOO.widget.Module.RESIZE_MONITOR_SECURE_URL;
+
+                }
+
+                resizeMonitor.id = "_yuiResizeMonitor";
+                resizeMonitor.style.visibility = "hidden";
+
+                document.body.appendChild(resizeMonitor);
+
+                resizeMonitor.style.width = "10em";
+                resizeMonitor.style.height = "10em";
+                resizeMonitor.style.position = "absolute";
+
+                var nLeft = -1 * resizeMonitor.offsetWidth,
+                    nTop = -1 * resizeMonitor.offsetHeight;
+
+                resizeMonitor.style.top = nTop + "px";
+                resizeMonitor.style.left =  nLeft + "px";
+                resizeMonitor.style.borderStyle = "none";
+                resizeMonitor.style.borderWidth = "0";
+                YAHOO.util.Dom.setStyle(resizeMonitor, "opacity", "0");
+
+                resizeMonitor.style.visibility = "visible";
+
+                if(!bIE) {
+
+                    var doc = resizeMonitor.contentWindow.document;
+
+                    doc.open();
+                    doc.close();
+
+                }
+            }
+
+			var fireTextResize = function() {
+				YAHOO.widget.Module.textResizeEvent.fire();
+			};
+
+            if(resizeMonitor && resizeMonitor.contentWindow) {
+                this.resizeMonitor = resizeMonitor;
+
+				YAHOO.widget.Module.textResizeEvent.subscribe(this.onDomResize, this, true);
+
+				if (! YAHOO.widget.Module.textResizeInitialized) {
+					if (! YAHOO.util.Event.addListener(this.resizeMonitor.contentWindow, "resize", fireTextResize)) {
+						// This will fail in IE if document.domain has changed, so we must change the listener to
+						// use the resizeMonitor element instead
+						YAHOO.util.Event.addListener(this.resizeMonitor, "resize", fireTextResize);
+					}
+					YAHOO.widget.Module.textResizeInitialized = true;
+				}
+            }
+
+        }
+
+	},
+
+	/**
+	* Event handler fired when the resize monitor element is resized.
+	* @method onDomResize
+	* @param {DOMEvent} e	The DOM resize event
+	* @param {Object} obj	The scope object passed to the handler
+	*/
+	onDomResize : function(e, obj) {
+
+        var nLeft = -1 * this.resizeMonitor.offsetWidth,
+            nTop = -1 * this.resizeMonitor.offsetHeight;
+
+        this.resizeMonitor.style.top = nTop + "px";
+        this.resizeMonitor.style.left =  nLeft + "px";
+
+	},
+
+	/**
+	* Sets the Module's header content to the HTML specified, or appends the passed element to the header. If no header is present, one will be automatically created.
+	* @method setHeader
+	* @param {String}	headerContent	The HTML used to set the header <em>OR</em>
+	* @param {HTMLElement}	headerContent	The HTMLElement to append to the header
+	*/
+	setHeader : function(headerContent) {
+		if (! this.header) {
+			this.header = document.createElement("DIV");
+			this.header.className = YAHOO.widget.Module.CSS_HEADER;
+		}
+
+		if (typeof headerContent == "string") {
+			this.header.innerHTML = headerContent;
+		} else {
+			this.header.innerHTML = "";
+			this.header.appendChild(headerContent);
+		}
+
+		this.changeHeaderEvent.fire(headerContent);
+		this.changeContentEvent.fire();
+	},
+
+	/**
+	* Appends the passed element to the header. If no header is present, one will be automatically created.
+	* @method appendToHeader
+	* @param {HTMLElement}	element	The element to append to the header
+	*/
+	appendToHeader : function(element) {
+		if (! this.header) {
+			this.header = document.createElement("DIV");
+			this.header.className = YAHOO.widget.Module.CSS_HEADER;
+		}
+
+		this.header.appendChild(element);
+		this.changeHeaderEvent.fire(element);
+		this.changeContentEvent.fire();
+	},
+
+	/**
+	* Sets the Module's body content to the HTML specified, or appends the passed element to the body. If no body is present, one will be automatically created.
+	* @method setBody
+	* @param {String}	bodyContent	The HTML used to set the body <em>OR</em>
+	* @param {HTMLElement}	bodyContent	The HTMLElement to append to the body
+	*/
+	setBody : function(bodyContent) {
+		if (! this.body) {
+			this.body = document.createElement("DIV");
+			this.body.className = YAHOO.widget.Module.CSS_BODY;
+		}
+
+		if (typeof bodyContent == "string")
+		{
+			this.body.innerHTML = bodyContent;
+		} else {
+			this.body.innerHTML = "";
+			this.body.appendChild(bodyContent);
+		}
+
+		this.changeBodyEvent.fire(bodyContent);
+		this.changeContentEvent.fire();
+	},
+
+	/**
+	* Appends the passed element to the body. If no body is present, one will be automatically created.
+	* @method appendToBody
+	* @param {HTMLElement}	element	The element to append to the body
+	*/
+	appendToBody : function(element) {
+		if (! this.body) {
+			this.body = document.createElement("DIV");
+			this.body.className = YAHOO.widget.Module.CSS_BODY;
+		}
+
+		this.body.appendChild(element);
+		this.changeBodyEvent.fire(element);
+		this.changeContentEvent.fire();
+	},
+
+	/**
+	* Sets the Module's footer content to the HTML specified, or appends the passed element to the footer. If no footer is present, one will be automatically created.
+	* @method setFooter
+	* @param {String}	footerContent	The HTML used to set the footer <em>OR</em>
+	* @param {HTMLElement}	footerContent	The HTMLElement to append to the footer
+	*/
+	setFooter : function(footerContent) {
+		if (! this.footer) {
+			this.footer = document.createElement("DIV");
+			this.footer.className = YAHOO.widget.Module.CSS_FOOTER;
+		}
+
+		if (typeof footerContent == "string") {
+			this.footer.innerHTML = footerContent;
+		} else {
+			this.footer.innerHTML = "";
+			this.footer.appendChild(footerContent);
+		}
+
+		this.changeFooterEvent.fire(footerContent);
+		this.changeContentEvent.fire();
+	},
+
+	/**
+	* Appends the passed element to the footer. If no footer is present, one will be automatically created.
+	* @method appendToFooter
+	* @param {HTMLElement}	element	The element to append to the footer
+	*/
+	appendToFooter : function(element) {
+		if (! this.footer) {
+			this.footer = document.createElement("DIV");
+			this.footer.className = YAHOO.widget.Module.CSS_FOOTER;
+		}
+
+		this.footer.appendChild(element);
+		this.changeFooterEvent.fire(element);
+		this.changeContentEvent.fire();
+	},
+
+	/**
+	* Renders the Module by inserting the elements that are not already in the main Module into their correct places. Optionally appends the Module to the specified node prior to the render's execution. NOTE: For Modules without existing markup, the appendToNode argument is REQUIRED. If this argument is ommitted and the current element is not present in the document, the function will return false, indicating that the render was a failure.
+	* @method render
+	* @param {String}	appendToNode	The element id to which the Module should be appended to prior to rendering <em>OR</em>
+	* @param {HTMLElement}	appendToNode	The element to which the Module should be appended to prior to rendering
+	* @param {HTMLElement}	moduleElement	OPTIONAL. The element that represents the actual Standard Module container.
+	* @return {Boolean} Success or failure of the render
+	*/
+	render : function(appendToNode, moduleElement) {
+		this.beforeRenderEvent.fire();
+
+		if (! moduleElement) {
+			moduleElement = this.element;
+		}
+
+		var me = this;
+		var appendTo = function(element) {
+			if (typeof element == "string") {
+				element = document.getElementById(element);
+			}
+
+			if (element) {
+				element.appendChild(me.element);
+				me.appendEvent.fire();
+			}
+		};
+
+		if (appendToNode) {
+			appendTo(appendToNode);
+		} else { // No node was passed in. If the element is not pre-marked up, this fails
+			if (! YAHOO.util.Dom.inDocument(this.element)) {
+				return false;
+			}
+		}
+
+		// Need to get everything into the DOM if it isn't already
+
+		if (this.header && ! YAHOO.util.Dom.inDocument(this.header)) {
+			// There is a header, but it's not in the DOM yet... need to add it
+			var firstChild = moduleElement.firstChild;
+			if (firstChild) { // Insert before first child if exists
+				moduleElement.insertBefore(this.header, firstChild);
+			} else { // Append to empty body because there are no children
+				moduleElement.appendChild(this.header);
+			}
+		}
+
+		if (this.body && ! YAHOO.util.Dom.inDocument(this.body)) {
+			// There is a body, but it's not in the DOM yet... need to add it
+			if (this.footer && YAHOO.util.Dom.isAncestor(this.moduleElement, this.footer)) { // Insert before footer if exists in DOM
+				moduleElement.insertBefore(this.body, this.footer);
+			} else { // Append to element because there is no footer
+				moduleElement.appendChild(this.body);
+			}
+		}
+
+		if (this.footer && ! YAHOO.util.Dom.inDocument(this.footer)) {
+			// There is a footer, but it's not in the DOM yet... need to add it
+			moduleElement.appendChild(this.footer);
+		}
+
+		this.renderEvent.fire();
+		return true;
+	},
+
+	/**
+	* Removes the Module element from the DOM and sets all child elements to null.
+	* @method destroy
+	*/
+	destroy : function() {
+		var parent;
+
+		if (this.element) {
+			YAHOO.util.Event.purgeElement(this.element, true);
+			parent = this.element.parentNode;
+		}
+		if (parent) {
+			parent.removeChild(this.element);
+		}
+
+		this.element = null;
+		this.header = null;
+		this.body = null;
+		this.footer = null;
+
+		for (var e in this) {
+			if (e instanceof YAHOO.util.CustomEvent) {
+				e.unsubscribeAll();
+			}
+		}
+
+		YAHOO.widget.Module.textResizeEvent.unsubscribe(this.onDomResize, this);
+
+		this.destroyEvent.fire();
+	},
+
+	/**
+	* Shows the Module element by setting the visible configuration property to true. Also fires two events: beforeShowEvent prior to the visibility change, and showEvent after.
+	* @method show
+	*/
+	show : function() {
+		this.cfg.setProperty("visible", true);
+	},
+
+	/**
+	* Hides the Module element by setting the visible configuration property to false. Also fires two events: beforeHideEvent prior to the visibility change, and hideEvent after.
+	* @method hide
+	*/
+	hide : function() {
+		this.cfg.setProperty("visible", false);
+	},
+
+	// BUILT-IN EVENT HANDLERS FOR MODULE //
+
+	/**
+	* Default event handler for changing the visibility property of a Module. By default, this is achieved by switching the "display" style between "block" and "none".
+	* This method is responsible for firing showEvent and hideEvent.
+	* @param {String} type	The CustomEvent type (usually the property name)
+	* @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
+	* @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
+	* @method configVisible
+	*/
+	configVisible : function(type, args, obj) {
+		var visible = args[0];
+		if (visible) {
+			this.beforeShowEvent.fire();
+			YAHOO.util.Dom.setStyle(this.element, "display", "block");
+			this.showEvent.fire();
+		} else {
+			this.beforeHideEvent.fire();
+			YAHOO.util.Dom.setStyle(this.element, "display", "none");
+			this.hideEvent.fire();
+		}
+	},
+
+	/**
+	* Default event handler for the "monitorresize" configuration property
+	* @param {String} type	The CustomEvent type (usually the property name)
+	* @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
+	* @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
+	* @method configMonitorResize
+	*/
+	configMonitorResize : function(type, args, obj) {
+		var monitor = args[0];
+		if (monitor) {
+			this.initResizeMonitor();
+		} else {
+			YAHOO.util.Event.removeListener(this.resizeMonitor, "resize", this.onDomResize);
+			this.resizeMonitor = null;
+		}
+	}
+};
+
+/**
+* Returns a String representation of the Object.
+* @method toString
+* @return {String}	The string representation of the Module
+*/
+YAHOO.widget.Module.prototype.toString = function() {
+	return "Module " + this.id;
+};
+
+/**
+* Overlay is a Module that is absolutely positioned above the page flow. It has convenience methods for positioning and sizing, as well as options for controlling zIndex and constraining the Overlay's position to the current visible viewport. Overlay also contains a dynamicly generated IFRAME which is placed beneath it for Internet Explorer 6 and 5.x so that it will be properly rendered above SELECT elements.
+* @namespace YAHOO.widget
+* @class Overlay
+* @extends YAHOO.widget.Module
+* @param {String}	el	The element ID representing the Overlay <em>OR</em>
+* @param {HTMLElement}	el	The element representing the Overlay
+* @param {Object}	userConfig	The configuration object literal containing 10/23/2006the configuration that should be set for this Overlay. See configuration documentation for more details.
+* @constructor
+*/
+YAHOO.widget.Overlay = function(el, userConfig) {
+	YAHOO.widget.Overlay.superclass.constructor.call(this, el, userConfig);
+};
+
+YAHOO.extend(YAHOO.widget.Overlay, YAHOO.widget.Module);
+
+/**
+* The URL that will be placed in the iframe
+* @property YAHOO.widget.Overlay.IFRAME_SRC
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Overlay.IFRAME_SRC = "javascript:false;";
+
+/**
+* Constant representing the top left corner of an element, used for configuring the context element alignment
+* @property YAHOO.widget.Overlay.TOP_LEFT
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Overlay.TOP_LEFT = "tl";
+
+/**
+* Constant representing the top right corner of an element, used for configuring the context element alignment
+* @property YAHOO.widget.Overlay.TOP_RIGHT
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Overlay.TOP_RIGHT = "tr";
+
+/**
+* Constant representing the top bottom left corner of an element, used for configuring the context element alignment
+* @property YAHOO.widget.Overlay.BOTTOM_LEFT
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Overlay.BOTTOM_LEFT = "bl";
+
+/**
+* Constant representing the bottom right corner of an element, used for configuring the context element alignment
+* @property YAHOO.widget.Overlay.BOTTOM_RIGHT
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Overlay.BOTTOM_RIGHT = "br";
+
+/**
+* Constant representing the default CSS class used for an Overlay
+* @property YAHOO.widget.Overlay.CSS_OVERLAY
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Overlay.CSS_OVERLAY = "overlay";
+
+/**
+* The Overlay initialization method, which is executed for Overlay and all of its subclasses. This method is automatically called by the constructor, and  sets up all DOM references for pre-existing markup, and creates required markup if it is not already present.
+* @method init
+* @param {String}	el	The element ID representing the Overlay <em>OR</em>
+* @param {HTMLElement}	el	The element representing the Overlay
+* @param {Object}	userConfig	The configuration object literal containing the configuration that should be set for this Overlay. See configuration documentation for more details.
+*/
+YAHOO.widget.Overlay.prototype.init = function(el, userConfig) {
+	YAHOO.widget.Overlay.superclass.init.call(this, el/*, userConfig*/);  // Note that we don't pass the user config in here yet because we only want it executed once, at the lowest subclass level
+
+	this.beforeInitEvent.fire(YAHOO.widget.Overlay);
+
+	YAHOO.util.Dom.addClass(this.element, YAHOO.widget.Overlay.CSS_OVERLAY);
+
+	if (userConfig) {
+		this.cfg.applyConfig(userConfig, true);
+	}
+
+	if (this.platform == "mac" && this.browser == "gecko") {
+		if (! YAHOO.util.Config.alreadySubscribed(this.showEvent,this.showMacGeckoScrollbars,this)) {
+			this.showEvent.subscribe(this.showMacGeckoScrollbars,this,true);
+		}
+		if (! YAHOO.util.Config.alreadySubscribed(this.hideEvent,this.hideMacGeckoScrollbars,this)) {
+			this.hideEvent.subscribe(this.hideMacGeckoScrollbars,this,true);
+		}
+	}
+
+	this.initEvent.fire(YAHOO.widget.Overlay);
+};
+
+/**
+* Initializes the custom events for Overlay which are fired automatically at appropriate times by the Overlay class.
+* @method initEvents
+*/
+YAHOO.widget.Overlay.prototype.initEvents = function() {
+	YAHOO.widget.Overlay.superclass.initEvents.call(this);
+
+	/**
+	* CustomEvent fired before the Overlay is moved.
+	* @event beforeMoveEvent
+	* @param {Number} x	x coordinate
+	* @param {Number} y	y coordinate
+	*/
+	this.beforeMoveEvent = new YAHOO.util.CustomEvent("beforeMove", this);
+
+	/**
+	* CustomEvent fired after the Overlay is moved.
+	* @event moveEvent
+	* @param {Number} x	x coordinate
+	* @param {Number} y	y coordinate
+	*/
+	this.moveEvent = new YAHOO.util.CustomEvent("move", this);
+};
+
+/**
+* Initializes the class's configurable properties which can be changed using the Overlay's Config object (cfg).
+* @method initDefaultConfig
+*/
+YAHOO.widget.Overlay.prototype.initDefaultConfig = function() {
+	YAHOO.widget.Overlay.superclass.initDefaultConfig.call(this);
+
+	// Add overlay config properties //
+
+	/**
+	* The absolute x-coordinate position of the Overlay
+	* @config x
+	* @type Number
+	* @default null
+	*/
+	this.cfg.addProperty("x", { handler:this.configX, validator:this.cfg.checkNumber, suppressEvent:true, supercedes:["iframe"] } );
+
+	/**
+	* The absolute y-coordinate position of the Overlay
+	* @config y
+	* @type Number
+	* @default null
+	*/
+	this.cfg.addProperty("y", { handler:this.configY, validator:this.cfg.checkNumber, suppressEvent:true, supercedes:["iframe"] } );
+
+	/**
+	* An array with the absolute x and y positions of the Overlay
+	* @config xy
+	* @type Number[]
+	* @default null
+	*/
+	this.cfg.addProperty("xy",{ handler:this.configXY, suppressEvent:true, supercedes:["iframe"] } );
+
+	/**
+	* The array of context arguments for context-sensitive positioning. The format is: [id or element, element corner, context corner]. For example, setting this property to ["img1", "tl", "bl"] would align the Overlay's top left corner to the context element's bottom left corner.
+	* @config context
+	* @type Array
+	* @default null
+	*/
+	this.cfg.addProperty("context",	{ handler:this.configContext, suppressEvent:true, supercedes:["iframe"] } );
+
+	/**
+	* True if the Overlay should be anchored to the center of the viewport.
+	* @config fixedcenter
+	* @type Boolean
+	* @default false
+	*/
+	this.cfg.addProperty("fixedcenter", { value:false, handler:this.configFixedCenter, validator:this.cfg.checkBoolean, supercedes:["iframe","visible"] } );
+
+	/**
+	* CSS width of the Overlay.
+	* @config width
+	* @type String
+	* @default null
+	*/
+	this.cfg.addProperty("width", { handler:this.configWidth, suppressEvent:true, supercedes:["iframe"] } );
+
+	/**
+	* CSS height of the Overlay.
+	* @config height
+	* @type String
+	* @default null
+	*/
+	this.cfg.addProperty("height", { handler:this.configHeight, suppressEvent:true, supercedes:["iframe"] } );
+
+	/**
+	* CSS z-index of the Overlay.
+	* @config zIndex
+	* @type Number
+	* @default null
+	*/
+	this.cfg.addProperty("zIndex", { value:null, handler:this.configzIndex } );
+
+	/**
+	* True if the Overlay should be prevented from being positioned out of the viewport.
+	* @config constraintoviewport
+	* @type Boolean
+	* @default false
+	*/
+	this.cfg.addProperty("constraintoviewport", { value:false, handler:this.configConstrainToViewport, validator:this.cfg.checkBoolean, supercedes:["iframe","x","y","xy"] } );
+
+	/**
+	* True if the Overlay should have an IFRAME shim (for correcting the select z-index bug in IE6 and below).
+	* @config iframe
+	* @type Boolean
+	* @default true for IE6 and below, false for all others
+	*/
+	this.cfg.addProperty("iframe", { value:(this.browser == "ie" ? true : false), handler:this.configIframe, validator:this.cfg.checkBoolean, supercedes:["zIndex"] } );
+};
+
+/**
+* Moves the Overlay to the specified position. This function is identical to calling this.cfg.setProperty("xy", [x,y]);
+* @method moveTo
+* @param {Number}	x	The Overlay's new x position
+* @param {Number}	y	The Overlay's new y position
+*/
+YAHOO.widget.Overlay.prototype.moveTo = function(x, y) {
+	this.cfg.setProperty("xy",[x,y]);
+};
+
+/**
+* Adds a special CSS class to the Overlay when Mac/Gecko is in use, to work around a Gecko bug where
+* scrollbars cannot be hidden. See https://bugzilla.mozilla.org/show_bug.cgi?id=187435
+* @method hideMacGeckoScrollbars
+*/
+YAHOO.widget.Overlay.prototype.hideMacGeckoScrollbars = function() {
+	YAHOO.util.Dom.removeClass(this.element, "show-scrollbars");
+	YAHOO.util.Dom.addClass(this.element, "hide-scrollbars");
+};
+
+/**
+* Removes a special CSS class from the Overlay when Mac/Gecko is in use, to work around a Gecko bug where
+* scrollbars cannot be hidden. See https://bugzilla.mozilla.org/show_bug.cgi?id=187435
+* @method showMacGeckoScrollbars
+*/
+YAHOO.widget.Overlay.prototype.showMacGeckoScrollbars = function() {
+	YAHOO.util.Dom.removeClass(this.element, "hide-scrollbars");
+	YAHOO.util.Dom.addClass(this.element, "show-scrollbars");
+};
+
+// BEGIN BUILT-IN PROPERTY EVENT HANDLERS //
+
+/**
+* The default event handler fired when the "visible" property is changed. This method is responsible for firing showEvent and hideEvent.
+* @method configVisible
+* @param {String} type	The CustomEvent type (usually the property name)
+* @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
+* @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
+*/
+YAHOO.widget.Overlay.prototype.configVisible = function(type, args, obj) {
+	var visible = args[0];
+
+	var currentVis = YAHOO.util.Dom.getStyle(this.element, "visibility");
+
+	if (currentVis == "inherit") {
+		var e = this.element.parentNode;
+		while (e.nodeType != 9 && e.nodeType != 11) {
+			currentVis = YAHOO.util.Dom.getStyle(e, "visibility");
+			if (currentVis != "inherit") { break; }
+			e = e.parentNode;
+		}
+		if (currentVis == "inherit") {
+			currentVis = "visible";
+		}
+	}
+
+	var effect = this.cfg.getProperty("effect");
+
+	var effectInstances = [];
+	if (effect) {
+		if (effect instanceof Array) {
+			for (var i=0;i<effect.length;i++) {
+				var eff = effect[i];
+				effectInstances[effectInstances.length] = eff.effect(this, eff.duration);
+			}
+		} else {
+			effectInstances[effectInstances.length] = effect.effect(this, effect.duration);
+		}
+	}
+
+	var isMacGecko = (this.platform == "mac" && this.browser == "gecko");
+
+	if (visible) { // Show
+		if (isMacGecko) {
+			this.showMacGeckoScrollbars();
+		}
+
+		if (effect) { // Animate in
+			if (visible) { // Animate in if not showing
+				if (currentVis != "visible" || currentVis === "") {
+					this.beforeShowEvent.fire();
+					for (var j=0;j<effectInstances.length;j++) {
+						var ei = effectInstances[j];
+						if (j === 0 && ! YAHOO.util.Config.alreadySubscribed(ei.animateInCompleteEvent,this.showEvent.fire,this.showEvent)) {
+							ei.animateInCompleteEvent.subscribe(this.showEvent.fire,this.showEvent,true); // Delegate showEvent until end of animateInComplete
+						}
+						ei.animateIn();
+					}
+				}
+			}
+		} else { // Show
+			if (currentVis != "visible" || currentVis === "") {
+				this.beforeShowEvent.fire();
+				YAHOO.util.Dom.setStyle(this.element, "visibility", "visible");
+				this.cfg.refireEvent("iframe");
+				this.showEvent.fire();
+			}
+		}
+
+	} else { // Hide
+		if (isMacGecko) {
+			this.hideMacGeckoScrollbars();
+		}
+
+		if (effect) { // Animate out if showing
+			if (currentVis == "visible") {
+				this.beforeHideEvent.fire();
+				for (var k=0;k<effectInstances.length;k++) {
+					var h = effectInstances[k];
+					if (k === 0 && ! YAHOO.util.Config.alreadySubscribed(h.animateOutCompleteEvent,this.hideEvent.fire,this.hideEvent)) {
+						h.animateOutCompleteEvent.subscribe(this.hideEvent.fire,this.hideEvent,true); // Delegate hideEvent until end of animateOutComplete
+					}
+					h.animateOut();
+				}
+			} else if (currentVis === "") {
+				YAHOO.util.Dom.setStyle(this.element, "visibility", "hidden");
+			}
+		} else { // Simple hide
+			if (currentVis == "visible" || currentVis === "") {
+				this.beforeHideEvent.fire();
+				YAHOO.util.Dom.setStyle(this.element, "visibility", "hidden");
+				this.cfg.refireEvent("iframe");
+				this.hideEvent.fire();
+			}
+		}
+	}
+};
+
+/**
+* Center event handler used for centering on scroll/resize, but only if the Overlay is visible
+* @method doCenterOnDOMEvent
+*/
+YAHOO.widget.Overlay.prototype.doCenterOnDOMEvent = function() {
+	if (this.cfg.getProperty("visible")) {
+		this.center();
+	}
+};
+
+/**
+* The default event handler fired when the "fixedcenter" property is changed.
+* @method configFixedCenter
+* @param {String} type	The CustomEvent type (usually the property name)
+* @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
+* @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
+*/
+YAHOO.widget.Overlay.prototype.configFixedCenter = function(type, args, obj) {
+	var val = args[0];
+
+	if (val) {
+		this.center();
+
+		if (! YAHOO.util.Config.alreadySubscribed(this.beforeShowEvent, this.center, this)) {
+			this.beforeShowEvent.subscribe(this.center, this, true);
+		}
+
+		if (! YAHOO.util.Config.alreadySubscribed(YAHOO.widget.Overlay.windowResizeEvent, this.doCenterOnDOMEvent, this)) {
+			YAHOO.widget.Overlay.windowResizeEvent.subscribe(this.doCenterOnDOMEvent, this, true);
+		}
+
+		if (! YAHOO.util.Config.alreadySubscribed(YAHOO.widget.Overlay.windowScrollEvent, this.doCenterOnDOMEvent, this)) {
+			YAHOO.widget.Overlay.windowScrollEvent.subscribe( this.doCenterOnDOMEvent, this, true);
+		}
+	} else {
+		YAHOO.widget.Overlay.windowResizeEvent.unsubscribe(this.doCenterOnDOMEvent, this);
+		YAHOO.widget.Overlay.windowScrollEvent.unsubscribe(this.doCenterOnDOMEvent, this);
+	}
+};
+
+/**
+* The default event handler fired when the "height" property is changed.
+* @method configHeight
+* @param {String} type	The CustomEvent type (usually the property name)
+* @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
+* @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
+*/
+YAHOO.widget.Overlay.prototype.configHeight = function(type, args, obj) {
+	var height = args[0];
+	var el = this.element;
+	YAHOO.util.Dom.setStyle(el, "height", height);
+	this.cfg.refireEvent("iframe");
+};
+
+/**
+* The default event handler fired when the "width" property is changed.
+* @method configWidth
+* @param {String} type	The CustomEvent type (usually the property name)
+* @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
+* @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
+*/
+YAHOO.widget.Overlay.prototype.configWidth = function(type, args, obj) {
+	var width = args[0];
+	var el = this.element;
+	YAHOO.util.Dom.setStyle(el, "width", width);
+	this.cfg.refireEvent("iframe");
+};
+
+/**
+* The default event handler fired when the "zIndex" property is changed.
+* @method configzIndex
+* @param {String} type	The CustomEvent type (usually the property name)
+* @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
+* @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
+*/
+YAHOO.widget.Overlay.prototype.configzIndex = function(type, args, obj) {
+	var zIndex = args[0];
+
+	var el = this.element;
+
+	if (! zIndex) {
+		zIndex = YAHOO.util.Dom.getStyle(el, "zIndex");
+		if (! zIndex || isNaN(zIndex)) {
+			zIndex = 0;
+		}
+	}
+
+	if (this.iframe) {
+		if (zIndex <= 0) {
+			zIndex = 1;
+		}
+		YAHOO.util.Dom.setStyle(this.iframe, "zIndex", (zIndex-1));
+	}
+
+	YAHOO.util.Dom.setStyle(el, "zIndex", zIndex);
+	this.cfg.setProperty("zIndex", zIndex, true);
+};
+
+/**
+* The default event handler fired when the "xy" property is changed.
+* @method configXY
+* @param {String} type	The CustomEvent type (usually the property name)
+* @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
+* @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
+*/
+YAHOO.widget.Overlay.prototype.configXY = function(type, args, obj) {
+	var pos = args[0];
+	var x = pos[0];
+	var y = pos[1];
+
+	this.cfg.setProperty("x", x);
+	this.cfg.setProperty("y", y);
+
+	this.beforeMoveEvent.fire([x,y]);
+
+	x = this.cfg.getProperty("x");
+	y = this.cfg.getProperty("y");
+
+	this.cfg.refireEvent("iframe");
+	this.moveEvent.fire([x,y]);
+};
+
+/**
+* The default event handler fired when the "x" property is changed.
+* @method configX
+* @param {String} type	The CustomEvent type (usually the property name)
+* @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
+* @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
+*/
+YAHOO.widget.Overlay.prototype.configX = function(type, args, obj) {
+	var x = args[0];
+	var y = this.cfg.getProperty("y");
+
+	this.cfg.setProperty("x", x, true);
+	this.cfg.setProperty("y", y, true);
+
+	this.beforeMoveEvent.fire([x,y]);
+
+	x = this.cfg.getProperty("x");
+	y = this.cfg.getProperty("y");
+
+	YAHOO.util.Dom.setX(this.element, x, true);
+
+	this.cfg.setProperty("xy", [x, y], true);
+
+	this.cfg.refireEvent("iframe");
+	this.moveEvent.fire([x, y]);
+};
+
+/**
+* The default event handler fired when the "y" property is changed.
+* @method configY
+* @param {String} type	The CustomEvent type (usually the property name)
+* @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
+* @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
+*/
+YAHOO.widget.Overlay.prototype.configY = function(type, args, obj) {
+	var x = this.cfg.getProperty("x");
+	var y = args[0];
+
+	this.cfg.setProperty("x", x, true);
+	this.cfg.setProperty("y", y, true);
+
+	this.beforeMoveEvent.fire([x,y]);
+
+	x = this.cfg.getProperty("x");
+	y = this.cfg.getProperty("y");
+
+	YAHOO.util.Dom.setY(this.element, y, true);
+
+	this.cfg.setProperty("xy", [x, y], true);
+
+	this.cfg.refireEvent("iframe");
+	this.moveEvent.fire([x, y]);
+};
+
+/**
+* Shows the iframe shim, if it has been enabled
+* @method showIframe
+*/
+YAHOO.widget.Overlay.prototype.showIframe = function() {
+	if (this.iframe) {
+		this.iframe.style.display = "block";
+	}
+};
+
+/**
+* Hides the iframe shim, if it has been enabled
+* @method hideIframe
+*/
+YAHOO.widget.Overlay.prototype.hideIframe = function() {
+	if (this.iframe) {
+		this.iframe.style.display = "none";
+	}
+};
+
+/**
+* The default event handler fired when the "iframe" property is changed.
+* @method configIframe
+* @param {String} type	The CustomEvent type (usually the property name)
+* @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
+* @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
+*/
+YAHOO.widget.Overlay.prototype.configIframe = function(type, args, obj) {
+
+	var val = args[0];
+
+	if (val) { // IFRAME shim is enabled
+
+		if (! YAHOO.util.Config.alreadySubscribed(this.showEvent, this.showIframe, this)) {
+			this.showEvent.subscribe(this.showIframe, this, true);
+		}
+		if (! YAHOO.util.Config.alreadySubscribed(this.hideEvent, this.hideIframe, this)) {
+			this.hideEvent.subscribe(this.hideIframe, this, true);
+		}
+
+		var x = this.cfg.getProperty("x");
+		var y = this.cfg.getProperty("y");
+
+		if (! x || ! y) {
+			this.syncPosition();
+			x = this.cfg.getProperty("x");
+			y = this.cfg.getProperty("y");
+		}
+
+		if (! isNaN(x) && ! isNaN(y)) {
+			if (! this.iframe) {
+				this.iframe = document.createElement("iframe");
+				if (this.isSecure) {
+					this.iframe.src = YAHOO.widget.Overlay.IFRAME_SRC;
+				}
+
+				var parent = this.element.parentNode;
+				if (parent) {
+					parent.appendChild(this.iframe);
+				} else {
+					document.body.appendChild(this.iframe);
+				}
+
+				YAHOO.util.Dom.setStyle(this.iframe, "position", "absolute");
+				YAHOO.util.Dom.setStyle(this.iframe, "border", "none");
+				YAHOO.util.Dom.setStyle(this.iframe, "margin", "0");
+				YAHOO.util.Dom.setStyle(this.iframe, "padding", "0");
+				YAHOO.util.Dom.setStyle(this.iframe, "opacity", "0");
+				if (this.cfg.getProperty("visible")) {
+					this.showIframe();
+				} else {
+					this.hideIframe();
+				}
+			}
+
+			var iframeDisplay = YAHOO.util.Dom.getStyle(this.iframe, "display");
+
+			if (iframeDisplay == "none") {
+				this.iframe.style.display = "block";
+			}
+
+			YAHOO.util.Dom.setXY(this.iframe, [x,y]);
+
+			var width = this.element.clientWidth;
+			var height = this.element.clientHeight;
+
+			YAHOO.util.Dom.setStyle(this.iframe, "width", (width+2) + "px");
+			YAHOO.util.Dom.setStyle(this.iframe, "height", (height+2) + "px");
+
+			if (iframeDisplay == "none") {
+				this.iframe.style.display = "none";
+			}
+		}
+	} else {
+		if (this.iframe) {
+			this.iframe.style.display = "none";
+		}
+		this.showEvent.unsubscribe(this.showIframe, this);
+		this.hideEvent.unsubscribe(this.hideIframe, this);
+	}
+};
+
+
+/**
+* The default event handler fired when the "constraintoviewport" property is changed.
+* @method configConstrainToViewport
+* @param {String} type	The CustomEvent type (usually the property name)
+* @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
+* @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
+*/
+YAHOO.widget.Overlay.prototype.configConstrainToViewport = function(type, args, obj) {
+	var val = args[0];
+	if (val) {
+		if (! YAHOO.util.Config.alreadySubscribed(this.beforeMoveEvent, this.enforceConstraints, this)) {
+			this.beforeMoveEvent.subscribe(this.enforceConstraints, this, true);
+		}
+	} else {
+		this.beforeMoveEvent.unsubscribe(this.enforceConstraints, this);
+	}
+};
+
+/**
+* The default event handler fired when the "context" property is changed.
+* @method configContext
+* @param {String} type	The CustomEvent type (usually the property name)
+* @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
+* @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
+*/
+YAHOO.widget.Overlay.prototype.configContext = function(type, args, obj) {
+	var contextArgs = args[0];
+
+	if (contextArgs) {
+		var contextEl = contextArgs[0];
+		var elementMagnetCorner = contextArgs[1];
+		var contextMagnetCorner = contextArgs[2];
+
+		if (contextEl) {
+			if (typeof contextEl == "string") {
+				this.cfg.setProperty("context", [document.getElementById(contextEl),elementMagnetCorner,contextMagnetCorner], true);
+			}
+
+			if (elementMagnetCorner && contextMagnetCorner) {
+				this.align(elementMagnetCorner, contextMagnetCorner);
+			}
+		}
+	}
+};
+
+
+// END BUILT-IN PROPERTY EVENT HANDLERS //
+
+/**
+* Aligns the Overlay to its context element using the specified corner points (represented by the constants TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, and BOTTOM_RIGHT.
+* @method align
+* @param {String} elementAlign		The String representing the corner of the Overlay that should be aligned to the context element
+* @param {String} contextAlign		The corner of the context element that the elementAlign corner should stick to.
+*/
+YAHOO.widget.Overlay.prototype.align = function(elementAlign, contextAlign) {
+	var contextArgs = this.cfg.getProperty("context");
+	if (contextArgs) {
+		var context = contextArgs[0];
+
+		var element = this.element;
+		var me = this;
+
+		if (! elementAlign) {
+			elementAlign = contextArgs[1];
+		}
+
+		if (! contextAlign) {
+			contextAlign = contextArgs[2];
+		}
+
+		if (element && context) {
+			var elementRegion = YAHOO.util.Dom.getRegion(element);
+			var contextRegion = YAHOO.util.Dom.getRegion(context);
+
+			var doAlign = function(v,h) {
+				switch (elementAlign) {
+					case YAHOO.widget.Overlay.TOP_LEFT:
+						me.moveTo(h,v);
+						break;
+					case YAHOO.widget.Overlay.TOP_RIGHT:
+						me.moveTo(h-element.offsetWidth,v);
+						break;
+					case YAHOO.widget.Overlay.BOTTOM_LEFT:
+						me.moveTo(h,v-element.offsetHeight);
+						break;
+					case YAHOO.widget.Overlay.BOTTOM_RIGHT:
+						me.moveTo(h-element.offsetWidth,v-element.offsetHeight);
+						break;
+				}
+			};
+
+			switch (contextAlign) {
+				case YAHOO.widget.Overlay.TOP_LEFT:
+					doAlign(contextRegion.top, contextRegion.left);
+					break;
+				case YAHOO.widget.Overlay.TOP_RIGHT:
+					doAlign(contextRegion.top, contextRegion.right);
+					break;
+				case YAHOO.widget.Overlay.BOTTOM_LEFT:
+					doAlign(contextRegion.bottom, contextRegion.left);
+					break;
+				case YAHOO.widget.Overlay.BOTTOM_RIGHT:
+					doAlign(contextRegion.bottom, contextRegion.right);
+					break;
+			}
+		}
+	}
+};
+
+/**
+* The default event handler executed when the moveEvent is fired, if the "constraintoviewport" is set to true.
+* @method enforceConstraints
+* @param {String} type	The CustomEvent type (usually the property name)
+* @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
+* @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
+*/
+YAHOO.widget.Overlay.prototype.enforceConstraints = function(type, args, obj) {
+	var pos = args[0];
+
+	var x = pos[0];
+	var y = pos[1];
+
+	var offsetHeight = this.element.offsetHeight;
+	var offsetWidth = this.element.offsetWidth;
+
+	var viewPortWidth = YAHOO.util.Dom.getViewportWidth();
+	var viewPortHeight = YAHOO.util.Dom.getViewportHeight();
+
+	var scrollX = document.documentElement.scrollLeft || document.body.scrollLeft;
+	var scrollY = document.documentElement.scrollTop || document.body.scrollTop;
+
+	var topConstraint = scrollY + 10;
+	var leftConstraint = scrollX + 10;
+	var bottomConstraint = scrollY + viewPortHeight - offsetHeight - 10;
+	var rightConstraint = scrollX + viewPortWidth - offsetWidth - 10;
+
+	if (x < leftConstraint) {
+		x = leftConstraint;
+	} else if (x > rightConstraint) {
+		x = rightConstraint;
+	}
+
+	if (y < topConstraint) {
+		y = topConstraint;
+	} else if (y > bottomConstraint) {
+		y = bottomConstraint;
+	}
+
+	this.cfg.setProperty("x", x, true);
+	this.cfg.setProperty("y", y, true);
+	this.cfg.setProperty("xy", [x,y], true);
+};
+
+/**
+* Centers the container in the viewport.
+* @method center
+*/
+YAHOO.widget.Overlay.prototype.center = function() {
+	var scrollX = document.documentElement.scrollLeft || document.body.scrollLeft;
+	var scrollY = document.documentElement.scrollTop || document.body.scrollTop;
+
+	var viewPortWidth = YAHOO.util.Dom.getClientWidth();
+	var viewPortHeight = YAHOO.util.Dom.getClientHeight();
+
+	var elementWidth = this.element.offsetWidth;
+	var elementHeight = this.element.offsetHeight;
+
+	var x = (viewPortWidth / 2) - (elementWidth / 2) + scrollX;
+	var y = (viewPortHeight / 2) - (elementHeight / 2) + scrollY;
+
+	this.cfg.setProperty("xy", [parseInt(x, 10), parseInt(y, 10)]);
+
+	this.cfg.refireEvent("iframe");
+};
+
+/**
+* Synchronizes the Panel's "xy", "x", and "y" properties with the Panel's position in the DOM. This is primarily used to update position information during drag & drop.
+* @method syncPosition
+*/
+YAHOO.widget.Overlay.prototype.syncPosition = function() {
+	var pos = YAHOO.util.Dom.getXY(this.element);
+	this.cfg.setProperty("x", pos[0], true);
+	this.cfg.setProperty("y", pos[1], true);
+	this.cfg.setProperty("xy", pos, true);
+};
+
+/**
+* Event handler fired when the resize monitor element is resized.
+* @method onDomResize
+* @param {DOMEvent} e	The resize DOM event
+* @param {Object} obj	The scope object
+*/
+YAHOO.widget.Overlay.prototype.onDomResize = function(e, obj) {
+	YAHOO.widget.Overlay.superclass.onDomResize.call(this, e, obj);
+	var me = this;
+	setTimeout(function() {
+		me.syncPosition();
+		me.cfg.refireEvent("iframe");
+		me.cfg.refireEvent("context");
+	}, 0);
+};
+
+/**
+* Removes the Overlay element from the DOM and sets all child elements to null.
+* @method destroy
+*/
+YAHOO.widget.Overlay.prototype.destroy = function() {
+	if (this.iframe) {
+		this.iframe.parentNode.removeChild(this.iframe);
+	}
+
+	this.iframe = null;
+
+	YAHOO.widget.Overlay.windowResizeEvent.unsubscribe(this.doCenterOnDOMEvent, this);
+	YAHOO.widget.Overlay.windowScrollEvent.unsubscribe(this.doCenterOnDOMEvent, this);
+
+	YAHOO.widget.Overlay.superclass.destroy.call(this);
+};
+
+/**
+* Returns a String representation of the object.
+* @method toString
+* @return {String} The string representation of the Overlay.
+*/
+YAHOO.widget.Overlay.prototype.toString = function() {
+	return "Overlay " + this.id;
+};
+
+/**
+* A singleton CustomEvent used for reacting to the DOM event for window scroll
+* @event YAHOO.widget.Overlay.windowScrollEvent
+*/
+YAHOO.widget.Overlay.windowScrollEvent = new YAHOO.util.CustomEvent("windowScroll");
+
+/**
+* A singleton CustomEvent used for reacting to the DOM event for window resize
+* @event YAHOO.widget.Overlay.windowResizeEvent
+*/
+YAHOO.widget.Overlay.windowResizeEvent = new YAHOO.util.CustomEvent("windowResize");
+
+/**
+* The DOM event handler used to fire the CustomEvent for window scroll
+* @method YAHOO.widget.Overlay.windowScrollHandler
+* @static
+* @param {DOMEvent} e The DOM scroll event
+*/
+YAHOO.widget.Overlay.windowScrollHandler = function(e) {
+	if (YAHOO.widget.Module.prototype.browser == "ie" || YAHOO.widget.Module.prototype.browser == "ie7") {
+		if (! window.scrollEnd) {
+			window.scrollEnd = -1;
+		}
+		clearTimeout(window.scrollEnd);
+		window.scrollEnd = setTimeout(function() { YAHOO.widget.Overlay.windowScrollEvent.fire(); }, 1);
+	} else {
+		YAHOO.widget.Overlay.windowScrollEvent.fire();
+	}
+};
+
+/**
+* The DOM event handler used to fire the CustomEvent for window resize
+* @method YAHOO.widget.Overlay.windowResizeHandler
+* @static
+* @param {DOMEvent} e The DOM resize event
+*/
+YAHOO.widget.Overlay.windowResizeHandler = function(e) {
+	if (YAHOO.widget.Module.prototype.browser == "ie" || YAHOO.widget.Module.prototype.browser == "ie7") {
+		if (! window.resizeEnd) {
+			window.resizeEnd = -1;
+		}
+		clearTimeout(window.resizeEnd);
+		window.resizeEnd = setTimeout(function() { YAHOO.widget.Overlay.windowResizeEvent.fire(); }, 100);
+	} else {
+		YAHOO.widget.Overlay.windowResizeEvent.fire();
+	}
+};
+
+/**
+* A boolean that indicated whether the window resize and scroll events have already been subscribed to.
+* @property YAHOO.widget.Overlay._initialized
+* @private
+* @type Boolean
+*/
+YAHOO.widget.Overlay._initialized = null;
+
+if (YAHOO.widget.Overlay._initialized === null) {
+	YAHOO.util.Event.addListener(window, "scroll", YAHOO.widget.Overlay.windowScrollHandler);
+	YAHOO.util.Event.addListener(window, "resize", YAHOO.widget.Overlay.windowResizeHandler);
+
+	YAHOO.widget.Overlay._initialized = true;
+}
+
+/**
+* OverlayManager is used for maintaining the focus status of multiple Overlays.* @namespace YAHOO.widget
+* @namespace YAHOO.widget
+* @class OverlayManager
+* @constructor
+* @param {Array}	overlays	Optional. A collection of Overlays to register with the manager.
+* @param {Object}	userConfig		The object literal representing the user configuration of the OverlayManager
+*/
+YAHOO.widget.OverlayManager = function(userConfig) {
+	this.init(userConfig);
+};
+
+/**
+* The CSS class representing a focused Overlay
+* @property YAHOO.widget.OverlayManager.CSS_FOCUSED
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.OverlayManager.CSS_FOCUSED = "focused";
+
+YAHOO.widget.OverlayManager.prototype = {
+	/**
+	* The class's constructor function
+	* @property contructor
+	* @type Function
+	*/
+	constructor : YAHOO.widget.OverlayManager,
+
+	/**
+	* The array of Overlays that are currently registered
+	* @property overlays
+	* @type YAHOO.widget.Overlay[]
+	*/
+	overlays : null,
+
+	/**
+	* Initializes the default configuration of the OverlayManager
+	* @method initDefaultConfig
+	*/
+	initDefaultConfig : function() {
+		/**
+		* The collection of registered Overlays in use by the OverlayManager
+		* @config overlays
+		* @type YAHOO.widget.Overlay[]
+		* @default null
+		*/
+		this.cfg.addProperty("overlays", { suppressEvent:true } );
+
+		/**
+		* The default DOM event that should be used to focus an Overlay
+		* @config focusevent
+		* @type String
+		* @default "mousedown"
+		*/
+		this.cfg.addProperty("focusevent", { value:"mousedown" } );
+	},
+
+	/**
+	* Initializes the OverlayManager
+	* @method init
+	* @param {YAHOO.widget.Overlay[]}	overlays	Optional. A collection of Overlays to register with the manager.
+	* @param {Object}	userConfig		The object literal representing the user configuration of the OverlayManager
+	*/
+	init : function(userConfig) {
+		/**
+		* The OverlayManager's Config object used for monitoring configuration properties.
+		* @property cfg
+		* @type YAHOO.util.Config
+		*/
+		this.cfg = new YAHOO.util.Config(this);
+
+		this.initDefaultConfig();
+
+		if (userConfig) {
+			this.cfg.applyConfig(userConfig, true);
+		}
+		this.cfg.fireQueue();
+
+		/**
+		* The currently activated Overlay
+		* @property activeOverlay
+		* @private
+		* @type YAHOO.widget.Overlay
+		*/
+		var activeOverlay = null;
+
+		/**
+		* Returns the currently focused Overlay
+		* @method getActive
+		* @return {YAHOO.widget.Overlay}	The currently focused Overlay
+		*/
+		this.getActive = function() {
+			return activeOverlay;
+		};
+
+		/**
+		* Focuses the specified Overlay
+		* @method focus
+		* @param {YAHOO.widget.Overlay} overlay	The Overlay to focus
+		* @param {String} overlay	The id of the Overlay to focus
+		*/
+		this.focus = function(overlay) {
+			var o = this.find(overlay);
+			if (o) {
+				this.blurAll();
+				activeOverlay = o;
+				YAHOO.util.Dom.addClass(activeOverlay.element, YAHOO.widget.OverlayManager.CSS_FOCUSED);
+				this.overlays.sort(this.compareZIndexDesc);
+				var topZIndex = YAHOO.util.Dom.getStyle(this.overlays[0].element, "zIndex");
+				if (! isNaN(topZIndex) && this.overlays[0] != overlay) {
+					activeOverlay.cfg.setProperty("zIndex", (parseInt(topZIndex, 10) + 2));
+				}
+				this.overlays.sort(this.compareZIndexDesc);
+			}
+		};
+
+		/**
+		* Removes the specified Overlay from the manager
+		* @method remove
+		* @param {YAHOO.widget.Overlay}	overlay	The Overlay to remove
+		* @param {String} overlay	The id of the Overlay to remove
+		*/
+		this.remove = function(overlay) {
+			var o = this.find(overlay);
+			if (o) {
+				var originalZ = YAHOO.util.Dom.getStyle(o.element, "zIndex");
+				o.cfg.setProperty("zIndex", -1000, true);
+				this.overlays.sort(this.compareZIndexDesc);
+				this.overlays = this.overlays.slice(0, this.overlays.length-1);
+				o.cfg.setProperty("zIndex", originalZ, true);
+
+				o.cfg.setProperty("manager", null);
+				o.focusEvent = null;
+				o.blurEvent = null;
+				o.focus = null;
+				o.blur = null;
+			}
+		};
+
+		/**
+		* Removes focus from all registered Overlays in the manager
+		* @method blurAll
+		*/
+		this.blurAll = function() {
+			activeOverlay = null;
+			for (var o=0;o<this.overlays.length;o++) {
+				YAHOO.util.Dom.removeClass(this.overlays[o].element, YAHOO.widget.OverlayManager.CSS_FOCUSED);
+			}
+		};
+
+		var overlays = this.cfg.getProperty("overlays");
+
+		if (! this.overlays) {
+			this.overlays = [];
+		}
+
+		if (overlays) {
+			this.register(overlays);
+			this.overlays.sort(this.compareZIndexDesc);
+		}
+	},
+
+	/**
+	* Registers an Overlay or an array of Overlays with the manager. Upon registration, the Overlay receives functions for focus and blur, along with CustomEvents for each.
+	* @method register
+	* @param {YAHOO.widget.Overlay}	overlay		An Overlay to register with the manager.
+	* @param {YAHOO.widget.Overlay[]}	overlay		An array of Overlays to register with the manager.
+	* @return	{Boolean}	True if any Overlays are registered.
+	*/
+	register : function(overlay) {
+		if (overlay instanceof YAHOO.widget.Overlay) {
+			overlay.cfg.addProperty("manager", { value:this } );
+
+			overlay.focusEvent = new YAHOO.util.CustomEvent("focus");
+			overlay.blurEvent = new YAHOO.util.CustomEvent("blur");
+
+			var mgr=this;
+
+			overlay.focus = function() {
+				mgr.focus(this);
+				this.focusEvent.fire();
+			};
+
+			overlay.blur = function() {
+				mgr.blurAll();
+				this.blurEvent.fire();
+			};
+
+			var focusOnDomEvent = function(e,obj) {
+				overlay.focus();
+			};
+
+			var focusevent = this.cfg.getProperty("focusevent");
+			YAHOO.util.Event.addListener(overlay.element,focusevent,focusOnDomEvent,this,true);
+
+			var zIndex = YAHOO.util.Dom.getStyle(overlay.element, "zIndex");
+			if (! isNaN(zIndex)) {
+				overlay.cfg.setProperty("zIndex", parseInt(zIndex, 10));
+			} else {
+				overlay.cfg.setProperty("zIndex", 0);
+			}
+
+			this.overlays.push(overlay);
+			return true;
+		} else if (overlay instanceof Array) {
+			var regcount = 0;
+			for (var i=0;i<overlay.length;i++) {
+				if (this.register(overlay[i])) {
+					regcount++;
+				}
+			}
+			if (regcount > 0) {
+				return true;
+			}
+		} else {
+			return false;
+		}
+	},
+
+	/**
+	* Attempts to locate an Overlay by instance or ID.
+	* @method find
+	* @param {YAHOO.widget.Overlay}	overlay		An Overlay to locate within the manager
+	* @param {String}	overlay		An Overlay id to locate within the manager
+	* @return	{YAHOO.widget.Overlay}	The requested Overlay, if found, or null if it cannot be located.
+	*/
+	find : function(overlay) {
+		if (overlay instanceof YAHOO.widget.Overlay) {
+			for (var o=0;o<this.overlays.length;o++) {
+				if (this.overlays[o] == overlay) {
+					return this.overlays[o];
+				}
+			}
+		} else if (typeof overlay == "string") {
+			for (var p=0;p<this.overlays.length;p++) {
+				if (this.overlays[p].id == overlay) {
+					return this.overlays[p];
+				}
+			}
+		}
+		return null;
+	},
+
+	/**
+	* Used for sorting the manager's Overlays by z-index.
+	* @method compareZIndexDesc
+	* @private
+	* @return {Number}	0, 1, or -1, depending on where the Overlay should fall in the stacking order.
+	*/
+	compareZIndexDesc : function(o1, o2) {
+		var zIndex1 = o1.cfg.getProperty("zIndex");
+		var zIndex2 = o2.cfg.getProperty("zIndex");
+
+		if (zIndex1 > zIndex2) {
+			return -1;
+		} else if (zIndex1 < zIndex2) {
+			return 1;
+		} else {
+			return 0;
+		}
+	},
+
+	/**
+	* Shows all Overlays in the manager.
+	* @method showAll
+	*/
+	showAll : function() {
+		for (var o=0;o<this.overlays.length;o++) {
+			this.overlays[o].show();
+		}
+	},
+
+	/**
+	* Hides all Overlays in the manager.
+	* @method hideAll
+	*/
+	hideAll : function() {
+		for (var o=0;o<this.overlays.length;o++) {
+			this.overlays[o].hide();
+		}
+	},
+
+
+	/**
+	* Returns a string representation of the object.
+	* @method toString
+	* @return {String}	The string representation of the OverlayManager
+	*/
+	toString : function() {
+		return "OverlayManager";
+	}
+
+};
+
+/**
+* KeyListener is a utility that provides an easy interface for listening for keydown/keyup events fired against DOM elements.
+* @namespace YAHOO.util
+* @class KeyListener
+* @constructor
+* @param {HTMLElement}	attachTo	The element or element ID to which the key event should be attached
+* @param {String}	attachTo	The element or element ID to which the key event should be attached
+* @param {Object}	keyData		The object literal representing the key(s) to detect. Possible attributes are shift(boolean), alt(boolean), ctrl(boolean) and keys(either an int or an array of ints representing keycodes).
+* @param {Function}	handler		The CustomEvent handler to fire when the key event is detected
+* @param {Object}	handler		An object literal representing the handler.
+* @param {String}	event		Optional. The event (keydown or keyup) to listen for. Defaults automatically to keydown.
+*/
+YAHOO.util.KeyListener = function(attachTo, keyData, handler, event) {
+	if (! event) {
+		event = YAHOO.util.KeyListener.KEYDOWN;
+	}
+
+	/**
+	* The CustomEvent fired internally when a key is pressed
+	* @event keyEvent
+	* @private
+	* @param {Object}	keyData		The object literal representing the key(s) to detect. Possible attributes are shift(boolean), alt(boolean), ctrl(boolean) and keys(either an int or an array of ints representing keycodes).
+	*/
+	var keyEvent = new YAHOO.util.CustomEvent("keyPressed");
+
+	/**
+	* The CustomEvent fired when the KeyListener is enabled via the enable() function
+	* @event enabledEvent
+	* @param {Object}	keyData		The object literal representing the key(s) to detect. Possible attributes are shift(boolean), alt(boolean), ctrl(boolean) and keys(either an int or an array of ints representing keycodes).
+	*/
+	this.enabledEvent = new YAHOO.util.CustomEvent("enabled");
+
+	/**
+	* The CustomEvent fired when the KeyListener is disabled via the disable() function
+	* @event disabledEvent
+	* @param {Object}	keyData		The object literal representing the key(s) to detect. Possible attributes are shift(boolean), alt(boolean), ctrl(boolean) and keys(either an int or an array of ints representing keycodes).
+	*/
+	this.disabledEvent = new YAHOO.util.CustomEvent("disabled");
+
+	if (typeof attachTo == 'string') {
+		attachTo = document.getElementById(attachTo);
+	}
+
+	if (typeof handler == 'function') {
+		keyEvent.subscribe(handler);
+	} else {
+		keyEvent.subscribe(handler.fn, handler.scope, handler.correctScope);
+	}
+
+	/**
+	* Handles the key event when a key is pressed.
+	* @method handleKeyPress
+	* @param {DOMEvent} e	The keypress DOM event
+	* @param {Object}	obj	The DOM event scope object
+	* @private
+	*/
+	function handleKeyPress(e, obj) {
+		if (! keyData.shift) {
+			keyData.shift = false;
+		}
+		if (! keyData.alt) {
+			keyData.alt = false;
+		}
+		if (! keyData.ctrl) {
+			keyData.ctrl = false;
+		}
+
+		// check held down modifying keys first
+		if (e.shiftKey == keyData.shift &&
+			e.altKey   == keyData.alt &&
+			e.ctrlKey  == keyData.ctrl) { // if we pass this, all modifiers match
+
+			var dataItem;
+			var keyPressed;
+
+			if (keyData.keys instanceof Array) {
+				for (var i=0;i<keyData.keys.length;i++) {
+					dataItem = keyData.keys[i];
+
+					if (dataItem == e.charCode ) {
+						keyEvent.fire(e.charCode, e);
+						break;
+					} else if (dataItem == e.keyCode) {
+						keyEvent.fire(e.keyCode, e);
+						break;
+					}
+				}
+			} else {
+				dataItem = keyData.keys;
+
+				if (dataItem == e.charCode ) {
+					keyEvent.fire(e.charCode, e);
+				} else if (dataItem == e.keyCode) {
+					keyEvent.fire(e.keyCode, e);
+				}
+			}
+		}
+	}
+
+	/**
+	* Enables the KeyListener by attaching the DOM event listeners to the target DOM element
+	* @method enable
+	*/
+	this.enable = function() {
+		if (! this.enabled) {
+			YAHOO.util.Event.addListener(attachTo, event, handleKeyPress);
+			this.enabledEvent.fire(keyData);
+		}
+		/**
+		* Boolean indicating the enabled/disabled state of the Tooltip
+		* @property enabled
+		* @type Boolean
+		*/
+		this.enabled = true;
+	};
+
+	/**
+	* Disables the KeyListener by removing the DOM event listeners from the target DOM element
+	* @method disable
+	*/
+	this.disable = function() {
+		if (this.enabled) {
+			YAHOO.util.Event.removeListener(attachTo, event, handleKeyPress);
+			this.disabledEvent.fire(keyData);
+		}
+		this.enabled = false;
+	};
+
+	/**
+	* Returns a String representation of the object.
+	* @method toString
+	* @return {String}	The string representation of the KeyListener
+	*/
+	this.toString = function() {
+		return "KeyListener [" + keyData.keys + "] " + attachTo.tagName + (attachTo.id ? "[" + attachTo.id + "]" : "");
+	};
+
+};
+
+/**
+* Constant representing the DOM "keydown" event.
+* @property YAHOO.util.KeyListener.KEYDOWN
+* @static
+* @final
+* @type String
+*/
+YAHOO.util.KeyListener.KEYDOWN = "keydown";
+
+/**
+* Constant representing the DOM "keyup" event.
+* @property YAHOO.util.KeyListener.KEYUP
+* @static
+* @final
+* @type String
+*/
+YAHOO.util.KeyListener.KEYUP = "keyup";
+
+/**
+* Tooltip is an implementation of Overlay that behaves like an OS tooltip, displaying when the user mouses over a particular element, and disappearing on mouse out.
+* @namespace YAHOO.widget
+* @class Tooltip
+* @extends YAHOO.widget.Overlay
+* @constructor
+* @param {String}	el	The element ID representing the Tooltip <em>OR</em>
+* @param {HTMLElement}	el	The element representing the Tooltip
+* @param {Object}	userConfig	The configuration object literal containing the configuration that should be set for this Overlay. See configuration documentation for more details.
+*/
+YAHOO.widget.Tooltip = function(el, userConfig) {
+	YAHOO.widget.Tooltip.superclass.constructor.call(this, el, userConfig);
+};
+
+YAHOO.extend(YAHOO.widget.Tooltip, YAHOO.widget.Overlay);
+
+/**
+* Constant representing the Tooltip CSS class
+* @property YAHOO.widget.Tooltip.CSS_TOOLTIP
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Tooltip.CSS_TOOLTIP = "tt";
+
+/**
+* The Tooltip initialization method. This method is automatically called by the constructor. A Tooltip is automatically rendered by the init method, and it also is set to be invisible by default, and constrained to viewport by default as well.
+* @method init
+* @param {String}	el	The element ID representing the Tooltip <em>OR</em>
+* @param {HTMLElement}	el	The element representing the Tooltip
+* @param {Object}	userConfig	The configuration object literal containing the configuration that should be set for this Tooltip. See configuration documentation for more details.
+*/
+YAHOO.widget.Tooltip.prototype.init = function(el, userConfig) {
+	if (document.readyState && document.readyState != "complete") {
+		var deferredInit = function() {
+			this.init(el, userConfig);
+		};
+		YAHOO.util.Event.addListener(window, "load", deferredInit, this, true);
+	} else {
+		YAHOO.widget.Tooltip.superclass.init.call(this, el);
+
+		this.beforeInitEvent.fire(YAHOO.widget.Tooltip);
+
+		YAHOO.util.Dom.addClass(this.element, YAHOO.widget.Tooltip.CSS_TOOLTIP);
+
+		if (userConfig) {
+			this.cfg.applyConfig(userConfig, true);
+		}
+
+		this.cfg.queueProperty("visible",false);
+		this.cfg.queueProperty("constraintoviewport",true);
+
+		this.setBody("");
+		this.render(this.cfg.getProperty("container"));
+
+		this.initEvent.fire(YAHOO.widget.Tooltip);
+	}
+};
+
+/**
+* Initializes the class's configurable properties which can be changed using the Overlay's Config object (cfg).
+* @method initDefaultConfig
+*/
+YAHOO.widget.Tooltip.prototype.initDefaultConfig = function() {
+	YAHOO.widget.Tooltip.superclass.initDefaultConfig.call(this);
+
+	/**
+	* Specifies whether the Tooltip should be kept from overlapping its context element.
+	* @config preventoverlap
+	* @type Boolean
+	* @default true
+	*/
+	this.cfg.addProperty("preventoverlap",		{ value:true, validator:this.cfg.checkBoolean, supercedes:["x","y","xy"] } );
+
+	/**
+	* The number of milliseconds to wait before showing a Tooltip on mouseover.
+	* @config showdelay
+	* @type Number
+	* @default 200
+	*/
+	this.cfg.addProperty("showdelay",			{ value:200, handler:this.configShowDelay, validator:this.cfg.checkNumber } );
+
+	/**
+	* The number of milliseconds to wait before automatically dismissing a Tooltip after the mouse has been resting on the context element.
+	* @config autodismissdelay
+	* @type Number
+	* @default 5000
+	*/
+	this.cfg.addProperty("autodismissdelay",	{ value:5000, handler:this.configAutoDismissDelay, validator:this.cfg.checkNumber } );
+
+	/**
+	* The number of milliseconds to wait before hiding a Tooltip on mouseover.
+	* @config hidedelay
+	* @type Number
+	* @default 250
+	*/
+	this.cfg.addProperty("hidedelay",			{ value:250, handler:this.configHideDelay, validator:this.cfg.checkNumber } );
+
+	/**
+	* Specifies the Tooltip's text.
+	* @config text
+	* @type String
+	* @default null
+	*/
+	this.cfg.addProperty("text",				{ handler:this.configText, suppressEvent:true } );
+
+	/**
+	* Specifies the container element that the Tooltip's markup should be rendered into.
+	* @config container
+	* @type HTMLElement/String
+	* @default document.body
+	*/
+	this.cfg.addProperty("container",			{ value:document.body, handler:this.configContainer } );
+
+	/**
+	* Specifies the element or elements that the Tooltip should be anchored to on mouseover.
+	* @config context
+	* @type HTMLElement[]/String[]
+	* @default null
+	*/
+
+};
+
+// BEGIN BUILT-IN PROPERTY EVENT HANDLERS //
+
+/**
+* The default event handler fired when the "text" property is changed.
+* @method configText
+* @param {String} type	The CustomEvent type (usually the property name)
+* @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
+* @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
+*/
+YAHOO.widget.Tooltip.prototype.configText = function(type, args, obj) {
+	var text = args[0];
+	if (text) {
+		this.setBody(text);
+	}
+};
+
+/**
+* The default event handler fired when the "container" property is changed.
+* @method configContainer
+* @param {String} type	The CustomEvent type (usually the property name)
+* @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
+* @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
+*/
+YAHOO.widget.Tooltip.prototype.configContainer = function(type, args, obj) {
+	var container = args[0];
+	if (typeof container == 'string') {
+		this.cfg.setProperty("container", document.getElementById(container), true);
+	}
+};
+
+/**
+* The default event handler fired when the "context" property is changed.
+* @method configContext
+* @param {String} type	The CustomEvent type (usually the property name)
+* @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
+* @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
+*/
+YAHOO.widget.Tooltip.prototype.configContext = function(type, args, obj) {
+	var context = args[0];
+	if (context) {
+
+		// Normalize parameter into an array
+		if (! (context instanceof Array)) {
+			if (typeof context == "string") {
+				this.cfg.setProperty("context", [document.getElementById(context)], true);
+			} else { // Assuming this is an element
+				this.cfg.setProperty("context", [context], true);
+			}
+			context = this.cfg.getProperty("context");
+		}
+
+
+		// Remove any existing mouseover/mouseout listeners
+		if (this._context) {
+			for (var c=0;c<this._context.length;++c) {
+				var el = this._context[c];
+				YAHOO.util.Event.removeListener(el, "mouseover", this.onContextMouseOver);
+				YAHOO.util.Event.removeListener(el, "mousemove", this.onContextMouseMove);
+				YAHOO.util.Event.removeListener(el, "mouseout", this.onContextMouseOut);
+			}
+		}
+
+		// Add mouseover/mouseout listeners to context elements
+		this._context = context;
+		for (var d=0;d<this._context.length;++d) {
+			var el2 = this._context[d];
+			YAHOO.util.Event.addListener(el2, "mouseover", this.onContextMouseOver, this);
+			YAHOO.util.Event.addListener(el2, "mousemove", this.onContextMouseMove, this);
+			YAHOO.util.Event.addListener(el2, "mouseout", this.onContextMouseOut, this);
+		}
+	}
+};
+
+// END BUILT-IN PROPERTY EVENT HANDLERS //
+
+// BEGIN BUILT-IN DOM EVENT HANDLERS //
+
+/**
+* The default event handler fired when the user moves the mouse while over the context element.
+* @method onContextMouseMove
+* @param {DOMEvent} e	The current DOM event
+* @param {Object}	obj	The object argument
+*/
+YAHOO.widget.Tooltip.prototype.onContextMouseMove = function(e, obj) {
+	obj.pageX = YAHOO.util.Event.getPageX(e);
+	obj.pageY = YAHOO.util.Event.getPageY(e);
+
+};
+
+/**
+* The default event handler fired when the user mouses over the context element.
+* @method onContextMouseOver
+* @param {DOMEvent} e	The current DOM event
+* @param {Object}	obj	The object argument
+*/
+YAHOO.widget.Tooltip.prototype.onContextMouseOver = function(e, obj) {
+
+	if (obj.hideProcId) {
+		clearTimeout(obj.hideProcId);
+		obj.hideProcId = null;
+	}
+
+	var context = this;
+	YAHOO.util.Event.addListener(context, "mousemove", obj.onContextMouseMove, obj);
+
+	if (context.title) {
+		obj._tempTitle = context.title;
+		context.title = "";
+	}
+
+	/**
+	* The unique process ID associated with the thread responsible for showing the Tooltip.
+	* @type int
+	*/
+	obj.showProcId = obj.doShow(e, context);
+};
+
+/**
+* The default event handler fired when the user mouses out of the context element.
+* @method onContextMouseOut
+* @param {DOMEvent} e	The current DOM event
+* @param {Object}	obj	The object argument
+*/
+YAHOO.widget.Tooltip.prototype.onContextMouseOut = function(e, obj) {
+	var el = this;
+
+	if (obj._tempTitle) {
+		el.title = obj._tempTitle;
+		obj._tempTitle = null;
+	}
+
+	if (obj.showProcId) {
+		clearTimeout(obj.showProcId);
+		obj.showProcId = null;
+	}
+
+	if (obj.hideProcId) {
+		clearTimeout(obj.hideProcId);
+		obj.hideProcId = null;
+	}
+
+
+	obj.hideProcId = setTimeout(function() {
+				obj.hide();
+				}, obj.cfg.getProperty("hidedelay"));
+};
+
+// END BUILT-IN DOM EVENT HANDLERS //
+
+/**
+* Processes the showing of the Tooltip by setting the timeout delay and offset of the Tooltip.
+* @method doShow
+* @param {DOMEvent} e	The current DOM event
+* @return {Number}	The process ID of the timeout function associated with doShow
+*/
+YAHOO.widget.Tooltip.prototype.doShow = function(e, context) {
+
+	var yOffset = 25;
+	if (this.browser == "opera" && context.tagName == "A") {
+		yOffset += 12;
+	}
+
+	var me = this;
+	return setTimeout(
+		function() {
+			if (me._tempTitle) {
+				me.setBody(me._tempTitle);
+			} else {
+				me.cfg.refireEvent("text");
+			}
+
+			me.moveTo(me.pageX, me.pageY + yOffset);
+			if (me.cfg.getProperty("preventoverlap")) {
+				me.preventOverlap(me.pageX, me.pageY);
+			}
+
+			YAHOO.util.Event.removeListener(context, "mousemove", me.onContextMouseMove);
+
+			me.show();
+			me.hideProcId = me.doHide();
+		},
+	this.cfg.getProperty("showdelay"));
+};
+
+/**
+* Sets the timeout for the auto-dismiss delay, which by default is 5 seconds, meaning that a tooltip will automatically dismiss itself after 5 seconds of being displayed.
+* @method doHide
+*/
+YAHOO.widget.Tooltip.prototype.doHide = function() {
+	var me = this;
+	return setTimeout(
+		function() {
+			me.hide();
+		},
+		this.cfg.getProperty("autodismissdelay"));
+};
+
+/**
+* Fired when the Tooltip is moved, this event handler is used to prevent the Tooltip from overlapping with its context element.
+* @method preventOverlay
+* @param {Number} pageX	The x coordinate position of the mouse pointer
+* @param {Number} pageY	The y coordinate position of the mouse pointer
+*/
+YAHOO.widget.Tooltip.prototype.preventOverlap = function(pageX, pageY) {
+
+	var height = this.element.offsetHeight;
+
+	var elementRegion = YAHOO.util.Dom.getRegion(this.element);
+
+	elementRegion.top -= 5;
+	elementRegion.left -= 5;
+	elementRegion.right += 5;
+	elementRegion.bottom += 5;
+
+	var mousePoint = new YAHOO.util.Point(pageX, pageY);
+
+	if (elementRegion.contains(mousePoint)) {
+		this.cfg.setProperty("y", (pageY-height-5));
+	}
+};
+
+/**
+* Returns a string representation of the object.
+* @method toString
+* @return {String}	The string representation of the Tooltip
+*/
+YAHOO.widget.Tooltip.prototype.toString = function() {
+	return "Tooltip " + this.id;
+};
+
+/**
+* Panel is an implementation of Overlay that behaves like an OS window, with a draggable header and an optional close icon at the top right.
+* @namespace YAHOO.widget
+* @class Panel
+* @extends YAHOO.widget.Overlay
+* @constructor
+* @param {String}	el	The element ID representing the Panel <em>OR</em>
+* @param {HTMLElement}	el	The element representing the Panel
+* @param {Object}	userConfig	The configuration object literal containing the configuration that should be set for this Panel. See configuration documentation for more details.
+*/
+YAHOO.widget.Panel = function(el, userConfig) {
+	YAHOO.widget.Panel.superclass.constructor.call(this, el, userConfig);
+};
+
+YAHOO.extend(YAHOO.widget.Panel, YAHOO.widget.Overlay);
+
+/**
+* Constant representing the default CSS class used for a Panel
+* @property YAHOO.widget.Panel.CSS_PANEL
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Panel.CSS_PANEL = "panel";
+
+/**
+* Constant representing the default CSS class used for a Panel's wrapping container
+* @property YAHOO.widget.Panel.CSS_PANEL_CONTAINER
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Panel.CSS_PANEL_CONTAINER = "panel-container";
+
+/**
+* The Overlay initialization method, which is executed for Overlay and all of its subclasses. This method is automatically called by the constructor, and  sets up all DOM references for pre-existing markup, and creates required markup if it is not already present.
+* @method init
+* @param {String}	el	The element ID representing the Overlay <em>OR</em>
+* @param {HTMLElement}	el	The element representing the Overlay
+* @param {Object}	userConfig	The configuration object literal containing the configuration that should be set for this Overlay. See configuration documentation for more details.
+*/
+YAHOO.widget.Panel.prototype.init = function(el, userConfig) {
+	YAHOO.widget.Panel.superclass.init.call(this, el/*, userConfig*/);  // Note that we don't pass the user config in here yet because we only want it executed once, at the lowest subclass level
+
+	this.beforeInitEvent.fire(YAHOO.widget.Panel);
+
+	YAHOO.util.Dom.addClass(this.element, YAHOO.widget.Panel.CSS_PANEL);
+
+	this.buildWrapper();
+
+	if (userConfig) {
+		this.cfg.applyConfig(userConfig, true);
+	}
+
+	this.beforeRenderEvent.subscribe(function() {
+		var draggable = this.cfg.getProperty("draggable");
+		if (draggable) {
+			if (! this.header) {
+				this.setHeader("&#160;");
+			}
+		}
+	}, this, true);
+
+	var me = this;
+
+	var doBlur = function() {
+		this.blur();
+	};
+
+	this.showMaskEvent.subscribe(function() {
+		var checkFocusable = function(el) {
+			if ((el.tagName == "A" || el.tagName == "BUTTON" || el.tagName == "SELECT" || el.tagName == "INPUT" || el.tagName == "TEXTAREA" || el.tagName == "FORM") && el.type != "hidden") {
+				if (! YAHOO.util.Dom.isAncestor(me.element, el)) {
+					YAHOO.util.Event.addListener(el, "focus", doBlur, el, true);
+					return true;
+				}
+			} else {
+				return false;
+			}
+		};
+
+		this.focusableElements = YAHOO.util.Dom.getElementsBy(checkFocusable);
+	}, this, true);
+
+	this.hideMaskEvent.subscribe(function() {
+		for (var i=0;i<this.focusableElements.length;i++) {
+			var el2 = this.focusableElements[i];
+			YAHOO.util.Event.removeListener(el2, "focus", doBlur);
+		}
+	}, this, true);
+
+	this.beforeShowEvent.subscribe(function() {
+		this.cfg.refireEvent("underlay");
+	}, this, true);
+
+	this.initEvent.fire(YAHOO.widget.Panel);
+};
+
+/**
+* Initializes the custom events for Module which are fired automatically at appropriate times by the Module class.
+*/
+YAHOO.widget.Panel.prototype.initEvents = function() {
+	YAHOO.widget.Panel.superclass.initEvents.call(this);
+
+	/**
+	* CustomEvent fired after the modality mask is shown
+	* @event showMaskEvent
+	*/
+	this.showMaskEvent = new YAHOO.util.CustomEvent("showMask");
+
+	/**
+	* CustomEvent fired after the modality mask is hidden
+	* @event hideMaskEvent
+	*/
+	this.hideMaskEvent = new YAHOO.util.CustomEvent("hideMask");
+
+	/**
+	* CustomEvent when the Panel is dragged
+	* @event dragEvent
+	*/
+	this.dragEvent = new YAHOO.util.CustomEvent("drag");
+};
+
+/**
+* Initializes the class's configurable properties which can be changed using the Panel's Config object (cfg).
+* @method initDefaultConfig
+*/
+YAHOO.widget.Panel.prototype.initDefaultConfig = function() {
+	YAHOO.widget.Panel.superclass.initDefaultConfig.call(this);
+
+	// Add panel config properties //
+
+	/**
+	* True if the Panel should display a "close" button
+	* @config close
+	* @type Boolean
+	* @default true
+	*/
+	this.cfg.addProperty("close", { value:true, handler:this.configClose, validator:this.cfg.checkBoolean, supercedes:["visible"] } );
+
+	/**
+	* True if the Panel should be draggable
+	* @config draggable
+	* @type Boolean
+	* @default true
+	*/
+	this.cfg.addProperty("draggable", { value:true,	handler:this.configDraggable, validator:this.cfg.checkBoolean, supercedes:["visible"] } );
+
+	/**
+	* Sets the type of underlay to display for the Panel. Valid values are "shadow", "matte", and "none".
+	* @config underlay
+	* @type String
+	* @default shadow
+	*/
+	this.cfg.addProperty("underlay", { value:"shadow", handler:this.configUnderlay, supercedes:["visible"] } );
+
+	/**
+	* True if the Panel should be displayed in a modal fashion, automatically creating a transparent mask over the document that will not be removed until the Panel is dismissed.
+	* @config modal
+	* @type Boolean
+	* @default false
+	*/
+	this.cfg.addProperty("modal",	{ value:false, handler:this.configModal, validator:this.cfg.checkBoolean, supercedes:["visible"] } );
+
+	/**
+	* A KeyListener (or array of KeyListeners) that will be enabled when the Panel is shown, and disabled when the Panel is hidden.
+	* @config keylisteners
+	* @type YAHOO.util.KeyListener[]
+	* @default null
+	*/
+	this.cfg.addProperty("keylisteners", { handler:this.configKeyListeners, suppressEvent:true, supercedes:["visible"] } );
+};
+
+// BEGIN BUILT-IN PROPERTY EVENT HANDLERS //
+
+/**
+* The default event handler fired when the "close" property is changed. The method controls the appending or hiding of the close icon at the top right of the Panel.
+* @method configClose
+* @param {String} type	The CustomEvent type (usually the property name)
+* @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
+* @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
+*/
+YAHOO.widget.Panel.prototype.configClose = function(type, args, obj) {
+	var val = args[0];
+
+	var doHide = function(e, obj) {
+		obj.hide();
+	};
+
+	if (val) {
+		if (! this.close) {
+			this.close = document.createElement("DIV");
+			YAHOO.util.Dom.addClass(this.close, "close");
+
+			if (this.isSecure) {
+				YAHOO.util.Dom.addClass(this.close, "secure");
+			} else {
+				YAHOO.util.Dom.addClass(this.close, "nonsecure");
+			}
+
+			this.close.innerHTML = "&#160;";
+			this.innerElement.appendChild(this.close);
+			YAHOO.util.Event.addListener(this.close, "click", doHide, this);
+		} else {
+			this.close.style.display = "block";
+		}
+	} else {
+		if (this.close) {
+			this.close.style.display = "none";
+		}
+	}
+};
+
+/**
+* The default event handler fired when the "draggable" property is changed.
+* @method configDraggable
+* @param {String} type	The CustomEvent type (usually the property name)
+* @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
+* @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
+*/
+YAHOO.widget.Panel.prototype.configDraggable = function(type, args, obj) {
+	var val = args[0];
+	if (val) {
+		if (this.header) {
+			YAHOO.util.Dom.setStyle(this.header,"cursor","move");
+			this.registerDragDrop();
+		}
+	} else {
+		if (this.dd) {
+			this.dd.unreg();
+		}
+		if (this.header) {
+			YAHOO.util.Dom.setStyle(this.header,"cursor","auto");
+		}
+	}
+};
+
+/**
+* The default event handler fired when the "underlay" property is changed.
+* @method configUnderlay
+* @param {String} type	The CustomEvent type (usually the property name)
+* @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
+* @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
+*/
+YAHOO.widget.Panel.prototype.configUnderlay = function(type, args, obj) {
+	var val = args[0];
+
+	switch (val.toLowerCase()) {
+		case "shadow":
+			YAHOO.util.Dom.removeClass(this.element, "matte");
+			YAHOO.util.Dom.addClass(this.element, "shadow");
+
+			if (! this.underlay) { // create if not already in DOM
+				this.underlay = document.createElement("DIV");
+				this.underlay.className = "underlay";
+				this.underlay.innerHTML = "&#160;";
+				this.element.appendChild(this.underlay);
+			}
+
+			this.sizeUnderlay();
+			break;
+		case "matte":
+			YAHOO.util.Dom.removeClass(this.element, "shadow");
+			YAHOO.util.Dom.addClass(this.element, "matte");
+			break;
+		default:
+			YAHOO.util.Dom.removeClass(this.element, "shadow");
+			YAHOO.util.Dom.removeClass(this.element, "matte");
+			break;
+	}
+};
+
+/**
+* The default event handler fired when the "modal" property is changed. This handler subscribes or unsubscribes to the show and hide events to handle the display or hide of the modality mask.
+* @method configModal
+* @param {String} type	The CustomEvent type (usually the property name)
+* @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
+* @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
+*/
+YAHOO.widget.Panel.prototype.configModal = function(type, args, obj) {
+	var modal = args[0];
+
+	if (modal) {
+		this.buildMask();
+
+		if (! YAHOO.util.Config.alreadySubscribed( this.beforeShowEvent, this.showMask, this ) ) {
+			this.beforeShowEvent.subscribe(this.showMask, this, true);
+		}
+		if (! YAHOO.util.Config.alreadySubscribed( this.hideEvent, this.hideMask, this) ) {
+			this.hideEvent.subscribe(this.hideMask, this, true);
+		}
+		if (! YAHOO.util.Config.alreadySubscribed( YAHOO.widget.Overlay.windowResizeEvent, this.sizeMask, this ) ) {
+			YAHOO.widget.Overlay.windowResizeEvent.subscribe(this.sizeMask, this, true);
+		}
+		if (! YAHOO.util.Config.alreadySubscribed( this.destroyEvent, this.removeMask, this) ) {
+			this.destroyEvent.subscribe(this.removeMask, this, true);
+		}
+
+		this.cfg.refireEvent("zIndex");
+	} else {
+		this.beforeShowEvent.unsubscribe(this.showMask, this);
+		this.hideEvent.unsubscribe(this.hideMask, this);
+		YAHOO.widget.Overlay.windowResizeEvent.unsubscribe(this.sizeMask, this);
+		this.destroyEvent.unsubscribe(this.removeMask, this);
+	}
+};
+
+/**
+* Removes the modality mask.
+* @method removeMask
+*/
+YAHOO.widget.Panel.prototype.removeMask = function() {
+	if (this.mask) {
+		if (this.mask.parentNode) {
+			this.mask.parentNode.removeChild(this.mask);
+		}
+		this.mask = null;
+	}
+};
+
+/**
+* The default event handler fired when the "keylisteners" property is changed.
+* @method configKeyListeners
+* @param {String} type	The CustomEvent type (usually the property name)
+* @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
+* @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
+*/
+YAHOO.widget.Panel.prototype.configKeyListeners = function(type, args, obj) {
+	var listeners = args[0];
+
+	if (listeners) {
+		if (listeners instanceof Array) {
+			for (var i=0;i<listeners.length;i++) {
+				var listener = listeners[i];
+
+				if (! YAHOO.util.Config.alreadySubscribed(this.showEvent, listener.enable, listener)) {
+					this.showEvent.subscribe(listener.enable, listener, true);
+				}
+				if (! YAHOO.util.Config.alreadySubscribed(this.hideEvent, listener.disable, listener)) {
+					this.hideEvent.subscribe(listener.disable, listener, true);
+					this.destroyEvent.subscribe(listener.disable, listener, true);
+				}
+			}
+		} else {
+			if (! YAHOO.util.Config.alreadySubscribed(this.showEvent, listeners.enable, listeners)) {
+				this.showEvent.subscribe(listeners.enable, listeners, true);
+			}
+			if (! YAHOO.util.Config.alreadySubscribed(this.hideEvent, listeners.disable, listeners)) {
+				this.hideEvent.subscribe(listeners.disable, listeners, true);
+				this.destroyEvent.subscribe(listeners.disable, listeners, true);
+			}
+		}
+	}
+};
+
+/**
+* The default event handler fired when the "height" property is changed.
+* @method configHeight
+* @param {String} type	The CustomEvent type (usually the property name)
+* @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
+* @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
+*/
+YAHOO.widget.Panel.prototype.configHeight = function(type, args, obj) {
+	var height = args[0];
+	var el = this.innerElement;
+	YAHOO.util.Dom.setStyle(el, "height", height);
+	this.cfg.refireEvent("underlay");
+	this.cfg.refireEvent("iframe");
+};
+
+/**
+* The default event handler fired when the "width" property is changed.
+* @method configWidth
+* @param {String} type	The CustomEvent type (usually the property name)
+* @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
+* @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
+*/
+YAHOO.widget.Panel.prototype.configWidth = function(type, args, obj) {
+	var width = args[0];
+	var el = this.innerElement;
+	YAHOO.util.Dom.setStyle(el, "width", width);
+	this.cfg.refireEvent("underlay");
+	this.cfg.refireEvent("iframe");
+};
+
+/**
+* The default event handler fired when the "zIndex" property is changed.
+* @method configzIndex
+* @param {String} type	The CustomEvent type (usually the property name)
+* @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
+* @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
+*/
+YAHOO.widget.Panel.prototype.configzIndex = function(type, args, obj) {
+	YAHOO.widget.Panel.superclass.configzIndex.call(this, type, args, obj);
+
+	var maskZ = 0;
+	var currentZ = YAHOO.util.Dom.getStyle(this.element, "zIndex");
+
+	if (this.mask) {
+		if (! currentZ || isNaN(currentZ)) {
+			currentZ = 0;
+		}
+
+		if (currentZ === 0) {
+			this.cfg.setProperty("zIndex", 1);
+		} else {
+			maskZ = currentZ - 1;
+			YAHOO.util.Dom.setStyle(this.mask, "zIndex", maskZ);
+		}
+
+	}
+};
+
+// END BUILT-IN PROPERTY EVENT HANDLERS //
+
+/**
+* Builds the wrapping container around the Panel that is used for positioning the shadow and matte underlays. The container element is assigned to a  local instance variable called container, and the element is reinserted inside of it.
+* @method buildWrapper
+*/
+YAHOO.widget.Panel.prototype.buildWrapper = function() {
+	var elementParent = this.element.parentNode;
+	var originalElement = this.element;
+
+	var wrapper = document.createElement("DIV");
+	wrapper.className = YAHOO.widget.Panel.CSS_PANEL_CONTAINER;
+	wrapper.id = originalElement.id + "_c";
+
+	if (elementParent) {
+		elementParent.insertBefore(wrapper, originalElement);
+	}
+
+	wrapper.appendChild(originalElement);
+
+	this.element = wrapper;
+	this.innerElement = originalElement;
+
+	YAHOO.util.Dom.setStyle(this.innerElement, "visibility", "inherit");
+};
+
+/**
+* Adjusts the size of the shadow based on the size of the element.
+* @method sizeUnderlay
+*/
+YAHOO.widget.Panel.prototype.sizeUnderlay = function() {
+	if (this.underlay && this.browser != "gecko" && this.browser != "safari") {
+		this.underlay.style.width = this.innerElement.offsetWidth + "px";
+		this.underlay.style.height = this.innerElement.offsetHeight + "px";
+	}
+};
+
+/**
+* Event handler fired when the resize monitor element is resized.
+* @method onDomResize
+* @param {DOMEvent} e	The resize DOM event
+* @param {Object} obj	The scope object
+*/
+YAHOO.widget.Panel.prototype.onDomResize = function(e, obj) {
+	YAHOO.widget.Panel.superclass.onDomResize.call(this, e, obj);
+	var me = this;
+	setTimeout(function() {
+		me.sizeUnderlay();
+	}, 0);
+};
+
+/**
+* Registers the Panel's header for drag & drop capability.
+* @method registerDragDrop
+*/
+YAHOO.widget.Panel.prototype.registerDragDrop = function() {
+	if (this.header) {
+		this.dd = new YAHOO.util.DD(this.element.id, this.id);
+
+		if (! this.header.id) {
+			this.header.id = this.id + "_h";
+		}
+
+		var me = this;
+
+		this.dd.startDrag = function() {
+
+			if (me.browser == "ie") {
+				YAHOO.util.Dom.addClass(me.element,"drag");
+			}
+
+			if (me.cfg.getProperty("constraintoviewport")) {
+				var offsetHeight = me.element.offsetHeight;
+				var offsetWidth = me.element.offsetWidth;
+
+				var viewPortWidth = YAHOO.util.Dom.getViewportWidth();
+				var viewPortHeight = YAHOO.util.Dom.getViewportHeight();
+
+				var scrollX = window.scrollX || document.documentElement.scrollLeft;
+				var scrollY = window.scrollY || document.documentElement.scrollTop;
+
+				var topConstraint = scrollY + 10;
+				var leftConstraint = scrollX + 10;
+				var bottomConstraint = scrollY + viewPortHeight - offsetHeight - 10;
+				var rightConstraint = scrollX + viewPortWidth - offsetWidth - 10;
+
+				this.minX = leftConstraint;
+				this.maxX = rightConstraint;
+				this.constrainX = true;
+
+				this.minY = topConstraint;
+				this.maxY = bottomConstraint;
+				this.constrainY = true;
+			} else {
+				this.constrainX = false;
+				this.constrainY = false;
+			}
+
+			me.dragEvent.fire("startDrag", arguments);
+		};
+
+		this.dd.onDrag = function() {
+			me.syncPosition();
+			me.cfg.refireEvent("iframe");
+			if (this.platform == "mac" && this.browser == "gecko") {
+				this.showMacGeckoScrollbars();
+			}
+
+			me.dragEvent.fire("onDrag", arguments);
+		};
+
+		this.dd.endDrag = function() {
+			if (me.browser == "ie") {
+				YAHOO.util.Dom.removeClass(me.element,"drag");
+			}
+
+			me.dragEvent.fire("endDrag", arguments);
+		};
+
+		this.dd.setHandleElId(this.header.id);
+		this.dd.addInvalidHandleType("INPUT");
+		this.dd.addInvalidHandleType("SELECT");
+		this.dd.addInvalidHandleType("TEXTAREA");
+	}
+};
+
+/**
+* Builds the mask that is laid over the document when the Panel is configured to be modal.
+* @method buildMask
+*/
+YAHOO.widget.Panel.prototype.buildMask = function() {
+	if (! this.mask) {
+		this.mask = document.createElement("DIV");
+		this.mask.id = this.id + "_mask";
+		this.mask.className = "mask";
+		this.mask.innerHTML = "&#160;";
+
+		var maskClick = function(e, obj) {
+			YAHOO.util.Event.stopEvent(e);
+		};
+
+		var firstChild = document.body.firstChild;
+		if (firstChild)	{
+			document.body.insertBefore(this.mask, document.body.firstChild);
+		} else {
+			document.body.appendChild(this.mask);
+		}
+	}
+};
+
+/**
+* Hides the modality mask.
+* @method hideMask
+*/
+YAHOO.widget.Panel.prototype.hideMask = function() {
+	if (this.cfg.getProperty("modal") && this.mask) {
+		this.mask.style.display = "none";
+		this.hideMaskEvent.fire();
+		YAHOO.util.Dom.removeClass(document.body, "masked");
+	}
+};
+
+/**
+* Shows the modality mask.
+* @method showMask
+*/
+YAHOO.widget.Panel.prototype.showMask = function() {
+	if (this.cfg.getProperty("modal") && this.mask) {
+		YAHOO.util.Dom.addClass(document.body, "masked");
+		this.sizeMask();
+		this.mask.style.display = "block";
+		this.showMaskEvent.fire();
+	}
+};
+
+/**
+* Sets the size of the modality mask to cover the entire scrollable area of the document
+* @method sizeMask
+*/
+YAHOO.widget.Panel.prototype.sizeMask = function() {
+	if (this.mask) {
+		this.mask.style.height = YAHOO.util.Dom.getDocumentHeight()+"px";
+		this.mask.style.width = YAHOO.util.Dom.getDocumentWidth()+"px";
+	}
+};
+
+/**
+* Renders the Panel by inserting the elements that are not already in the main Panel into their correct places. Optionally appends the Panel to the specified node prior to the render's execution. NOTE: For Panels without existing markup, the appendToNode argument is REQUIRED. If this argument is ommitted and the current element is not present in the document, the function will return false, indicating that the render was a failure.
+* @method render
+* @param {String}	appendToNode	The element id to which the Module should be appended to prior to rendering <em>OR</em>
+* @param {HTMLElement}	appendToNode	The element to which the Module should be appended to prior to rendering
+* @return {boolean} Success or failure of the render
+*/
+YAHOO.widget.Panel.prototype.render = function(appendToNode) {
+	return YAHOO.widget.Panel.superclass.render.call(this, appendToNode, this.innerElement);
+};
+
+/**
+* Returns a String representation of the object.
+* @method toString
+* @return {String} The string representation of the Panel.
+*/
+YAHOO.widget.Panel.prototype.toString = function() {
+	return "Panel " + this.id;
+};
+
+/**
+* Dialog is an implementation of Panel that can be used to submit form data. Built-in functionality for buttons with event handlers is included, and button sets can be build dynamically, or the preincluded ones for Submit/Cancel and OK/Cancel can be utilized. Forms can be processed in 3 ways -- via an asynchronous Connection utility call, a simple form POST or GET, or manually.
+* @namespace YAHOO.widget
+* @class Dialog
+* @extends YAHOO.widget.Panel
+* @constructor
+* @param {String}	el	The element ID representing the Dialog <em>OR</em>
+* @param {HTMLElement}	el	The element representing the Dialog
+* @param {Object}	userConfig	The configuration object literal containing the configuration that should be set for this Dialog. See configuration documentation for more details.
+*/
+YAHOO.widget.Dialog = function(el, userConfig) {
+	YAHOO.widget.Dialog.superclass.constructor.call(this, el, userConfig);
+};
+
+YAHOO.extend(YAHOO.widget.Dialog, YAHOO.widget.Panel);
+
+/**
+* Constant representing the default CSS class used for a Dialog
+* @property YAHOO.widget.Dialog.CSS_DIALOG
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Dialog.CSS_DIALOG = "dialog";
+
+/**
+* Initializes the class's configurable properties which can be changed using the Dialog's Config object (cfg).
+* @method initDefaultConfig
+*/
+YAHOO.widget.Dialog.prototype.initDefaultConfig = function() {
+	YAHOO.widget.Dialog.superclass.initDefaultConfig.call(this);
+
+	/**
+	* The internally maintained callback object for use with the Connection utility
+	* @property callback
+	* @type Object
+	*/
+	this.callback = {
+		/**
+		* The function to execute upon success of the Connection submission
+		* @property callback.success
+		* @type Function
+		*/
+		success : null,
+		/**
+		* The function to execute upon failure of the Connection submission
+		* @property callback.failure
+		* @type Function
+		*/
+		failure : null,
+		/**
+		* The arbitraty argument or arguments to pass to the Connection callback functions
+		* @property callback.argument
+		* @type Object
+		*/
+		argument: null
+	};
+
+	// Add form dialog config properties //
+
+	/**
+	* The method to use for posting the Dialog's form. Possible values are "async", "form", and "manual".
+	* @config postmethod
+	* @type String
+	* @default async
+	*/
+	this.cfg.addProperty("postmethod", { value:"async", handler:this.configPostMethod, validator:function(val) {
+													if (val != "form" && val != "async" && val != "none" && val != "manual") {
+														return false;
+													} else {
+														return true;
+													}
+												} });
+
+	/**
+	* Object literal(s) defining the buttons for the Dialog's footer.
+	* @config buttons
+	* @type Object[]
+	* @default "none"
+	*/
+	this.cfg.addProperty("buttons",		{ value:"none",	handler:this.configButtons } );
+};
+
+/**
+* Initializes the custom events for Dialog which are fired automatically at appropriate times by the Dialog class.
+* @method initEvents
+*/
+YAHOO.widget.Dialog.prototype.initEvents = function() {
+	YAHOO.widget.Dialog.superclass.initEvents.call(this);
+
+	/**
+	* CustomEvent fired prior to submission
+	* @event beforeSumitEvent
+	*/
+	this.beforeSubmitEvent	= new YAHOO.util.CustomEvent("beforeSubmit");
+
+	/**
+	* CustomEvent fired after submission
+	* @event submitEvent
+	*/
+	this.submitEvent		= new YAHOO.util.CustomEvent("submit");
+
+	/**
+	* CustomEvent fired prior to manual submission
+	* @event manualSubmitEvent
+	*/
+	this.manualSubmitEvent	= new YAHOO.util.CustomEvent("manualSubmit");
+
+	/**
+	* CustomEvent fired prior to asynchronous submission
+	* @event asyncSubmitEvent
+	*/
+	this.asyncSubmitEvent	= new YAHOO.util.CustomEvent("asyncSubmit");
+
+	/**
+	* CustomEvent fired prior to form-based submission
+	* @event formSubmitEvent
+	*/
+	this.formSubmitEvent	= new YAHOO.util.CustomEvent("formSubmit");
+
+	/**
+	* CustomEvent fired after cancel
+	* @event cancelEvent
+	*/
+	this.cancelEvent		= new YAHOO.util.CustomEvent("cancel");
+};
+
+/**
+* The Dialog initialization method, which is executed for Dialog and all of its subclasses. This method is automatically called by the constructor, and  sets up all DOM references for pre-existing markup, and creates required markup if it is not already present.
+* @method init
+* @param {String}	el	The element ID representing the Dialog <em>OR</em>
+* @param {HTMLElement}	el	The element representing the Dialog
+* @param {Object}	userConfig	The configuration object literal containing the configuration that should be set for this Dialog. See configuration documentation for more details.
+*/
+YAHOO.widget.Dialog.prototype.init = function(el, userConfig) {
+	YAHOO.widget.Dialog.superclass.init.call(this, el/*, userConfig*/);  // Note that we don't pass the user config in here yet because we only want it executed once, at the lowest subclass level
+
+	this.beforeInitEvent.fire(YAHOO.widget.Dialog);
+
+	YAHOO.util.Dom.addClass(this.element, YAHOO.widget.Dialog.CSS_DIALOG);
+
+	this.cfg.setProperty("visible", false);
+
+	if (userConfig) {
+		this.cfg.applyConfig(userConfig, true);
+	}
+
+	this.showEvent.subscribe(this.focusFirst, this, true);
+	this.beforeHideEvent.subscribe(this.blurButtons, this, true);
+
+	this.beforeRenderEvent.subscribe(function() {
+		var buttonCfg = this.cfg.getProperty("buttons");
+		if (buttonCfg && buttonCfg != "none") {
+			if (! this.footer) {
+				this.setFooter("");
+			}
+		}
+	}, this, true);
+
+	this.initEvent.fire(YAHOO.widget.Dialog);
+};
+
+/**
+* Performs the submission of the Dialog form depending on the value of "postmethod" property.
+* @method doSubmit
+*/
+YAHOO.widget.Dialog.prototype.doSubmit = function() {
+	var pm = this.cfg.getProperty("postmethod");
+	switch (pm) {
+		case "async":
+			var method = this.form.getAttribute("method") || 'POST';
+			method = method.toUpperCase();
+			YAHOO.util.Connect.setForm(this.form);
+			var cObj = YAHOO.util.Connect.asyncRequest(method, this.form.getAttribute("action"), this.callback);
+			this.asyncSubmitEvent.fire();
+			break;
+		case "form":
+			this.form.submit();
+			this.formSubmitEvent.fire();
+			break;
+		case "none":
+		case "manual":
+			this.manualSubmitEvent.fire();
+			break;
+	}
+};
+
+/**
+* Prepares the Dialog's internal FORM object, creating one if one is not currently present.
+* @method registerForm
+*/
+YAHOO.widget.Dialog.prototype.registerForm = function() {
+	var form = this.element.getElementsByTagName("FORM")[0];
+
+	if (! form) {
+		var formHTML = "<form name=\"frm_" + this.id + "\" action=\"\"></form>";
+		this.body.innerHTML += formHTML;
+		form = this.element.getElementsByTagName("FORM")[0];
+	}
+
+	this.firstFormElement = function() {
+		for (var f=0;f<form.elements.length;f++ ) {
+			var el = form.elements[f];
+			if (el.focus) {
+				if (el.type && el.type != "hidden") {
+					return el;
+				}
+			}
+		}
+		return null;
+	}();
+
+	this.lastFormElement = function() {
+		for (var f=form.elements.length-1;f>=0;f-- ) {
+			var el = form.elements[f];
+			if (el.focus) {
+				if (el.type && el.type != "hidden") {
+					return el;
+				}
+			}
+		}
+		return null;
+	}();
+
+	this.form = form;
+
+	if (this.cfg.getProperty("modal") && this.form) {
+
+		var me = this;
+
+		var firstElement = this.firstFormElement || this.firstButton;
+		if (firstElement) {
+			this.preventBackTab = new YAHOO.util.KeyListener(firstElement, { shift:true, keys:9 }, {fn:me.focusLast, scope:me, correctScope:true} );
+			this.showEvent.subscribe(this.preventBackTab.enable, this.preventBackTab, true);
+			this.hideEvent.subscribe(this.preventBackTab.disable, this.preventBackTab, true);
+		}
+
+		var lastElement = this.lastButton || this.lastFormElement;
+		if (lastElement) {
+			this.preventTabOut = new YAHOO.util.KeyListener(lastElement, { shift:false, keys:9 }, {fn:me.focusFirst, scope:me, correctScope:true} );
+			this.showEvent.subscribe(this.preventTabOut.enable, this.preventTabOut, true);
+			this.hideEvent.subscribe(this.preventTabOut.disable, this.preventTabOut, true);
+		}
+	}
+};
+
+// BEGIN BUILT-IN PROPERTY EVENT HANDLERS //
+
+/**
+* The default event handler for the "buttons" configuration property
+* @method configButtons
+* @param {String} type	The CustomEvent type (usually the property name)
+* @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
+* @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
+*/
+YAHOO.widget.Dialog.prototype.configButtons = function(type, args, obj) {
+	var buttons = args[0];
+	if (buttons != "none") {
+		this.buttonSpan = null;
+		this.buttonSpan = document.createElement("SPAN");
+		this.buttonSpan.className = "button-group";
+
+		for (var b=0;b<buttons.length;b++) {
+			var button = buttons[b];
+
+			var htmlButton = document.createElement("BUTTON");
+			htmlButton.setAttribute("type", "button");
+
+			if (button.isDefault) {
+				htmlButton.className = "default";
+				this.defaultHtmlButton = htmlButton;
+			}
+
+			htmlButton.appendChild(document.createTextNode(button.text));
+			YAHOO.util.Event.addListener(htmlButton, "click", button.handler, this, true);
+
+			this.buttonSpan.appendChild(htmlButton);
+			button.htmlButton = htmlButton;
+
+			if (b === 0) {
+				this.firstButton = button.htmlButton;
+			}
+
+			if (b == (buttons.length-1)) {
+				this.lastButton = button.htmlButton;
+			}
+
+		}
+
+		this.setFooter(this.buttonSpan);
+
+		this.cfg.refireEvent("iframe");
+		this.cfg.refireEvent("underlay");
+	} else { // Do cleanup
+		if (this.buttonSpan) {
+			if (this.buttonSpan.parentNode) {
+				this.buttonSpan.parentNode.removeChild(this.buttonSpan);
+			}
+
+			this.buttonSpan = null;
+			this.firstButton = null;
+			this.lastButton = null;
+			this.defaultHtmlButton = null;
+		}
+	}
+};
+
+
+/**
+* The default event handler used to focus the first field of the form when the Dialog is shown.
+* @method focusFirst
+*/
+YAHOO.widget.Dialog.prototype.focusFirst = function(type,args,obj) {
+	if (args) {
+		var e = args[1];
+		if (e) {
+			YAHOO.util.Event.stopEvent(e);
+		}
+	}
+
+	if (this.firstFormElement) {
+		this.firstFormElement.focus();
+	} else {
+		this.focusDefaultButton();
+	}
+};
+
+/**
+* Sets the focus to the last button in the button or form element in the Dialog
+* @method focusLast
+*/
+YAHOO.widget.Dialog.prototype.focusLast = function(type,args,obj) {
+	if (args) {
+		var e = args[1];
+		if (e) {
+			YAHOO.util.Event.stopEvent(e);
+		}
+	}
+
+	var buttons = this.cfg.getProperty("buttons");
+	if (buttons && buttons instanceof Array) {
+		this.focusLastButton();
+	} else {
+		if (this.lastFormElement) {
+			this.lastFormElement.focus();
+		}
+	}
+};
+
+/**
+* Sets the focus to the button that is designated as the default. By default, his handler is executed when the show event is fired.
+* @method focusDefaultButton
+*/
+YAHOO.widget.Dialog.prototype.focusDefaultButton = function() {
+	if (this.defaultHtmlButton) {
+		this.defaultHtmlButton.focus();
+	}
+};
+
+/**
+* Blurs all the html buttons
+* @method blurButtons
+*/
+YAHOO.widget.Dialog.prototype.blurButtons = function() {
+	var buttons = this.cfg.getProperty("buttons");
+	if (buttons && buttons instanceof Array) {
+		var html = buttons[0].htmlButton;
+		if (html) {
+			html.blur();
+		}
+	}
+};
+
+/**
+* Sets the focus to the first button in the button list
+* @method focusFirstButton
+*/
+YAHOO.widget.Dialog.prototype.focusFirstButton = function() {
+	var buttons = this.cfg.getProperty("buttons");
+	if (buttons && buttons instanceof Array) {
+		var html = buttons[0].htmlButton;
+		if (html) {
+			html.focus();
+		}
+	}
+};
+
+/**
+* Sets the focus to the first button in the button list
+* @method focusLastButton
+*/
+YAHOO.widget.Dialog.prototype.focusLastButton = function() {
+	var buttons = this.cfg.getProperty("buttons");
+	if (buttons && buttons instanceof Array) {
+		var html = buttons[buttons.length-1].htmlButton;
+		if (html) {
+			html.focus();
+		}
+	}
+};
+
+/**
+* The default event handler for the "postmethod" configuration property
+* @method configPostMethod
+* @param {String} type	The CustomEvent type (usually the property name)
+* @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
+* @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
+*/
+YAHOO.widget.Dialog.prototype.configPostMethod = function(type, args, obj) {
+	var postmethod = args[0];
+
+	this.registerForm();
+	YAHOO.util.Event.addListener(this.form, "submit", function(e) {
+														YAHOO.util.Event.stopEvent(e);
+														this.submit();
+														this.form.blur();
+													  }, this, true);
+};
+
+// END BUILT-IN PROPERTY EVENT HANDLERS //
+
+/**
+* Built-in function hook for writing a validation function that will be checked for a "true" value prior to a submit. This function, as implemented by default, always returns true, so it should be overridden if validation is necessary.
+* @method validate
+*/
+YAHOO.widget.Dialog.prototype.validate = function() {
+	return true;
+};
+
+/**
+* Executes a submit of the Dialog followed by a hide, if validation is successful.
+* @method submit
+*/
+YAHOO.widget.Dialog.prototype.submit = function() {
+	if (this.validate()) {
+		this.beforeSubmitEvent.fire();
+		this.doSubmit();
+		this.submitEvent.fire();
+		this.hide();
+		return true;
+	} else {
+		return false;
+	}
+};
+
+/**
+* Executes the cancel of the Dialog followed by a hide.
+* @method cancel
+*/
+YAHOO.widget.Dialog.prototype.cancel = function() {
+	this.cancelEvent.fire();
+	this.hide();
+};
+
+/**
+* Returns a JSON-compatible data structure representing the data currently contained in the form.
+* @method getData
+* @return {Object} A JSON object reprsenting the data of the current form.
+*/
+YAHOO.widget.Dialog.prototype.getData = function() {
+	var form = this.form;
+	var data = {};
+
+	if (form) {
+		for (var i=0;i<form.elements.length;i++) {
+			var formItem = form.elements[i];
+			if (formItem) {
+				if (formItem.tagName) { // Got a single form item
+					switch (formItem.tagName) {
+						case "INPUT":
+							switch (formItem.type) {
+								case "checkbox":
+									data[formItem.name] = formItem.checked;
+									break;
+								case "textbox":
+								case "text":
+								case "hidden":
+									data[formItem.name] = formItem.value;
+									break;
+							}
+							break;
+						case "TEXTAREA":
+							data[formItem.name] = formItem.value;
+							break;
+						case "SELECT":
+							var val = [];
+							for (var x=0;x<formItem.options.length;x++)	{
+								var option = formItem.options[x];
+								if (option.selected) {
+									var selval = option.value;
+									if (! selval || selval === "") {
+										selval = option.text;
+									}
+									val[val.length] = selval;
+								}
+							}
+							data[formItem.name] = val;
+							break;
+					}
+				} else if (formItem[0] && formItem[0].tagName) { // this is an array of form items
+					if (formItem[0].tagName == "INPUT") {
+						switch (formItem[0].type) {
+							case "radio":
+								for (var r=0; r<formItem.length; r++) {
+									var radio = formItem[r];
+									if (radio.checked) {
+										data[radio.name] = radio.value;
+										break;
+									}
+								}
+								break;
+							case "checkbox":
+								var cbArray = [];
+								for (var c=0; c<formItem.length; c++) {
+									var check = formItem[c];
+									if (check.checked) {
+										cbArray[cbArray.length] = check.value;
+									}
+								}
+								data[formItem[0].name] = cbArray;
+								break;
+						}
+					}
+				}
+			}
+		}
+	}
+	return data;
+};
+
+/**
+* Returns a string representation of the object.
+* @method toString
+* @return {String}	The string representation of the Dialog
+*/
+YAHOO.widget.Dialog.prototype.toString = function() {
+	return "Dialog " + this.id;
+};
+
+/**
+* SimpleDialog is a simple implementation of Dialog that can be used to submit a single value. Forms can be processed in 3 ways -- via an asynchronous Connection utility call, a simple form POST or GET, or manually.
+* @namespace YAHOO.widget
+* @class SimpleDialog
+* @extends YAHOO.widget.Dialog
+* @constructor
+* @param {String}	el	The element ID representing the SimpleDialog <em>OR</em>
+* @param {HTMLElement}	el	The element representing the SimpleDialog
+* @param {Object}	userConfig	The configuration object literal containing the configuration that should be set for this SimpleDialog. See configuration documentation for more details.
+*/
+YAHOO.widget.SimpleDialog = function(el, userConfig) {
+	YAHOO.widget.SimpleDialog.superclass.constructor.call(this, el, userConfig);
+};
+
+YAHOO.extend(YAHOO.widget.SimpleDialog, YAHOO.widget.Dialog);
+
+/**
+* Constant for the standard network icon for a blocking action
+* @property YAHOO.widget.SimpleDialog.ICON_BLOCK
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.SimpleDialog.ICON_BLOCK = "nt/ic/ut/bsc/blck16_1.gif";
+
+/**
+* Constant for the standard network icon for alarm
+* @property YAHOO.widget.SimpleDialog.ICON_ALARM
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.SimpleDialog.ICON_ALARM = "nt/ic/ut/bsc/alrt16_1.gif";
+
+/**
+* Constant for the standard network icon for help
+* @property YAHOO.widget.SimpleDialog.ICON_HELP
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.SimpleDialog.ICON_HELP  = "nt/ic/ut/bsc/hlp16_1.gif";
+
+/**
+* Constant for the standard network icon for info
+* @property YAHOO.widget.SimpleDialog.ICON_INFO
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.SimpleDialog.ICON_INFO  = "nt/ic/ut/bsc/info16_1.gif";
+
+/**
+* Constant for the standard network icon for warn
+* @property YAHOO.widget.SimpleDialog.ICON_WARN
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.SimpleDialog.ICON_WARN  = "nt/ic/ut/bsc/warn16_1.gif";
+
+/**
+* Constant for the standard network icon for a tip
+* @property YAHOO.widget.SimpleDialog.ICON_TIP
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.SimpleDialog.ICON_TIP   = "nt/ic/ut/bsc/tip16_1.gif";
+
+/**
+* Constant representing the default CSS class used for a SimpleDialog
+* @property YAHOO.widget.SimpleDialog.CSS_SIMPLEDIALOG
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.SimpleDialog.CSS_SIMPLEDIALOG = "simple-dialog";
+
+/**
+* Initializes the class's configurable properties which can be changed using the SimpleDialog's Config object (cfg).
+* @method initDefaultConfig
+*/
+YAHOO.widget.SimpleDialog.prototype.initDefaultConfig = function() {
+	YAHOO.widget.SimpleDialog.superclass.initDefaultConfig.call(this);
+
+	// Add dialog config properties //
+
+	/**
+	* Sets the informational icon for the SimpleDialog
+	* @config icon
+	* @type String
+	* @default "none"
+	*/
+	this.cfg.addProperty("icon",	{ value:"none",	handler:this.configIcon, suppressEvent:true } );
+
+	/**
+	* Sets the text for the SimpleDialog
+	* @config text
+	* @type String
+	* @default ""
+	*/
+	this.cfg.addProperty("text",	{ value:"", handler:this.configText, suppressEvent:true, supercedes:["icon"] } );
+};
+
+
+/**
+* The SimpleDialog initialization method, which is executed for SimpleDialog and all of its subclasses. This method is automatically called by the constructor, and  sets up all DOM references for pre-existing markup, and creates required markup if it is not already present.
+* @method init
+* @param {String}	el	The element ID representing the SimpleDialog <em>OR</em>
+* @param {HTMLElement}	el	The element representing the SimpleDialog
+* @param {Object}	userConfig	The configuration object literal containing the configuration that should be set for this SimpleDialog. See configuration documentation for more details.
+*/
+YAHOO.widget.SimpleDialog.prototype.init = function(el, userConfig) {
+	YAHOO.widget.SimpleDialog.superclass.init.call(this, el/*, userConfig*/);  // Note that we don't pass the user config in here yet because we only want it executed once, at the lowest subclass level
+
+	this.beforeInitEvent.fire(YAHOO.widget.SimpleDialog);
+
+	YAHOO.util.Dom.addClass(this.element, YAHOO.widget.SimpleDialog.CSS_SIMPLEDIALOG);
+
+	this.cfg.queueProperty("postmethod", "manual");
+
+	if (userConfig) {
+		this.cfg.applyConfig(userConfig, true);
+	}
+
+	this.beforeRenderEvent.subscribe(function() {
+		if (! this.body) {
+			this.setBody("");
+		}
+	}, this, true);
+
+	this.initEvent.fire(YAHOO.widget.SimpleDialog);
+
+};
+/**
+* Prepares the SimpleDialog's internal FORM object, creating one if one is not currently present, and adding the value hidden field.
+* @method registerForm
+*/
+YAHOO.widget.SimpleDialog.prototype.registerForm = function() {
+	YAHOO.widget.SimpleDialog.superclass.registerForm.call(this);
+	this.form.innerHTML += "<input type=\"hidden\" name=\"" + this.id + "\" value=\"\"/>";
+};
+
+// BEGIN BUILT-IN PROPERTY EVENT HANDLERS //
+
+/**
+* Fired when the "icon" property is set.
+* @method configIcon
+* @param {String} type	The CustomEvent type (usually the property name)
+* @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
+* @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
+*/
+YAHOO.widget.SimpleDialog.prototype.configIcon = function(type,args,obj) {
+	var icon = args[0];
+	if (icon && icon != "none") {
+		var iconHTML = "<img src=\"" + this.imageRoot + icon + "\" class=\"icon\" />";
+		this.body.innerHTML = iconHTML + this.body.innerHTML;
+	}
+};
+
+/**
+* Fired when the "text" property is set.
+* @method configText
+* @param {String} type	The CustomEvent type (usually the property name)
+* @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
+* @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
+*/
+YAHOO.widget.SimpleDialog.prototype.configText = function(type,args,obj) {
+	var text = args[0];
+	if (text) {
+		this.setBody(text);
+		this.cfg.refireEvent("icon");
+	}
+};
+// END BUILT-IN PROPERTY EVENT HANDLERS //
+
+/**
+* Returns a string representation of the object.
+* @method toString
+* @return {String}	The string representation of the SimpleDialog
+*/
+YAHOO.widget.SimpleDialog.prototype.toString = function() {
+	return "SimpleDialog " + this.id;
+};
+
+/**
+* ContainerEffect encapsulates animation transitions that are executed when an Overlay is shown or hidden.
+* @namespace YAHOO.widget
+* @class ContainerEffect
+* @constructor
+* @param {YAHOO.widget.Overlay}	overlay		The Overlay that the animation should be associated with
+* @param {Object}	attrIn		The object literal representing the animation arguments to be used for the animate-in transition. The arguments for this literal are: attributes(object, see YAHOO.util.Anim for description), duration(Number), and method(i.e. YAHOO.util.Easing.easeIn).
+* @param {Object}	attrOut		The object literal representing the animation arguments to be used for the animate-out transition. The arguments for this literal are: attributes(object, see YAHOO.util.Anim for description), duration(Number), and method(i.e. YAHOO.util.Easing.easeIn).
+* @param {HTMLElement}	targetElement	Optional. The target element that should be animated during the transition. Defaults to overlay.element.
+* @param {class}	Optional. The animation class to instantiate. Defaults to YAHOO.util.Anim. Other options include YAHOO.util.Motion.
+*/
+YAHOO.widget.ContainerEffect = function(overlay, attrIn, attrOut, targetElement, animClass) {
+	if (! animClass) {
+		animClass = YAHOO.util.Anim;
+	}
+
+	/**
+	* The overlay to animate
+	* @property overlay
+	* @type YAHOO.widget.Overlay
+	*/
+	this.overlay = overlay;
+	/**
+	* The animation attributes to use when transitioning into view
+	* @property attrIn
+	* @type Object
+	*/
+	this.attrIn = attrIn;
+	/**
+	* The animation attributes to use when transitioning out of view
+	* @property attrOut
+	* @type Object
+	*/
+	this.attrOut = attrOut;
+	/**
+	* The target element to be animated
+	* @property targetElement
+	* @type HTMLElement
+	*/
+	this.targetElement = targetElement || overlay.element;
+	/**
+	* The animation class to use for animating the overlay
+	* @property animClass
+	* @type class
+	*/
+	this.animClass = animClass;
+};
+
+/**
+* Initializes the animation classes and events.
+* @method init
+*/
+YAHOO.widget.ContainerEffect.prototype.init = function() {
+	this.beforeAnimateInEvent = new YAHOO.util.CustomEvent("beforeAnimateIn");
+	this.beforeAnimateOutEvent = new YAHOO.util.CustomEvent("beforeAnimateOut");
+
+	this.animateInCompleteEvent = new YAHOO.util.CustomEvent("animateInComplete");
+	this.animateOutCompleteEvent = new YAHOO.util.CustomEvent("animateOutComplete");
+
+	this.animIn = new this.animClass(this.targetElement, this.attrIn.attributes, this.attrIn.duration, this.attrIn.method);
+	this.animIn.onStart.subscribe(this.handleStartAnimateIn, this);
+	this.animIn.onTween.subscribe(this.handleTweenAnimateIn, this);
+	this.animIn.onComplete.subscribe(this.handleCompleteAnimateIn, this);
+
+	this.animOut = new this.animClass(this.targetElement, this.attrOut.attributes, this.attrOut.duration, this.attrOut.method);
+	this.animOut.onStart.subscribe(this.handleStartAnimateOut, this);
+	this.animOut.onTween.subscribe(this.handleTweenAnimateOut, this);
+	this.animOut.onComplete.subscribe(this.handleCompleteAnimateOut, this);
+};
+
+/**
+* Triggers the in-animation.
+* @method animateIn
+*/
+YAHOO.widget.ContainerEffect.prototype.animateIn = function() {
+	this.beforeAnimateInEvent.fire();
+	this.animIn.animate();
+};
+
+/**
+* Triggers the out-animation.
+* @method animateOut
+*/
+YAHOO.widget.ContainerEffect.prototype.animateOut = function() {
+	this.beforeAnimateOutEvent.fire();
+	this.animOut.animate();
+};
+
+/**
+* The default onStart handler for the in-animation.
+* @method handleStartAnimateIn
+* @param {String} type	The CustomEvent type
+* @param {Object[]}	args	The CustomEvent arguments
+* @param {Object} obj	The scope object
+*/
+YAHOO.widget.ContainerEffect.prototype.handleStartAnimateIn = function(type, args, obj) { };
+/**
+* The default onTween handler for the in-animation.
+* @method handleTweenAnimateIn
+* @param {String} type	The CustomEvent type
+* @param {Object[]}	args	The CustomEvent arguments
+* @param {Object} obj	The scope object
+*/
+YAHOO.widget.ContainerEffect.prototype.handleTweenAnimateIn = function(type, args, obj) { };
+/**
+* The default onComplete handler for the in-animation.
+* @method handleCompleteAnimateIn
+* @param {String} type	The CustomEvent type
+* @param {Object[]}	args	The CustomEvent arguments
+* @param {Object} obj	The scope object
+*/
+YAHOO.widget.ContainerEffect.prototype.handleCompleteAnimateIn = function(type, args, obj) { };
+
+/**
+* The default onStart handler for the out-animation.
+* @method handleStartAnimateOut
+* @param {String} type	The CustomEvent type
+* @param {Object[]}	args	The CustomEvent arguments
+* @param {Object} obj	The scope object
+*/
+YAHOO.widget.ContainerEffect.prototype.handleStartAnimateOut = function(type, args, obj) { };
+/**
+* The default onTween handler for the out-animation.
+* @method handleTweenAnimateOut
+* @param {String} type	The CustomEvent type
+* @param {Object[]}	args	The CustomEvent arguments
+* @param {Object} obj	The scope object
+*/
+YAHOO.widget.ContainerEffect.prototype.handleTweenAnimateOut = function(type, args, obj) { };
+/**
+* The default onComplete handler for the out-animation.
+* @method handleCompleteAnimateOut
+* @param {String} type	The CustomEvent type
+* @param {Object[]}	args	The CustomEvent arguments
+* @param {Object} obj	The scope object
+*/
+YAHOO.widget.ContainerEffect.prototype.handleCompleteAnimateOut = function(type, args, obj) { };
+
+/**
+* Returns a string representation of the object.
+* @method toString
+* @return {String}	The string representation of the ContainerEffect
+*/
+YAHOO.widget.ContainerEffect.prototype.toString = function() {
+	var output = "ContainerEffect";
+	if (this.overlay) {
+		output += " [" + this.overlay.toString() + "]";
+	}
+	return output;
+};
+
+/**
+* A pre-configured ContainerEffect instance that can be used for fading an overlay in and out.
+* @method FADE
+* @static
+* @param {Overlay}	overlay	The Overlay object to animate
+* @param {Number}	dur	The duration of the animation
+* @return {ContainerEffect}	The configured ContainerEffect object
+*/
+YAHOO.widget.ContainerEffect.FADE = function(overlay, dur) {
+	var fade = new YAHOO.widget.ContainerEffect(overlay, { attributes:{opacity: {from:0, to:1}}, duration:dur, method:YAHOO.util.Easing.easeIn }, { attributes:{opacity: {to:0}}, duration:dur, method:YAHOO.util.Easing.easeOut}, overlay.element );
+
+	fade.handleStartAnimateIn = function(type,args,obj) {
+		YAHOO.util.Dom.addClass(obj.overlay.element, "hide-select");
+
+		if (! obj.overlay.underlay) {
+			obj.overlay.cfg.refireEvent("underlay");
+		}
+
+		if (obj.overlay.underlay) {
+			obj.initialUnderlayOpacity = YAHOO.util.Dom.getStyle(obj.overlay.underlay, "opacity");
+			obj.overlay.underlay.style.filter = null;
+		}
+
+		YAHOO.util.Dom.setStyle(obj.overlay.element, "visibility", "visible");
+		YAHOO.util.Dom.setStyle(obj.overlay.element, "opacity", 0);
+	};
+
+	fade.handleCompleteAnimateIn = function(type,args,obj) {
+		YAHOO.util.Dom.removeClass(obj.overlay.element, "hide-select");
+
+		if (obj.overlay.element.style.filter) {
+			obj.overlay.element.style.filter = null;
+		}
+
+		if (obj.overlay.underlay) {
+			YAHOO.util.Dom.setStyle(obj.overlay.underlay, "opacity", obj.initialUnderlayOpacity);
+		}
+
+		obj.overlay.cfg.refireEvent("iframe");
+		obj.animateInCompleteEvent.fire();
+	};
+
+	fade.handleStartAnimateOut = function(type, args, obj) {
+		YAHOO.util.Dom.addClass(obj.overlay.element, "hide-select");
+
+		if (obj.overlay.underlay) {
+			obj.overlay.underlay.style.filter = null;
+		}
+	};
+
+	fade.handleCompleteAnimateOut =  function(type, args, obj) {
+		YAHOO.util.Dom.removeClass(obj.overlay.element, "hide-select");
+		if (obj.overlay.element.style.filter) {
+			obj.overlay.element.style.filter = null;
+		}
+		YAHOO.util.Dom.setStyle(obj.overlay.element, "visibility", "hidden");
+		YAHOO.util.Dom.setStyle(obj.overlay.element, "opacity", 1);
+
+		obj.overlay.cfg.refireEvent("iframe");
+
+		obj.animateOutCompleteEvent.fire();
+	};
+
+	fade.init();
+	return fade;
+};
+
+
+/**
+* A pre-configured ContainerEffect instance that can be used for sliding an overlay in and out.
+* @method SLIDE
+* @static
+* @param {Overlay}	overlay	The Overlay object to animate
+* @param {Number}	dur	The duration of the animation
+* @return {ContainerEffect}	The configured ContainerEffect object
+*/
+YAHOO.widget.ContainerEffect.SLIDE = function(overlay, dur) {
+	var x = overlay.cfg.getProperty("x") || YAHOO.util.Dom.getX(overlay.element);
+	var y = overlay.cfg.getProperty("y") || YAHOO.util.Dom.getY(overlay.element);
+
+	var clientWidth = YAHOO.util.Dom.getClientWidth();
+	var offsetWidth = overlay.element.offsetWidth;
+
+	var slide = new YAHOO.widget.ContainerEffect(overlay, {
+															attributes:{ points: { to:[x, y] } },
+															duration:dur,
+															method:YAHOO.util.Easing.easeIn
+														},
+														{
+															attributes:{ points: { to:[(clientWidth+25), y] } },
+															duration:dur,
+															method:YAHOO.util.Easing.easeOut
+														},
+														overlay.element,
+														YAHOO.util.Motion);
+
+
+	slide.handleStartAnimateIn = function(type,args,obj) {
+		obj.overlay.element.style.left = (-25-offsetWidth) + "px";
+		obj.overlay.element.style.top  = y + "px";
+	};
+
+	slide.handleTweenAnimateIn = function(type, args, obj) {
+
+
+		var pos = YAHOO.util.Dom.getXY(obj.overlay.element);
+
+		var currentX = pos[0];
+		var currentY = pos[1];
+
+		if (YAHOO.util.Dom.getStyle(obj.overlay.element, "visibility") == "hidden" && currentX < x) {
+			YAHOO.util.Dom.setStyle(obj.overlay.element, "visibility", "visible");
+		}
+
+		obj.overlay.cfg.setProperty("xy", [currentX,currentY], true);
+		obj.overlay.cfg.refireEvent("iframe");
+	};
+
+	slide.handleCompleteAnimateIn = function(type, args, obj) {
+		obj.overlay.cfg.setProperty("xy", [x,y], true);
+		obj.startX = x;
+		obj.startY = y;
+		obj.overlay.cfg.refireEvent("iframe");
+		obj.animateInCompleteEvent.fire();
+	};
+
+	slide.handleStartAnimateOut = function(type, args, obj) {
+		var vw = YAHOO.util.Dom.getViewportWidth();
+
+		var pos = YAHOO.util.Dom.getXY(obj.overlay.element);
+
+		var yso = pos[1];
+
+		var currentTo = obj.animOut.attributes.points.to;
+		obj.animOut.attributes.points.to = [(vw+25), yso];
+	};
+
+	slide.handleTweenAnimateOut = function(type, args, obj) {
+		var pos = YAHOO.util.Dom.getXY(obj.overlay.element);
+
+		var xto = pos[0];
+		var yto = pos[1];
+
+		obj.overlay.cfg.setProperty("xy", [xto,yto], true);
+		obj.overlay.cfg.refireEvent("iframe");
+	};
+
+	slide.handleCompleteAnimateOut = function(type, args, obj) {
+		YAHOO.util.Dom.setStyle(obj.overlay.element, "visibility", "hidden");
+
+		obj.overlay.cfg.setProperty("xy", [x,y]);
+		obj.animateOutCompleteEvent.fire();
+	};
+
+	slide.init();
+	return slide;
+};
\ No newline at end of file

Added: jifty/branches/schema-plugins/share/web/static/js/yui/dom.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/yui/dom.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,893 @@
+/*
+Copyright (c) 2006, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.net/yui/license.txt
+version: 0.12.1
+*/
+
+/**
+ * The dom module provides helper methods for manipulating Dom elements.
+ * @module dom
+ *
+ */
+
+(function() {
+    var Y = YAHOO.util,     // internal shorthand
+        getStyle,           // for load time browser branching
+        setStyle,           // ditto
+        id_counter = 0,     // for use with generateId
+        propertyCache = {}; // for faster hyphen converts
+
+    // brower detection
+    var ua = navigator.userAgent.toLowerCase(),
+        isOpera = (ua.indexOf('opera') > -1),
+        isSafari = (ua.indexOf('safari') > -1),
+        isGecko = (!isOpera && !isSafari && ua.indexOf('gecko') > -1),
+        isIE = (!isOpera && ua.indexOf('msie') > -1);
+
+    // regex cache
+    var patterns = {
+        HYPHEN: /(-[a-z])/i
+    };
+
+
+    var toCamel = function(property) {
+        if ( !patterns.HYPHEN.test(property) ) {
+            return property; // no hyphens
+        }
+
+        if (propertyCache[property]) { // already converted
+            return propertyCache[property];
+        }
+
+        while( patterns.HYPHEN.exec(property) ) {
+            property = property.replace(RegExp.$1,
+                    RegExp.$1.substr(1).toUpperCase());
+        }
+
+        propertyCache[property] = property;
+        return property;
+        //return property.replace(/-([a-z])/gi, function(m0, m1) {return m1.toUpperCase()}) // cant use function as 2nd arg yet due to safari bug
+    };
+
+    // branching at load instead of runtime
+    if (document.defaultView && document.defaultView.getComputedStyle) { // W3C DOM method
+        getStyle = function(el, property) {
+            var value = null;
+
+            var computed = document.defaultView.getComputedStyle(el, '');
+            if (computed) { // test computed before touching for safari
+                value = computed[toCamel(property)];
+            }
+
+            return el.style[property] || value;
+        };
+    } else if (document.documentElement.currentStyle && isIE) { // IE method
+        getStyle = function(el, property) {
+            switch( toCamel(property) ) {
+                case 'opacity' :// IE opacity uses filter
+                    var val = 100;
+                    try { // will error if no DXImageTransform
+                        val = el.filters['DXImageTransform.Microsoft.Alpha'].opacity;
+
+                    } catch(e) {
+                        try { // make sure its in the document
+                            val = el.filters('alpha').opacity;
+                        } catch(e) {
+                        }
+                    }
+                    return val / 100;
+                    break;
+                default:
+                    // test currentStyle before touching
+                    var value = el.currentStyle ? el.currentStyle[property] : null;
+                    return ( el.style[property] || value );
+            }
+        };
+    } else { // default to inline only
+        getStyle = function(el, property) { return el.style[property]; };
+    }
+
+    if (isIE) {
+        setStyle = function(el, property, val) {
+            switch (property) {
+                case 'opacity':
+                    if ( typeof el.style.filter == 'string' ) { // in case not appended
+                        el.style.filter = 'alpha(opacity=' + val * 100 + ')';
+
+                        if (!el.currentStyle || !el.currentStyle.hasLayout) {
+                            el.style.zoom = 1; // when no layout or cant tell
+                        }
+                    }
+                    break;
+                default:
+                el.style[property] = val;
+            }
+        };
+    } else {
+        setStyle = function(el, property, val) {
+            el.style[property] = val;
+        };
+    }
+
+    /**
+     * Provides helper methods for DOM elements.
+     * @namespace YAHOO.util
+     * @class Dom
+     */
+    YAHOO.util.Dom = {
+        /**
+         * Returns an HTMLElement reference.
+         * @method get
+         * @param {String | HTMLElement |Array} el Accepts a string to use as an ID for getting a DOM reference, an actual DOM reference, or an Array of IDs and/or HTMLElements.
+         * @return {HTMLElement | Array} A DOM reference to an HTML element or an array of HTMLElements.
+         */
+        get: function(el) {
+            if (!el) { return null; } // nothing to work with
+
+            if (typeof el != 'string' && !(el instanceof Array) ) { // assuming HTMLElement or HTMLCollection, so pass back as is
+                return el;
+            }
+
+            if (typeof el == 'string') { // ID
+                return document.getElementById(el);
+            }
+            else { // array of ID's and/or elements
+                var collection = [];
+                for (var i = 0, len = el.length; i < len; ++i) {
+                    collection[collection.length] = Y.Dom.get(el[i]);
+                }
+
+                return collection;
+            }
+
+            return null; // safety, should never happen
+        },
+
+        /**
+         * Normalizes currentStyle and ComputedStyle.
+         * @method getStyle
+         * @param {String | HTMLElement |Array} el Accepts a string to use as an ID, an actual DOM reference, or an Array of IDs and/or HTMLElements.
+         * @param {String} property The style property whose value is returned.
+         * @return {String | Array} The current value of the style property for the element(s).
+         */
+        getStyle: function(el, property) {
+            property = toCamel(property);
+
+            var f = function(element) {
+                return getStyle(element, property);
+            };
+
+            return Y.Dom.batch(el, f, Y.Dom, true);
+        },
+
+        /**
+         * Wrapper for setting style properties of HTMLElements.  Normalizes "opacity" across modern browsers.
+         * @method setStyle
+         * @param {String | HTMLElement | Array} el Accepts a string to use as an ID, an actual DOM reference, or an Array of IDs and/or HTMLElements.
+         * @param {String} property The style property to be set.
+         * @param {String} val The value to apply to the given property.
+         */
+        setStyle: function(el, property, val) {
+            property = toCamel(property);
+
+            var f = function(element) {
+                setStyle(element, property, val);
+
+            };
+
+            Y.Dom.batch(el, f, Y.Dom, true);
+        },
+
+        /**
+         * Gets the current position of an element based on page coordinates.  Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
+         * @method getXY
+         * @param {String | HTMLElement | Array} el Accepts a string to use as an ID, an actual DOM reference, or an Array of IDs and/or HTMLElements
+         * @return {Array} The XY position of the element(s)
+         */
+        getXY: function(el) {
+            var f = function(el) {
+
+            // has to be part of document to have pageXY
+                if (el.parentNode === null || el.offsetParent === null ||
+                        this.getStyle(el, 'display') == 'none') {
+                    return false;
+                }
+
+                var parentNode = null;
+                var pos = [];
+                var box;
+
+                if (el.getBoundingClientRect) { // IE
+                    box = el.getBoundingClientRect();
+                    var doc = document;
+                    if ( !this.inDocument(el) && parent.document != document) {// might be in a frame, need to get its scroll
+                        doc = parent.document;
+
+                        if ( !this.isAncestor(doc.documentElement, el) ) {
+                            return false;
+                        }
+
+                    }
+
+                    var scrollTop = Math.max(doc.documentElement.scrollTop, doc.body.scrollTop);
+                    var scrollLeft = Math.max(doc.documentElement.scrollLeft, doc.body.scrollLeft);
+
+                    return [box.left + scrollLeft, box.top + scrollTop];
+                }
+                else { // safari, opera, & gecko
+                    pos = [el.offsetLeft, el.offsetTop];
+                    parentNode = el.offsetParent;
+                    if (parentNode != el) {
+                        while (parentNode) {
+                            pos[0] += parentNode.offsetLeft;
+                            pos[1] += parentNode.offsetTop;
+                            parentNode = parentNode.offsetParent;
+                        }
+                    }
+                    if (isSafari && this.getStyle(el, 'position') == 'absolute' ) { // safari doubles in some cases
+                        pos[0] -= document.body.offsetLeft;
+                        pos[1] -= document.body.offsetTop;
+                    }
+                }
+
+                if (el.parentNode) { parentNode = el.parentNode; }
+                else { parentNode = null; }
+
+                while (parentNode && parentNode.tagName.toUpperCase() != 'BODY' && parentNode.tagName.toUpperCase() != 'HTML')
+                { // account for any scrolled ancestors
+                    if (Y.Dom.getStyle(parentNode, 'display') != 'inline') { // work around opera inline scrollLeft/Top bug
+                        pos[0] -= parentNode.scrollLeft;
+                        pos[1] -= parentNode.scrollTop;
+                    }
+
+                    if (parentNode.parentNode) {
+                        parentNode = parentNode.parentNode;
+                    } else { parentNode = null; }
+                }
+
+
+                return pos;
+            };
+
+            return Y.Dom.batch(el, f, Y.Dom, true);
+        },
+
+        /**
+         * Gets the current X position of an element based on page coordinates.  The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
+         * @method getX
+         * @param {String | HTMLElement | Array} el Accepts a string to use as an ID, an actual DOM reference, or an Array of IDs and/or HTMLElements
+         * @return {String | Array} The X position of the element(s)
+         */
+        getX: function(el) {
+            var f = function(el) {
+                return Y.Dom.getXY(el)[0];
+            };
+
+            return Y.Dom.batch(el, f, Y.Dom, true);
+        },
+
+        /**
+         * Gets the current Y position of an element based on page coordinates.  Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
+         * @method getY
+         * @param {String | HTMLElement | Array} el Accepts a string to use as an ID, an actual DOM reference, or an Array of IDs and/or HTMLElements
+         * @return {String | Array} The Y position of the element(s)
+         */
+        getY: function(el) {
+            var f = function(el) {
+                return Y.Dom.getXY(el)[1];
+            };
+
+            return Y.Dom.batch(el, f, Y.Dom, true);
+        },
+
+        /**
+         * Set the position of an html element in page coordinates, regardless of how the element is positioned.
+         * The element(s) must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
+         * @method setXY
+         * @param {String | HTMLElement | Array} el Accepts a string to use as an ID, an actual DOM reference, or an Array of IDs and/or HTMLElements
+         * @param {Array} pos Contains X & Y values for new position (coordinates are page-based)
+         * @param {Boolean} noRetry By default we try and set the position a second time if the first fails
+         */
+        setXY: function(el, pos, noRetry) {
+            var f = function(el) {
+                var style_pos = this.getStyle(el, 'position');
+                if (style_pos == 'static') { // default to relative
+                    this.setStyle(el, 'position', 'relative');
+                    style_pos = 'relative';
+                }
+
+                var pageXY = this.getXY(el);
+                if (pageXY === false) { // has to be part of doc to have pageXY
+                    return false;
+                }
+
+                var delta = [ // assuming pixels; if not we will have to retry
+                    parseInt( this.getStyle(el, 'left'), 10 ),
+                    parseInt( this.getStyle(el, 'top'), 10 )
+                ];
+
+                if ( isNaN(delta[0]) ) {// in case of 'auto'
+                    delta[0] = (style_pos == 'relative') ? 0 : el.offsetLeft;
+                }
+                if ( isNaN(delta[1]) ) { // in case of 'auto'
+                    delta[1] = (style_pos == 'relative') ? 0 : el.offsetTop;
+                }
+
+                if (pos[0] !== null) { el.style.left = pos[0] - pageXY[0] + delta[0] + 'px'; }
+                if (pos[1] !== null) { el.style.top = pos[1] - pageXY[1] + delta[1] + 'px'; }
+
+                if (!noRetry) {
+                    var newXY = this.getXY(el);
+
+                    // if retry is true, try one more time if we miss
+                   if ( (pos[0] !== null && newXY[0] != pos[0]) ||
+                        (pos[1] !== null && newXY[1] != pos[1]) ) {
+                       this.setXY(el, pos, true);
+                   }
+                }
+
+            };
+
+            Y.Dom.batch(el, f, Y.Dom, true);
+        },
+
+        /**
+         * Set the X position of an html element in page coordinates, regardless of how the element is positioned.
+         * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
+         * @method setX
+         * @param {String | HTMLElement | Array} el Accepts a string to use as an ID, an actual DOM reference, or an Array of IDs and/or HTMLElements.
+         * @param {Int} x The value to use as the X coordinate for the element(s).
+         */
+        setX: function(el, x) {
+            Y.Dom.setXY(el, [x, null]);
+        },
+
+        /**
+         * Set the Y position of an html element in page coordinates, regardless of how the element is positioned.
+         * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
+         * @method setY
+         * @param {String | HTMLElement | Array} el Accepts a string to use as an ID, an actual DOM reference, or an Array of IDs and/or HTMLElements.
+         * @param {Int} x To use as the Y coordinate for the element(s).
+         */
+        setY: function(el, y) {
+            Y.Dom.setXY(el, [null, y]);
+        },
+
+        /**
+         * Returns the region position of the given element.
+         * The element must be part of the DOM tree to have a region (display:none or elements not appended return false).
+         * @method getRegion
+         * @param {String | HTMLElement | Array} el Accepts a string to use as an ID, an actual DOM reference, or an Array of IDs and/or HTMLElements.
+         * @return {Region | Array} A Region or array of Region instances containing "top, left, bottom, right" member data.
+         */
+        getRegion: function(el) {
+            var f = function(el) {
+                var region = new Y.Region.getRegion(el);
+                return region;
+            };
+
+            return Y.Dom.batch(el, f, Y.Dom, true);
+        },
+
+        /**
+         * Returns the width of the client (viewport).
+         * @method getClientWidth
+         * @deprecated Now using getViewportWidth.  This interface left intact for back compat.
+         * @return {Int} The width of the viewable area of the page.
+         */
+        getClientWidth: function() {
+            return Y.Dom.getViewportWidth();
+        },
+
+        /**
+         * Returns the height of the client (viewport).
+         * @method getClientHeight
+         * @deprecated Now using getViewportHeight.  This interface left intact for back compat.
+         * @return {Int} The height of the viewable area of the page.
+         */
+        getClientHeight: function() {
+            return Y.Dom.getViewportHeight();
+        },
+
+        /**
+         * Returns a array of HTMLElements with the given class.
+         * For optimized performance, include a tag and/or root node when possible.
+         * @method getElementsByClassName
+         * @param {String} className The class name to match against
+         * @param {String} tag (optional) The tag name of the elements being collected
+         * @param {String | HTMLElement} root (optional) The HTMLElement or an ID to use as the starting point
+         * @return {Array} An array of elements that have the given class name
+         */
+        getElementsByClassName: function(className, tag, root) {
+            var method = function(el) { return Y.Dom.hasClass(el, className); };
+            return Y.Dom.getElementsBy(method, tag, root);
+        },
+
+        /**
+         * Determines whether an HTMLElement has the given className.
+         * @method hasClass
+         * @param {String | HTMLElement | Array} el The element or collection to test
+         * @param {String} className the class name to search for
+         * @return {Boolean | Array} A boolean value or array of boolean values
+         */
+        hasClass: function(el, className) {
+            var re = new RegExp('(?:^|\\s+)' + className + '(?:\\s+|$)');
+
+            var f = function(el) {
+                return re.test(el['className']);
+            };
+
+            return Y.Dom.batch(el, f, Y.Dom, true);
+        },
+
+        /**
+         * Adds a class name to a given element or collection of elements.
+         * @method addClass
+         * @param {String | HTMLElement | Array} el The element or collection to add the class to
+         * @param {String} className the class name to add to the class attribute
+         */
+        addClass: function(el, className) {
+            var f = function(el) {
+                if (this.hasClass(el, className)) { return; } // already present
+
+
+                el['className'] = [el['className'], className].join(' ');
+            };
+
+            Y.Dom.batch(el, f, Y.Dom, true);
+        },
+
+        /**
+         * Removes a class name from a given element or collection of elements.
+         * @method removeClass
+         * @param {String | HTMLElement | Array} el The element or collection to remove the class from
+         * @param {String} className the class name to remove from the class attribute
+         */
+        removeClass: function(el, className) {
+            var re = new RegExp('(?:^|\\s+)' + className + '(?:\\s+|$)', 'g');
+
+            var f = function(el) {
+                if (!this.hasClass(el, className)) { return; } // not present
+
+
+                var c = el['className'];
+                el['className'] = c.replace(re, ' ');
+                if ( this.hasClass(el, className) ) { // in case of multiple adjacent
+                    this.removeClass(el, className);
+                }
+
+            };
+
+            Y.Dom.batch(el, f, Y.Dom, true);
+        },
+
+        /**
+         * Replace a class with another class for a given element or collection of elements.
+         * If no oldClassName is present, the newClassName is simply added.
+         * @method replaceClass
+         * @param {String | HTMLElement | Array} el The element or collection to remove the class from
+         * @param {String} oldClassName the class name to be replaced
+         * @param {String} newClassName the class name that will be replacing the old class name
+         */
+        replaceClass: function(el, oldClassName, newClassName) {
+            if (oldClassName === newClassName) { // avoid infinite loop
+                return false;
+            }
+
+            var re = new RegExp('(?:^|\\s+)' + oldClassName + '(?:\\s+|$)', 'g');
+
+            var f = function(el) {
+
+                if ( !this.hasClass(el, oldClassName) ) {
+                    this.addClass(el, newClassName); // just add it if nothing to replace
+                    return; // note return
+                }
+
+                el['className'] = el['className'].replace(re, ' ' + newClassName + ' ');
+
+                if ( this.hasClass(el, oldClassName) ) { // in case of multiple adjacent
+                    this.replaceClass(el, oldClassName, newClassName);
+                }
+            };
+
+            Y.Dom.batch(el, f, Y.Dom, true);
+        },
+
+        /**
+         * Generates a unique ID
+         * @method generateId
+         * @param {String | HTMLElement | Array} el (optional) An optional element array of elements to add an ID to (no ID is added if one is already present).
+         * @param {String} prefix (optional) an optional prefix to use (defaults to "yui-gen").
+         * @return {String | Array} The generated ID, or array of generated IDs (or original ID if already present on an element)
+         */
+        generateId: function(el, prefix) {
+            prefix = prefix || 'yui-gen';
+            el = el || {};
+
+            var f = function(el) {
+                if (el) {
+                    el = Y.Dom.get(el);
+                } else {
+                    el = {}; // just generating ID in this case
+                }
+
+                if (!el.id) {
+                    el.id = prefix + id_counter++;
+                } // dont override existing
+
+
+                return el.id;
+            };
+
+            return Y.Dom.batch(el, f, Y.Dom, true);
+        },
+
+        /**
+         * Determines whether an HTMLElement is an ancestor of another HTML element in the DOM hierarchy.
+         * @method isAncestor
+         * @param {String | HTMLElement} haystack The possible ancestor
+         * @param {String | HTMLElement} needle The possible descendent
+         * @return {Boolean} Whether or not the haystack is an ancestor of needle
+         */
+        isAncestor: function(haystack, needle) {
+            haystack = Y.Dom.get(haystack);
+            if (!haystack || !needle) { return false; }
+
+            var f = function(needle) {
+                if (haystack.contains && !isSafari) { // safari "contains" is broken
+                    return haystack.contains(needle);
+                }
+                else if ( haystack.compareDocumentPosition ) {
+                    return !!(haystack.compareDocumentPosition(needle) & 16);
+                }
+                else { // loop up and test each parent
+                    var parent = needle.parentNode;
+
+                    while (parent) {
+                        if (parent == haystack) {
+                            return true;
+                        }
+                        else if (!parent.tagName || parent.tagName.toUpperCase() == 'HTML') {
+                            return false;
+                        }
+
+                        parent = parent.parentNode;
+                    }
+                    return false;
+                }
+            };
+
+            return Y.Dom.batch(needle, f, Y.Dom, true);
+        },
+
+        /**
+         * Determines whether an HTMLElement is present in the current document.
+         * @method inDocument
+         * @param {String | HTMLElement} el The element to search for
+         * @return {Boolean} Whether or not the element is present in the current document
+         */
+        inDocument: function(el) {
+            var f = function(el) {
+                return this.isAncestor(document.documentElement, el);
+            };
+
+            return Y.Dom.batch(el, f, Y.Dom, true);
+        },
+
+        /**
+         * Returns a array of HTMLElements that pass the test applied by supplied boolean method.
+         * For optimized performance, include a tag and/or root node when possible.
+         * @method getElementsBy
+         * @param {Function} method - A boolean method for testing elements which receives the element as its only argument.
+
+         * @param {String} tag (optional) The tag name of the elements being collected
+         * @param {String | HTMLElement} root (optional) The HTMLElement or an ID to use as the starting point
+         */
+        getElementsBy: function(method, tag, root) {
+            tag = tag || '*';
+
+            var nodes = [];
+
+            if (root) {
+                root = Y.Dom.get(root);
+                if (!root) { // if no root node, then no children
+                    return nodes;
+                }
+            } else {
+                root = document;
+            }
+
+            var elements = root.getElementsByTagName(tag);
+
+            if ( !elements.length && (tag == '*' && root.all) ) {
+                elements = root.all; // IE < 6
+            }
+
+            for (var i = 0, len = elements.length; i < len; ++i) {
+                if ( method(elements[i]) ) { nodes[nodes.length] = elements[i]; }
+            }
+
+
+            return nodes;
+        },
+
+        /**
+         * Returns an array of elements that have had the supplied method applied.
+         * The method is called with the element(s) as the first arg, and the optional param as the second ( method(el, o) ).
+         * @method batch
+         * @param {String | HTMLElement | Array} el (optional) An element or array of elements to apply the method to
+         * @param {Function} method The method to apply to the element(s)
+         * @param {Any} o (optional) An optional arg that is passed to the supplied method
+         * @param {Boolean} override (optional) Whether or not to override the scope of "method" with "o"
+         * @return {HTMLElement | Array} The element(s) with the method applied
+         */
+        batch: function(el, method, o, override) {
+            var id = el;
+            el = Y.Dom.get(el);
+
+            var scope = (override) ? o : window;
+
+            if (!el || el.tagName || !el.length) { // is null or not a collection (tagName for SELECT and others that can be both an element and a collection)
+                if (!el) {
+                    return false;
+                }
+                return method.call(scope, el, o);
+            }
+
+            var collection = [];
+
+            for (var i = 0, len = el.length; i < len; ++i) {
+                if (!el[i]) {
+                    id = el[i];
+                }
+                collection[collection.length] = method.call(scope, el[i], o);
+            }
+
+            return collection;
+        },
+
+        /**
+         * Returns the height of the document.
+         * @method getDocumentHeight
+         * @return {Int} The height of the actual document (which includes the body and its margin).
+         */
+        getDocumentHeight: function() {
+            var scrollHeight = (document.compatMode != 'CSS1Compat') ? document.body.scrollHeight : document.documentElement.scrollHeight;
+
+            var h = Math.max(scrollHeight, Y.Dom.getViewportHeight());
+            return h;
+        },
+
+        /**
+         * Returns the width of the document.
+         * @method getDocumentWidth
+         * @return {Int} The width of the actual document (which includes the body and its margin).
+         */
+        getDocumentWidth: function() {
+            var scrollWidth = (document.compatMode != 'CSS1Compat') ? document.body.scrollWidth : document.documentElement.scrollWidth;
+            var w = Math.max(scrollWidth, Y.Dom.getViewportWidth());
+            return w;
+        },
+
+        /**
+         * Returns the current height of the viewport.
+         * @method getViewportHeight
+         * @return {Int} The height of the viewable area of the page (excludes scrollbars).
+         */
+        getViewportHeight: function() {
+            var height = self.innerHeight; // Safari, Opera
+            var mode = document.compatMode;
+
+            if ( (mode || isIE) && !isOpera ) { // IE, Gecko
+                height = (mode == 'CSS1Compat') ?
+                        document.documentElement.clientHeight : // Standards
+                        document.body.clientHeight; // Quirks
+            }
+
+            return height;
+        },
+
+        /**
+         * Returns the current width of the viewport.
+         * @method getViewportWidth
+         * @return {Int} The width of the viewable area of the page (excludes scrollbars).
+         */
+
+        getViewportWidth: function() {
+            var width = self.innerWidth;  // Safari
+            var mode = document.compatMode;
+
+            if (mode || isIE) { // IE, Gecko, Opera
+                width = (mode == 'CSS1Compat') ?
+                        document.documentElement.clientWidth : // Standards
+                        document.body.clientWidth; // Quirks
+            }
+            return width;
+        }
+    };
+})();
+/**
+ * A region is a representation of an object on a grid.  It is defined
+ * by the top, right, bottom, left extents, so is rectangular by default.  If
+ * other shapes are required, this class could be extended to support it.
+ * @namespace YAHOO.util
+ * @class Region
+ * @param {Int} t the top extent
+ * @param {Int} r the right extent
+ * @param {Int} b the bottom extent
+ * @param {Int} l the left extent
+ * @constructor
+ */
+YAHOO.util.Region = function(t, r, b, l) {
+
+    /**
+     * The region's top extent
+     * @property top
+     * @type Int
+     */
+    this.top = t;
+
+    /**
+     * The region's top extent as index, for symmetry with set/getXY
+     * @property 1
+     * @type Int
+     */
+    this[1] = t;
+
+    /**
+     * The region's right extent
+     * @property right
+     * @type int
+     */
+    this.right = r;
+
+    /**
+     * The region's bottom extent
+     * @property bottom
+     * @type Int
+     */
+    this.bottom = b;
+
+    /**
+     * The region's left extent
+     * @property left
+     * @type Int
+     */
+    this.left = l;
+
+    /**
+     * The region's left extent as index, for symmetry with set/getXY
+     * @property 0
+     * @type Int
+     */
+    this[0] = l;
+};
+
+/**
+ * Returns true if this region contains the region passed in
+ * @method contains
+ * @param  {Region}  region The region to evaluate
+ * @return {Boolean}        True if the region is contained with this region,
+ *                          else false
+ */
+YAHOO.util.Region.prototype.contains = function(region) {
+    return ( region.left   >= this.left   &&
+             region.right  <= this.right  &&
+             region.top    >= this.top    &&
+             region.bottom <= this.bottom    );
+
+};
+
+/**
+ * Returns the area of the region
+ * @method getArea
+ * @return {Int} the region's area
+ */
+YAHOO.util.Region.prototype.getArea = function() {
+    return ( (this.bottom - this.top) * (this.right - this.left) );
+};
+
+/**
+ * Returns the region where the passed in region overlaps with this one
+ * @method intersect
+ * @param  {Region} region The region that intersects
+ * @return {Region}        The overlap region, or null if there is no overlap
+ */
+YAHOO.util.Region.prototype.intersect = function(region) {
+    var t = Math.max( this.top,    region.top    );
+    var r = Math.min( this.right,  region.right  );
+    var b = Math.min( this.bottom, region.bottom );
+    var l = Math.max( this.left,   region.left   );
+
+    if (b >= t && r >= l) {
+        return new YAHOO.util.Region(t, r, b, l);
+    } else {
+        return null;
+    }
+};
+
+/**
+ * Returns the region representing the smallest region that can contain both
+ * the passed in region and this region.
+ * @method union
+ * @param  {Region} region The region that to create the union with
+ * @return {Region}        The union region
+ */
+YAHOO.util.Region.prototype.union = function(region) {
+    var t = Math.min( this.top,    region.top    );
+    var r = Math.max( this.right,  region.right  );
+    var b = Math.max( this.bottom, region.bottom );
+    var l = Math.min( this.left,   region.left   );
+
+    return new YAHOO.util.Region(t, r, b, l);
+};
+
+/**
+ * toString
+ * @method toString
+ * @return string the region properties
+ */
+YAHOO.util.Region.prototype.toString = function() {
+    return ( "Region {"    +
+             "top: "       + this.top    +
+             ", right: "   + this.right  +
+             ", bottom: "  + this.bottom +
+             ", left: "    + this.left   +
+             "}" );
+};
+
+/**
+ * Returns a region that is occupied by the DOM element
+ * @method getRegion
+ * @param  {HTMLElement} el The element
+ * @return {Region}         The region that the element occupies
+ * @static
+ */
+YAHOO.util.Region.getRegion = function(el) {
+    var p = YAHOO.util.Dom.getXY(el);
+
+    var t = p[1];
+    var r = p[0] + el.offsetWidth;
+    var b = p[1] + el.offsetHeight;
+    var l = p[0];
+
+    return new YAHOO.util.Region(t, r, b, l);
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * A point is a region that is special in that it represents a single point on
+ * the grid.
+ * @namespace YAHOO.util
+ * @class Point
+ * @param {Int} x The X position of the point
+ * @param {Int} y The Y position of the point
+ * @constructor
+ * @extends YAHOO.util.Region
+ */
+YAHOO.util.Point = function(x, y) {
+   if (x instanceof Array) { // accept output from Dom.getXY
+      y = x[1];
+      x = x[0];
+   }
+
+    /**
+     * The X position of the point, which is also the right, left and index zero (for Dom.getXY symmetry)
+     * @property x
+     * @type Int
+     */
+
+    this.x = this.right = this.left = this[0] = x;
+
+    /**
+     * The Y position of the point, which is also the top, bottom and index one (for Dom.getXY symmetry)
+     * @property y
+     * @type Int
+     */
+    this.y = this.top = this.bottom = this[1] = y;
+};
+
+YAHOO.util.Point.prototype = new YAHOO.util.Region();
+

Added: jifty/branches/schema-plugins/share/web/static/js/yui/event.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/yui/event.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,1771 @@
+/*                                                                                                                                                      
+Copyright (c) 2006, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.net/yui/license.txt
+version: 0.12.1
+*/ 
+
+/**
+ * The CustomEvent class lets you define events for your application
+ * that can be subscribed to by one or more independent component.
+ *
+ * @param {String}  type The type of event, which is passed to the callback
+ *                  when the event fires
+ * @param {Object}  oScope The context the event will fire from.  "this" will
+ *                  refer to this object in the callback.  Default value: 
+ *                  the window object.  The listener can override this.
+ * @param {boolean} silent pass true to prevent the event from writing to
+ *                  the debugsystem
+ * @param {int}     signature the signature that the custom event subscriber
+ *                  will receive. YAHOO.util.CustomEvent.LIST or 
+ *                  YAHOO.util.CustomEvent.FLAT.  The default is
+ *                  YAHOO.util.CustomEvent.LIST.
+ * @namespace YAHOO.util
+ * @class CustomEvent
+ * @constructor
+ */
+YAHOO.util.CustomEvent = function(type, oScope, silent, signature) {
+
+    /**
+     * The type of event, returned to subscribers when the event fires
+     * @property type
+     * @type string
+     */
+    this.type = type;
+
+    /**
+     * The scope the the event will fire from by default.  Defaults to the window 
+     * obj
+     * @property scope
+     * @type object
+     */
+    this.scope = oScope || window;
+
+    /**
+     * By default all custom events are logged in the debug build, set silent
+     * to true to disable debug outpu for this event.
+     * @property silent
+     * @type boolean
+     */
+    this.silent = silent;
+
+    /**
+     * Custom events support two styles of arguments provided to the event
+     * subscribers.  
+     * <ul>
+     * <li>YAHOO.util.CustomEvent.LIST: 
+     *   <ul>
+     *   <li>param1: event name</li>
+     *   <li>param2: array of arguments sent to fire</li>
+     *   <li>param3: <optional> a custom object supplied by the subscriber</li>
+     *   </ul>
+     * </li>
+     * <li>YAHOO.util.CustomEvent.FLAT
+     *   <ul>
+     *   <li>param1: the first argument passed to fire.  If you need to
+     *           pass multiple parameters, use and array or object literal</li>
+     *   <li>param2: <optional> a custom object supplied by the subscriber</li>
+     *   </ul>
+     * </li>
+     * </ul>
+     *   @property signature
+     *   @type int
+     */
+    this.signature = signature || YAHOO.util.CustomEvent.LIST;
+
+    /**
+     * The subscribers to this event
+     * @property subscribers
+     * @type Subscriber[]
+     */
+    this.subscribers = [];
+
+    if (!this.silent) {
+    }
+
+    var onsubscribeType = "_YUICEOnSubscribe";
+
+    // Only add subscribe events for events that are not generated by 
+    // CustomEvent
+    if (type !== onsubscribeType) {
+
+        /**
+         * Custom events provide a custom event that fires whenever there is
+         * a new subscriber to the event.  This provides an opportunity to
+         * handle the case where there is a non-repeating event that has
+         * already fired has a new subscriber.  
+         *
+         * @event subscribeEvent
+         * @type YAHOO.util.CustomEvent
+         * @param {Function} fn The function to execute
+         * @param {Object}   obj An object to be passed along when the event 
+         *                       fires
+         * @param {boolean|Object}  override If true, the obj passed in becomes 
+         *                                   the execution scope of the listener.
+         *                                   if an object, that object becomes the
+         *                                   the execution scope.
+         */
+        this.subscribeEvent = 
+                new YAHOO.util.CustomEvent(onsubscribeType, this, true);
+
+    } 
+};
+
+/**
+ * Subscriber listener sigature constant.  The LIST type returns three
+ * parameters: the event type, the array of args passed to fire, and
+ * the optional custom object
+ * @property YAHOO.util.CustomEvent.LIST
+ * @static
+ * @type int
+ */
+YAHOO.util.CustomEvent.LIST = 0;
+
+/**
+ * Subscriber listener sigature constant.  The FLAT type returns two
+ * parameters: the first argument passed to fire and the optional 
+ * custom object
+ * @property YAHOO.util.CustomEvent.FLAT
+ * @static
+ * @type int
+ */
+YAHOO.util.CustomEvent.FLAT = 1;
+
+YAHOO.util.CustomEvent.prototype = {
+
+    /**
+     * Subscribes the caller to this event
+     * @method subscribe
+     * @param {Function} fn        The function to execute
+     * @param {Object}   obj       An object to be passed along when the event 
+     *                             fires
+     * @param {boolean|Object}  override If true, the obj passed in becomes 
+     *                                   the execution scope of the listener.
+     *                                   if an object, that object becomes the
+     *                                   the execution scope.
+     */
+    subscribe: function(fn, obj, override) {
+        if (this.subscribeEvent) {
+            this.subscribeEvent.fire(fn, obj, override);
+        }
+
+        this.subscribers.push( new YAHOO.util.Subscriber(fn, obj, override) );
+    },
+
+    /**
+     * Unsubscribes the caller from this event
+     * @method unsubscribe
+     * @param {Function} fn  The function to execute
+     * @param {Object}   obj  The custom object passed to subscribe (optional)
+     * @return {boolean} True if the subscriber was found and detached.
+     */
+    unsubscribe: function(fn, obj) {
+        var found = false;
+        for (var i=0, len=this.subscribers.length; i<len; ++i) {
+            var s = this.subscribers[i];
+            if (s && s.contains(fn, obj)) {
+                this._delete(i);
+                found = true;
+            }
+        }
+
+        return found;
+    },
+
+    /**
+     * Notifies the subscribers.  The callback functions will be executed
+     * from the scope specified when the event was created, and with the 
+     * following parameters:
+     *   <ul>
+     *   <li>The type of event</li>
+     *   <li>All of the arguments fire() was executed with as an array</li>
+     *   <li>The custom object (if any) that was passed into the subscribe() 
+     *       method</li>
+     *   </ul>
+     * @method fire 
+     * @param {Object*} arguments an arbitrary set of parameters to pass to 
+     *                            the handler.
+     * @return {boolean} false if one of the subscribers returned false, 
+     *                   true otherwise
+     */
+    fire: function() {
+        var len=this.subscribers.length;
+        if (!len && this.silent) {
+            return true;
+        }
+
+        var args=[], ret=true, i;
+
+        for (i=0; i<arguments.length; ++i) {
+            args.push(arguments[i]);
+        }
+
+        var argslength = args.length;
+
+        if (!this.silent) {
+        }
+
+        for (i=0; i<len; ++i) {
+            var s = this.subscribers[i];
+            if (s) {
+                if (!this.silent) {
+                }
+
+                var scope = s.getScope(this.scope);
+
+                if (this.signature == YAHOO.util.CustomEvent.FLAT) {
+                    var param = null;
+                    if (args.length > 0) {
+                        param = args[0];
+                    }
+                    ret = s.fn.call(scope, param, s.obj);
+                } else {
+                    ret = s.fn.call(scope, this.type, args, s.obj);
+                }
+                if (false === ret) {
+                    if (!this.silent) {
+                    }
+
+                    //break;
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    },
+
+    /**
+     * Removes all listeners
+     * @method unsubscribeAll
+     */
+    unsubscribeAll: function() {
+        for (var i=0, len=this.subscribers.length; i<len; ++i) {
+            this._delete(len - 1 - i);
+        }
+    },
+
+    /**
+     * @method _delete
+     * @private
+     */
+    _delete: function(index) {
+        var s = this.subscribers[index];
+        if (s) {
+            delete s.fn;
+            delete s.obj;
+        }
+
+        // delete this.subscribers[index];
+        this.subscribers.splice(index, 1);
+    },
+
+    /**
+     * @method toString
+     */
+    toString: function() {
+         return "CustomEvent: " + "'" + this.type  + "', " + 
+             "scope: " + this.scope;
+
+    }
+};
+
+/////////////////////////////////////////////////////////////////////
+
+/**
+ * Stores the subscriber information to be used when the event fires.
+ * @param {Function} fn       The function to execute
+ * @param {Object}   obj      An object to be passed along when the event fires
+ * @param {boolean}  override If true, the obj passed in becomes the execution
+ *                            scope of the listener
+ * @class Subscriber
+ * @constructor
+ */
+YAHOO.util.Subscriber = function(fn, obj, override) {
+
+    /**
+     * The callback that will be execute when the event fires
+     * @property fn
+     * @type function
+     */
+    this.fn = fn;
+
+    /**
+     * An optional custom object that will passed to the callback when
+     * the event fires
+     * @property obj
+     * @type object
+     */
+    this.obj = obj || null;
+
+    /**
+     * The default execution scope for the event listener is defined when the
+     * event is created (usually the object which contains the event).
+     * By setting override to true, the execution scope becomes the custom
+     * object passed in by the subscriber.  If override is an object, that 
+     * object becomes the scope.
+     * @property override
+     * @type boolean|object
+     */
+    this.override = override;
+
+};
+
+/**
+ * Returns the execution scope for this listener.  If override was set to true
+ * the custom obj will be the scope.  If override is an object, that is the
+ * scope, otherwise the default scope will be used.
+ * @method getScope
+ * @param {Object} defaultScope the scope to use if this listener does not
+ *                              override it.
+ */
+YAHOO.util.Subscriber.prototype.getScope = function(defaultScope) {
+    if (this.override) {
+        if (this.override === true) {
+            return this.obj;
+        } else {
+            return this.override;
+        }
+    }
+    return defaultScope;
+};
+
+/**
+ * Returns true if the fn and obj match this objects properties.
+ * Used by the unsubscribe method to match the right subscriber.
+ *
+ * @method contains
+ * @param {Function} fn the function to execute
+ * @param {Object} obj an object to be passed along when the event fires
+ * @return {boolean} true if the supplied arguments match this 
+ *                   subscriber's signature.
+ */
+YAHOO.util.Subscriber.prototype.contains = function(fn, obj) {
+    if (obj) {
+        return (this.fn == fn && this.obj == obj);
+    } else {
+        return (this.fn == fn);
+    }
+};
+
+/**
+ * @method toString
+ */
+YAHOO.util.Subscriber.prototype.toString = function() {
+    return "Subscriber { obj: " + (this.obj || "")  + 
+           ", override: " +  (this.override || "no") + " }";
+};
+
+/**
+ * The Event Utility provides utilities for managing DOM Events and tools
+ * for building event systems
+ *
+ * @module event
+ * @title Event Utility
+ * @namespace YAHOO.util
+ * @requires yahoo
+ */
+
+// The first instance of Event will win if it is loaded more than once.
+if (!YAHOO.util.Event) {
+
+/**
+ * The event utility provides functions to add and remove event listeners,
+ * event cleansing.  It also tries to automatically remove listeners it
+ * registers during the unload event.
+ *
+ * @class Event
+ * @static
+ */
+    YAHOO.util.Event = function() {
+
+        /**
+         * True after the onload event has fired
+         * @property loadComplete
+         * @type boolean
+         * @static
+         * @private
+         */
+        var loadComplete =  false;
+
+        /**
+         * Cache of wrapped listeners
+         * @property listeners
+         * @type array
+         * @static
+         * @private
+         */
+        var listeners = [];
+
+        /**
+         * User-defined unload function that will be fired before all events
+         * are detached
+         * @property unloadListeners
+         * @type array
+         * @static
+         * @private
+         */
+        var unloadListeners = [];
+
+        /**
+         * Cache of DOM0 event handlers to work around issues with DOM2 events
+         * in Safari
+         * @property legacyEvents
+         * @static
+         * @private
+         */
+        var legacyEvents = [];
+
+        /**
+         * Listener stack for DOM0 events
+         * @property legacyHandlers
+         * @static
+         * @private
+         */
+        var legacyHandlers = [];
+
+        /**
+         * The number of times to poll after window.onload.  This number is
+         * increased if additional late-bound handlers are requested after
+         * the page load.
+         * @property retryCount
+         * @static
+         * @private
+         */
+        var retryCount = 0;
+
+        /**
+         * onAvailable listeners
+         * @property onAvailStack
+         * @static
+         * @private
+         */
+        var onAvailStack = [];
+
+        /**
+         * Lookup table for legacy events
+         * @property legacyMap
+         * @static
+         * @private
+         */
+        var legacyMap = [];
+
+        /**
+         * Counter for auto id generation
+         * @property counter
+         * @static
+         * @private
+         */
+        var counter = 0;
+
+        return { // PREPROCESS
+
+            /**
+             * The number of times we should look for elements that are not
+             * in the DOM at the time the event is requested after the document
+             * has been loaded.  The default is 200 at amp;50 ms, so it will poll
+             * for 10 seconds or until all outstanding handlers are bound
+             * (whichever comes first).
+             * @property POLL_RETRYS
+             * @type int
+             * @static
+             * @final
+             */
+            POLL_RETRYS: 200,
+
+            /**
+             * The poll interval in milliseconds
+             * @property POLL_INTERVAL
+             * @type int
+             * @static
+             * @final
+             */
+            POLL_INTERVAL: 20,
+
+            /**
+             * Element to bind, int constant
+             * @property EL
+             * @type int
+             * @static
+             * @final
+             */
+            EL: 0,
+
+            /**
+             * Type of event, int constant
+             * @property TYPE
+             * @type int
+             * @static
+             * @final
+             */
+            TYPE: 1,
+
+            /**
+             * Function to execute, int constant
+             * @property FN
+             * @type int
+             * @static
+             * @final
+             */
+            FN: 2,
+
+            /**
+             * Function wrapped for scope correction and cleanup, int constant
+             * @property WFN
+             * @type int
+             * @static
+             * @final
+             */
+            WFN: 3,
+
+            /**
+             * Object passed in by the user that will be returned as a 
+             * parameter to the callback, int constant
+             * @property OBJ
+             * @type int
+             * @static
+             * @final
+             */
+            OBJ: 3,
+
+            /**
+             * Adjusted scope, either the element we are registering the event
+             * on or the custom object passed in by the listener, int constant
+             * @property ADJ_SCOPE
+             * @type int
+             * @static
+             * @final
+             */
+            ADJ_SCOPE: 4,
+
+            /**
+             * Safari detection is necessary to work around the preventDefault
+             * bug that makes it so you can't cancel a href click from the 
+             * handler.  There is not a capabilities check we can use here.
+             * @property isSafari
+             * @private
+             * @static
+             */
+            isSafari: (/Safari|Konqueror|KHTML/gi).test(navigator.userAgent),
+
+            /**
+             * IE detection needed to properly calculate pageX and pageY.  
+             * capabilities checking didn't seem to work because another 
+             * browser that does not provide the properties have the values 
+             * calculated in a different manner than IE.
+             * @property isIE
+             * @private
+             * @static
+             */
+            isIE: (!this.isSafari && !navigator.userAgent.match(/opera/gi) && 
+                    navigator.userAgent.match(/msie/gi)),
+
+            /**
+             * poll handle
+             * @property _interval
+             * @private
+             */
+            _interval: null,
+
+            /**
+             * @method startInterval
+             * @static
+             * @private
+             */
+            startInterval: function() {
+                if (!this._interval) {
+                    var self = this;
+                    var callback = function() { self._tryPreloadAttach(); };
+                    this._interval = setInterval(callback, this.POLL_INTERVAL);
+                    // this.timeout = setTimeout(callback, i);
+                }
+            },
+
+            /**
+             * Executes the supplied callback when the item with the supplied
+             * id is found.  This is meant to be used to execute behavior as
+             * soon as possible as the page loads.  If you use this after the
+             * initial page load it will poll for a fixed time for the element.
+             * The number of times it will poll and the frequency are
+             * configurable.  By default it will poll for 10 seconds.
+             *
+             * @method onAvailable
+             *
+             * @param {string}   p_id the id of the element to look for.
+             * @param {function} p_fn what to execute when the element is found.
+             * @param {object}   p_obj an optional object to be passed back as
+             *                   a parameter to p_fn.
+             * @param {boolean}  p_override If set to true, p_fn will execute
+             *                   in the scope of p_obj
+             *
+             * @static
+             */
+            onAvailable: function(p_id, p_fn, p_obj, p_override) {
+                onAvailStack.push( { id:         p_id, 
+                                     fn:         p_fn, 
+                                     obj:        p_obj, 
+                                     override:   p_override, 
+                                     checkReady: false    } );
+
+                retryCount = this.POLL_RETRYS;
+                this.startInterval();
+            },
+
+            /**
+             * Works the same way as onAvailable, but additionally checks the
+             * state of sibling elements to determine if the content of the
+             * available element is safe to modify.
+             *
+             * @method onContentReady
+             *
+             * @param {string}   p_id the id of the element to look for.
+             * @param {function} p_fn what to execute when the element is ready.
+             * @param {object}   p_obj an optional object to be passed back as
+             *                   a parameter to p_fn.
+             * @param {boolean}  p_override If set to true, p_fn will execute
+             *                   in the scope of p_obj
+             *
+             * @static
+             */
+            onContentReady: function(p_id, p_fn, p_obj, p_override) {
+                onAvailStack.push( { id:         p_id, 
+                                     fn:         p_fn, 
+                                     obj:        p_obj, 
+                                     override:   p_override,
+                                     checkReady: true      } );
+
+                retryCount = this.POLL_RETRYS;
+                this.startInterval();
+            },
+
+            /**
+             * Appends an event handler
+             *
+             * @method addListener
+             *
+             * @param {Object}   el        The html element to assign the 
+             *                             event to
+             * @param {String}   sType     The type of event to append
+             * @param {Function} fn        The method the event invokes
+             * @param {Object}   obj    An arbitrary object that will be 
+             *                             passed as a parameter to the handler
+             * @param {boolean}  override  If true, the obj passed in becomes
+             *                             the execution scope of the listener
+             * @return {boolean} True if the action was successful or defered,
+             *                        false if one or more of the elements 
+             *                        could not have the listener attached,
+             *                        or if the operation throws an exception.
+             * @static
+             */
+            addListener: function(el, sType, fn, obj, override) {
+
+                if (!fn || !fn.call) {
+                    return false;
+                }
+
+                // The el argument can be an array of elements or element ids.
+                if ( this._isValidCollection(el)) {
+                    var ok = true;
+                    for (var i=0,len=el.length; i<len; ++i) {
+                        ok = this.on(el[i], 
+                                       sType, 
+                                       fn, 
+                                       obj, 
+                                       override) && ok;
+                    }
+                    return ok;
+
+                } else if (typeof el == "string") {
+                    var oEl = this.getEl(el);
+                    // If the el argument is a string, we assume it is 
+                    // actually the id of the element.  If the page is loaded
+                    // we convert el to the actual element, otherwise we 
+                    // defer attaching the event until onload event fires
+
+                    // check to see if we need to delay hooking up the event 
+                    // until after the page loads.
+                    if (oEl) {
+                        el = oEl;
+                    } else {
+                        // defer adding the event until the element is available
+                        this.onAvailable(el, function() {
+                           YAHOO.util.Event.on(el, sType, fn, obj, override);
+                        });
+
+                        return true;
+                    }
+                }
+
+                // Element should be an html element or an array if we get 
+                // here.
+                if (!el) {
+                    return false;
+                }
+
+                // we need to make sure we fire registered unload events 
+                // prior to automatically unhooking them.  So we hang on to 
+                // these instead of attaching them to the window and fire the
+                // handles explicitly during our one unload event.
+                if ("unload" == sType && obj !== this) {
+                    unloadListeners[unloadListeners.length] =
+                            [el, sType, fn, obj, override];
+                    return true;
+                }
+
+
+                // if the user chooses to override the scope, we use the custom
+                // object passed in, otherwise the executing scope will be the
+                // HTML element that the event is registered on
+                var scope = el;
+                if (override) {
+                    if (override === true) {
+                        scope = obj;
+                    } else {
+                        scope = override;
+                    }
+                }
+
+                // wrap the function so we can return the obj object when
+                // the event fires;
+                var wrappedFn = function(e) {
+                        return fn.call(scope, YAHOO.util.Event.getEvent(e), 
+                                obj);
+                    };
+
+                var li = [el, sType, fn, wrappedFn, scope];
+                var index = listeners.length;
+                // cache the listener so we can try to automatically unload
+                listeners[index] = li;
+
+                if (this.useLegacyEvent(el, sType)) {
+                    var legacyIndex = this.getLegacyIndex(el, sType);
+
+                    // Add a new dom0 wrapper if one is not detected for this
+                    // element
+                    if ( legacyIndex == -1 || 
+                                el != legacyEvents[legacyIndex][0] ) {
+
+                        legacyIndex = legacyEvents.length;
+                        legacyMap[el.id + sType] = legacyIndex;
+
+                        // cache the signature for the DOM0 event, and 
+                        // include the existing handler for the event, if any
+                        legacyEvents[legacyIndex] = 
+                            [el, sType, el["on" + sType]];
+                        legacyHandlers[legacyIndex] = [];
+
+                        el["on" + sType] = 
+                            function(e) {
+                                YAHOO.util.Event.fireLegacyEvent(
+                                    YAHOO.util.Event.getEvent(e), legacyIndex);
+                            };
+                    }
+
+                    // add a reference to the wrapped listener to our custom
+                    // stack of events
+                    //legacyHandlers[legacyIndex].push(index);
+                    legacyHandlers[legacyIndex].push(li);
+
+                } else {
+                    try {
+                        this._simpleAdd(el, sType, wrappedFn, false);
+                    } catch(e) {
+                        // handle an error trying to attach an event.  If it fails
+                        // we need to clean up the cache
+                        this.removeListener(el, sType, fn);
+                        return false;
+                    }
+                }
+
+                return true;
+                
+            },
+
+            /**
+             * When using legacy events, the handler is routed to this object
+             * so we can fire our custom listener stack.
+             * @method fireLegacyEvent
+             * @static
+             * @private
+             */
+            fireLegacyEvent: function(e, legacyIndex) {
+                var ok = true;
+
+                var le = legacyHandlers[legacyIndex];
+                for (var i=0,len=le.length; i<len; ++i) {
+                    var li = le[i];
+                    if ( li && li[this.WFN] ) {
+                        var scope = li[this.ADJ_SCOPE];
+                        var ret = li[this.WFN].call(scope, e);
+                        ok = (ok && ret);
+                    }
+                }
+
+                return ok;
+            },
+
+            /**
+             * Returns the legacy event index that matches the supplied 
+             * signature
+             * @method getLegacyIndex
+             * @static
+             * @private
+             */
+            getLegacyIndex: function(el, sType) {
+                var key = this.generateId(el) + sType;
+                if (typeof legacyMap[key] == "undefined") { 
+                    return -1;
+                } else {
+                    return legacyMap[key];
+                }
+            },
+
+            /**
+             * Logic that determines when we should automatically use legacy
+             * events instead of DOM2 events.
+             * @method useLegacyEvent
+             * @static
+             * @private
+             */
+            useLegacyEvent: function(el, sType) {
+                if (!el.addEventListener && !el.attachEvent) {
+                    return true;
+                } else if (this.isSafari) {
+                    if ("click" == sType || "dblclick" == sType) {
+                        return true;
+                    }
+                }
+                return false;
+            },
+                    
+            /**
+             * Removes an event handler
+             *
+             * @method removeListener
+             *
+             * @param {Object} el the html element or the id of the element to 
+             * assign the event to.
+             * @param {String} sType the type of event to remove.
+             * @param {Function} fn the method the event invokes.  If fn is
+             * undefined, then all event handlers for the type of event are 
+             * removed.
+             * @return {boolean} true if the unbind was successful, false 
+             * otherwise.
+             * @static
+             */
+            removeListener: function(el, sType, fn) {
+                var i, len;
+
+                // The el argument can be a string
+                if (typeof el == "string") {
+                    el = this.getEl(el);
+                // The el argument can be an array of elements or element ids.
+                } else if ( this._isValidCollection(el)) {
+                    var ok = true;
+                    for (i=0,len=el.length; i<len; ++i) {
+                        ok = ( this.removeListener(el[i], sType, fn) && ok );
+                    }
+                    return ok;
+                }
+
+                if (!fn || !fn.call) {
+                    //return false;
+                    return this.purgeElement(el, false, sType);
+                }
+
+                if ("unload" == sType) {
+
+                    for (i=0, len=unloadListeners.length; i<len; i++) {
+                        var li = unloadListeners[i];
+                        if (li && 
+                            li[0] == el && 
+                            li[1] == sType && 
+                            li[2] == fn) {
+                                unloadListeners.splice(i, 1);
+                                return true;
+                        }
+                    }
+
+                    return false;
+                }
+
+                var cacheItem = null;
+
+                // The index is a hidden parameter; needed to remove it from
+                // the method signature because it was tempting users to
+                // try and take advantage of it, which is not possible.
+                var index = arguments[3];
+  
+                if ("undefined" == typeof index) {
+                    index = this._getCacheIndex(el, sType, fn);
+                }
+
+                if (index >= 0) {
+                    cacheItem = listeners[index];
+                }
+
+                if (!el || !cacheItem) {
+                    return false;
+                }
+
+
+                if (this.useLegacyEvent(el, sType)) {
+                    var legacyIndex = this.getLegacyIndex(el, sType);
+                    var llist = legacyHandlers[legacyIndex];
+                    if (llist) {
+                        for (i=0, len=llist.length; i<len; ++i) {
+                            li = llist[i];
+                            if (li && 
+                                li[this.EL] == el && 
+                                li[this.TYPE] == sType && 
+                                li[this.FN] == fn) {
+                                    llist.splice(i, 1);
+                                    break;
+                            }
+                        }
+                    }
+
+                } else {
+                    try {
+                        this._simpleRemove(el, sType, cacheItem[this.WFN], false);
+                    } catch(e) {
+                        return false;
+                    }
+                }
+
+                // removed the wrapped handler
+                delete listeners[index][this.WFN];
+                delete listeners[index][this.FN];
+                listeners.splice(index, 1);
+
+                return true;
+
+            },
+
+            /**
+             * Returns the event's target element
+             * @method getTarget
+             * @param {Event} ev the event
+             * @param {boolean} resolveTextNode when set to true the target's
+             *                  parent will be returned if the target is a 
+             *                  text node.  @deprecated, the text node is
+             *                  now resolved automatically
+             * @return {HTMLElement} the event's target
+             * @static
+             */
+            getTarget: function(ev, resolveTextNode) {
+                var t = ev.target || ev.srcElement;
+                return this.resolveTextNode(t);
+            },
+
+            /**
+             * In some cases, some browsers will return a text node inside
+             * the actual element that was targeted.  This normalizes the
+             * return value for getTarget and getRelatedTarget.
+             * @method resolveTextNode
+             * @param {HTMLElement} node node to resolve
+             * @return {HTMLElement} the normized node
+             * @static
+             */
+            resolveTextNode: function(node) {
+                // if (node && node.nodeName && 
+                        // "#TEXT" == node.nodeName.toUpperCase()) {
+                if (node && 3 == node.nodeType) {
+                    return node.parentNode;
+                } else {
+                    return node;
+                }
+            },
+
+            /**
+             * Returns the event's pageX
+             * @method getPageX
+             * @param {Event} ev the event
+             * @return {int} the event's pageX
+             * @static
+             */
+            getPageX: function(ev) {
+                var x = ev.pageX;
+                if (!x && 0 !== x) {
+                    x = ev.clientX || 0;
+
+                    if ( this.isIE ) {
+                        x += this._getScrollLeft();
+                    }
+                }
+
+                return x;
+            },
+
+            /**
+             * Returns the event's pageY
+             * @method getPageY
+             * @param {Event} ev the event
+             * @return {int} the event's pageY
+             * @static
+             */
+            getPageY: function(ev) {
+                var y = ev.pageY;
+                if (!y && 0 !== y) {
+                    y = ev.clientY || 0;
+
+                    if ( this.isIE ) {
+                        y += this._getScrollTop();
+                    }
+                }
+
+                return y;
+            },
+
+            /**
+             * Returns the pageX and pageY properties as an indexed array.
+             * @method getXY
+             * @type int[]
+             * @static
+             */
+            getXY: function(ev) {
+                return [this.getPageX(ev), this.getPageY(ev)];
+            },
+
+            /**
+             * Returns the event's related target 
+             * @method getRelatedTarget
+             * @param {Event} ev the event
+             * @return {HTMLElement} the event's relatedTarget
+             * @static
+             */
+            getRelatedTarget: function(ev) {
+                var t = ev.relatedTarget;
+                if (!t) {
+                    if (ev.type == "mouseout") {
+                        t = ev.toElement;
+                    } else if (ev.type == "mouseover") {
+                        t = ev.fromElement;
+                    }
+                }
+
+                return this.resolveTextNode(t);
+            },
+
+            /**
+             * Returns the time of the event.  If the time is not included, the
+             * event is modified using the current time.
+             * @method getTime
+             * @param {Event} ev the event
+             * @return {Date} the time of the event
+             * @static
+             */
+            getTime: function(ev) {
+                if (!ev.time) {
+                    var t = new Date().getTime();
+                    try {
+                        ev.time = t;
+                    } catch(e) { 
+                        return t;
+                    }
+                }
+
+                return ev.time;
+            },
+
+            /**
+             * Convenience method for stopPropagation + preventDefault
+             * @method stopEvent
+             * @param {Event} ev the event
+             * @static
+             */
+            stopEvent: function(ev) {
+                this.stopPropagation(ev);
+                this.preventDefault(ev);
+            },
+
+            /**
+             * Stops event propagation
+             * @method stopPropagation
+             * @param {Event} ev the event
+             * @static
+             */
+            stopPropagation: function(ev) {
+                if (ev.stopPropagation) {
+                    ev.stopPropagation();
+                } else {
+                    ev.cancelBubble = true;
+                }
+            },
+
+            /**
+             * Prevents the default behavior of the event
+             * @method preventDefault
+             * @param {Event} ev the event
+             * @static
+             */
+            preventDefault: function(ev) {
+                if (ev.preventDefault) {
+                    ev.preventDefault();
+                } else {
+                    ev.returnValue = false;
+                }
+            },
+             
+            /**
+             * Finds the event in the window object, the caller's arguments, or
+             * in the arguments of another method in the callstack.  This is
+             * executed automatically for events registered through the event
+             * manager, so the implementer should not normally need to execute
+             * this function at all.
+             * @method getEvent
+             * @param {Event} e the event parameter from the handler
+             * @return {Event} the event 
+             * @static
+             */
+            getEvent: function(e) {
+                var ev = e || window.event;
+
+                if (!ev) {
+                    var c = this.getEvent.caller;
+                    while (c) {
+                        ev = c.arguments[0];
+                        if (ev && Event == ev.constructor) {
+                            break;
+                        }
+                        c = c.caller;
+                    }
+                }
+
+                return ev;
+            },
+
+            /**
+             * Returns the charcode for an event
+             * @method getCharCode
+             * @param {Event} ev the event
+             * @return {int} the event's charCode
+             * @static
+             */
+            getCharCode: function(ev) {
+                return ev.charCode || ev.keyCode || 0;
+            },
+
+            /**
+             * Locating the saved event handler data by function ref
+             *
+             * @method _getCacheIndex
+             * @static
+             * @private
+             */
+            _getCacheIndex: function(el, sType, fn) {
+                for (var i=0,len=listeners.length; i<len; ++i) {
+                    var li = listeners[i];
+                    if ( li                 && 
+                         li[this.FN] == fn  && 
+                         li[this.EL] == el  && 
+                         li[this.TYPE] == sType ) {
+                        return i;
+                    }
+                }
+
+                return -1;
+            },
+
+            /**
+             * Generates an unique ID for the element if it does not already 
+             * have one.
+             * @method generateId
+             * @param el the element to create the id for
+             * @return {string} the resulting id of the element
+             * @static
+             */
+            generateId: function(el) {
+                var id = el.id;
+
+                if (!id) {
+                    id = "yuievtautoid-" + counter;
+                    ++counter;
+                    el.id = id;
+                }
+
+                return id;
+            },
+
+            /**
+             * We want to be able to use getElementsByTagName as a collection
+             * to attach a group of events to.  Unfortunately, different 
+             * browsers return different types of collections.  This function
+             * tests to determine if the object is array-like.  It will also 
+             * fail if the object is an array, but is empty.
+             * @method _isValidCollection
+             * @param o the object to test
+             * @return {boolean} true if the object is array-like and populated
+             * @static
+             * @private
+             */
+            _isValidCollection: function(o) {
+                // this.logger.debug(o.constructor.toString())
+                // this.logger.debug(typeof o)
+
+                return ( o                    && // o is something
+                         o.length             && // o is indexed
+                         typeof o != "string" && // o is not a string
+                         !o.tagName           && // o is not an HTML element
+                         !o.alert             && // o is not a window
+                         typeof o[0] != "undefined" );
+
+            },
+
+            /**
+             * @private
+             * @property elCache
+             * DOM element cache
+             * @static
+             */
+            elCache: {},
+
+            /**
+             * We cache elements bound by id because when the unload event 
+             * fires, we can no longer use document.getElementById
+             * @method getEl
+             * @static
+             * @private
+             */
+            getEl: function(id) {
+                return document.getElementById(id);
+            },
+
+            /**
+             * Clears the element cache
+             * @deprecated Elements are not cached any longer
+             * @method clearCache
+             * @static
+             * @private
+             */
+            clearCache: function() { },
+
+            /**
+             * hook up any deferred listeners
+             * @method _load
+             * @static
+             * @private
+             */
+            _load: function(e) {
+                loadComplete = true;
+                var EU = YAHOO.util.Event;
+                // Remove the listener to assist with the IE memory issue, but not
+                // for other browsers because FF 1.0x does not like it.
+                if (this.isIE) {
+                    EU._simpleRemove(window, "load", EU._load);
+                }
+            },
+
+            /**
+             * Polling function that runs before the onload event fires, 
+             * attempting to attach to DOM Nodes as soon as they are 
+             * available
+             * @method _tryPreloadAttach
+             * @static
+             * @private
+             */
+            _tryPreloadAttach: function() {
+
+                if (this.locked) {
+                    return false;
+                }
+
+                this.locked = true;
+
+
+                // keep trying until after the page is loaded.  We need to 
+                // check the page load state prior to trying to bind the 
+                // elements so that we can be certain all elements have been 
+                // tested appropriately
+                var tryAgain = !loadComplete;
+                if (!tryAgain) {
+                    tryAgain = (retryCount > 0);
+                }
+
+                // onAvailable
+                var notAvail = [];
+                for (var i=0,len=onAvailStack.length; i<len ; ++i) {
+                    var item = onAvailStack[i];
+                    if (item) {
+                        var el = this.getEl(item.id);
+
+                        if (el) {
+                            // The element is available, but not necessarily ready
+                            // @todo verify IE7 compatibility
+                            // @todo should we test parentNode.nextSibling?
+                            // @todo re-evaluate global content ready
+                            if ( !item.checkReady || 
+                                    loadComplete || 
+                                    el.nextSibling ||
+                                    (document && document.body) ) {
+
+                                var scope = el;
+                                if (item.override) {
+                                    if (item.override === true) {
+                                        scope = item.obj;
+                                    } else {
+                                        scope = item.override;
+                                    }
+                                }
+                                item.fn.call(scope, item.obj);
+                                //delete onAvailStack[i];
+                                // null out instead of delete for Opera
+                                onAvailStack[i] = null;
+                            }
+                        } else {
+                            notAvail.push(item);
+                        }
+                    }
+                }
+
+                retryCount = (notAvail.length === 0) ? 0 : retryCount - 1;
+
+                if (tryAgain) {
+                    onAvailStack = notAvail; // cleanse the array
+                    this.startInterval();
+                } else {
+                    clearInterval(this._interval);
+                    this._interval = null;
+                }
+
+                this.locked = false;
+
+                return true;
+
+            },
+
+            /**
+             * Removes all listeners attached to the given element via addListener.
+             * Optionally, the node's children can also be purged.
+             * Optionally, you can specify a specific type of event to remove.
+             * @method purgeElement
+             * @param {HTMLElement} el the element to purge
+             * @param {boolean} recurse recursively purge this element's children
+             * as well.  Use with caution.
+             * @param {string} sType optional type of listener to purge. If
+             * left out, all listeners will be removed
+             * @static
+             */
+            purgeElement: function(el, recurse, sType) {
+                var elListeners = this.getListeners(el, sType);
+                if (elListeners) {
+                    for (var i=0,len=elListeners.length; i<len ; ++i) {
+                        var l = elListeners[i];
+                        // can't use the index on the changing collection
+                        //this.removeListener(el, l.type, l.fn, l.index);
+                        this.removeListener(el, l.type, l.fn);
+                    }
+                }
+
+                if (recurse && el && el.childNodes) {
+                    for (i=0,len=el.childNodes.length; i<len ; ++i) {
+                        this.purgeElement(el.childNodes[i], recurse, sType);
+                    }
+                }
+            },
+
+            /**
+             * Returns all listeners attached to the given element via addListener.
+             * Optionally, you can specify a specific type of event to return.
+             * @method getListeners
+             * @param el {HTMLElement} the element to inspect 
+             * @param sType {string} optional type of listener to return. If
+             * left out, all listeners will be returned
+             * @return {Object} the listener. Contains the following fields:
+             * &nbsp;&nbsp;type:   (string)   the type of event
+             * &nbsp;&nbsp;fn:     (function) the callback supplied to addListener
+             * &nbsp;&nbsp;obj:    (object)   the custom object supplied to addListener
+             * &nbsp;&nbsp;adjust: (boolean)  whether or not to adjust the default scope
+             * &nbsp;&nbsp;index:  (int)      its position in the Event util listener cache
+             * @static
+             */           
+            getListeners: function(el, sType) {
+                var elListeners = [];
+                if (listeners && listeners.length > 0) {
+                    for (var i=0,len=listeners.length; i<len ; ++i) {
+                        var l = listeners[i];
+                        if ( l  && l[this.EL] === el && 
+                                (!sType || sType === l[this.TYPE]) ) {
+                            elListeners.push({
+                                type:   l[this.TYPE],
+                                fn:     l[this.FN],
+                                obj:    l[this.OBJ],
+                                adjust: l[this.ADJ_SCOPE],
+                                index:  i
+                            });
+                        }
+                    }
+                }
+
+                return (elListeners.length) ? elListeners : null;
+            },
+
+            /**
+             * Removes all listeners registered by pe.event.  Called 
+             * automatically during the unload event.
+             * @method _unload
+             * @static
+             * @private
+             */
+            _unload: function(e) {
+
+                var EU = YAHOO.util.Event, i, j, l, len, index;
+
+                for (i=0,len=unloadListeners.length; i<len; ++i) {
+                    l = unloadListeners[i];
+                    if (l) {
+                        var scope = window;
+                        if (l[EU.ADJ_SCOPE]) {
+                            if (l[EU.ADJ_SCOPE] === true) {
+                                scope = l[EU.OBJ];
+                            } else {
+                                scope = l[EU.ADJ_SCOPE];
+                            }
+                        }
+                        l[EU.FN].call(scope, EU.getEvent(e), l[EU.OBJ] );
+                        unloadListeners[i] = null;
+                        l=null;
+                        scope=null;
+                    }
+                }
+
+                unloadListeners = null;
+
+                if (listeners && listeners.length > 0) {
+                    j = listeners.length;
+                    while (j) {
+                        index = j-1;
+                        l = listeners[index];
+                        if (l) {
+                            EU.removeListener(l[EU.EL], l[EU.TYPE], 
+                                    l[EU.FN], index);
+                        } 
+                        j = j - 1;
+                    }
+                    l=null;
+
+                    EU.clearCache();
+                }
+
+                for (i=0,len=legacyEvents.length; i<len; ++i) {
+                    // dereference the element
+                    //delete legacyEvents[i][0];
+                    legacyEvents[i][0] = null;
+
+                    // delete the array item
+                    //delete legacyEvents[i];
+                    legacyEvents[i] = null;
+                }
+
+                legacyEvents = null;
+
+                EU._simpleRemove(window, "unload", EU._unload);
+
+            },
+
+            /**
+             * Returns scrollLeft
+             * @method _getScrollLeft
+             * @static
+             * @private
+             */
+            _getScrollLeft: function() {
+                return this._getScroll()[1];
+            },
+
+            /**
+             * Returns scrollTop
+             * @method _getScrollTop
+             * @static
+             * @private
+             */
+            _getScrollTop: function() {
+                return this._getScroll()[0];
+            },
+
+            /**
+             * Returns the scrollTop and scrollLeft.  Used to calculate the 
+             * pageX and pageY in Internet Explorer
+             * @method _getScroll
+             * @static
+             * @private
+             */
+            _getScroll: function() {
+                var dd = document.documentElement, db = document.body;
+                if (dd && (dd.scrollTop || dd.scrollLeft)) {
+                    return [dd.scrollTop, dd.scrollLeft];
+                } else if (db) {
+                    return [db.scrollTop, db.scrollLeft];
+                } else {
+                    return [0, 0];
+                }
+            },
+
+            /**
+             * Adds a DOM event directly without the caching, cleanup, scope adj, etc
+             *
+             * @method _simpleAdd
+             * @param {HTMLElement} el      the element to bind the handler to
+             * @param {string}      sType   the type of event handler
+             * @param {function}    fn      the callback to invoke
+             * @param {boolen}      capture capture or bubble phase
+             * @static
+             * @private
+             */
+            _simpleAdd: function () {
+                if (window.addEventListener) {
+                    return function(el, sType, fn, capture) {
+                        el.addEventListener(sType, fn, (capture));
+                    };
+                } else if (window.attachEvent) {
+                    return function(el, sType, fn, capture) {
+                        el.attachEvent("on" + sType, fn);
+                    };
+                } else {
+                    return function(){};
+                }
+            }(),
+
+            /**
+             * Basic remove listener
+             *
+             * @method _simpleRemove
+             * @param {HTMLElement} el      the element to bind the handler to
+             * @param {string}      sType   the type of event handler
+             * @param {function}    fn      the callback to invoke
+             * @param {boolen}      capture capture or bubble phase
+             * @static
+             * @private
+             */
+            _simpleRemove: function() {
+                if (window.removeEventListener) {
+                    return function (el, sType, fn, capture) {
+                        el.removeEventListener(sType, fn, (capture));
+                    };
+                } else if (window.detachEvent) {
+                    return function (el, sType, fn) {
+                        el.detachEvent("on" + sType, fn);
+                    };
+                } else {
+                    return function(){};
+                }
+            }()
+        };
+
+    }();
+
+    (function() {
+        var EU = YAHOO.util.Event;
+
+        /**
+         * YAHOO.util.Event.on is an alias for addListener
+         * @method on
+         * @see addListener
+         * @static
+         */
+        EU.on = EU.addListener;
+
+        // YAHOO.mix(EU, YAHOO.util.EventProvider.prototype);
+        // EU.createEvent("DOMContentReady");
+        // EU.subscribe("DOMContentReady", EU._load);
+
+        if (document && document.body) {
+            EU._load();
+        } else {
+            // EU._simpleAdd(document, "DOMContentLoaded", EU._load);
+            EU._simpleAdd(window, "load", EU._load);
+        }
+        EU._simpleAdd(window, "unload", EU._unload);
+        EU._tryPreloadAttach();
+    })();
+}
+
+/**
+ * EventProvider is designed to be used with YAHOO.augment to wrap 
+ * CustomEvents in an interface that allows events to be subscribed to 
+ * and fired by name.  This makes it possible for implementing code to
+ * subscribe to an event that either has not been created yet, or will
+ * not be created at all.
+ *
+ * @Class EventProvider
+ */
+YAHOO.util.EventProvider = function() { };
+
+YAHOO.util.EventProvider.prototype = {
+
+    /**
+     * Private storage of custom events
+     * @property __yui_events
+     * @type Object[]
+     * @private
+     */
+    __yui_events: null,
+
+    /**
+     * Private storage of custom event subscribers
+     * @property __yui_subscribers
+     * @type Object[]
+     * @private
+     */
+    __yui_subscribers: null,
+    
+    /**
+     * Subscribe to a CustomEvent by event type
+     *
+     * @method subscribe
+     * @param p_type     {string}   the type, or name of the event
+     * @param p_fn       {function} the function to exectute when the event fires
+     * @param p_obj
+     * @param p_obj      {Object}   An object to be passed along when the event 
+     *                              fires
+     * @param p_override {boolean}  If true, the obj passed in becomes the 
+     *                              execution scope of the listener
+     */
+    subscribe: function(p_type, p_fn, p_obj, p_override) {
+
+        this.__yui_events = this.__yui_events || {};
+        var ce = this.__yui_events[p_type];
+
+        if (ce) {
+            ce.subscribe(p_fn, p_obj, p_override);
+        } else {
+            this.__yui_subscribers = this.__yui_subscribers || {};
+            var subs = this.__yui_subscribers;
+            if (!subs[p_type]) {
+                subs[p_type] = [];
+            }
+            subs[p_type].push(
+                { fn: p_fn, obj: p_obj, override: p_override } );
+        }
+    },
+
+    /**
+     * Unsubscribes the from the specified event
+     * @method unsubscribe
+     * @param p_type {string}   The type, or name of the event
+     * @param p_fn   {Function} The function to execute
+     * @param p_obj  {Object}   The custom object passed to subscribe (optional)
+     * @return {boolean} true if the subscriber was found and detached.
+     */
+    unsubscribe: function(p_type, p_fn, p_obj) {
+        this.__yui_events = this.__yui_events || {};
+        var ce = this.__yui_events[p_type];
+        if (ce) {
+            return ce.unsubscribe(p_fn, p_obj);
+        } else {
+            return false;
+        }
+    },
+
+    /**
+     * Creates a new custom event of the specified type.  If a custom event
+     * by that name already exists, it will not be re-created.  In either
+     * case the custom event is returned. 
+     *
+     * @method createEvent
+     *
+     * @param p_type {string} the type, or name of the event
+     * @param p_config {object} optional config params.  Valid properties are:
+     *
+     *  <ul>
+     *    <li>
+     *      scope: defines the default execution scope.  If not defined
+     *      the default scope will be this instance.
+     *    </li>
+     *    <li>
+     *      silent: if true, the custom event will not generate log messages.
+     *      This is false by default.
+     *    </li>
+     *    <li>
+     *      onSubscribeCallback: specifies a callback to execute when the
+     *      event has a new subscriber.  This will fire immediately for
+     *      each queued subscriber if any exist prior to the creation of
+     *      the event.
+     *    </li>
+     *  </ul>
+     *
+     *  @return {CustomEvent} the custom event
+     *
+     */
+    createEvent: function(p_type, p_config) {
+
+        this.__yui_events = this.__yui_events || {};
+        var opts = p_config || {};
+        var events = this.__yui_events;
+
+        if (events[p_type]) {
+        } else {
+
+            var scope  = opts.scope  || this;
+            var silent = opts.silent || null;
+
+            var ce = new YAHOO.util.CustomEvent(p_type, scope, silent,
+                    YAHOO.util.CustomEvent.FLAT);
+            events[p_type] = ce;
+
+            if (opts.onSubscribeCallback) {
+                ce.subscribeEvent.subscribe(opts.onSubscribeCallback);
+            }
+
+            this.__yui_subscribers = this.__yui_subscribers || {};
+            var qs = this.__yui_subscribers[p_type];
+
+            if (qs) {
+                for (var i=0; i<qs.length; ++i) {
+                    ce.subscribe(qs[i].fn, qs[i].obj, qs[i].override);
+                }
+            }
+        }
+
+        return events[p_type];
+    },
+
+   /**
+     * Fire a custom event by name.  The callback functions will be executed
+     * from the scope specified when the event was created, and with the 
+     * following parameters:
+     *   <ul>
+     *   <li>The first argument fire() was executed with</li>
+     *   <li>The custom object (if any) that was passed into the subscribe() 
+     *       method</li>
+     *   </ul>
+     * @method fireEvent
+     * @param p_type    {string}  the type, or name of the event
+     * @param arguments {Object*} an arbitrary set of parameters to pass to 
+     *                            the handler.
+     * @return {boolean} the return value from CustomEvent.fire, or null if 
+     *                   the custom event does not exist.
+     */
+    fireEvent: function(p_type, arg1, arg2, etc) {
+
+        this.__yui_events = this.__yui_events || {};
+        var ce = this.__yui_events[p_type];
+
+        if (ce) {
+            var args = [];
+            for (var i=1; i<arguments.length; ++i) {
+                args.push(arguments[i]);
+            }
+            return ce.fire.apply(ce, args);
+        } else {
+            return null;
+        }
+    },
+
+    /**
+     * Returns true if the custom event of the provided type has been created
+     * with createEvent.
+     * @method hasEvent
+     * @param type {string} the type, or name of the event
+     */
+    hasEvent: function(type) {
+        if (this.__yui_events) {
+            if (this.__yui_events[type]) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+};
+

Added: jifty/branches/schema-plugins/share/web/static/js/yui/tabview.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/yui/tabview.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,1950 @@
+/*
+Copyright (c) 2006, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.net/yui/license.txt
+version: 0.12.0
+*/
+(function() {
+
+    YAHOO.util.Lang = {
+        isArray: function(val) { // frames lose type, so test constructor string
+            if (val.constructor && val.constructor.toString().indexOf('Array') > -1) {
+                return true;
+            } else {
+                return YAHOO.util.Lang.isObject(val) && val.constructor == Array;
+            }
+        },
+
+        isBoolean: function(val) {
+            return typeof val == 'boolean';
+        },
+
+        isFunction: function(val) {
+            return typeof val == 'function';
+        },
+
+        isNull: function(val) {
+            return val === null;
+        },
+
+        isNumber: function(val) {
+            return !isNaN(val);
+        },
+
+        isObject: function(val) {
+            return typeof val == 'object' || YAHOO.util.Lang.isFunction(val);
+        },
+
+        isString: function(val) {
+            return typeof val == 'string';
+        },
+
+        isUndefined: function(val) {
+            return typeof val == 'undefined';
+        }
+    };
+})();/**
+ * Provides Attribute configurations.
+ * @namespace YAHOO.util
+ * @class Attribute
+ * @constructor
+ * @param hash {Object} The intial Attribute.
+ * @param {YAHOO.util.AttributeProvider} The owner of the Attribute instance.
+ */
+
+YAHOO.util.Attribute = function(hash, owner) {
+    if (owner) {
+        this.owner = owner;
+        this.configure(hash, true);
+    }
+};
+
+YAHOO.util.Attribute.prototype = {
+	/**
+     * The name of the attribute.
+	 * @property name
+	 * @type String
+	 */
+    name: undefined,
+
+	/**
+     * The value of the attribute.
+	 * @property value
+	 * @type String
+	 */
+    value: null,
+
+	/**
+     * The owner of the attribute.
+	 * @property owner
+	 * @type YAHOO.util.AttributeProvider
+	 */
+    owner: null,
+
+	/**
+     * Whether or not the attribute is read only.
+	 * @property readOnly
+	 * @type Boolean
+	 */
+    readOnly: false,
+
+	/**
+     * Whether or not the attribute can only be written once.
+	 * @property writeOnce
+	 * @type Boolean
+	 */
+    writeOnce: false,
+
+	/**
+     * The attribute's initial configuration.
+     * @private
+	 * @property _initialConfig
+	 * @type Object
+	 */
+    _initialConfig: null,
+
+	/**
+     * Whether or not the attribute's value has been set.
+     * @private
+	 * @property _written
+	 * @type Boolean
+	 */
+    _written: false,
+
+	/**
+     * The method to use when setting the attribute's value.
+     * The method recieves the new value as the only argument.
+	 * @property method
+	 * @type Function
+	 */
+    method: null,
+
+	/**
+     * The validator to use when setting the attribute's value.
+	 * @property validator
+	 * @type Function
+     * @return Boolean
+	 */
+    validator: null,
+
+    /**
+     * Retrieves the current value of the attribute.
+     * @method getValue
+     * @return {any} The current value of the attribute.
+     */
+    getValue: function() {
+        return this.value;
+    },
+
+    /**
+     * Sets the value of the attribute and fires beforeChange and change events.
+     * @method setValue
+     * @param {Any} value The value to apply to the attribute.
+     * @param {Boolean} silent If true the change events will not be fired.
+     * @return {Boolean} Whether or not the value was set.
+     */
+    setValue: function(value, silent) {
+        var beforeRetVal;
+        var owner = this.owner;
+        var name = this.name;
+
+        var event = {
+            type: name,
+            prevValue: this.getValue(),
+            newValue: value
+        };
+
+        if (this.readOnly || ( this.writeOnce && this._written) ) {
+            return false; // write not allowed
+        }
+
+        if (this.validator && !this.validator.call(owner, value) ) {
+            return false; // invalid value
+        }
+
+        if (!silent) {
+            beforeRetVal = owner.fireBeforeChangeEvent(event);
+            if (beforeRetVal === false) {
+                YAHOO.log('setValue ' + name +
+                        'cancelled by beforeChange event', 'info', 'Attribute');
+                return false;
+            }
+        }
+
+        if (this.method) {
+            this.method.call(owner, value);
+        }
+
+        this.value = value;
+        this._written = true;
+
+        event.type = name;
+
+        if (!silent) {
+            this.owner.fireChangeEvent(event);
+        }
+
+        return true;
+    },
+
+    /**
+     * Allows for configuring the Attribute's properties.
+     * @method configure
+     * @param {Object} map A key-value map of Attribute properties.
+     * @param {Boolean} init Whether or not this should become the initial config.
+     */
+    configure: function(map, init) {
+        map = map || {};
+        this._written = false; // reset writeOnce
+        this._initialConfig = this._initialConfig || {};
+
+        for (var key in map) {
+            if ( key && map.hasOwnProperty(key) ) {
+                this[key] = map[key];
+                if (init) {
+                    this._initialConfig[key] = map[key];
+                }
+            }
+        }
+    },
+
+    /**
+     * Resets the value to the initial config value.
+     * @method resetValue
+     * @return {Boolean} Whether or not the value was set.
+     */
+    resetValue: function() {
+        return this.setValue(this._initialConfig.value);
+    },
+
+    /**
+     * Resets the attribute config to the initial config state.
+     * @method resetConfig
+     */
+    resetConfig: function() {
+        this.configure(this._initialConfig);
+    },
+
+    /**
+     * Resets the value to the current value.
+     * Useful when values may have gotten out of sync with actual properties.
+     * @method refresh
+     * @return {Boolean} Whether or not the value was set.
+     */
+    refresh: function(silent) {
+        this.setValue(this.value, silent);
+    }
+};(function() {
+    var Lang = YAHOO.util.Lang;
+
+    /*
+    Copyright (c) 2006, Yahoo! Inc. All rights reserved.
+    Code licensed under the BSD License:
+    http://developer.yahoo.net/yui/license.txt
+    */
+
+    /**
+     * Provides and manages YAHOO.util.Attribute instances
+     * @namespace YAHOO.util
+     * @class AttributeProvider
+     * @uses YAHOO.util.EventProvider
+     */
+    YAHOO.util.AttributeProvider = function() {};
+
+    YAHOO.util.AttributeProvider.prototype = {
+
+        /**
+         * A key-value map of Attribute configurations
+         * @property _configs
+         * @protected (may be used by subclasses and augmentors)
+         * @private
+         * @type {Object}
+         */
+        _configs: null,
+        /**
+         * Returns the current value of the attribute.
+         * @method get
+         * @param {String} key The attribute whose value will be returned.
+         */
+        get: function(key){
+            var configs = this._configs || {};
+            var config = configs[key];
+
+            if (!config) {
+                YAHOO.log(key + ' not found', 'error', 'AttributeProvider');
+                return undefined;
+            }
+
+            return config.value;
+        },
+
+        /**
+         * Sets the value of a config.
+         * @method set
+         * @param {String} key The name of the attribute
+         * @param {Any} value The value to apply to the attribute
+         * @param {Boolean} silent Whether or not to suppress change events
+         * @return {Boolean} Whether or not the value was set.
+         */
+        set: function(key, value, silent){
+            var configs = this._configs || {};
+            var config = configs[key];
+
+            if (!config) {
+                YAHOO.log('set failed: ' + key + ' not found',
+                        'error', 'AttributeProvider');
+                return false;
+            }
+
+            return config.setValue(value, silent);
+        },
+
+        /**
+         * Returns an array of attribute names.
+         * @method getAttributeKeys
+         * @return {Array} An array of attribute names.
+         */
+        getAttributeKeys: function(){
+            var configs = this._configs;
+            var keys = [];
+            var config;
+            for (var key in configs) {
+                config = configs[key];
+                if ( configs.hasOwnProperty(key) &&
+                        !Lang.isUndefined(config) ) {
+                    keys[keys.length] = key;
+                }
+            }
+
+            return keys;
+        },
+
+        /**
+         * Sets multiple attribute values.
+         * @method setAttributes
+         * @param {Object} map  A key-value map of attributes
+         * @param {Boolean} silent Whether or not to suppress change events
+         */
+        setAttributes: function(map, silent){
+            for (var key in map) {
+                if ( map.hasOwnProperty(key) ) {
+                    this.set(key, map[key], silent);
+                }
+            }
+        },
+
+        /**
+         * Resets the specified attribute's value to its initial value.
+         * @method resetValue
+         * @param {String} key The name of the attribute
+         * @param {Boolean} silent Whether or not to suppress change events
+         * @return {Boolean} Whether or not the value was set
+         */
+        resetValue: function(key, silent){
+            var configs = this._configs || {};
+            if (configs[key]) {
+                this.set(key, configs[key]._initialConfig.value, silent);
+                return true;
+            }
+            return false;
+        },
+
+        /**
+         * Sets the attribute's value to its current value.
+         * @method refresh
+         * @param {String | Array} key The attribute(s) to refresh
+         * @param {Boolean} silent Whether or not to suppress change events
+         */
+        refresh: function(key, silent){
+            var configs = this._configs;
+
+            key = ( ( Lang.isString(key) ) ? [key] : key ) ||
+                    this.getAttributeKeys();
+
+            for (var i = 0, len = key.length; i < len; ++i) {
+                if ( // only set if there is a value and not null
+                    configs[key[i]] &&
+                    ! Lang.isUndefined(configs[key[i]].value) &&
+                    ! Lang.isNull(configs[key[i]].value) ) {
+                    configs[key[i]].refresh(silent);
+                }
+            }
+        },
+
+        /**
+         * Adds an Attribute to the AttributeProvider instance.
+         * @method register
+         * @param {String} key The attribute's name
+         * @param {Object} map A key-value map containing the
+         * attribute's properties.
+         */
+        register: function(key, map) {
+            this._configs = this._configs || {};
+
+            if (this._configs[key]) { // dont override
+                return false;
+            }
+
+            map.name = key;
+            this._configs[key] = new YAHOO.util.Attribute(map, this);
+            return true;
+        },
+
+        /**
+         * Returns the attribute's properties.
+         * @method getAttributeConfig
+         * @param {String} key The attribute's name
+         * @private
+         * @return {object} A key-value map containing all of the
+         * attribute's properties.
+         */
+        getAttributeConfig: function(key) {
+            var configs = this._configs || {};
+            var config = configs[key] || {};
+            var map = {}; // returning a copy to prevent overrides
+
+            for (key in config) {
+                if ( config.hasOwnProperty(key) ) {
+                    map[key] = config[key];
+                }
+            }
+
+            return map;
+        },
+
+        /**
+         * Sets or updates an Attribute instance's properties.
+         * @method configureAttribute
+         * @param {String} key The attribute's name.
+         * @param {Object} map A key-value map of attribute properties
+         * @param {Boolean} init Whether or not this should become the intial config.
+         */
+        configureAttribute: function(key, map, init) {
+            var configs = this._configs || {};
+
+            if (!configs[key]) {
+                YAHOO.log('unable to configure, ' + key + ' not found',
+                        'error', 'AttributeProvider');
+                return false;
+            }
+
+            configs[key].configure(map, init);
+        },
+
+        /**
+         * Resets an attribute to its intial configuration.
+         * @method resetAttributeConfig
+         * @param {String} key The attribute's name.
+         * @private
+         */
+        resetAttributeConfig: function(key){
+            var configs = this._configs || {};
+            configs[key].resetConfig();
+        },
+
+        /**
+         * Fires the attribute's beforeChange event.
+         * @method fireBeforeChangeEvent
+         * @param {String} key The attribute's name.
+         * @param {Obj} e The event object to pass to handlers.
+         */
+        fireBeforeChangeEvent: function(e) {
+            var type = 'before';
+            type += e.type.charAt(0).toUpperCase() + e.type.substr(1) + 'Change';
+            e.type = type;
+            return this.fireEvent(e.type, e);
+        },
+
+        /**
+         * Fires the attribute's change event.
+         * @method fireChangeEvent
+         * @param {String} key The attribute's name.
+         * @param {Obj} e The event object to pass to the handlers.
+         */
+        fireChangeEvent: function(e) {
+            e.type += 'Change';
+            return this.fireEvent(e.type, e);
+        }
+    };
+
+    YAHOO.augment(YAHOO.util.AttributeProvider, YAHOO.util.EventProvider);
+})();(function() {
+// internal shorthand
+var Dom = YAHOO.util.Dom,
+    Lang = YAHOO.util.Lang,
+    EventPublisher = YAHOO.util.EventPublisher,
+    AttributeProvider = YAHOO.util.AttributeProvider;
+
+/**
+ * Element provides an interface to an HTMLElement's attributes and common
+ * methods.  Other commonly used attributes are added as well.
+ * @namespace YAHOO.util
+ * @class Element
+ * @uses YAHOO.util.AttributeProvider
+ * @constructor
+ * @param el {HTMLElement | String} The html element that
+ * represents the Element.
+ * @param {Object} map A key-value map of initial config names and values
+ */
+YAHOO.util.Element = function(el, map) {
+    if (arguments.length) {
+        this.init(el, map);
+    }
+};
+
+YAHOO.util.Element.prototype = {
+	/**
+     * Dom events supported by the Element instance.
+	 * @property DOM_EVENTS
+	 * @type Object
+	 */
+    DOM_EVENTS: null,
+
+	/**
+     * Wrapper for HTMLElement method.
+	 * @method appendChild
+	 * @param {Boolean} deep Whether or not to do a deep clone
+	 */
+    appendChild: function(child) {
+        child = child.get ? child.get('element') : child;
+        this.get('element').appendChild(child);
+    },
+
+	/**
+     * Wrapper for HTMLElement method.
+	 * @method getElementsByTagName
+	 * @param {String} tag The tagName to collect
+	 */
+    getElementsByTagName: function(tag) {
+        return this.get('element').getElementsByTagName(tag);
+    },
+
+	/**
+     * Wrapper for HTMLElement method.
+	 * @method hasChildNodes
+	 * @return {Boolean} Whether or not the element has childNodes
+	 */
+    hasChildNodes: function() {
+        return this.get('element').hasChildNodes();
+    },
+
+	/**
+     * Wrapper for HTMLElement method.
+	 * @method insertBefore
+	 * @param {HTMLElement} element The HTMLElement to insert
+	 * @param {HTMLElement} before The HTMLElement to insert
+     * the element before.
+	 */
+    insertBefore: function(element, before) {
+        element = element.get ? element.get('element') : element;
+        before = (before && before.get) ? before.get('element') : before;
+
+        this.get('element').insertBefore(element, before);
+    },
+
+	/**
+     * Wrapper for HTMLElement method.
+	 * @method removeChild
+	 * @param {HTMLElement} child The HTMLElement to remove
+	 */
+    removeChild: function(child) {
+        child = child.get ? child.get('element') : child;
+        this.get('element').removeChild(child);
+        return true;
+    },
+
+	/**
+     * Wrapper for HTMLElement method.
+	 * @method replaceChild
+	 * @param {HTMLElement} newNode The HTMLElement to insert
+	 * @param {HTMLElement} oldNode The HTMLElement to replace
+	 */
+    replaceChild: function(newNode, oldNode) {
+        newNode = newNode.get ? newNode.get('element') : newNode;
+        oldNode = oldNode.get ? oldNode.get('element') : oldNode;
+        return this.get('element').replaceChild(newNode, oldNode);
+    },
+
+
+    /**
+     * Registers Element specific attributes.
+     * @method initAttributes
+     * @param {Object} map A key-value map of initial attribute configs
+     */
+    initAttributes: function(map) {
+        map = map || {};
+        var element = Dom.get(map.element) || null;
+
+        /**
+         * The HTMLElement the Element instance refers to.
+         * @config element
+         * @type HTMLElement
+         */
+        this.register('element', {
+            value: element,
+            readOnly: true
+         });
+    },
+
+    /**
+     * Adds a listener for the given event.  These may be DOM or
+     * customEvent listeners.  Any event that is fired via fireEvent
+     * can be listened for.  All handlers receive an event object.
+     * @method addListener
+     * @param {String} type The name of the event to listen for
+     * @param {Function} fn The handler to call when the event fires
+     * @param {Any} obj A variable to pass to the handler
+     * @param {Object} scope The object to use for the scope of the handler
+     */
+    addListener: function(type, fn, obj, scope) {
+        var el = this.get('element');
+        var scope = scope || this;
+
+        el = this.get('id') || el;
+
+        if (!this._events[type]) { // create on the fly
+            if ( this.DOM_EVENTS[type] ) {
+                YAHOO.util.Event.addListener(el, type, function(e) {
+                    if (e.srcElement && !e.target) { // supplement IE with target
+                        e.target = e.srcElement;
+                    }
+                    this.fireEvent(type, e);
+                }, obj, scope);
+            }
+
+            this.createEvent(type, this);
+            this._events[type] = true;
+        }
+
+        this.subscribe.apply(this, arguments); // notify via customEvent
+    },
+
+
+    /**
+     * Alias for addListener
+     * @method on
+     * @param {String} type The name of the event to listen for
+     * @param {Function} fn The function call when the event fires
+     * @param {Any} obj A variable to pass to the handler
+     * @param {Object} scope The object to use for the scope of the handler
+     */
+    on: function() { this.addListener.apply(this, arguments); },
+
+
+    /**
+     * Remove an event listener
+     * @method removeListener
+     * @param {String} type The name of the event to listen for
+     * @param {Function} fn The function call when the event fires
+     */
+    removeListener: function(type, fn) {
+        this.unsubscribe.apply(this, arguments);
+    },
+
+	/**
+     * Wrapper for Dom method.
+	 * @method addClass
+	 * @param {String} className The className to add
+	 */
+    addClass: function(className) {
+        Dom.addClass(this.get('element'), className);
+    },
+
+	/**
+     * Wrapper for Dom method.
+	 * @method getElementsByClassName
+	 * @param {String} className The className to collect
+	 * @param {String} tag (optional) The tag to use in
+     * conjunction with class name
+     * @return {Array} Array of HTMLElements
+	 */
+    getElementsByClassName: function(className, tag) {
+        return Dom.getElementsByClassName(className, tag,
+                this.get('element') );
+    },
+
+	/**
+     * Wrapper for Dom method.
+	 * @method hasClass
+	 * @param {String} className The className to add
+     * @return {Boolean} Whether or not the element has the class name
+	 */
+    hasClass: function(className) {
+        return Dom.hasClass(this.get('element'), className);
+    },
+
+	/**
+     * Wrapper for Dom method.
+	 * @method removeClass
+	 * @param {String} className The className to remove
+	 */
+    removeClass: function(className) {
+        return Dom.removeClass(this.get('element'), className);
+    },
+
+	/**
+     * Wrapper for Dom method.
+	 * @method replaceClass
+	 * @param {String} oldClassName The className to replace
+	 * @param {String} newClassName The className to add
+	 */
+    replaceClass: function(oldClassName, newClassName) {
+        return Dom.replaceClass(this.get('element'),
+                oldClassName, newClassName);
+    },
+
+	/**
+     * Wrapper for Dom method.
+	 * @method setStyle
+	 * @param {String} property The style property to set
+	 * @param {String} value The value to apply to the style property
+	 */
+    setStyle: function(property, value) {
+        return Dom.setStyle(this.get('element'),  property, value);
+    },
+
+	/**
+     * Wrapper for Dom method.
+	 * @method getStyle
+	 * @param {String} property The style property to retrieve
+	 * @return {String} The current value of the property
+	 */
+    getStyle: function(property) {
+        return Dom.getStyle(this.get('element'),  property);
+    },
+
+	/**
+     * Apply any queued set calls.
+	 * @method fireQueue
+	 */
+    fireQueue: function() {
+        var queue = this._queue;
+        for (var i = 0, len = queue.length; i < len; ++i) {
+            this[queue[i][0]].apply(this, queue[i][1]);
+        }
+    },
+
+	/**
+     * Appends the HTMLElement into either the supplied parentNode.
+	 * @method appendTo
+	 * @param {HTMLElement | Element} parentNode The node to append to
+	 * @param {HTMLElement | Element} before An optional node to insert before
+	 */
+    appendTo: function(parent, before) {
+        parent = (parent.get) ?  parent.get('element') : Dom.get(parent);
+
+        before = (before && before.get) ?
+                before.get('element') : Dom.get(before);
+        var element = this.get('element');
+
+        var newAddition =  !Dom.inDocument(element);
+
+        if (!element) {
+            YAHOO.log('appendTo failed: element not available',
+                    'error', 'Element');
+            return false;
+        }
+
+        if (!parent) {
+            YAHOO.log('appendTo failed: parent not available',
+                    'error', 'Element');
+            return false;
+        }
+
+        if (element.parent != parent) {
+            if (before) {
+                parent.insertBefore(element, before);
+            } else {
+                parent.appendChild(element);
+            }
+        }
+
+        YAHOO.log(element + 'appended to ' + parent);
+
+        if (!newAddition) {
+            return false; // note return; no refresh if in document
+        }
+
+        // if a new addition, refresh HTMLElement any applied attributes
+        var keys = this.getAttributeKeys();
+
+        for (var key in keys) { // only refresh HTMLElement attributes
+            if ( !Lang.isUndefined(element[key]) ) {
+                this.refresh(key);
+            }
+        }
+    },
+
+    get: function(key) {
+        var configs = this._configs || {};
+        var el = configs.element; // avoid loop due to 'element'
+        if (el && !configs[key] && !Lang.isUndefined(el.value[key]) ) {
+            return el.value[key];
+        }
+
+        return AttributeProvider.prototype.get.call(this, key);
+    },
+
+    set: function(key, value, silent) {
+        var el = this.get('element');
+        if (!el) {
+            this._queue[key] = ['set', arguments];
+            return false;
+        }
+
+        // set it on the element if not a property
+        if ( !this._configs[key] && !Lang.isUndefined(el[key]) ) {
+            _registerHTMLAttr(this, key);
+        }
+
+        return AttributeProvider.prototype.set.apply(this, arguments);
+    },
+
+    register: function(key) { // protect html attributes
+        var configs = this._configs || {};
+        var element = this.get('element') || null;
+
+        if ( element && !Lang.isUndefined(element[key]) ) {
+            YAHOO.log(key + ' is reserved for ' + element,
+                    'error', 'Element');
+            return false;
+        }
+
+        return AttributeProvider.prototype.register.apply(this, arguments);
+    },
+
+    configureAttribute: function(property, map, init) { // protect html attributes
+        if (!this._configs[property] && this._configs.element &&
+                !Lang.isUndefined(this._configs.element[property]) ) {
+            _registerHTMLAttr(this, property, map);
+            return false;
+        }
+
+        return AttributeProvider.prototype.configure.apply(this, arguments);
+    },
+
+    getAttributeKeys: function() {
+        var el = this.get('element');
+        var keys = AttributeProvider.prototype.getAttributeKeys.call(this);
+
+        //add any unconfigured element keys
+        for (var key in el) {
+            if (!this._configs[key]) {
+                keys[key] = keys[key] || el[key];
+            }
+        }
+
+        return keys;
+    },
+
+    init: function(el, attr) {
+        this._queue = this._queue || [];
+        this._events = this._events || {};
+        this._configs = this._configs || {};
+        attr = attr || {};
+        attr.element = attr.element || el || null;
+
+        this.DOM_EVENTS = {
+            'click': true,
+            'keydown': true,
+            'keypress': true,
+            'keyup': true,
+            'mousedown': true,
+            'mousemove': true,
+            'mouseout': true,
+            'mouseover': true,
+            'mouseup': true
+        };
+
+        var readyHandler = function() {
+            this.initAttributes(attr);
+
+            this.setAttributes(attr, true);
+            this.fireQueue();
+            this.fireEvent('contentReady', {
+                type: 'contentReady',
+                target: attr.element
+            });
+        };
+
+        if ( Lang.isString(el) ) {
+            _registerHTMLAttr(this, 'id', { value: el });
+            YAHOO.util.Event.onAvailable(el, function() {
+                attr.element = Dom.get(el);
+                this.fireEvent('available', {
+                    type: 'available',
+                    target: attr.element
+                });
+            }, this, true);
+
+            YAHOO.util.Event.onContentReady(el, function() {
+                readyHandler.call(this);
+            }, this, true);
+        } else {
+            readyHandler.call(this);
+        }
+    }
+};
+
+/**
+ * Sets the value of the property and fires beforeChange and change events.
+ * @private
+ * @method _registerHTMLAttr
+ * @param {YAHOO.util.Element} element The Element instance to
+ * register the config to.
+ * @param {String} key The name of the config to register
+ * @param {Object} map A key-value map of the config's params
+ */
+var _registerHTMLAttr = function(self, key, map) {
+    var el = self.get('element');
+    map = map || {};
+    map.name = key;
+    map.method = map.method || function(value) {
+        el[key] = value;
+    };
+    map.value = map.value || el[key];
+    self._configs[key] = new YAHOO.util.Attribute(map, self);
+};
+
+/**
+ * Fires when the Element's HTMLElement can be retrieved by Id.
+ * <p>See: <a href="#addListener">Element.addListener</a></p>
+ * <p><strong>Event fields:</strong><br>
+ * <code>&lt;String&gt; type</code> available<br>
+ * <code>&lt;HTMLElement&gt;
+ * target</code> the HTMLElement bound to this Element instance<br>
+ * <p><strong>Usage:</strong><br>
+ * <code>var handler = function(e) {var target = e.target};<br>
+ * myTabs.addListener('available', handler);</code></p>
+ * @event available
+ */
+
+/**
+ * Fires when the Element's HTMLElement subtree is rendered.
+ * <p>See: <a href="#addListener">Element.addListener</a></p>
+ * <p><strong>Event fields:</strong><br>
+ * <code>&lt;String&gt; type</code> contentReady<br>
+ * <code>&lt;HTMLElement&gt;
+ * target</code> the HTMLElement bound to this Element instance<br>
+ * <p><strong>Usage:</strong><br>
+ * <code>var handler = function(e) {var target = e.target};<br>
+ * myTabs.addListener('contentReady', handler);</code></p>
+ * @event contentReady
+ */
+
+YAHOO.augment(YAHOO.util.Element, AttributeProvider);
+})();(function() {
+    var Dom = YAHOO.util.Dom,
+        Event = YAHOO.util.Event,
+        Lang = YAHOO.util.Lang;
+
+    /**
+     * A representation of a Tab's label and content.
+     * @namespace YAHOO.widget
+     * @class Tab
+     * @extends YAHOO.util.Element
+     * @constructor
+     * @param element {HTMLElement | String} (optional) The html element that
+     * represents the TabView. An element will be created if none provided.
+     * @param {Object} properties A key map of initial properties
+     */
+    Tab = function(el, attr) {
+        attr = attr || {};
+        if (arguments.length == 1 && !Lang.isString(el) && !el.nodeName) {
+            attr = el;
+            el = attr.element;
+        }
+
+        if (!el && !attr.element) {
+            el = _createTabElement.call(this, attr);
+        }
+
+        this.loadHandler =  {
+            success: function(o) {
+                this.set('content', o.responseText);
+            },
+            failure: function(o) {
+                YAHOO.log('loading failed: ' + o.statusText,
+                        'error', 'Tab');
+            }
+        };
+
+        Tab.superclass.constructor.call(this, el, attr);
+
+        this.DOM_EVENTS = {}; // delegating to tabView
+    };
+
+    YAHOO.extend(Tab, YAHOO.util.Element);
+    var proto = Tab.prototype;
+
+    /**
+     * The default tag name for a Tab's inner element.
+     * @property LABEL_INNER_TAGNAME
+     * @type String
+     * @default "em"
+     */
+    proto.LABEL_TAGNAME = 'em';
+
+    /**
+     * The class name applied to active tabs.
+     * @property ACTIVE_CLASSNAME
+     * @type String
+     * @default "on"
+     */
+    proto.ACTIVE_CLASSNAME = 'selected';
+
+    /**
+     * The class name applied to disabled tabs.
+     * @property DISABLED_CLASSNAME
+     * @type String
+     * @default "disabled"
+     */
+    proto.DISABLED_CLASSNAME = 'disabled';
+
+    /**
+     * The class name applied to dynamic tabs while loading.
+     * @property LOADING_CLASSNAME
+     * @type String
+     * @default "disabled"
+     */
+    proto.LOADING_CLASSNAME = 'loading';
+
+    /**
+     * Provides a reference to the connection request object when data is
+     * loaded dynamically.
+     * @property dataConnection
+     * @type Object
+     */
+    proto.dataConnection = null;
+
+    /**
+     * Object containing success and failure callbacks for loading data.
+     * @property loadHandler
+     * @type object
+     */
+    proto.loadHandler = null;
+
+    /**
+     * Provides a readable name for the tab.
+     * @method toString
+     * @return String
+     */
+    proto.toString = function() {
+        var el = this.get('element');
+        var id = el.id || el.tagName;
+        return "Tab " + id;
+    };
+
+    /**
+     * Registers TabView specific properties.
+     * @method initAttributes
+     * @param {Object} attr Hash of initial attributes
+     */
+    proto.initAttributes = function(attr) {
+        attr = attr || {};
+        Tab.superclass.initAttributes.call(this, attr);
+
+        var el = this.get('element');
+
+        /**
+         * The event that triggers the tab's activation.
+         * @config activationEvent
+         * @type String
+         */
+        this.register('activationEvent', {
+            value: attr.activationEvent || 'click'
+        });
+
+        /**
+         * The element that contains the tab's label.
+         * @config labelEl
+         * @type HTMLElement
+         */
+        this.register('labelEl', {
+            value: attr.labelEl || _getlabelEl.call(this),
+            method: function(value) {
+                var current = this.get('labelEl');
+
+                if (current) {
+                    if (current == value) {
+                        return false; // already set
+                    }
+
+                    this.replaceChild(value, current);
+                } else if (el.firstChild) { // ensure label is firstChild by default
+                    this.insertBefore(value, el.firstChild);
+                } else {
+                    this.appendChild(value);
+                }
+            }
+        });
+
+        /**
+         * The tab's label text (or innerHTML).
+         * @config label
+         * @type String
+         */
+        this.register('label', {
+            value: attr.label || _getLabel.call(this),
+            method: function(value) {
+                var labelEl = this.get('labelEl');
+                if (!labelEl) { // create if needed
+                    this.set('labelEl', _createlabelEl.call(this));
+                }
+
+                _setLabel.call(this, value);
+            }
+        });
+
+        /**
+         * The HTMLElement that contains the tab's content.
+         * @config contentEl
+         * @type HTMLElement
+         */
+        this.register('contentEl', { // TODO: apply className?
+            value: attr.contentEl || document.createElement('div'),
+            method: function(value) {
+                var current = this.get('contentEl');
+
+                if (current) {
+                    if (current == value) {
+                        return false; // already set
+                    }
+                    this.replaceChild(value, current);
+                }
+            }
+        });
+
+        /**
+         * The tab's content.
+         * @config content
+         * @type String
+         */
+        this.register('content', {
+            value: attr.content, // TODO: what about existing?
+            method: function(value) {
+                this.get('contentEl').innerHTML = value;
+            }
+        });
+
+        var _dataLoaded = false;
+
+        /**
+         * The tab's data source, used for loading content dynamically.
+         * @config dataSrc
+         * @type String
+         */
+        this.register('dataSrc', {
+            value: attr.dataSrc
+        });
+
+        /**
+         * Whether or not content should be reloaded for every view.
+         * @config cacheData
+         * @type Boolean
+         * @default false
+         */
+        this.register('cacheData', {
+            value: attr.cacheData || false,
+            validator: Lang.isBoolean
+        });
+
+        /**
+         * The method to use for the data request.
+         * @config loadMethod
+         * @type String
+         * @default "GET"
+         */
+        this.register('loadMethod', {
+            value: attr.loadMethod || 'GET',
+            validator: Lang.isString
+        });
+
+        /**
+         * Whether or not any data has been loaded from the server.
+         * @config dataLoaded
+         * @type Boolean
+         */
+        this.register('dataLoaded', {
+            value: false,
+            validator: Lang.isBoolean,
+            writeOnce: true
+        });
+
+        /**
+         * Number if milliseconds before aborting and calling failure handler.
+         * @config dataTimeout
+         * @type Number
+         * @default null
+         */
+        this.register('dataTimeout', {
+            value: attr.dataTimeout || null,
+            validator: Lang.isNumber
+        });
+
+        /**
+         * Whether or not the tab is currently active.
+         * If a dataSrc is set for the tab, the content will be loaded from
+         * the given source.
+         * @config active
+         * @type Boolean
+         */
+        this.register('active', {
+            value: attr.active || this.hasClass(this.ACTIVE_CLASSNAME),
+            method: function(value) {
+                if (value === true) {
+                    this.addClass(this.ACTIVE_CLASSNAME);
+                    this.set('title', 'active');
+                } else {
+                    this.removeClass(this.ACTIVE_CLASSNAME);
+                    this.set('title', '');
+                }
+            },
+            validator: function(value) {
+                return Lang.isBoolean(value) && !this.get('disabled') ;
+            }
+        });
+
+        /**
+         * Whether or not the tab is disabled.
+         * @config disabled
+         * @type Boolean
+         */
+        this.register('disabled', {
+            value: attr.disabled || this.hasClass(this.DISABLED_CLASSNAME),
+            method: function(value) {
+                if (value === true) {
+                    Dom.addClass(this.get('element'), this.DISABLED_CLASSNAME);
+                } else {
+                    Dom.removeClass(this.get('element'), this.DISABLED_CLASSNAME);
+                }
+            },
+            validator: Lang.isBoolean
+        });
+
+        /**
+         * The href of the tab's anchor element.
+         * @config href
+         * @type String
+         * @default '#'
+         */
+        this.register('href', {
+            value: attr.href || '#',
+            method: function(value) {
+                this.getElementsByTagName('a')[0].href = value;
+            },
+            validator: Lang.isString
+        });
+
+        /**
+         * The Whether or not the tab's content is visible.
+         * @config contentVisible
+         * @type Boolean
+         * @default false
+         */
+        this.register('contentVisible', {
+            value: attr.contentVisible,
+            method: function(value) {
+                if (value == true) {
+                    this.get('contentEl').style.display = 'block';
+
+                    if ( this.get('dataSrc') ) {
+                     // load dynamic content unless already loaded and caching
+                        if ( !this.get('dataLoaded') || !this.get('cacheData') ) {
+                            _dataConnect.call(this);
+                        }
+                    }
+                } else {
+                    this.get('contentEl').style.display = 'none';
+                }
+            },
+            validator: Lang.isBoolean
+        });
+    };
+
+    var _createTabElement = function(attr) {
+        var el = document.createElement('li');
+        var a = document.createElement('a');
+
+        a.href = attr.href || '#';
+
+        el.appendChild(a);
+
+        var label = attr.label || null;
+        var labelEl = attr.labelEl || null;
+
+        if (labelEl) { // user supplied labelEl
+            if (!label) { // user supplied label
+                label = _getLabel.call(this, labelEl);
+            }
+        } else {
+            labelEl = _createlabelEl.call(this);
+        }
+
+        a.appendChild(labelEl);
+
+        return el;
+    };
+
+    var _getlabelEl = function() {
+        return this.getElementsByTagName(this.LABEL_TAGNAME)[0];
+    };
+
+    var _createlabelEl = function() {
+        var el = document.createElement(this.LABEL_TAGNAME);
+        return el;
+    };
+
+    var _setLabel = function(label) {
+        var el = this.get('labelEl');
+        el.innerHTML = label;
+    };
+
+    var _getLabel = function() {
+        var label,
+            el = this.get('labelEl');
+
+            if (!el) {
+                return undefined;
+            }
+
+        return el.innerHTML;
+    };
+
+    var _dataConnect = function() {
+        if (!YAHOO.util.Connect) {
+            YAHOO.log('YAHOO.util.Connect dependency not met',
+                    'error', 'Tab');
+            return false;
+        }
+
+        Dom.addClass(this.get('contentEl').parentNode, this.LOADING_CLASSNAME);
+
+        this.dataConnection = YAHOO.util.Connect.asyncRequest(
+            this.get('loadMethod'),
+            this.get('dataSrc'),
+            {
+                success: function(o) {
+                    this.loadHandler.success.call(this, o);
+                    this.set('dataLoaded', true);
+                    this.dataConnection = null;
+                    Dom.removeClass(this.get('contentEl').parentNode,
+                            this.LOADING_CLASSNAME);
+                },
+                failure: function(o) {
+                    this.loadHandler.failure.call(this, o);
+                    this.dataConnection = null;
+                    Dom.removeClass(this.get('contentEl').parentNode,
+                            this.LOADING_CLASSNAME);
+                },
+                scope: this,
+                timeout: this.get('dataTimeout')
+            }
+        );
+    };
+
+    YAHOO.widget.Tab = Tab;
+
+    /**
+     * Fires before the active state is changed.
+     * <p>See: <a href="YAHOO.util.Element.html#addListener">Element.addListener</a></p>
+     * <p>If handler returns false, the change will be cancelled, and the value will not
+     * be set.</p>
+     * <p><strong>Event fields:</strong><br>
+     * <code>&lt;String&gt; type</code> beforeActiveChange<br>
+     * <code>&lt;Boolean&gt;
+     * prevValue</code> the current value<br>
+     * <code>&lt;Boolean&gt;
+     * newValue</code> the new value</p>
+     * <p><strong>Usage:</strong><br>
+     * <code>var handler = function(e) {var previous = e.prevValue};<br>
+     * myTabs.addListener('beforeActiveChange', handler);</code></p>
+     * @event beforeActiveChange
+     */
+
+    /**
+     * Fires after the active state is changed.
+     * <p>See: <a href="YAHOO.util.Element.html#addListener">Element.addListener</a></p>
+     * <p><strong>Event fields:</strong><br>
+     * <code>&lt;String&gt; type</code> activeChange<br>
+     * <code>&lt;Boolean&gt;
+     * prevValue</code> the previous value<br>
+     * <code>&lt;Boolean&gt;
+     * newValue</code> the updated value</p>
+     * <p><strong>Usage:</strong><br>
+     * <code>var handler = function(e) {var previous = e.prevValue};<br>
+     * myTabs.addListener('activeChange', handler);</code></p>
+     * @event activeChange
+     */
+
+    /**
+     * Fires before the tab label is changed.
+     * <p>See: <a href="YAHOO.util.Element.html#addListener">Element.addListener</a></p>
+     * <p>If handler returns false, the change will be cancelled, and the value will not
+     * be set.</p>
+     * <p><strong>Event fields:</strong><br>
+     * <code>&lt;String&gt; type</code> beforeLabelChange<br>
+     * <code>&lt;String&gt;
+     * prevValue</code> the current value<br>
+     * <code>&lt;String&gt;
+     * newValue</code> the new value</p>
+     * <p><strong>Usage:</strong><br>
+     * <code>var handler = function(e) {var previous = e.prevValue};<br>
+     * myTabs.addListener('beforeLabelChange', handler);</code></p>
+     * @event beforeLabelChange
+     */
+
+    /**
+     * Fires after the tab label is changed.
+     * <p>See: <a href="YAHOO.util.Element.html#addListener">Element.addListener</a></p>
+     * <p><strong>Event fields:</strong><br>
+     * <code>&lt;String&gt; type</code> labelChange<br>
+     * <code>&lt;String&gt;
+     * prevValue</code> the previous value<br>
+     * <code>&lt;String&gt;
+     * newValue</code> the updated value</p>
+     * <p><strong>Usage:</strong><br>
+     * <code>var handler = function(e) {var previous = e.prevValue};<br>
+     * myTabs.addListener('labelChange', handler);</code></p>
+     * @event labelChange
+     */
+
+    /**
+     * Fires before the tab content is changed.
+     * <p>See: <a href="YAHOO.util.Element.html#addListener">Element.addListener</a></p>
+     * <p>If handler returns false, the change will be cancelled, and the value will not
+     * be set.</p>
+     * <p><strong>Event fields:</strong><br>
+     * <code>&lt;String&gt; type</code> beforeContentChange<br>
+     * <code>&lt;String&gt;
+     * prevValue</code> the current value<br>
+     * <code>&lt;String&gt;
+     * newValue</code> the new value</p>
+     * <p><strong>Usage:</strong><br>
+     * <code>var handler = function(e) {var previous = e.prevValue};<br>
+     * myTabs.addListener('beforeContentChange', handler);</code></p>
+     * @event beforeContentChange
+     */
+
+    /**
+     * Fires after the tab content is changed.
+     * <p>See: <a href="YAHOO.util.Element.html#addListener">Element.addListener</a></p>
+     * <p><strong>Event fields:</strong><br>
+     * <code>&lt;String&gt; type</code> contentChange<br>
+     * <code>&lt;String&gt;
+     * prevValue</code> the previous value<br>
+     * <code>&lt;Boolean&gt;
+     * newValue</code> the updated value</p>
+     * <p><strong>Usage:</strong><br>
+     * <code>var handler = function(e) {var previous = e.prevValue};<br>
+     * myTabs.addListener('contentChange', handler);</code></p>
+     * @event contentChange
+     */
+})();(function() {
+
+    /**
+     * The tabview module provides a widget for managing content bound to tabs.
+     * @module tabview
+     *
+     */
+    /**
+     * A widget to control tabbed views.
+     * @namespace YAHOO.widget
+     * @class TabView
+     * @extends YAHOO.util.Element
+     * @constructor
+     * @param {HTMLElement | String | Object} el(optional) The html
+     * element that represents the TabView, or the attribute object to use.
+     * An element will be created if none provided.
+     * @param {Object} attr (optional) A key map of the tabView's
+     * initial attributes.  Ignored if first arg is attributes object.
+     */
+    YAHOO.widget.TabView = function(el, attr) {
+        attr = attr || {};
+        if (arguments.length == 1 && !Lang.isString(el) && !el.nodeName) {
+            attr = el; // treat first arg as attr object
+            el = attr.element || null;
+        }
+
+        if (!el && !attr.element) { // create if we dont have one
+            el = _createTabViewElement.call(this, attr);
+        }
+    	YAHOO.widget.TabView.superclass.constructor.call(this, el, attr);
+    };
+
+    YAHOO.extend(YAHOO.widget.TabView, YAHOO.util.Element);
+
+    var proto = YAHOO.widget.TabView.prototype;
+    var Dom = YAHOO.util.Dom;
+    var Lang = YAHOO.util.Lang;
+    var Event = YAHOO.util.Event;
+    var Tab = YAHOO.widget.Tab;
+
+
+    /**
+     * The className to add when building from scratch.
+     * @property CLASSNAME
+     * @default "navset"
+     */
+    proto.CLASSNAME = 'yui-navset';
+
+    /**
+     * The className of the HTMLElement containing the TabView's tab elements
+     * to look for when building from existing markup, or to add when building
+     * from scratch.
+     * All childNodes of the tab container are treated as Tabs when building
+     * from existing markup.
+     * @property TAB_PARENT_CLASSNAME
+     * @default "nav"
+     */
+    proto.TAB_PARENT_CLASSNAME = 'yui-nav';
+
+    /**
+     * The className of the HTMLElement containing the TabView's label elements
+     * to look for when building from existing markup, or to add when building
+     * from scratch.
+     * All childNodes of the content container are treated as content elements when
+     * building from existing markup.
+     * @property CONTENT_PARENT_CLASSNAME
+     * @default "nav-content"
+     */
+    proto.CONTENT_PARENT_CLASSNAME = 'yui-content';
+
+    proto._tabParent = null;
+    proto._contentParent = null;
+
+    /**
+     * Adds a Tab to the TabView instance.
+     * If no index is specified, the tab is added to the end of the tab list.
+     * @method addTab
+     * @param {YAHOO.widget.Tab} tab A Tab instance to add.
+     * @param {Integer} index The position to add the tab.
+     * @return void
+     */
+    proto.addTab = function(tab, index) {
+        var tabs = this.get('tabs');
+        if (!tabs) { // not ready yet
+            this._queue[this._queue.length] = ['addTab', arguments];
+            return false;
+        }
+
+        index = (index === undefined) ? tabs.length : index;
+
+        var before = this.getTab(index);
+
+        var self = this;
+        var el = this.get('element');
+        var tabParent = this._tabParent;
+        var contentParent = this._contentParent;
+
+        var tabElement = tab.get('element');
+        var contentEl = tab.get('contentEl');
+
+        if ( before ) {
+            tabParent.insertBefore(tabElement, before.get('element'));
+        } else {
+            tabParent.appendChild(tabElement);
+        }
+
+        if ( contentEl && !Dom.isAncestor(contentParent, contentEl) ) {
+            contentParent.appendChild(contentEl);
+        }
+
+        if ( !tab.get('active') ) {
+            tab.set('contentVisible', false, true); /* hide if not active */
+        } else {
+            this.set('activeTab', tab, true);
+
+        }
+
+        var activate = function(e) {
+            YAHOO.util.Event.preventDefault(e);
+            self.set('activeTab', this);
+        };
+
+        tab.addListener( tab.get('activationEvent'), activate);
+
+        tab.addListener('activationEventChange', function(e) {
+            if (e.prevValue != e.newValue) {
+                tab.removeListener(e.prevValue, activate);
+                tab.addListener(e.newValue, activate);
+            }
+        });
+
+        tabs.splice(index, 0, tab);
+    };
+
+    /**
+     * Routes childNode events.
+     * @method DOMEventHandler
+     * @param {event} e The Dom event that is being handled.
+     * @return void
+     */
+    proto.DOMEventHandler = function(e) {
+        var el = this.get('element');
+        var target = YAHOO.util.Event.getTarget(e);
+        var tabParent = this._tabParent;
+
+        if (Dom.isAncestor(tabParent, target) ) {
+            var tabEl;
+            var tab = null;
+            var contentEl;
+            var tabs = this.get('tabs');
+
+            for (var i = 0, len = tabs.length; i < len; i++) {
+                tabEl = tabs[i].get('element');
+                contentEl = tabs[i].get('contentEl');
+
+                if ( target == tabEl || Dom.isAncestor(tabEl, target) ) {
+                    tab = tabs[i];
+                    break; // note break
+                }
+            }
+
+            if (tab) {
+                tab.fireEvent(e.type, e);
+            }
+        }
+    };
+
+    /**
+     * Returns the Tab instance at the specified index.
+     * @method getTab
+     * @param {Integer} index The position of the Tab.
+     * @return YAHOO.widget.Tab
+     */
+    proto.getTab = function(index) {
+    	return this.get('tabs')[index];
+    };
+
+    /**
+     * Returns the index of given tab.
+     * @method getTabIndex
+     * @param {YAHOO.widget.Tab} tab The tab whose index will be returned.
+     * @return int
+     */
+    proto.getTabIndex = function(tab) {
+        var index = null;
+        var tabs = this.get('tabs');
+    	for (var i = 0, len = tabs.length; i < len; ++i) {
+            if (tab == tabs[i]) {
+                index = i;
+                break;
+            }
+        }
+
+        return index;
+    };
+
+    /**
+     * Removes the specified Tab from the TabView.
+     * @method removeTab
+     * @param {YAHOO.widget.Tab} item The Tab instance to be removed.
+     * @return void
+     */
+    proto.removeTab = function(tab) {
+        var tabCount = this.get('tabs').length;
+
+        var index = this.getTabIndex(tab);
+        var nextIndex = index + 1;
+        if ( tab == this.get('activeTab') ) { // select next tab
+            if (tabCount > 1) {
+                if (index + 1 == tabCount) {
+                    this.set('activeIndex', index - 1);
+                } else {
+                    this.set('activeIndex', index + 1);
+                }
+            }
+        }
+
+        this._tabParent.removeChild( tab.get('element') );
+        this._contentParent.removeChild( tab.get('contentEl') );
+        this._configs.tabs.value.splice(index, 1);
+
+    };
+
+    /**
+     * Provides a readable name for the TabView instance.
+     * @method toString
+     * @return String
+     */
+    proto.toString = function() {
+        var name = this.get('id') || this.get('tagName');
+        return "TabView " + name;
+    };
+
+    /**
+     * The transiton to use when switching between tabs.
+     * @method contentTransition
+     */
+    proto.contentTransition = function(newTab, oldTab) {
+        newTab.set('contentVisible', true);
+        oldTab.set('contentVisible', false);
+    };
+
+    /**
+     * Registers TabView specific properties.
+     * @method initAttributes
+     * @param {Object} attr Hash of initial attributes
+     */
+    proto.initAttributes = function(attr) {
+        YAHOO.widget.TabView.superclass.initAttributes.call(this, attr);
+
+        if (!attr.orientation) {
+            attr.orientation = 'top';
+        }
+
+        var el = this.get('element');
+
+        /**
+         * The Tabs belonging to the TabView instance.
+         * @config tabs
+         * @type Array
+         */
+        this.register('tabs', {
+            value: [],
+            readOnly: true
+        });
+
+        /**
+         * The container of the tabView's label elements.
+         * @property _tabParent
+         * @private
+         * @type HTMLElement
+         */
+        this._tabParent =
+                this.getElementsByClassName(this.TAB_PARENT_CLASSNAME,
+                        'ul' )[0] || _createTabParent.call(this);
+
+        /**
+         * The container of the tabView's content elements.
+         * @property _contentParent
+         * @type HTMLElement
+         * @private
+         */
+        this._contentParent =
+                this.getElementsByClassName(this.CONTENT_PARENT_CLASSNAME,
+                        'div')[0] ||  _createContentParent.call(this);
+
+        /**
+         * How the Tabs should be oriented relative to the TabView.
+         * @config orientation
+         * @type String
+         * @default "top"
+         */
+        this.register('orientation', {
+            value: attr.orientation,
+            method: function(value) {
+                var current = this.get('orientation');
+                this.addClass('yui-navset-' + value);
+
+                if (current != value) {
+                    this.removeClass('yui-navset-' + current);
+                }
+
+                switch(value) {
+                    case 'bottom':
+                    this.appendChild(this._tabParent);
+                    break;
+                }
+            }
+        });
+
+        /**
+         * The index of the tab currently active.
+         * @config activeIndex
+         * @type Int
+         */
+        this.register('activeIndex', {
+            value: attr.activeIndex,
+            method: function(value) {
+                this.set('activeTab', this.getTab(value));
+            },
+            validator: function(value) {
+                return !this.getTab(value).get('disabled'); // cannot activate if disabled
+            }
+        });
+
+        /**
+         * The tab currently active.
+         * @config activeTab
+         * @type YAHOO.widget.Tab
+         */
+        this.register('activeTab', {
+            value: attr.activeTab,
+            method: function(tab) {
+                var activeTab = this.get('activeTab');
+
+                if (tab) {
+                    tab.set('active', true);
+                }
+
+                if (activeTab && activeTab != tab) {
+                    activeTab.set('active', false);
+                }
+
+                if (activeTab && tab != activeTab) { // no transition if only 1
+                    this.contentTransition(tab, activeTab);
+                } else if (tab) {
+                    tab.set('contentVisible', true);
+                }
+            },
+            validator: function(value) {
+                return !value.get('disabled'); // cannot activate if disabled
+            }
+        });
+
+        if ( this._tabParent ) {
+            _initTabs.call(this);
+        }
+
+        for (var type in this.DOM_EVENTS) {
+            if ( this.DOM_EVENTS.hasOwnProperty(type) ) {
+                this.addListener.call(this, type, this.DOMEventHandler);
+            }
+        }
+    };
+
+    /**
+     * Creates Tab instances from a collection of HTMLElements.
+     * @method createTabs
+     * @private
+     * @param {Array|HTMLCollection} elements The elements to use for Tabs.
+     * @return void
+     */
+    var _initTabs = function() {
+        var tab,
+            attr,
+            contentEl;
+
+        var el = this.get('element');
+        var tabs = _getChildNodes(this._tabParent);
+        var contentElements = _getChildNodes(this._contentParent);
+
+        for (var i = 0, len = tabs.length; i < len; ++i) {
+            attr = {};
+
+            if (contentElements[i]) {
+                attr.contentEl = contentElements[i];
+            }
+
+            tab = new YAHOO.widget.Tab(tabs[i], attr);
+            this.addTab(tab);
+
+            if (tab.hasClass(tab.ACTIVE_CLASSNAME) ) {
+                this._configs.activeTab.value = tab; // dont invoke method
+            }
+        }
+    };
+
+    var _createTabViewElement = function(attr) {
+        var el = document.createElement('div');
+
+        if ( this.CLASSNAME ) {
+            el.className = this.CLASSNAME;
+        }
+
+        return el;
+    };
+
+    var _createTabParent = function(attr) {
+        var el = document.createElement('ul');
+
+        if ( this.TAB_PARENT_CLASSNAME ) {
+            el.className = this.TAB_PARENT_CLASSNAME;
+        }
+
+        this.get('element').appendChild(el);
+
+        return el;
+    };
+
+    var _createContentParent = function(attr) {
+        var el = document.createElement('div');
+
+        if ( this.CONTENT_PARENT_CLASSNAME ) {
+            el.className = this.CONTENT_PARENT_CLASSNAME;
+        }
+
+        this.get('element').appendChild(el);
+
+        return el;
+    };
+
+    var _getChildNodes = function(el) {
+        var nodes = [];
+        var childNodes = el.childNodes;
+
+        for (var i = 0, len = childNodes.length; i < len; ++i) {
+            if (childNodes[i].nodeType == 1) {
+                nodes[nodes.length] = childNodes[i];
+            }
+        }
+
+        return nodes;
+    };
+
+/**
+ * Fires before the activeTab is changed.
+ * <p>See: <a href="YAHOO.util.Element.html#addListener">Element.addListener</a></p>
+ * <p>If handler returns false, the change will be cancelled, and the value will not
+ * be set.</p>
+ * <p><strong>Event fields:</strong><br>
+ * <code>&lt;String&gt; type</code> beforeActiveTabChange<br>
+ * <code>&lt;<a href="YAHOO.widget.Tab.html">YAHOO.widget.Tab</a>&gt;
+ * prevValue</code> the currently active tab<br>
+ * <code>&lt;<a href="YAHOO.widget.Tab.html">YAHOO.widget.Tab</a>&gt;
+ * newValue</code> the tab to be made active</p>
+ * <p><strong>Usage:</strong><br>
+ * <code>var handler = function(e) {var previous = e.prevValue};<br>
+ * myTabs.addListener('beforeActiveTabChange', handler);</code></p>
+ * @event beforeActiveTabChange
+ */
+
+/**
+ * Fires after the activeTab is changed.
+ * <p>See: <a href="YAHOO.util.Element.html#addListener">Element.addListener</a></p>
+ * <p><strong>Event fields:</strong><br>
+ * <code>&lt;String&gt; type</code> activeTabChange<br>
+ * <code>&lt;<a href="YAHOO.widget.Tab.html">YAHOO.widget.Tab</a>&gt;
+ * prevValue</code> the formerly active tab<br>
+ * <code>&lt;<a href="YAHOO.widget.Tab.html">YAHOO.widget.Tab</a>&gt;
+ * newValue</code> the new active tab</p>
+ * <p><strong>Usage:</strong><br>
+ * <code>var handler = function(e) {var previous = e.prevValue};<br>
+ * myTabs.addListener('activeTabChange', handler);</code></p>
+ * @event activeTabChange
+ */
+
+/**
+ * Fires before the orientation is changed.
+ * <p>See: <a href="YAHOO.util.Element.html#addListener">Element.addListener</a></p>
+ * <p>If handler returns false, the change will be cancelled, and the value will not
+ * be set.</p>
+ * <p><strong>Event fields:</strong><br>
+ * <code>&lt;String&gt; type</code> beforeOrientationChange<br>
+ * <code>&lt;String&gt;
+ * prevValue</code> the current orientation<br>
+ * <code>&lt;String&gt;
+ * newValue</code> the new orientation to be applied</p>
+ * <p><strong>Usage:</strong><br>
+ * <code>var handler = function(e) {var previous = e.prevValue};<br>
+ * myTabs.addListener('beforeOrientationChange', handler);</code></p>
+ * @event beforeOrientationChange
+ */
+
+/**
+ * Fires after the orientation is changed.
+ * <p>See: <a href="YAHOO.util.Element.html#addListener">Element.addListener</a></p>
+ * <p><strong>Event fields:</strong><br>
+ * <code>&lt;String&gt; type</code> orientationChange<br>
+ * <code>&lt;String&gt;
+ * prevValue</code> the former orientation<br>
+ * <code>&lt;String&gt;
+ * newValue</code> the new orientation</p>
+ * <p><strong>Usage:</strong><br>
+ * <code>var handler = function(e) {var previous = e.prevValue};<br>
+ * myTabs.addListener('orientationChange', handler);</code></p>
+ * @event orientationChange
+ */
+})();
\ No newline at end of file

Added: jifty/branches/schema-plugins/share/web/static/js/yui/yahoo.js
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/static/js/yui/yahoo.js	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,144 @@
+/*                                                                                                                                                      
+Copyright (c) 2006, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.net/yui/license.txt
+version: 0.12.1
+*/ 
+/**
+ * The YAHOO object is the single global object used by YUI Library.  It
+ * contains utility function for setting up namespaces, inheritance, and
+ * logging.  YAHOO.util, YAHOO.widget, and YAHOO.example are namespaces
+ * created automatically for and used by the library.
+ * @module yahoo
+ * @title  YAHOO Global
+ */
+
+if (typeof YAHOO == "undefined") {
+    /**
+     * The YAHOO global namespace object
+     * @class YAHOO
+     * @static
+     */
+    var YAHOO = {};
+}
+
+/**
+ * Returns the namespace specified and creates it if it doesn't exist
+ * <pre>
+ * YAHOO.namespace("property.package");
+ * YAHOO.namespace("YAHOO.property.package");
+ * </pre>
+ * Either of the above would create YAHOO.property, then
+ * YAHOO.property.package
+ *
+ * Be careful when naming packages. Reserved words may work in some browsers
+ * and not others. For instance, the following will fail in Safari:
+ * <pre>
+ * YAHOO.namespace("really.long.nested.namespace");
+ * </pre>
+ * This fails because "long" is a future reserved word in ECMAScript
+ *
+ * @method namespace
+ * @static
+ * @param  {String*} arguments 1-n namespaces to create 
+ * @return {Object}  A reference to the last namespace object created
+ */
+YAHOO.namespace = function() {
+    var a=arguments, o=null, i, j, d;
+    for (i=0; i<a.length; ++i) {
+        d=a[i].split(".");
+        o=YAHOO;
+
+        // YAHOO is implied, so it is ignored if it is included
+        for (j=(d[0] == "YAHOO") ? 1 : 0; j<d.length; ++j) {
+            o[d[j]]=o[d[j]] || {};
+            o=o[d[j]];
+        }
+    }
+
+    return o;
+};
+
+/**
+ * Uses YAHOO.widget.Logger to output a log message, if the widget is available.
+ *
+ * @method log
+ * @static
+ * @param  {String}  msg  The message to log.
+ * @param  {String}  cat  The log category for the message.  Default
+ *                        categories are "info", "warn", "error", time".
+ *                        Custom categories can be used as well. (opt)
+ * @param  {String}  src  The source of the the message (opt)
+ * @return {Boolean}      True if the log operation was successful.
+ */
+YAHOO.log = function(msg, cat, src) {
+    var l=YAHOO.widget.Logger;
+    if(l && l.log) {
+        return l.log(msg, cat, src);
+    } else {
+        return false;
+    }
+};
+
+/**
+ * Utility to set up the prototype, constructor and superclass properties to
+ * support an inheritance strategy that can chain constructors and methods.
+ *
+ * @method extend
+ * @static
+ * @param {Function} subc   the object to modify
+ * @param {Function} superc the object to inherit
+ * @param {Object} overrides  additional properties/methods to add to the
+ *                              subclass prototype.  These will override the
+ *                              matching items obtained from the superclass 
+ *                              if present.
+ */
+YAHOO.extend = function(subc, superc, overrides) {
+    var F = function() {};
+    F.prototype=superc.prototype;
+    subc.prototype=new F();
+    subc.prototype.constructor=subc;
+    subc.superclass=superc.prototype;
+    if (superc.prototype.constructor == Object.prototype.constructor) {
+        superc.prototype.constructor=superc;
+    }
+
+    if (overrides) {
+        for (var i in overrides) {
+            subc.prototype[i]=overrides[i];
+        }
+    }
+};
+
+/**
+ * Applies all prototype properties in the supplier to the receiver if the
+ * receiver does not have these properties yet.  Optionally, one or more
+ * methods/properties can be specified (as additional parameters).  This
+ * option will overwrite the property if receiver has it already.
+ *
+ * @method augment
+ * @static
+ * @param {Function} r  the object to receive the augmentation
+ * @param {Function} s  the object that supplies the properties to augment
+ * @param {String*}  arguments zero or more properties methods to augment the
+ *                             receiver with.  If none specified, everything
+ *                             in the supplier will be used unless it would
+ *                             overwrite an existing property in the receiver
+ */
+YAHOO.augment = function(r, s) {
+    var rp=r.prototype, sp=s.prototype, a=arguments, i, p;
+    if (a[2]) {
+        for (i=2; i<a.length; ++i) {
+            rp[a[i]] = sp[a[i]];
+        }
+    } else {
+        for (p in sp) { 
+            if (!rp[p]) {
+                rp[p] = sp[p];
+            }
+        }
+    }
+};
+
+YAHOO.namespace("util", "widget", "example");
+

Added: jifty/branches/schema-plugins/share/web/templates/=/subs
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/=/subs	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,58 @@
+<%args>
+$forever => 1
+</%args>
+<%init>
+
+$r->content_type("text/html; charset=utf-8");
+$r->headers_out->{'Pragma'} = 'no-cache';
+$r->headers_out->{'Cache-control'} = 'no-cache';
+$r->send_http_header;
+
+my $writer = XML::Writer->new;
+$writer->xmlDecl( "UTF-8", "yes" );
+
+my $begin = <<'END';
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/2002/REC-xhtml1-20020801/DTD/xhtml1-strict.dtd">
+<html><head><title></title></head>
+END
+chomp $begin;
+
+
+if ($forever) {
+    my $whitespace = " " x ( 1024 - length $begin );
+    $begin =~ s/<body>$/$whitespace<body>/s;
+}
+
+$m->print($begin);
+$m->flush_buffer;
+$writer->startTag("body");
+
+
+    while (1) {
+        my $sent = write_subs_once($writer);
+        flush STDOUT;
+        last if ($sent && !$forever);
+        sleep 1;
+    }
+    $writer->endTag();
+    return;
+
+
+
+
+
+sub write_subs_once {
+    my $writer = shift;
+    Jifty::Subs::Render->render(
+        Jifty->web->session->id,
+        sub {
+            my ( $mode, $name, $content ) = @_;
+            $writer->startTag( "pushfrag", mode => $mode );
+            $writer->startTag( "fragment", id   => $name );
+            $writer->dataElement( "content", $content );
+            $writer->endTag();
+            $writer->endTag();
+            } );
+}
+</%init>

Added: jifty/branches/schema-plugins/share/web/templates/__jifty/admin/_elements/nav
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/__jifty/admin/_elements/nav	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,11 @@
+<%init>
+
+my $nav = Jifty->web->navigation->child("Administration" => url => '/__jifty/admin/');
+foreach my $model (Jifty->class_loader->models) {
+    next unless $model->isa('Jifty::Record');
+    next unless ($model =~ /^(?:.*)::(.*?)$/);
+    my $type = $1;
+    $nav->child($type   => url => '/__jifty/admin/model/'.$type);
+}
+return;
+</%init>

Added: jifty/branches/schema-plugins/share/web/templates/__jifty/admin/action/dhandler
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/__jifty/admin/action/dhandler	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,27 @@
+<%init>
+my $action_class = Jifty->api->qualify($m->dhandler_arg);
+
+my $action = Jifty->web->new_action(
+    class   => $action_class,
+    moniker => "run-$action_class",
+);
+
+$action->sticky_on_failure(1);
+
+</%init>
+<&|/_elements/wrapper &>
+
+<% Jifty->web->form->start %>
+
+% for ($action->argument_names) {
+<% $action->form_field($_) %>
+% }
+
+<% Jifty->web->form->submit( label => _("Run the action") ) %>
+
+<% Jifty->web->form->end %>
+
+<h2>Done?</h2>
+<% Jifty->web->link( url => "/__jifty/admin/", label => _('Back to the admin console')) %>
+
+</&>

Added: jifty/branches/schema-plugins/share/web/templates/__jifty/admin/autohandler
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/__jifty/admin/autohandler	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,10 @@
+<%init>
+
+# If "AdminMode" is turned off in Jifty's config file, don't let people at the admin UI.
+unless (Jifty->config->framework('AdminMode')) {
+    $m->redirect('/__jifty/error/permission_denied'); 
+    $m->abort();
+}
+$m->comp('_elements/nav');
+$m->call_next();
+</%init>

Added: jifty/branches/schema-plugins/share/web/templates/__jifty/admin/fragments/list/header
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/__jifty/admin/fragments/list/header	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,61 @@
+<%args>
+$object_type
+$mask_val => ""
+$mask_field => ""
+$sort_by => ""
+$order => ""
+$list_path
+</%args>
+<%init>
+my $record_class = Jifty->app_class("Model", $object_type);
+my $record = $record_class->new();
+ my $update = Jifty->web->new_action(class => 'Update'.$object_type);
+</%init>
+<div class="jifty_admin_header">
+
+% foreach my $argument ($update->argument_names) {
+% unless( $argument eq $mask_field ||  $argument eq 'id' || $argument =~ /_confirm$/i
+%        && lc $update->arguments->{$argument}{render_as} eq 'password') {
+<span class="<% ($sort_by && !$order && $sort_by eq $argument)?'up_select':'up' %>">
+<%
+    Jifty->web->link(
+        label   => _("asc"),
+        onclick => 
+            { 
+            replace_with => $list_path.'list' ,
+            args   => {
+                object_type => $object_type,
+                limit_val => $mask_val,
+                limit_field => $mask_field,
+                list_path => $list_path,
+                sort_by => $argument,
+                order => undef
+                },
+            },
+        #as_button => 1
+        )
+%></span>
+<span class="<% ($sort_by && $order && $sort_by eq $argument )?'down_select':'down' %>">
+<%
+    Jifty->web->link(
+        label   => _("desc"),
+        onclick => 
+            {
+            replace_with => $list_path.'list',
+            args   => {
+                object_type => $object_type,
+                limit_val => $mask_val,
+                limit_field => $mask_field,
+                list_path => $list_path,
+                sort_by => $argument,
+                order => 'D'
+                },
+            },
+        #as_button => 1
+        )
+%></span>
+<span class="field"><% $argument %></span>
+% }
+% }
+<hr />
+</div>

Added: jifty/branches/schema-plugins/share/web/templates/__jifty/admin/fragments/list/list
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/__jifty/admin/fragments/list/list	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,128 @@
+<%doc>
+
+You can copy this fragment directory to your app.
+And use it in a page like this :
+
+<% Jifty->web->region(name => "admin-subdomain",
+                      path => "/admin/fragments/list/list",
+                      defaults => { object_type => 'SubDomain',
+                                    list_path => '/admin/fragments/list/',
+                                    limit_field => 'domain',
+                                    limit_val => $domain,
+                                    search_slot => 0
+                                    per_page => 5,
+                                    render_submit => 1 }) %>
+
+limit_field, limit_val allow add to your sql query 
+ ... WHERE $limit_field = $limit_val
+so you can make "sub views" on a table limited by limit_field / limit_val
+
+</%doc>
+<%args>
+$object_type
+$page => 1
+$new_slot => 1
+$search_slot => 1
+$item_path => "/__jifty/admin/fragments/list/view"
+$list_path => "/__jifty/admin/fragments/list/"
+$limit_field => ""
+$limit_val => ""
+$per_page => 25
+$sort_by => undef
+$order => undef
+</%args>
+<%init>
+my $collection_class = Jifty->app_class("Model", $object_type."Collection");
+my $search = Jifty->web->response->result('search');
+my $collection = $collection_class->new();
+
+if(!$search) {
+   if ( $limit_field && $limit_val ) {
+      $collection->limit(column => $limit_field, value => $limit_val);
+   } else {
+      $collection->unlimit();
+   }
+   $collection->order_by(column => $sort_by, order=>'ASC') if ($sort_by && !$order);
+   $collection->order_by(column => $sort_by, order=>'DESC') if ($sort_by && $order);
+} else {
+    $collection = $search->content('search');
+    warn $collection->build_select_query;
+}
+
+$collection->set_page_info( current_page => $page,
+                            per_page     => $per_page
+                           );
+</%init>
+% if ($search_slot) {
+<%perl>    
+my $search_region = Jifty::Web::PageRegion->new(
+   name     => 'search',
+   path     => '/__jifty/empty',
+);
+</%perl>
+
+<% Jifty->web->link(
+    onclick => [{
+        region       => $search_region->qualified_name,
+        replace_with => $list_path.'search',
+        toggle       => 1,
+        args         => { object_type => $object_type }
+    },
+    ],
+    label => _('Toggle search')
+  )
+%>
+
+<% $search_region->render %>
+% }
+
+% if ($collection->pager->last_page > 1) {
+    <span class="page-count"><%_('Page %1 of %2', $page, $collection->pager->last_page) %></span>   
+% }
+
+<div class="list">
+% if ($collection->pager->total_entries == 0) {
+ <% _('No items found') %>
+% } else {
+  <% _('%1 entries', $collection-> count) %> 
+  <& $list_path.'header', object_type => $object_type, list_path => $list_path, 
+    mask_field => $limit_field, mask_val => $limit_val, sort_by => $sort_by, order => $order &>
+% }
+
+<%perl>
+while ( my $item = $collection->next ) {
+    Jifty->web->region(
+        name     => 'item-' . $item->id,
+        path     => $item_path,
+        defaults => { id => $item->id, object_type => $object_type, list_path => $list_path,
+                      mask_field => $limit_field , mask_val => $limit_val }
+    );
+}
+
+</%perl>
+</div>
+
+<div class="paging">
+% if ($collection->pager->previous_page) {
+<span class="prev-page">
+  <% Jifty->web->link( label => _("Previous Page"), onclick => { args => { page => $collection->pager->previous_page } } ) %>
+</span>
+% }
+% if ($collection->pager->next_page) {
+<span class="next-page">
+  <% Jifty->web->link( label => _("Next Page"), onclick => { args => { page => $collection->pager->next_page } } ) %>
+</span>
+% }
+</div>
+
+% if ($new_slot) {
+<% Jifty->web->region(
+        name => 'new_item',
+        path => $list_path.'new_item',
+        defaults => {   object_type => $object_type, list_path => $list_path,
+                     mask_field => $limit_field , mask_val => $limit_val },
+        ) %>
+
+% }
+
+

Added: jifty/branches/schema-plugins/share/web/templates/__jifty/admin/fragments/list/new_item
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/__jifty/admin/fragments/list/new_item	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,47 @@
+<%args>
+$object_type 
+$region
+$mask_field => ""
+$mask_val => ""
+$list_path
+</%args>
+<%init>
+my $record_class = Jifty->app_class("Model", $object_type);
+my $create = Jifty->web->new_action(class => 'Create'.$object_type);
+</%init>
+% if ($mask_field) {
+  <% $create->hidden($mask_field,$mask_val) %>
+% }
+<div class="jifty_admin create item inline">
+% foreach my $argument ($create->argument_names) {
+%  if ( $argument ne $mask_field ) {
+        <%$create->form_field($argument)%>
+%  }
+%}
+</div>
+<%
+
+Jifty->web->form->submit(
+    label    => _('Create'),
+    onclick  => [
+                 { submit => $create },
+                 { refresh_self => 1 },
+                 {
+                   element => $region->parent->get_element('div.list'),
+                   append => $list_path.'view',
+                   args   => { 
+                              object_type => $object_type,
+                              list_path => $list_path,
+                              id          => { result_of => $create, name => 'id' },
+                             },
+                 },
+                ]
+    ) %>
+
+<%doc>
+
+When you hit "save" and create a item, you want to put a fragment
+containing the new item in the associated list and refresh the current
+fragment
+
+</%doc>

Added: jifty/branches/schema-plugins/share/web/templates/__jifty/admin/fragments/list/search
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/__jifty/admin/fragments/list/search	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,27 @@
+<%args>
+$object_type
+</%args>
+<%init>
+my $search = Jifty->web->new_action(
+    class             => "Search".$object_type,
+    moniker           => "search",
+    sticky_on_success => 1,
+);
+
+</%init>
+<div class="jifty_admin">
+% for my $arg ($search->argument_names) {
+ <% $search->form_field($arg) %>
+% }
+
+<% $search->button(
+    label   => _('Search'),
+    onclick => {
+        submit  => $search,
+        refresh => Jifty->web->current_region->parent,
+        args    => { page => 1}
+    }
+  )
+%>
+<hr />
+</div>

Added: jifty/branches/schema-plugins/share/web/templates/__jifty/admin/fragments/list/update
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/__jifty/admin/fragments/list/update	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,52 @@
+<%args>
+$id => undef
+$object_type
+$mask_field => ""
+$mask_val => ""
+$list_path
+</%args>
+<%init>
+my $record_class = Jifty->app_class("Model", $object_type);
+my $record = $record_class->new();
+$record->load($id);
+my $update = Jifty->web->new_action(
+    class   => "Update".$object_type,
+    moniker => "update-" . Jifty->web->serial,
+    record  => $record
+);
+</%init>
+<div class="jifty_admin update item inline <%$object_type%>">
+<div class="editlink">
+  <% Jifty->web->link(
+      label   => _('Save'),
+      onclick => [
+          { submit => $update },
+          {   replace_with => $list_path.'view',
+              args         => { object_type => $object_type, id => $id, list_path => $list_path }
+          }
+      ]
+      ) %>
+
+  <% Jifty->web->link(
+      label     => _('Cancel'),
+      onclick   => {
+          replace_with => $list_path.'view',
+          args         => { object_type => $object_type, id => $id, list_path => $list_path }
+        },
+      as_button => 1
+  ) %>
+
+</div>
+
+% if ($mask_field) {
+<% $update->hidden($mask_field, $mask_val) %>
+% }
+% foreach my $argument ($update->argument_names) {
+%  if ( $argument ne $mask_field ) {
+<%$update->form_field($argument)%>
+%  }
+%}
+
+<hr />
+</div>
+

Added: jifty/branches/schema-plugins/share/web/templates/__jifty/admin/fragments/list/view
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/__jifty/admin/fragments/list/view	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,61 @@
+<%args>
+$id => undef
+$object_type
+$mask_field => ""
+$mask_val => ""
+$list_path
+</%args>
+<%init>
+my $record_class = Jifty->app_class("Model", $object_type);
+my $record = $record_class->new();
+$record->load($id);
+my $update = Jifty->web->new_action(
+    class   => "Update".$object_type,
+    moniker => "update-" . Jifty->web->serial,
+    record  => $record
+);
+my $delete = Jifty->web->new_action(
+    class   => "Delete".$object_type,
+    moniker => "delete-" . Jifty->web->serial,
+    record  => $record
+);
+
+</%init>
+<div class="jifty_admin read item inline">
+  
+<%
+    Jifty->web->form->submit(
+        class   => "editlink",
+        label   => _('Delete'),
+        onclick => [
+         { confirm => _("Confirm delete?")},
+         { submit => $delete },
+         { delete =>  Jifty->web->current_region->qualified_name }
+        ]
+        )
+%> 
+
+<%
+    Jifty->web->link(
+        label   => _('Edit'),
+        class   => "editlink",
+        onclick => {
+            replace_with => $list_path.'update',
+            args         => { object_type => $object_type, id => $id, list_path => $list_path,
+                            mask_field => $mask_field, mask_val => $mask_val }
+            },
+   #     as_button => 1
+        )
+%>
+
+<% $delete->hidden('id',$id) %>
+% foreach my $argument ($update->argument_names) {
+% unless( $argument eq $mask_field ||  $argument =~ /_confirm$/
+%        && lc $update->arguments->{$argument}{render_as} eq 'password') {
+  <%$update->form_field($argument, render_mode => 'read')%>
+% }
+% }
+
+<hr />
+</div>
+

Added: jifty/branches/schema-plugins/share/web/templates/__jifty/admin/index.html
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/__jifty/admin/index.html	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,31 @@
+<&| /_elements/wrapper, title => _('Jifty Administrative Console') &>
+
+<h1><% _('Database Administration') %></h1>
+
+<p><% _('This console lets you manage the records in your Jifty database. Below, you should see a list of all your database tables. Feel free to go through and add, delete or modify records.') %></p>
+
+<p><% _('To disable this administrative console, add "AdminMode: 0" under the "framework:" settings in the config file (etc/config.yml).') %></p>
+    
+<h2><% _('Models') %></h2>
+<ul>
+% foreach my $model (Jifty->class_loader->models) {
+% next unless $model->isa('Jifty::Record');
+% next unless ($model =~ /^(?:.*)::(.*?)$/);
+% my $type = $1;
+<li><% Jifty->web->link( url => '/__jifty/admin/model/'.$type, label => $type)%>
+%}
+</ul>
+
+<h2><% _('Actions') %></h2>
+<ul>
+% foreach my $action (Jifty->api->actions) {
+% Jifty::Util->require($action);
+% next if ( $action->can('autogenerated') and $action->autogenerated);
+<li><% Jifty->web->link( url => '/__jifty/admin/action/'.$action, label => $action) %></li>
+% }
+</ul>
+
+<h2><% _('Done?') %></h2>
+<% Jifty->web->return( to => "/", label => _('Back to the application')) %>
+
+</&>

Added: jifty/branches/schema-plugins/share/web/templates/__jifty/admin/model/dhandler
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/__jifty/admin/model/dhandler	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,23 @@
+<%init>
+my $object_type = $m->dhandler_arg;
+
+my $collection_class =  Jifty->app_class("Model", $object_type."Collection");
+my $records = $collection_class->new();
+$records->unlimit;
+</%init>
+
+<&| /_elements/wrapper, title => _("Manage %1 records", $object_type) &>
+
+<h1><% _('Manage records:') %> <%$object_type%></h1>
+
+<% Jifty->web->form->start %>
+<% Jifty->web->region(name => "admin-$object_type",
+                      path => "/__jifty/admin/fragments/list/list", 
+                      defaults => { object_type => $object_type , 
+                                    render_submit => 1 }) %>
+<% Jifty->web->form->end %>
+
+<h2> <% _('Done?') %> </h2>
+<% Jifty->web->link( url => "/__jifty/admin/", label => _('Back to the admin console')) %>
+
+</&>

Added: jifty/branches/schema-plugins/share/web/templates/__jifty/autocomplete.xml
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/__jifty/autocomplete.xml	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,21 @@
+<%init>
+# Note: the only point to this file is to set the content_type; the actual
+# behavior is accomplished inside the framework.  It will go away once we
+# have infrastructure for serving things of various content-types.
+$r->content_type('text/xml; charset=UTF-8');
+my $ref =  Jifty->web->response->result('autocomplete')->content;
+my @options = @{$ref->{'completions'}||[]};
+</%init>
+<body>
+<ul>
+% foreach my $item ( @options) {
+%    if (!ref($item)) {
+<li><% $item %></li>
+%    } elsif (exists $item->{label}) {
+<li><span class="informal"><% $item->{label} %></span><span class="hidden_value"><% $item->{value} %></span></li>
+%    } else {
+<li><% $item->{value} %></li>
+%    }
+%}
+</ul>
+</body>

Added: jifty/branches/schema-plugins/share/web/templates/__jifty/css/dhandler
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/__jifty/css/dhandler	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,39 @@
+<%init>
+if ( $m->dhandler_arg !~ /^[0-9a-f]{32}\.css$/ ) {
+    # This doesn't look like a real request for squished CSS,
+    # so redirect to a more failsafe place
+    Jifty->web->redirect( "/static/css/" . $m->dhandler_arg );
+}
+
+Jifty->web->generate_css;
+
+use HTTP::Date ();
+
+if ( Jifty->handler->cgi->http('If-Modified-Since')
+        and $m->dhandler_arg eq Jifty->web->cached_css_digest . '.css' )
+{
+    Jifty->log->debug("Returning 304 for cached css");
+    $r->header_out( Status => 304 );
+    return;
+}
+
+$r->content_type("text/css");
+$r->header_out( 'Expires' => HTTP::Date::time2str(time + 31536000) );
+
+# XXX TODO: If we start caching the squished CSS in a file somewhere, we
+# can have the static handler serve it, which would take care of gzipping
+# for us.
+use Compress::Zlib qw();
+
+if ( Jifty::View::Static::Handler->client_accepts_gzipped_content ) {
+    Jifty->log->debug("Sending gzipped squished CSS");
+    $r->header_out( "Content-Encoding" => "gzip" );
+    binmode STDOUT;
+    print Compress::Zlib::memGzip( Jifty->web->cached_css );
+}
+else {
+    Jifty->log->debug("Sending squished CSS");
+    print Jifty->web->cached_css;
+}
+return;
+</%init>

Added: jifty/branches/schema-plugins/share/web/templates/__jifty/empty
==============================================================================

Added: jifty/branches/schema-plugins/share/web/templates/__jifty/error/_elements/error_text
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/__jifty/error/_elements/error_text	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,21 @@
+<h1>Sorry, something went awry</h1>
+<p>For one reason or another, you got to a web page that caused a bit
+of an error. And then you got to our "basic" error handler. Which
+means we haven't written a pretty, easy to understand error message
+for you just yet. The message we <em>do</em> have is:</p>
+
+<blockquote>
+<b><% $error %></b>
+</blockquote>
+
+<p>There's a pretty good chance that error message doesn't mean
+anything to you, but we'd rather you have a little bit of information
+about what went wrong than nothing. We've logged this error, so we
+know we need to write something friendly explaining just what happened
+and how to fix it.</p>
+
+<p>For now <% Jifty->web->link( url =>"/", label => 'head on back
+home')%> and try to forget that we let you down.</p>
+<%args>
+$error
+</%args>
\ No newline at end of file

Added: jifty/branches/schema-plugins/share/web/templates/__jifty/error/_elements/wrapper
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/__jifty/error/_elements/wrapper	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,30 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+<head>
+  <title>Mason error</title>  
+  <link rel="stylesheet" type="text/css" href="/__jifty/error/error.css" media="all" />
+</head>
+<body>
+  <div id="headers">
+    <h1 class="title">Mason error</h1>
+  </div>
+  <div id="content">
+    <a name="content"></a>
+% if (Jifty->config->framework('AdminMode') ) {
+<div class="warning admin_mode">
+Alert: Jifty <% Jifty->web->tangent( label => 'administration mode' , url => '/__jifty/admin/')%> is enabled.
+</div>
+% }
+  <% Jifty->web->render_messages %>
+
+  <% $m->content |n%>
+
+  </div>
+</body>
+</html>
+<%doc>
+
+This exists as a fallback wrapper, in case the mason error in question
+is caused by the Jifty app's wrapper, for instance.
+
+</%doc>
\ No newline at end of file

Added: jifty/branches/schema-plugins/share/web/templates/__jifty/error/autohandler
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/__jifty/error/autohandler	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,4 @@
+<%flags>
+inherit => undef
+</%flags>
+% $m->call_next
\ No newline at end of file

Added: jifty/branches/schema-plugins/share/web/templates/__jifty/error/dhandler
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/__jifty/error/dhandler	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,8 @@
+<&|/_elements/wrapper, title => 'Something went awry' &>
+
+<& _elements/error_text, error => $m->dhandler_arg &>
+
+</&>
+<%init>
+Jifty->log->error("Unhandled web error ". $m->dhandler_arg);
+</%init>

Added: jifty/branches/schema-plugins/share/web/templates/__jifty/error/error.css
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/__jifty/error/error.css	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,13 @@
+h1 {
+    color: red;
+}
+
+<%init>
+$r->content_type("text/css");
+</%init>
+<%doc>
+
+This exists as a fallback CSS, in case the Jifty app's CSS is causing
+the error.
+
+</%doc>
\ No newline at end of file

Added: jifty/branches/schema-plugins/share/web/templates/__jifty/error/mason_internal_error
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/__jifty/error/mason_internal_error	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,71 @@
+<&| $wrapper, title => _("Mason error") &>
+
+Error in <& .line, file => $file, line => "@lines" &>
+<pre><% $msg %></pre>
+
+<% Jifty->web->return( label => _("Try again") ) %>
+
+<h2>Call stack</h2>
+<ul>
+% for my $frame (@stack) {
+%   next if $frame->filename =~ m{/HTML/Mason/};
+    <li><& .line, file => $frame->filename, line => $frame->line &></li>
+% }
+</ul>
+ 
+</&>
+
+<%def .line>
+<%args>
+$file
+$line
+</%args>
+%   my $path;
+%   if (-w $file) {
+%     $path = $file;
+%     for (map {$_->[1]} @{Jifty->handler->mason->interp->comp_root}) {
+%       last if $path =~ s/^\Q$_\E//;
+%     }
+%    if ($path ne $file) {
+template <% Jifty->web->tangent( url =>"/__jifty/edit/mason_component$path",
+                                 label => _("%1 line %2", $path, $line),
+                                 parameters => { line => $line } ) %>
+%    } else {
+<% Jifty->web->tangent( url =>"/__jifty/edit/library$path",
+                        label => _("%1 line %2", $path, $line),
+                        parameters => { line => $line } ) %>
+%   }
+%  } else {
+<% _("%1 line %2", $file, $line) %>
+% }
+</%def>
+
+<%init>
+my $wrapper = "/_elements/wrapper";
+
+my $cont = Jifty->web->request->continuation;
+$wrapper = "/__jifty/error/_elements/wrapper"
+  if $cont
+  and $cont->request->path eq "/__jifty/error/mason_internal_error";
+
+# If we're not in devel, bail
+if ( not Jifty->config->framework("DevelMode") or not $cont ) {
+  $m->comp(
+    $wrapper,
+      content => sub {
+        $m->comp( "_elements/error_text", error => "mason internal error" );
+      },
+      title => "Something went awry"
+  );
+  $m->abort;
+}
+
+my $e   = $cont->response->error;
+my $msg = $e->message;
+$msg =~ s/, <\S+> (line|chunk) \d+\././;
+
+my $info  = $e->analyze_error;
+my $file  = $info->{file};
+my @lines = @{ $info->{lines} };
+my @stack = @{ $info->{frames} };
+</%init>

Added: jifty/branches/schema-plugins/share/web/templates/__jifty/halo
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/__jifty/halo	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,148 @@
+<div><a href="#" id="render_info" onclick="Element.toggle('render_info_tree'); return false">Page info</a></div>
+<div style="display: none" id="render_info_tree">
+% foreach my $item (@stack) {
+%     if ( $item->{depth} > $depth ) {
+<ul>
+%     } elsif ( $item->{depth} < $depth ) {
+%         for ($item->{depth}+1 .. $depth) {
+</li></ul>
+%         }
+</li>
+%     } elsif ( $item->{depth} == $depth ) {
+</li>
+%     }
+
+<li><a href="#" class="halo_comp_info" onmouseover="halo_over('<% $item->{id} %>')"
+                                       onmouseout="halo_out('<% $item->{id} %>')"
+                                       onclick="halo_toggle('<% $item->{id} %>'); return false;">
+<% $item->{'name'} %> - <% $item->{'render_time'} %></a> 
+% unless ($item->{subcomponent}) {
+<% Jifty->web->tangent( url =>"/__jifty/edit/mason_component/".$item->{'path'}, label => 'Edit') %>
+% }
+% $depth = $item->{'depth'};
+% }
+
+% for (1 .. $depth) {
+</li></ul>
+% }
+</div>
+
+% foreach my $item (@stack) {
+<& .frame, frame => $item &>
+% }
+<%args>
+ at stack
+</%args>
+<%init>
+for my $id (0..$#stack) {
+    my @kids;
+    my $looking = $id;
+    while (++$looking <= $#stack and $stack[$looking]->{depth} >= $stack[$id]->{depth} + 1) {
+        push @kids, {id => $stack[$looking]{id}, path => $stack[$looking]{path}, render_time => $stack[$looking]{render_time}}
+          if $stack[$looking]->{depth} == $stack[$id]->{depth} + 1;
+    }
+    $stack[$id]{kids} = \@kids;
+
+    if ($stack[$id]{depth} > 1) {
+        $looking = $id;
+        $looking-- while ($stack[$looking]{depth} >= $stack[$id]{depth});
+        $stack[$id]{parent} = {id => $stack[$looking]{id}, path => $stack[$looking]{path}, render_time => $stack[$looking]{render_time}};
+    }
+}
+
+my $depth = 0;
+
+</%init>
+
+
+
+<%def .frame>
+<div class="halo_actions" id="halo-<% $id %>-menu" style="display: none; top: 5px; left: 500px; min-width: 200px; width: 300px; z-index: 5;">
+<h1 id="halo-<% $id %>-title">
+  <span style="float: right;"><a href="#" onclick="halo_toggle('<% $id %>'); return false">[ X ]</a></span>
+  <% $frame->{name} %>
+</h1>
+<div style="position: absolute; bottom: 3px; right: 3px"><span class="resize" title="Resize" id="halo-<% $id %>-resize"></span></div>
+
+<div class="body">
+<div class="path"><% $frame->{path} %></div>
+<div class="time">Rendered in <% $frame->{'render_time'} %>s</div>
+</div>
+% if ($frame->{parent}) {
+<div class="section">Parent</div>
+<div class="body"><ul>
+<li><a href="#" class="halo_comp_info" onmouseover="halo_over('<% $frame->{parent}{id} %>')"
+                                       onmouseout="halo_out('<% $frame->{parent}{id} %>')"
+                                       onclick="halo_toggle('<% $frame->{parent}{id} %>'); return false;">
+<% $frame->{parent}{'path'} %> - <% $frame->{parent}{'render_time'} %></a></li>
+</ul></div>
+% }
+% if (@{$frame->{kids}}) {
+<div class="section">Children</div>
+<div class="body"><ul>
+% for my $item (@{$frame->{kids}}) {
+<li><a href="#" class="halo_comp_info" onmouseover="halo_over('<% $item->{id} %>')"
+                                       onmouseout="halo_out('<% $item->{id} %>')"
+                                       onclick="halo_toggle('<% $item->{id} %>'); return false;">
+<% $item->{'path'} %> - <% $item->{'render_time'} %></a></li>
+% }
+</ul>
+</div>
+% }
+% if (@args) {
+<div class="section">Variables</div>
+<div class="body"><ul class="fixed">
+% for my $e (@args) {
+<li><b><% $e->[0] %></b>:
+% if ($e->[1]) {
+% my $expanded = Jifty->web->serial;
+<a href="#" onclick="Element.toggle('<% $expanded %>'); return false"><% $e->[1] %></a>
+<div id="<% $expanded %>" style="display: none; position: absolute; left: 200px; border: 1px solid black; background: #ccc; padding: 1em; padding-top: 0; width: 300px; height: 500px; overflow: auto"><pre><% Jifty::YAML::Dump($e->[2]) %></pre></div>
+% } elsif (defined $e->[2]) {
+<% $e->[2] %>
+% } else {
+<i>undef</i>
+% }
+</li>
+% }
+</ul></div>
+% }
+% if (@stmts) {
+<div class="section"><%_('SQL Statements')%></div>
+<div class="body" style="height: 300px; overflow: auto"><ul>
+% for (@stmts) {
+<li>
+<span class="fixed"><% $_->[1] %></span><br />
+% if (@{$_->[2]}) {
+<b>Bindings:</b> <tt><% join(',', map {defined $_ ? ($_ =~ /[^[:space:][:graph:]]/ ? "*BLOB*" : $_ ) : "undef"} @{$_->[2]}) %></tt><br />
+% }
+<i><% _('%1 seconds', $_->[3]) %></i>
+</li>
+% }
+</ul></div>
+% }
+<div class="section">
+% unless ($frame->{subcomponent}) {
+<% Jifty->web->tangent( url =>"/__jifty/edit/mason_component/".$frame->{'path'}, label => 'Edit') %>
+% } else {
+&nbsp;     
+% }
+</div>
+</div>
+<%args>
+$frame
+</%args>
+<%init>
+my $id = $frame->{id};
+
+my @args;
+while (my ($key, $value) = splice(@{$frame->{args}},0,2)) {
+    push @args, [$key, ref($value), $value];
+}
+ at args = sort {$a->[0] cmp $b->[0]} @args;
+
+my $prev = '';
+my @stmts = @{$frame->{'sql_statements'}};
+
+</%init>
+</%def>

Added: jifty/branches/schema-plugins/share/web/templates/__jifty/js/dhandler
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/__jifty/js/dhandler	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,39 @@
+<%init>
+if ( $m->dhandler_arg !~ /^[0-9a-f]{32}\.js$/ ) {
+    # This doesn't look like a real request for squished JS,
+    # so redirect to a more failsafe place
+    Jifty->web->redirect( "/static/js/" . $m->dhandler_arg );
+}
+
+Jifty->web->generate_javascript;
+
+use HTTP::Date ();
+
+if ( Jifty->handler->cgi->http('If-Modified-Since')
+        and $m->dhandler_arg eq Jifty->web->cached_javascript_digest . '.js' )
+{
+    Jifty->log->debug("Returning 304 for cached javascript");
+    $r->header_out( Status => 304 );
+    return;
+}
+
+$r->content_type("application/x-javascript");
+$r->header_out( 'Expires' => HTTP::Date::time2str(time + 31536000) );
+
+# XXX TODO: If we start caching the squished JS in a file somewhere, we
+# can have the static handler serve it, which would take care of gzipping
+# for us.
+use Compress::Zlib qw();
+
+if ( Jifty::View::Static::Handler->client_accepts_gzipped_content ) {
+    Jifty->log->debug("Sending gzipped squished JS");
+    $r->header_out( "Content-Encoding" => "gzip" );
+    binmode STDOUT;
+    print Compress::Zlib::memGzip( Jifty->web->cached_javascript );
+}
+else {
+    Jifty->log->debug("Sending squished JS");
+    print Jifty->web->cached_javascript;
+}
+return;
+</%init>

Added: jifty/branches/schema-plugins/share/web/templates/__jifty/online_docs/autohandler
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/__jifty/online_docs/autohandler	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,10 @@
+<%init>
+
+# If "AdminMode" is turned off in Jifty's config file, don't let people at the admin UI.
+unless (Jifty->config->framework('AdminMode')) {
+    $m->redirect('/__jifty/error/permission_denied'); 
+    $m->abort();
+}
+#$m->comp('_elements/nav');
+$m->call_next();
+</%init>

Added: jifty/branches/schema-plugins/share/web/templates/__jifty/online_docs/content.html
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/__jifty/online_docs/content.html	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >
+<head>
+<title><% _( $n || 'Jifty') %> - <%_('Jifty Pod Online')%></title>
+<style type="text/css"><!--
+a { text-decoration: none }
+a:hover { text-decoration: underline }
+a:focus { background: #99ff99; border: 1px black dotted }
+--></style>
+</head>
+<body>
+<%PERL>
+my $jifty_dirname = Jifty::Util->jifty_root."/";
+my $app_dirname = Jifty::Util->app_root."/lib/";
+$n =~ s/::/\//g;
+
+my @options = (
+    $app_dirname.$n.".pod",
+    $app_dirname.$n.".pm",
+    $jifty_dirname.$n.".pod",
+    $jifty_dirname.$n.".pm");
+
+my $total_body;
+foreach my $file (@options) {
+    next unless -r "$file";
+    local $/;
+    my $fh;
+    open $fh, "$file" or next;
+    $total_body = <$fh>;
+    close $fh;
+}
+my $body;
+my $schema;
+my $converter = Pod::Simple::HTML->new();
+if($n !~ /^Jifty\//) {
+    if ($total_body =~ /package (.*?)::Schema;(.*)package/ismx) {
+         $schema = $2;
+    }
+}
+
+
+$converter->output_string( \$body );
+$converter->parse_string_document($total_body);
+$body =~ s{.*?<body [^>]+>}{}s;
+$body =~ s{</body>\s*</html>\s*$}{};
+$n    =~ s{/}{::}g;
+$m->print("<h1>$n</h1>");
+$m->print("<h2>"._('Schema')."</h2><pre>$schema</pre>") if ($schema);
+$body =~ s{<a href="http://search\.cpan\.org/perldoc\?(Jifty%3A%3A[^"]+)"([^>]*)>}{<a href="content.html?n=$1"$2>}g;
+$body =~ s!</li>\n\t<ul>!<ul>!;
+$body =~ s!</ul>!</ul></li>!;
+$body =~ s!<p></p>!!;
+$body =~ s!<a name=!<a id=!g;
+$body =~ s!__index__!index!g;
+$m->print($body);
+</%PERL>
+</body></html>
+<%ARGS>
+$Target    => '&method=content'
+$n  => 'Jifty'
+</%ARGS>
+<%once>
+require File::Basename;
+require File::Find;
+require File::Temp;
+require File::Spec;
+require Pod::Simple::HTML;
+</%once>

Added: jifty/branches/schema-plugins/share/web/templates/__jifty/online_docs/index.html
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/__jifty/online_docs/index.html	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
+"http://www.w3.org/TR/html4/">
+<html lang="en">
+<head>
+<title><%_( $n || 'Jifty') %> - <%_('Online Documentation')%></title>
+<style type="text/css"><!--
+a { text-decoration: none }
+a:hover { text-decoration: underline }
+a:focus { background: #99ff99; border: 1px black dotted }
+--></style>
+</head>
+<FRAMESET COLS="*, 250">
+    <FRAME src="./content.html" name="podcontent">
+    <FRAME src="./toc.html" name="podtoc">
+    <NOFRAMES>
+        <a style="display: none" href="#toc"><%_('Table of Contents')%></a>
+<& content.html, Target => '' &>
+        <h1><a id="toc"><%_('Table of Contents')%></a></h1>
+<& toc.html, Target => '' &>
+    </NOFRAMES>
+</FRAMESET>
+<%args>
+$n => undef
+</%args>

Added: jifty/branches/schema-plugins/share/web/templates/__jifty/online_docs/toc.html
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/__jifty/online_docs/toc.html	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >
+<head>
+<title><% _($n || 'Jifty') %> - <%_('Jifty Developer Documentation Online')%></title>
+<style type="text/css"><!--
+a { text-decoration: none }
+a:hover { text-decoration: underline }
+a:focus { background: #99ff99; border: 1px black dotted }
+--></style>
+</head>
+<body style="background: #dddddd">
+<%PERL>
+my @found;
+File::Find::find(
+    { untaint => 1,
+      wanted => sub {
+        return unless /(\w+)\.(?:pm|pod)$/;
+        my $name = $File::Find::name;
+        $name =~ s/.*lib\b.//;
+        $name =~ s!\.(?:pm|pod)!!i;
+        $name =~ s!\W!::!g;
+        push @found, $name;
+    },follow => ($^O ne 'MSWin32') },
+    Jifty::Util->app_root ."/lib",
+);
+
+
+File::Find::find(
+    { untaint => 1,
+      wanted => sub {
+        return unless $File::Find::name =~ /^(?:.*?)(Jifty.*?\.(?:pm|pod))$/;
+        my $name = $1;
+        $name =~ s/.*lib\b.//;
+        $name =~ s!\.(?:pm|pod)!!i;
+        $name =~ s!\/!::!g;
+        push @found, $name;
+    },follow => ($^O ne 'MSWin32') },
+    Jifty::Util->jifty_root,
+);
+
+my $indent = 0;
+my $prev = '';
+foreach my $file (sort @found) {
+    my ($parent, $name) = ($1, $2) if $file =~ /(?:(.*)::)?(\w+)$/;
+    $parent = '' unless defined $parent;
+    if ($file =~ /^$prev\::(.*)/) {
+        my $foo = $1;
+        while ($foo =~ s/(\w+):://) {
+            $indent++;
+            $m->print(('&nbsp;&nbsp;&nbsp;' x $indent));
+            $m->print("$1<br />");
+        }
+        $indent++;
+    } elsif ($prev !~ /^$parent\::/) {
+        $indent = 0 unless length $parent;
+        while ($parent =~ s/(\w+)//) {
+            next if $prev =~ s/\b$1:://;
+            while ($prev =~ s/:://) {
+                $indent--;
+            }
+            $m->print(('&nbsp;&nbsp;&nbsp;' x $indent));
+            $m->print("$1<br />");
+            $indent++;
+        }
+    } elsif ($prev =~ /^$parent\::(.*::)/) {
+        my $foo = $1;
+        while ($foo =~ s/:://) {
+            $indent--;
+        }
+    }
+    $m->print( ( '&nbsp;&nbsp;&nbsp;' x $indent )
+      . '<a target="podcontent" href="content.html?n=' . $file . '">' . $name
+      . '</a><br />' ."\n" );
+    $prev = $file;
+}
+
+</%PERL>
+</body></html>
+<%INIT>
+require File::Basename;
+require File::Find;
+require File::Temp;
+require File::Spec;
+require Pod::Simple::HTML;
+</%INIT>
+<%ARGS>
+$n  => ''
+$method => ''
+$Target    => '&method=content'
+</%ARGS>

Added: jifty/branches/schema-plugins/share/web/templates/__jifty/validator.xml
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/__jifty/validator.xml	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,69 @@
+<%init>
+$r->content_type('text/xml; charset=UTF-8');
+
+my $output = "";
+use XML::Writer;
+my $writer = XML::Writer->new( OUTPUT => \$output );
+$writer->xmlDecl( "UTF-8", "yes" );
+$writer->startTag("validation");
+for my $ra ( Jifty->web->request->actions ) {
+    my $action = Jifty->web->new_action_from_request($ra);
+    $writer->startTag( "validationaction", id => $action->register_name );
+    for my $arg ( $action->argument_names ) {
+        if ( not $action->arguments->{$arg}->{ajax_validates} ) {
+            $writer->emptyTag( "ignored", id => $action->error_div_id($arg) );
+            $writer->emptyTag( "ignored", id => $action->warning_div_id($arg) );
+        } elsif ( not defined $action->argument_value($arg)
+                  or length $action->argument_value($arg) == 0 ) {
+            $writer->emptyTag( "blank", id => $action->error_div_id($arg) );
+            $writer->emptyTag( "blank", id => $action->warning_div_id($arg) );
+        } elsif ( $action->result->field_error($arg) ) {
+            $writer->dataElement(
+                "error",
+                $action->result->field_error($arg),
+                id => $action->error_div_id($arg)
+            );
+            $writer->emptyTag( "ok", id => $action->warning_div_id($arg) );
+        } elsif ( $action->result->field_warning($arg) ) {
+            $writer->dataElement(
+                "warning",
+                $action->result->field_warning($arg),
+                id => $action->warning_div_id($arg)
+            );
+            $writer->emptyTag( "ok", id => $action->error_div_id($arg) );
+        } else {
+            $writer->emptyTag( "ok", id => $action->error_div_id($arg) );
+            $writer->emptyTag( "ok", id => $action->warning_div_id($arg) );
+        }
+    }
+    $writer->endTag();
+    $writer->startTag( "canonicalizeaction", id => $action->register_name );
+    for my $arg ( $action->argument_names ) {
+        if (($ra->arguments->{$arg} || "") eq ($action->argument_value($arg) || "")) {
+            # if the value doesn't change, it can be ignored.
+            # canonicalizers can change other parts of the action, so we want to send all changes
+            $writer->emptyTag( "ignored", name => $action->form_field_name($arg) );
+        } elsif ( not defined $action->argument_value($arg)
+            or length $action->argument_value($arg) == 0 ) {
+            $writer->emptyTag( "blank", name => $action->form_field_name($arg) );
+        } else {
+            $writer->dataElement(
+                "update",
+                $action->argument_value($arg),
+                name => $action->form_field_name($arg)
+            );
+        }
+        if ( $action->result->field_canonicalization_note($arg) ) {
+            $writer->dataElement(
+                "canonicalization_note",
+                $action->result->field_canonicalization_note($arg),
+                id => $action->canonicalization_note_div_id($arg)
+            );
+        }
+    }
+    $writer->endTag();
+}
+$writer->endTag();
+$m->out($output);
+$m->abort();
+</%init>

Added: jifty/branches/schema-plugins/share/web/templates/__jifty/webservices/json
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/__jifty/webservices/json	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,2 @@
+% $r->content_type("text/x-json");
+<% Jifty::JSON::objToJson({Jifty->web->response->results}) |n%>

Added: jifty/branches/schema-plugins/share/web/templates/__jifty/webservices/xml
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/__jifty/webservices/xml	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,105 @@
+<%init>
+my $output = "";
+my $writer = XML::Writer->new( OUTPUT => \$output, UNSAFE => 1 );
+$writer->xmlDecl( "UTF-8", "yes" );
+$writer->startTag("response");
+for my $f ( Jifty->web->request->fragments ) {
+    # Set up the region stack
+    local Jifty->web->{'region_stack'} = [];
+    my @regions;
+    do {
+        push @regions, $f;
+    } while ($f = $f->parent);
+
+    for $f (reverse @regions) {
+        my $new = Jifty->web->get_region( join '-', grep {$_} Jifty->web->qualified_region, $f->name );
+
+        # Arguments can be complex mapped hash values.  Get their
+        # real values by mapping.
+        my %defaults = %{$f->arguments || {}};
+        for (keys %defaults) {
+            my ($key, $value) = Jifty::Request::Mapper->map(destination => $_, source => $defaults{$_});
+            delete $defaults{$_};
+            $defaults{$key} = $value;
+        }
+
+        $new ||= Jifty::Web::PageRegion->new(
+            name           => $f->name,
+            path           => $f->path,
+            region_wrapper => $f->wrapper,
+            parent         => Jifty->web->current_region,
+            defaults       => \%defaults,
+        );
+        $new->enter;
+    }
+
+    # Stuff the rendered region into the XML
+    $writer->startTag( "fragment", id => Jifty->web->current_region->qualified_name );
+    my %args = %{ Jifty->web->current_region->arguments };
+    $writer->dataElement( "argument", $args{$_}, name => $_) for sort keys %args;
+    $writer->cdataElement( "content", Jifty->web->current_region->as_string );
+    $writer->endTag();
+
+    Jifty->web->current_region->exit while Jifty->web->current_region;
+}
+
+my %results = Jifty->web->response->results;
+for (keys %results) {
+    $writer->startTag("result", moniker => $_, class => $results{$_}->action_class);
+    $writer->dataElement("success", $results{$_}->success);
+
+    $writer->dataElement("message", $results{$_}->message) if $results{$_}->message;
+    $writer->dataElement("error", $results{$_}->error) if $results{$_}->error;
+
+    my %warnings = $results{$_}->field_warnings;
+    my %errors   = $results{$_}->field_errors;
+    my %fields; $fields{$_}++ for keys(%warnings), keys(%errors);
+    for (sort keys %fields) {
+        next unless $warnings{$_} or $errors{$_};
+        $writer->startTag("field", name => $_);
+        $writer->dataElement("warning", $warnings{$_}) if $warnings{$_};
+        $writer->dataElement("error", $errors{$_}) if $errors{$_};
+        $writer->endTag();
+    }
+
+    # XXX TODO: Hack because we don't have a good way to serialize
+    # Jifty::DBI::Record's yet, which are technically circular data
+    # structures at some level (current_user of a
+    # current_user->user_object is itself)
+    use Scalar::Util qw(blessed);
+    my $content = $results{$_}->content;
+
+    sub stripkids {
+        my $top = shift;
+        if ( not ref $top ) { return $top }
+        elsif (
+            blessed($top)
+            and (  $top->isa("Jifty::DBI::Record")
+                or $top->isa("Jifty::DBI::Collection") )
+            )
+        {
+            return undef;
+        } elsif ( ref $top eq 'HASH' ) {
+            foreach my $item ( keys %$top ) {
+                $top->{$item} = stripkids( $top->{$item} );
+            }
+        } elsif ( ref $top eq 'ARRAY' ) {
+            for ( 0 .. $#{$top} ) {
+                $top->[$_] = stripkids( $top->[$_] );
+            }
+        }
+        return $top;
+    }
+
+    $content = stripkids($content);
+    use XML::Simple;
+    $writer->raw(XML::Simple::XMLout($content, NoAttr => 1, RootName => "content", NoIndent => 1))
+      if keys %{$content};
+
+    $writer->endTag();
+}
+
+$writer->endTag();
+$r->content_type('text/xml; charset=utf-8');
+Jifty->web->out($output);
+</%init>

Added: jifty/branches/schema-plugins/share/web/templates/__jifty/webservices/yaml
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/__jifty/webservices/yaml	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,2 @@
+% $r->content_type("text/x-yaml");
+<% Jifty::YAML::Dump({Jifty->web->response->results}) |n%>

Added: jifty/branches/schema-plugins/share/web/templates/_elements/header
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/_elements/header	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,17 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+<head>
+  <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+  <meta name="robots" content="all" />
+  
+  <title><% _($title) %></title>
+  
+  <% Jifty->web->include_css %>
+  <% Jifty->web->include_javascript %> 
+</head>
+<%args>
+$title => ""
+</%args>
+<%init>
+$r->content_type('text/html; charset=utf-8');
+</%init>

Added: jifty/branches/schema-plugins/share/web/templates/_elements/keybindings
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/_elements/keybindings	Mon Jan 29 21:05:02 2007
@@ -0,0 +1 @@
+<div id="keybindings"></div>

Added: jifty/branches/schema-plugins/share/web/templates/_elements/menu
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/_elements/menu	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,34 @@
+<%args>
+$menu => undef
+</%args>
+<%init>
+# Default to the app menu
+$menu = Jifty->web->navigation if not defined $menu;
+</%init>
+% my @children = $menu->children;
+% if ( @children ) {
+<ul class="menu">
+<%perl>
+$m->comp( ".menu", item => $_ )
+    for sort { $a->sort_order <=> $b->sort_order }
+             @children;
+</%perl>
+</ul>
+% }
+
+<%def .menu>
+  <%args>
+    $item
+  </%args>
+  <%init>
+    my @kids = $item->children;
+  </%init>
+  <li <%  $item->active ? 'class="active"' : '' |n %>><% $item->as_link |n %><% @kids ? '' : '</li>' |n %>
+% if (@kids) {
+    <ul class="submenu">
+% $m->comp(".menu", item => $_) for @kids;
+    </ul>
+  </li>
+% }
+</%def>
+

Added: jifty/branches/schema-plugins/share/web/templates/_elements/nav
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/_elements/nav	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,9 @@
+<%init>
+my $top = Jifty->web->navigation;
+$top->child(Home       => url => "/", sort_order => 1);
+ if (Jifty->config->framework('AdminMode') ) {
+    $top->child(Administration       => url => "/__jifty/admin/", sort_order => 998);
+    $top->child(OnlineDocs       => url => "/__jifty/online_docs/", label => 'Online docs',  sort_order => 999);
+ }
+return();
+</%init>

Added: jifty/branches/schema-plugins/share/web/templates/_elements/page_nav
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/_elements/page_nav	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,5 @@
+% if ( @{Jifty->web->page_navigation->children} ) {
+<div class="page_nav">
+<& /_elements/menu, menu => Jifty->web->page_navigation &>
+</div>
+% }

Added: jifty/branches/schema-plugins/share/web/templates/_elements/sidebar
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/_elements/sidebar	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,12 @@
+<div id="salutation">
+% if (Jifty->web->current_user->id and Jifty->web->current_user->user_object) {
+% my $u = Jifty->web->current_user->user_object;
+% my $method = $u->_brief_description;
+<%_('Hiya, %1.',$u->$method())%>
+% }  else {
+<%_("You're not currently signed in.")%>
+% }
+</div>
+<div id="navigation">
+<& /_elements/menu, menu => Jifty->web->navigation &>
+</div>

Added: jifty/branches/schema-plugins/share/web/templates/_elements/wrapper
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/_elements/wrapper	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,37 @@
+<body>
+  <div id="headers">
+    <%Jifty->web->link( url => "/", label => _(Jifty->config->framework('ApplicationName')))%>
+    <h1 class="title"><% _($title) %></h1>
+  </div>
+  <& sidebar &>
+  <div id="content">
+    <a name="content"></a>
+% if (Jifty->config->framework('AdminMode') ) {
+<div class="warning admin_mode">
+<%_('Alert')%>: <% Jifty->web->tangent( label => _('Administration mode is enabled.') , url => '/__jifty/admin/')%>
+</div>
+% }
+  <% Jifty->web->render_messages %>
+  <% $m->content |n%>
+  <& /_elements/keybindings &>
+  </div>
+  <div id="jifty-wait-message" style="display: none"><%_('Loading...')%></div>
+% Jifty::Mason::Halo->render_component_tree() if (Jifty->config->framework('DevelMode') );
+%# This is required for jifty server push.  If you maintain your own
+%# wrapper, make sure you have this as well.
+% if ( Jifty->config->framework('PubSub')->{'Enable'} && Jifty::Subs->list ) {
+<script>new Jifty.Subs({}).start();</script>
+% }
+</body>
+</html>
+% Jifty->handler->stash->{'in_body'} = 0;
+<%args>
+$title => ""
+</%args>
+<%init>
+# First we set up the header. 
+$m->comp( 'header', title => $title);
+# now that we've printed out the header, we're inside the body, so it's safe to print
+# halo markers.
+Jifty->handler->stash->{'in_body'} = 1;
+</%init>

Added: jifty/branches/schema-plugins/share/web/templates/autohandler
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/autohandler	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,11 @@
+<%init>
+$r->content_type('text/html; charset=utf-8');
+
+if ($m->base_comp->path =~ m|/_elements/|) {
+    # Requesting an internal component by hand -- naughty
+    $m->redirect("/errors/requested_private_component");
+}
+$m->comp('/_elements/nav');
+$m->call_next();
+return;
+</%init>

Added: jifty/branches/schema-plugins/share/web/templates/dhandler
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/dhandler	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,17 @@
+<&| /_elements/wrapper, title => _("Something's not quite right") &>
+
+<div id="overview">
+
+<p><%_("You got to a page that we don't think exists.  Anyway, the software has logged this error. Sorry about this.")%></p>
+
+<p><%Jifty->web->link( url => "/", label => _('Go back home...'))%></p>
+
+</div>
+</&>
+<%doc>
+Used as a poor man's 404 handler
+</%doc>
+<%init>
+Jifty->log->error("404: user tried to get to ".$m->dhandler_arg);
+$r->header_out( Status => '404');
+</%init>

Added: jifty/branches/schema-plugins/share/web/templates/helpers/calendar.html
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/helpers/calendar.html	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,89 @@
+<& /_elements/header, title => _('Calendar'_ &>
+<body class="calpopup">
+
+<a href="#" onclick="window.close(); return false;"><% _('Close window')%></a>
+
+<div class="calendar">
+  <table>
+    <caption>
+      <a class="prev" href="calendar.html?DisplayedMonth=<%$prev_month%>&DisplayedYear=<%$prev_year%>&field=<%$field%>">Prev</a>
+      <span class="month"><% $months[$DisplayedMonth-1] %> <% _($DisplayedYear) %></span>
+      <a class="next" href="calendar.html?DisplayedMonth=<%$next_month%>&DisplayedYear=<%$next_year%>&field=<%$field%>">Next</a>
+    </caption>
+    <tr>
+% foreach my $wday (@weekdays) {
+      <th><%_($wday)%></th>
+% }
+    </tr>
+% foreach my $week (@cal) {
+    <tr>
+%     foreach my $day (@{$week}) {
+      <td>
+%         if ($day) {
+%             my $datestr = sprintf('%04d-%02d-%02d', $DisplayedYear, $DisplayedMonth, $day);
+        <a href="#" onclick="updateParentField('<% $field %>','<% $datestr %>'); return false;"><% $day %></a>
+%         } else {
+        &nbsp;
+%         }
+      </td>
+%     } #foreach $day
+    </tr>
+% } # foreach $week
+  </table>
+  <span class="calendar today">
+%             my $datestr = sprintf('%04d-%02d-%02d', $today[5]+1900,$today[4]+1, $today[3]);
+        <a href="#" onclick="updateParentField('<% $field %>','<% $datestr %>'); return false;">Today</a>
+  
+  </span>
+  <span class="calendar tomorrow">
+% my @tomorrow = localtime(time()+86400);
+% $datestr = sprintf('%04d-%02d-%02d', $tomorrow[5]+1900,$tomorrow[4]+1, $tomorrow[3]);
+        <a href="#" onclick="updateParentField('<% $field %>','<% $datestr %>'); return false;">Tomorrow</a>
+  </span>
+</div>
+</div>
+</body>
+</html>
+% $m->abort();
+<%init>
+my @today = localtime(time());
+
+my @weekdays;
+push @weekdays, $_
+  for qw(Sun Mon Tue Wed Thu Fri Sat);
+
+my @months;
+push @months, $_
+  for qw(January February March April May June July August
+         September October November December);
+
+unless ($DisplayedYear) {
+    $DisplayedMonth = $today[4] + 1;
+    $DisplayedYear  = ($today[5] + 1900);
+}
+
+my ($prev_year, $next_year, $prev_month, $next_month);
+$prev_month = $next_month = $DisplayedMonth;
+$prev_year  = $next_year  = $DisplayedYear;
+
+$next_month++;
+$prev_month--;
+
+if ($DisplayedMonth == 12) {
+    $next_year++;
+    $next_month = 1;
+}
+elsif ($DisplayedMonth == 1) {
+    $prev_month = 12;
+    $prev_year--;
+}
+
+use Calendar::Simple;
+my @cal = Calendar::Simple::calendar($DisplayedMonth, $DisplayedYear);
+</%init>
+
+<%args>
+$field => 'none'
+$DisplayedMonth => undef
+$DisplayedYear => undef
+</%args>

Added: jifty/branches/schema-plugins/share/web/templates/index.html
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/share/web/templates/index.html	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,4 @@
+<&|/_elements/wrapper, title => _('Welcome to your new Jifty application') &>
+<img src="/static/images/pony.jpg" 
+     alt="<%_('You said you wanted a pony. (Source %1)','http://hdl.loc.gov/loc.pnp/cph.3c13461')%>" />
+</&>

Added: jifty/branches/schema-plugins/t/00-load.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/00-load.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,6 @@
+#!/usr/bin/env perl -w
+use strict;
+use Test::More tests => 2;
+
+use_ok('Jifty::Everything');
+use_ok('Jifty::Test');

Added: jifty/branches/schema-plugins/t/01-dependencies.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/01-dependencies.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,73 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+=head1 DESCRIPTION
+
+Makes sure that all of the modules that are 'use'd are listed in the
+Makefile.PL as dependencies.
+
+=cut
+
+use Test::More qw(no_plan);
+use File::Find;
+use Module::CoreList;
+
+my %used;
+find( \&wanted, qw/ lib bin t /);
+
+sub wanted {
+    return unless -f $_;
+    return if $File::Find::dir =~ m!/.svn($|/)!;
+    return if $File::Find::name =~ /~$/;
+    return if $File::Find::name =~ /\.(pod|html)$/;
+    
+    # read in the file from disk
+    my $filename = $_;
+    local $/;
+    open(FILE, $filename) or return;
+    my $data = <FILE>;
+    close(FILE);
+
+    # strip pod, in a really idiotic way.  Good enough though
+    $data =~ s/^=head.+?(^=cut|\Z)//gms;
+
+    # look for use and use base statements
+    $used{$1}{$filename}++ while $data =~ /^\s*use\s+([\w:]+)/gm;
+    while ($data =~ m|^\s*use base qw.([\w\s:]+)|gm) {
+        $used{$_}{$filename}++ for split ' ', $1;
+    }
+}
+
+my %required;
+{ 
+    local $/;
+    ok(open(MAKEFILE,"Makefile.PL"), "Opened Makefile");
+    my $data = <MAKEFILE>;
+    close(FILE);
+    while ($data =~ /^\s*?(?:requires|recommends)\('([\w:]+)'(?:\s*=>\s*['"]?([\d\.]+)['"]?)?.*?(?:#(.*))?$/gm) {
+        $required{$1} = $2;
+        if (defined $3 and length $3) {
+            $required{$_} = undef for split ' ', $3;
+        }
+    }
+}
+
+for (sort keys %used) {
+    my $first_in = Module::CoreList->first_release($_);
+    next if defined $first_in and $first_in <= 5.00803;
+    next if /^(Jifty|Jifty::DBI|inc|t|TestApp|Application)(::|$)/;
+    ok(exists $required{$_}, "$_ in Makefile.PL")
+      or diag("used in ", join ", ", sort keys %{ $used{$_ } });
+    delete $used{$_};
+    delete $required{$_};
+}
+
+for (sort keys %required) {
+    my $first_in = Module::CoreList->first_release($_, $required{$_});
+    fail("Required module $_ is already in core") if defined $first_in and $first_in <= 5.00803;
+}
+
+1;
+

Added: jifty/branches/schema-plugins/t/01-test-web.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/01-test-web.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,38 @@
+#!/usr/bin/perl -w
+
+use Jifty::Test tests => 5;
+
+my $web = Jifty::Test->web;
+isa_ok( $web->request,  "Jifty::Request"  );
+isa_ok( $web->response, "Jifty::Response" );
+
+{
+    {
+        package JiftyApp::Action::Foo;
+        use base 'Jifty::Action';
+    }
+
+    # Fool Jifty into thinking this is already loaded.
+    local $INC{"JiftyApp/Action/Foo.pm"} = 1;
+
+    my $action = $web->new_action( class => "Foo" );
+    isa_ok( $action, "JiftyApp::Action::Foo" );
+}
+
+
+{
+    package Jifty::Request::Subclass;
+    use base qw(Jifty::Request);
+
+    package Jifty::Response::Subclass;
+    use base qw(Jifty::Response);
+}
+
+
+# Make sure Jifty::Test->web doesn't blow over existing requests and reponses.
+Jifty->web->request (Jifty::Request::Subclass->new );
+Jifty->web->response(Jifty::Response::Subclass->new);
+
+$web = Jifty::Test->web;
+isa_ok( $web->request,  "Jifty::Request::Subclass"  );
+isa_ok( $web->response, "Jifty::Response::Subclass" );

Added: jifty/branches/schema-plugins/t/01-version_checks.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/01-version_checks.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,45 @@
+#!/usr/bin/env perl -w
+use strict;
+use Test::More qw(no_plan);
+
+# by Eric Wilhelm in response to Randal Schwartz pointing out that
+# CPAN.pm chokes on the VERSION >... construct
+# I dare not mention it here.
+
+use ExtUtils::MakeMaker;
+use_ok('Jifty::Everything');
+
+# XXX there may be a more cross-platform and harness-friendly way to say
+# this.  Tricky bit is that the harness absolutifies the lib paths or
+# plans to chdir() somewhere.
+
+# just look for Jifty.pm
+my $dir = $INC{'Jifty.pm'};
+$dir =~ s/Jifty\.pm$//;
+$dir = quotemeta $dir;  # as MSWin32 has backslashes in the path
+my @files = grep({$_ =~ m/^$dir/} map({$INC{$_}} grep(/^Jifty\//, keys(%INC))));
+ok(scalar(@files));
+
+foreach my $file (@files) {
+	# Gah! parse_version complains on stderr!
+	my ($e, @a) = error_catch(sub {MM->parse_version($file)});
+	ok(($e || '') eq '', $file) or warn "$e ";
+}
+
+# runs subroutine reference, looking for error message $look in STDERR
+# and runs tests based on $name
+#   ($errs, @ans) = error_catch(sub {$this->test()});
+#
+sub error_catch {
+	my ($sub) = @_;
+	my $TO_ERR;
+	open($TO_ERR, '<&STDERR');
+	close(STDERR);
+	my $catch;
+	open(STDERR, '>', \$catch);
+	my @ans = $sub->();
+	open(STDERR, ">&", $TO_ERR);
+	close($TO_ERR);
+	return($catch, @ans);
+} # end subroutine error_catch definition
+########################################################################

Added: jifty/branches/schema-plugins/t/02-connect.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/02-connect.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,14 @@
+#!/usr/bin/env perl -w
+use strict;
+
+use Jifty::Test tests => 6;
+
+use_ok('Jifty');
+can_ok('Jifty', 'handle');
+
+isa_ok(Jifty->handle, "Jifty::DBI::Handle");
+isa_ok(Jifty->handle, "Jifty::DBI::Handle::".Jifty->config->framework('Database')->{'Driver'}); 
+
+can_ok(Jifty->handle->dbh, 'ping');
+ok(Jifty->handle->dbh->ping);
+

Added: jifty/branches/schema-plugins/t/03-form-protocol.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/03-form-protocol.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,444 @@
+use t::Jifty;
+
+run_is_deeply;
+
+__DATA__
+=== one action
+--- form
+J:A-mymoniker: DoSomething
+J:A:F-id-mymoniker: 23
+J:A:F-something-mymoniker: else
+J:ACTIONS: mymoniker
+--- request
+path: /
+state_variables: {}
+actions:
+  mymoniker:
+    moniker: mymoniker
+    class: DoSomething
+    active: 1
+    has_run: 0
+    arguments:
+        id: 23
+        something: else
+arguments:
+  J:A-mymoniker: DoSomething
+  J:A:F-id-mymoniker: 23
+  J:A:F-something-mymoniker: else
+  J:ACTIONS: mymoniker
+fragments: {}
+=== two actions
+--- form
+J:A-mymoniker: DoSomething
+J:A:F-id-mymoniker: 23
+J:A:F-something-mymoniker: else
+J:A-second: DoSomething
+J:A:F-id-second: 42
+J:A:F-something-second: bla
+J:ACTIONS: mymoniker!second
+--- request
+path: /
+state_variables: {}
+actions:
+  mymoniker:
+    moniker: mymoniker
+    class: DoSomething
+    active: 1
+    has_run: 0
+    arguments:
+        id: 23
+        something: else
+  second:
+    moniker: second
+    class: DoSomething
+    active: 1
+    has_run: 0
+    arguments:
+        id: 42
+        something: bla
+arguments:
+  J:A-mymoniker: DoSomething
+  J:A:F-id-mymoniker: 23
+  J:A:F-something-mymoniker: else
+  J:A-second: DoSomething
+  J:A:F-id-second: 42
+  J:A:F-something-second: bla
+  J:ACTIONS: mymoniker!second
+fragments: {}
+=== two different actions
+--- form
+J:A-mymoniker: DoSomething
+J:A:F-id-mymoniker: 23
+J:A:F-something-mymoniker: else
+J:A-second: DoThat
+J:A:F-id-second: 42
+J:A:F-something-second: bla
+J:ACTIONS: mymoniker!second
+--- request
+path: /
+state_variables: {}
+actions:
+  mymoniker:
+    moniker: mymoniker
+    class: DoSomething
+    active: 1
+    has_run: 0
+    arguments:
+        id: 23
+        something: else
+  second:
+    moniker: second
+    class: DoThat
+    active: 1
+    has_run: 0
+    arguments:
+        id: 42
+        something: bla
+arguments:
+  J:A-mymoniker: DoSomething
+  J:A:F-id-mymoniker: 23
+  J:A:F-something-mymoniker: else
+  J:A-second: DoThat
+  J:A:F-id-second: 42
+  J:A:F-something-second: bla
+  J:ACTIONS: mymoniker!second
+fragments: {}
+=== ignore arguments without actions
+--- form
+J:A-mymoniker: DoSomething
+J:A:F-id-mymoniker: 23
+J:A:F-something-mymoniker: else
+J:A:F-id-second: 42
+J:A:F-something-second: bla
+J:ACTIONS: mymoniker!second
+--- request
+path: /
+state_variables: {}
+actions:
+  mymoniker:
+    moniker: mymoniker
+    class: DoSomething
+    active: 1
+    has_run: 0
+    arguments:
+        id: 23
+        something: else
+arguments:
+  J:A-mymoniker: DoSomething
+  J:A:F-id-mymoniker: 23
+  J:A:F-something-mymoniker: else
+  J:A:F-id-second: 42
+  J:A:F-something-second: bla
+  J:ACTIONS: mymoniker!second
+fragments: {}
+=== one active, one inactive action
+--- form
+J:A-mymoniker: DoSomething
+J:A:F-id-mymoniker: 23
+J:A:F-something-mymoniker: else
+J:A-second: DoThat
+J:A:F-id-second: 42
+J:A:F-something-second: bla
+J:ACTIONS: second
+--- request
+path: /
+state_variables: {}
+actions:
+  mymoniker:
+    moniker: mymoniker
+    class: DoSomething
+    active: 0
+    has_run: 0
+    arguments:
+        id: 23
+        something: else
+  second:
+    moniker: second
+    class: DoThat
+    active: 1
+    has_run: 0
+    arguments:
+        id: 42
+        something: bla
+arguments:
+  J:A-mymoniker: DoSomething
+  J:A:F-id-mymoniker: 23
+  J:A:F-something-mymoniker: else
+  J:A-second: DoThat
+  J:A:F-id-second: 42
+  J:A:F-something-second: bla
+  J:ACTIONS: second
+fragments: {}
+=== two actions, no J:ACTIONS
+--- form
+J:A-mymoniker: DoSomething
+J:A:F-id-mymoniker: 23
+J:A:F-something-mymoniker: else
+J:A-second: DoThat
+J:A:F-id-second: 42
+J:A:F-something-second: bla
+--- request
+path: /
+state_variables: {}
+actions:
+  mymoniker:
+    moniker: mymoniker
+    class: DoSomething
+    active: 1
+    has_run: 0
+    arguments:
+        id: 23
+        something: else
+  second:
+    moniker: second
+    class: DoThat
+    active: 1
+    has_run: 0
+    arguments:
+        id: 42
+        something: bla
+arguments:
+  J:A-mymoniker: DoSomething
+  J:A:F-id-mymoniker: 23
+  J:A:F-something-mymoniker: else
+  J:A-second: DoThat
+  J:A:F-id-second: 42
+  J:A:F-something-second: bla
+fragments: {}
+=== ignore totally random stuff
+--- form
+J:A: bloopybloopy
+J:A-mymoniker: DoSomething
+J:A:E-id-mymoniker: 5423
+J:A:F-id-mymoniker: 23
+asdfk-asdfkjasdlf:J:A:F-asdkfjllsadf: bla
+J:A:F-something-mymoniker: else
+foo: bar
+J:A-second: DoThat
+J:A:F-id-second: 42
+J:A:F-something-second: bla
+--- request
+path: /
+state_variables: {}
+actions:
+  mymoniker:
+    moniker: mymoniker
+    class: DoSomething
+    active: 1
+    has_run: 0
+    arguments:
+        id: 23
+        something: else
+  second:
+    moniker: second
+    class: DoThat
+    active: 1
+    has_run: 0
+    arguments:
+        id: 42
+        something: bla
+arguments:
+  J:A: bloopybloopy
+  J:A-mymoniker: DoSomething
+  J:A:E-id-mymoniker: 5423
+  J:A:F-id-mymoniker: 23
+  asdfk-asdfkjasdlf:J:A:F-asdkfjllsadf: bla
+  J:A:F-something-mymoniker: else
+  foo: bar
+  J:A-second: DoThat
+  J:A:F-id-second: 42
+  J:A:F-something-second: bla
+fragments: {}
+=== order doesn't matter
+--- form
+J:A:F-id-mymoniker: 23
+J:A:F-something-second: bla
+J:A:F-id-second: 42
+J:A-second: DoThat
+J:A:F-something-mymoniker: else
+J:A-mymoniker: DoSomething
+--- request
+path: /
+state_variables: {}
+actions:
+  mymoniker:
+    moniker: mymoniker
+    class: DoSomething
+    active: 1
+    has_run: 0
+    arguments:
+        id: 23
+        something: else
+  second:
+    moniker: second
+    class: DoThat
+    active: 1
+    has_run: 0
+    arguments:
+        id: 42
+        something: bla
+arguments:
+  J:A:F-id-mymoniker: 23
+  J:A:F-something-second: bla
+  J:A:F-id-second: 42
+  J:A-second: DoThat
+  J:A:F-something-mymoniker: else
+  J:A-mymoniker: DoSomething
+fragments: {}
+=== fallbacks being ignored
+--- form
+J:A-mymoniker: DoSomething
+J:A:F-id-mymoniker: 23
+J:A:F:F-id-mymoniker: 96
+J:A:F-something-mymoniker: else
+J:ACTIONS: mymoniker
+--- request
+path: /
+state_variables: {}
+actions:
+  mymoniker:
+    moniker: mymoniker
+    class: DoSomething
+    active: 1
+    has_run: 0
+    arguments:
+        id: 23
+        something: else
+arguments:
+  J:A-mymoniker: DoSomething
+  J:A:F-id-mymoniker: 23
+  J:A:F:F-id-mymoniker: 96
+  J:A:F-something-mymoniker: else
+  J:ACTIONS: mymoniker
+fragments: {}
+=== fallbacks being ignored (other order)
+--- form
+J:A-mymoniker: DoSomething
+J:A:F:F-id-mymoniker: 96
+J:A:F-id-mymoniker: 23
+J:A:F-something-mymoniker: else
+J:ACTIONS: mymoniker
+--- request
+path: /
+state_variables: {}
+actions:
+  mymoniker:
+    moniker: mymoniker
+    class: DoSomething
+    active: 1
+    has_run: 0
+    arguments:
+        id: 23
+        something: else
+arguments:
+  J:A-mymoniker: DoSomething
+  J:A:F:F-id-mymoniker: 96
+  J:A:F-id-mymoniker: 23
+  J:A:F-something-mymoniker: else
+  J:ACTIONS: mymoniker
+fragments: {}
+=== fallbacks being used
+--- form
+J:A-mymoniker: DoSomething
+J:A:F:F-id-mymoniker: 96
+J:A:F-something-mymoniker: else
+J:ACTIONS: mymoniker
+--- request
+path: /
+state_variables: {}
+actions:
+  mymoniker:
+    moniker: mymoniker
+    class: DoSomething
+    active: 1
+    has_run: 0
+    arguments:
+        id: 96
+        something: else
+arguments:
+  J:A-mymoniker: DoSomething
+  J:A:F:F-id-mymoniker: 96
+  J:A:F-something-mymoniker: else
+  J:ACTIONS: mymoniker
+fragments: {}
+=== two different actions, one with fallback, one without
+--- form
+J:A-mymoniker: DoSomething
+J:A:F-id-mymoniker: 23
+J:A:F-something-mymoniker: else
+J:A:F:F-something-second: bla
+J:A-second: DoThat
+J:A:F-id-second: 42
+J:A:F-something-second: feepy
+J:ACTIONS: mymoniker!second
+--- request
+path: /
+state_variables: {}
+actions:
+  mymoniker:
+    moniker: mymoniker
+    class: DoSomething
+    active: 1
+    has_run: 0
+    arguments:
+        id: 23
+        something: else
+  second:
+    moniker: second
+    class: DoThat
+    active: 1
+    has_run: 0
+    arguments:
+        id: 42
+        something: feepy
+arguments:
+  J:A-mymoniker: DoSomething
+  J:A:F-id-mymoniker: 23
+  J:A:F-something-mymoniker: else
+  J:A:F:F-something-second: bla
+  J:A-second: DoThat
+  J:A:F-id-second: 42
+  J:A:F-something-second: feepy
+  J:ACTIONS: mymoniker!second
+fragments: {}
+=== just validating
+---- form
+J:A-mymoniker: DoSomething
+J:A:F-id-mymoniker: 23
+J:A:F-something-mymoniker: else
+J:A-second: DoSomething
+J:VALIDATE: 1
+J:A:F-id-second: 42
+J:A:F-something-second: bla
+J:ACTIONS: mymoniker;second
+--- request
+path: /
+state_variables: {}
+actions:
+  mymoniker:
+    moniker: mymoniker
+    class: DoSomething
+    active: 1
+    has_run: 0
+    arguments:
+        id: 23
+        something: else
+  second:
+    moniker: second
+    class: DoSomething
+    active: 1
+    has_run: 0
+    arguments:
+        id: 42
+        something: bla
+just_validating: 1
+arguments:
+  J:A-mymoniker: DoSomething
+  J:A:F-id-mymoniker: 23
+  J:A:F-something-mymoniker: else
+  J:A-second: DoSomething
+  J:VALIDATE: 1
+  J:A:F-id-second: 42
+  J:A:F-something-second: bla
+  J:ACTIONS: mymoniker!second
+fragments: {}

Added: jifty/branches/schema-plugins/t/03-is_passing-no_plan.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/03-is_passing-no_plan.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,15 @@
+#!/usr/bin/env perl -w
+
+use strict;
+
+# This is specificly testing with no_plan.
+use Jifty::Test 'no_plan';
+
+ok( !Jifty::Test->is_done );
+ok( Jifty::Test->is_done );
+ok( Jifty::Test->is_passing );
+
+ok( !Jifty::Test->test_in_isolation( sub {
+    fail();
+    return Jifty::Test->is_passing;
+}));

Added: jifty/branches/schema-plugins/t/03-is_passing.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/03-is_passing.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,35 @@
+#!/usr/bin/env perl -w
+
+use strict;
+
+use Jifty::Test tests => 7;
+
+my $tb = Jifty::Test->builder;
+
+ok( Jifty::Test->is_passing, 'is_passing, with no tests yet run' );
+ok( Jifty::Test->is_passing, '            with tests run' );
+ok( !Jifty::Test->is_done );
+
+ok( Jifty::Test->test_in_isolation( sub {
+    fail();
+    return !Jifty::Test->is_passing;
+}));
+
+ok( Jifty::Test->test_in_isolation( sub {
+    $tb->current_test( $tb->expected_tests - 1 );
+    fail();
+    return Jifty::Test->is_done;
+}));
+
+ok( Jifty::Test->test_in_isolation( sub {
+    $tb->current_test( $tb->expected_tests - 1 );
+    pass() for 1..2;
+    return !Jifty::Test->is_passing;
+}));
+
+ok( Jifty::Test->test_in_isolation( sub {
+    $tb->current_test( $tb->expected_tests - 1 );
+    pass();
+    pass();
+    return !Jifty::Test->is_done;
+}));

Added: jifty/branches/schema-plugins/t/03-test-mailbox.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/03-test-mailbox.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,11 @@
+#!/usr/bin/env perl -w
+
+use strict;
+
+use Jifty::Test tests => 2;
+
+Jifty::Test->setup_mailbox;
+ok -r Jifty::Test->mailbox or diag $!;
+
+Jifty::Test->teardown_mailbox;
+ok !-e Jifty::Test->mailbox or diag $!;

Added: jifty/branches/schema-plugins/t/04-test_file.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/04-test_file.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,41 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+
+use Shell::Command;
+
+use Jifty::Test 'no_plan';
+
+{
+    my $tmpfile = "t/foo";
+    is( Jifty::Test->test_file( $tmpfile ), $tmpfile );
+    touch( $tmpfile );
+
+    ok -e $tmpfile;
+    Jifty::Test->_ending;
+    ok !-e $tmpfile;
+}
+
+
+{
+    my @tmpfiles = ("t/foo", "t/bar");
+    is( Jifty::Test->test_file( $_ ), $_ ) for @tmpfiles;
+    touch( $_ ) for @tmpfiles;
+
+    ok -e $_ for @tmpfiles;
+    Jifty::Test->_ending;
+    ok !-e $_ for @tmpfiles;
+}
+
+
+{
+    my $tmpfile = "t/bar";
+    Jifty::Test->test_in_isolation( sub {
+        fail();
+        touch $tmpfile;
+        Jifty::Test->_ending;
+    });
+
+    ok -e $tmpfile;
+}

Added: jifty/branches/schema-plugins/t/05-dispatcher.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/05-dispatcher.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,33 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+use Test::More skip_all => 'Not written yet';
+use_ok('Jifty::Dispatcher');
+use_ok('Jifty');
+ok(Jifty->new(no_handle => 1));
+my $d = Jifty::Dispatcher->new();
+
+can_ok($d,'on');
+
+
+ok(Jifty::Dispatcher::on( condition => sub { 1 }, action => sub {2}, priority => 25));
+my @entries = Jifty->handler->dispatcher->entries();
+is (scalar @entries, 1);
+is (&{$entries[0]->{condition}},1);
+is (&{$entries[0]->{action}},2);
+
+ok(Jifty::Dispatcher::on( condition => sub { 1 }, action => sub {2}, priority => 25));
+ at entries = Jifty->handler->dispatcher->entries();
+is (scalar @entries, 2);
+
+eval 'package Jifty::Dispatcher;  on url "foo", run { qq{xxx} }; ';
+
+ok(!$@, $@);
+ at entries = Jifty->handler->dispatcher->entries();
+
+is (scalar @entries, 3);
+
+
+

Added: jifty/branches/schema-plugins/t/06-forms.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/06-forms.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,153 @@
+use warnings;
+use strict;
+use Jifty::Test tests => 24;
+
+use_ok ('Jifty::Web::Form::Field');
+
+can_ok('Jifty::Web::Form::Field', 'new');
+can_ok('Jifty::Web::Form::Field', 'name');
+
+my $field = Jifty::Web::Form::Field->new();
+
+# Form::Fields don't work without a framework
+is($field->name, undef);
+ok($field->name('Jesse'));
+is($field->name, 'Jesse');
+
+is($field->class, '');
+is($field->class('basic'),'basic');
+is($field->class(),'basic');
+is($field->name, 'Jesse');
+
+is ($field->type, 'text', "type defaults to text");
+
+# Test render_XXX methods:
+SKIP: {
+    eval {
+        require Test::MockObject;
+        require Test::MockModule;
+    };
+    skip "Test::MockObject and Test::MockModule required",
+        13 if $@;
+
+    my $out; # buf to store mason outputs
+
+    # mock up an instance of Jifty::Result
+    my $result = Test::MockObject->new;
+    $result->set_series('field_error', 'error: invalid email address', '');
+    $result->set_series('field_warning', 'warning: password too short', '');
+    $result->set_series('field_canonicalization_note', "I've changed it!", '');
+
+    # mock up an instance of Jifty::Action
+    my $action = Test::MockObject->new;
+    $action->set_always('result', $result);
+    $action->set_always('form_field_name', 'search_keys');
+    $action->set_series('error_div_id', 'FOO-error', 'no-error');
+    $action->set_series('warning_div_id', 'BAR-warning', 'no-warning');
+    $action->set_series('canonicalization_note_div_id', 'canonicalize', 'nothing');
+
+    # mock up an instance of Jifty::Web
+    my $web = Test::MockObject->new;
+    $web->mock('out', sub {
+        shift;
+        if (@_) { $out .= "@_" }
+        $out;
+    });
+    $web->set_always('serial', 32);
+
+    # mock up the Jifty package
+    my $module = new Test::MockModule('Jifty');
+    $module->mock('web', sub { $web });
+
+    # Test nonempty render_XXX:
+
+    $field = Jifty::Web::Form::Field->new();
+    $field->action($action);
+    $field->class('blah');
+    $field->name('agentz');
+
+    # Test render_lable:
+    $field->label('Audrey Tang');
+    $out = '';
+    $field->render_label;
+    is $out, qq{<label class="label text blah argument-agentz" for="search_keys-32">}.
+        qq{Audrey Tang</label>\n};
+
+    # Test render_hints:
+    $field->hints('She is here!');
+    $out = '';
+    $field->render_hints;
+    is $out, qq{<span class="hints text blah argument-agentz">She is here!</span>\n};
+
+    # Test render_errors:
+    $out = '';
+    $field->render_errors;
+    is $out, qq{<span class="error text blah argument-agentz" id="FOO-error">}.
+        qq{error: invalid email address</span>\n};
+
+    # Test render_warnings:
+    $out = '';
+    $field->render_warnings;
+    is $out, qq{<span class="warning text blah argument-agentz" id="BAR-warning">}.
+        qq{warning: password too short</span>\n};
+
+    # Test render_canonicalization_notes:
+    $out = '';
+    $field->render_canonicalization_notes;
+    is $out, qq{<span class="canonicalization_note text blah argument-agentz" }.
+        qq{id="canonicalize">I've changed it!</span>\n};
+
+    # Test render_preamble:
+    $out = '';
+    $field->preamble("preamble's here!");
+    $field->render_preamble;
+    is $out, qq{<span class="preamble text blah argument-agentz">}.
+        qq{preamble's here!</span>\n};
+
+    # Test empty labels:
+    $field->name('yichun');
+    $field->label('');
+    $field->class('');
+    $out = '';
+    $field->render_label;
+    is $out, qq{<label class="label text  argument-yichun" for="search_keys-32"></label>\n};
+
+    # Test default labels:
+    $field->name('yichun');
+    $field->label(undef);
+    $field->class('hey');
+    $out = '';
+    $field->render_label;
+    is $out, qq{<label class="label text hey argument-yichun" for="search_keys-32">}.
+        qq{yichun</label>\n};
+
+    # Test empty hints:
+    $field->name('xunxin');
+    $field->class('ujs');
+    $field->hints('');
+    $out = '';
+    $field->render_hints;
+    is $out, qq{<span class="hints text ujs argument-xunxin"></span>\n};
+
+    # Test empty errors:
+    $out = '';
+    $field->render_errors;
+    is $out, qq{<span class="error text ujs argument-xunxin" id="no-error"></span>\n};
+
+    # Test empty warnings:
+    $out = '';
+    $field->render_warnings;
+    is $out, qq{<span class="warning text ujs argument-xunxin" id="no-warning"></span>\n};
+
+    # Test empty canonicalization note:
+    $out = '';
+    $field->render_canonicalization_notes;
+    is $out, qq{<span class="canonicalization_note text ujs argument-xunxin" }.
+        qq{id="nothing"></span>\n};
+
+    # Test empty preambles:
+    $out = '';
+    $field->preamble("");
+    $field->render_preamble;
+    is $out, qq{<span class="preamble text ujs argument-xunxin"></span>\n};
+}

Added: jifty/branches/schema-plugins/t/07-limit-actions.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/07-limit-actions.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,54 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+=head1 DESCRIPTION
+
+Tests that Jifty->api->(allow|deny) work; this is to
+limit what users can do with temporary credentials (LetMes, etc)
+
+=cut
+
+use Jifty::Test tests => 21;
+
+use_ok('Jifty::API');
+
+my $api = Jifty::API->new();
+
+ok($api->is_allowed("Jifty::Action::Autocomplete"), "Some Jifty actions are allowed");
+ok(!$api->is_allowed("Jifty::Action::Record::Update"), "Most are not");
+ok($api->is_allowed("Foo"), "Unqualified tasks default to positive limit");
+ok($api->is_allowed("JiftyApp::Action::Foo"), "Qualified tasks default to positive limit");
+
+eval { $api->allow ( qr'.*' ); };
+like($@, qr/security reasons/, "Can't allow all actions");
+
+$api->allow ( qr'Foo' );
+ok($api->is_allowed("Foo"), "Positive limit doesn't cause negative limit");
+
+$api->deny ( qr'Foo' );
+ok(!$api->is_allowed("Foo"), "Later negative limit overrides");
+ 
+$api->allow ( qr'Foo' );
+ok($api->is_allowed("Foo"), "Even later positive limit overrides again");
+
+$api->deny  ( qr'Foo' );
+ok(!$api->is_allowed("Foo"), "Regex negative limit");
+ok(!$api->is_allowed("JiftyApp::Action::Foo"), "Regex negative limit, qualified");
+ok(!$api->is_allowed("FooBar"), "Matches anywhere");
+ok(!$api->is_allowed("ILikeFood"), "Matches anywhere");
+ok($api->is_allowed("Bar"), "Doesn't impact other positive");
+ok($api->is_allowed("JiftyApp::Action::Bar"), "Doesn't impact other positive, qualified");
+
+$api->allow  ( 'ILikeFood' );
+ok($api->is_allowed("ILikeFood"), "Positive string exact match, unqualified on unqualified");
+ok($api->is_allowed("JiftyApp::Action::ILikeFood"), "Positive string exact match, unqualified on qualified");
+ok(!$api->is_allowed("ILikeFood::More"), "Positive string subclass match, unqualified on unqualified");
+
+$api->allow  ( 'JiftyApp::Action::ILikeFood' );
+ok($api->is_allowed("ILikeFood"), "Positive string exact match, qualified on unqualified");
+ok($api->is_allowed("JiftyApp::Action::ILikeFood"), "Positive string exact match, qualified on qualified");
+ok(!$api->is_allowed("ILikeFood::More"), "Positive string subclass match, qualified on unqualified");
+
+1;

Added: jifty/branches/schema-plugins/t/08-client.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/08-client.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,19 @@
+use warnings;
+use strict;
+
+use Jifty::Test tests => 4;
+
+use_ok ('Jifty::Client');
+
+my $server=Jifty::Test->make_server;
+isa_ok($server, 'Jifty::Server');
+
+my $URL = $server->started_ok;
+
+my $client = Jifty::Client->new;
+
+$client->get($URL);
+ok($client->success(), "Jifty client can connect to the server");
+
+# XXX TODO need more tests to make sure that our client can connect
+# and do meaningful operations with a Jifty server
\ No newline at end of file

Added: jifty/branches/schema-plugins/t/09-url.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/09-url.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,21 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+=head1 DESCRIPTION
+
+Tests that URLs constructed with Jifty->web->url are correct
+
+=cut
+
+use Jifty::Test tests => 5;
+
+like(Jifty->web->url, qr{^http://localhost:\d+/$}, 'basic call works');
+like(Jifty->web->url(path => 'foo/bar'), qr{^http://localhost:\d+/foo/bar$}, 'path works');
+like(Jifty->web->url(path => '/foo/bar'), qr{^http://localhost:\d+/foo/bar$}, 'path with leading slash works');
+  
+$ENV{HTTP_HOST} = 'example.com';
+
+is(Jifty->web->url, 'http://example.com/', 'setting hostname via env works');
+is(Jifty->web->url(path => 'foo/bar'), 'http://example.com/foo/bar', 'hostname via env and path works');

Added: jifty/branches/schema-plugins/t/10-i18n.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/10-i18n.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,27 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More tests => 2;
+
+use DateTime;
+use Jifty::Everything;
+
+my $dt = DateTime->new( year => 1950, month => 1, day => 1 );
+
+Jifty->new( no_handle => 1 );
+my $lh = Jifty::I18N->new();
+
+
+
+# the localization method used to break DateTime object stringification
+is($dt,_($dt));
+
+# Substitution needs to work, even in the default locale
+
+my $base = "I have %1 concrete mixers";
+
+is(_($base,2), "I have 2 concrete mixers");
+
+

Added: jifty/branches/schema-plugins/t/11-config-files.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/11-config-files.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,22 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+=head1 DESCRIPTION
+
+This is a template for your own tests. Copy it and modify it.
+
+=cut
+
+use Jifty::Test tests => 2;
+
+use_ok('Jifty');
+
+Jifty->new(no_handle => 1);
+
+is(Jifty->config->framework('WhichConfigFile'), 'site', "We set the driver to what's in the site config file");
+
+
+1;
+

Added: jifty/branches/schema-plugins/t/12-param-schema.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/12-param-schema.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,58 @@
+use strict;
+use warnings;
+
+=head1 DESCRIPTION
+
+Tests Jifty::Param::Schema
+
+=cut
+
+use Test::More tests => 16;
+
+package Foo::Action::Bar;
+use Jifty::Param::Schema;
+use Jifty::Action schema {
+
+param keys =>
+    max_length is 30,
+    label is 'Search Keys',
+    hints are 'Enter your search keys here!',
+    default is 'blah blah blah';
+    type is 'text';
+
+param 'keys2';
+
+param whole_word_only =>
+    type is 'checkbox',
+    label is '',
+    hints are 'Whole word only',
+    default is 1;
+};
+
+package main;
+#use YAML::Syck;
+
+my $args = Foo::Action::Bar->arguments;
+#warn Dump($args);
+
+my $keys = $args->{keys};
+ok $keys, 'keys okay';
+is $keys->{max_length}, 30, 'max_length ok';
+is $keys->{label}, 'Search Keys', 'label ok';
+is $keys->{type}, 'text', 'type ok';
+is $keys->{hints}, 'Enter your search keys here!', 'hints okay';
+is $keys->{default_value}, 'blah blah blah', 'default_value okay';
+
+my $keys2 = $args->{keys2};
+ok $keys2, 'keys okay';
+is $keys2->{label}, undef, 'label undefined';
+is $keys2->{type}, 'text', 'type defaults to "text"';
+is $keys2->{hints}, undef, 'hints undefined';
+is $keys2->{default_value}, '', 'default_value defaults to ""';
+
+my $word_only = $args->{whole_word_only};
+ok $word_only, 'keys okay';
+is $word_only->{label}, '', 'label ok';
+is $word_only->{type}, 'checkbox', 'type ok';
+is $word_only->{hints}, 'Whole word only', 'hints ok';
+is $word_only->{default_value}, 1, 'default_value set to 1';

Added: jifty/branches/schema-plugins/t/99-pod-coverage.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/99-pod-coverage.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,12 @@
+use Test::More;
+eval "use Test::Pod::Coverage 1.00";
+plan skip_all => "Test::Pod::Coverage 1.00 required for testing POD coverage" if $@;
+all_pod_coverage_ok( );
+
+# Workaround for dumb bug (fixed in 5.8.7) where Test::Builder thinks that
+# certain "die"s that happen inside evals are not actually inside evals,
+# because caller() is broken if you turn on $^P like Module::Refresh does
+#
+# (I mean, if we've gotten to this line, then clearly the test didn't die, no?)
+Test::Builder->new->{Test_Died} = 0;
+

Added: jifty/branches/schema-plugins/t/99-pod.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/99-pod.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,5 @@
+use Test::More;
+eval "use Test::Pod 1.00";
+plan skip_all => "Test::Pod 1.00 required for testing POD" if $@;
+all_pod_files_ok();
+

Added: jifty/branches/schema-plugins/t/Continuations/bin/jifty
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/Continuations/bin/jifty	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,15 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+use File::Basename qw(dirname); 
+use UNIVERSAL::require;
+
+BEGIN {
+    Jifty::Util->require or die $UNIVERSAL::require::ERROR;
+    my $root = Jifty::Util->app_root;
+    unshift @INC, "$root/lib" if ($root);
+}
+
+use Jifty::Script;
+$SIG{INT} = $SIG{TERM} = sub { warn "Stopped\n"; exit; };
+Jifty::Script->dispatch();

Added: jifty/branches/schema-plugins/t/Continuations/lib/Continuations/Action/CrossBridge.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/Continuations/lib/Continuations/Action/CrossBridge.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,36 @@
+package Continuations::Action::CrossBridge;
+
+use Jifty::Param::Schema;
+use Jifty::Action schema {
+
+param 'name';
+param 'quest';
+param 'colour' => valid are ("Blue, I mean greeeeeen!", "Green");
+
+};
+
+sub validate_quest {
+    my $self = shift;
+    my $value = shift ||'';
+    if ($value !~ /grail/i) {
+        return $self->validation_error( quest => "Something about the grail" );
+    }
+    return $self->validation_ok( 'quest' );
+}
+
+sub validate_colour {
+    my $self = shift;
+    my $value = shift ||'';
+    if ($value =~ /blue/i) {
+        return $self->validation_error( colour => "That'll get you thrown off the bridge");
+    }
+    return $self->validation_ok('colour');
+}
+
+sub take_action {
+    my $self = shift;
+
+    $self->result->message("You crossed the bridge!");
+}
+
+1;

Added: jifty/branches/schema-plugins/t/Continuations/lib/Continuations/Action/GetGrail.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/Continuations/lib/Continuations/Action/GetGrail.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,11 @@
+package Continuations::Action::GetGrail;
+
+use base qw/Jifty::Action/;
+
+sub take_action {
+    my $self = shift;
+
+    $self->result->message("You got the grail!");
+}
+
+1;

Added: jifty/branches/schema-plugins/t/Continuations/lib/Continuations/Dispatcher.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/Continuations/lib/Continuations/Dispatcher.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,20 @@
+package Continuations::Dispatcher;
+use Jifty::Dispatcher -base;
+
+my $before = 0;
+before '/tutorial' => run {
+    unless (Jifty->web->session->get('got_help')) {
+        Jifty->web->tangent(url => '/index-help.html');
+    }
+    set been_helped => ++$before;
+};
+
+on '/tutorial' => run {
+    show '/index.html';
+};
+
+before '/index-help.html' => run {
+    Jifty->web->session->set(got_help => 1);
+};
+
+1;

Added: jifty/branches/schema-plugins/t/Continuations/share/web/templates/autohandler
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/Continuations/share/web/templates/autohandler	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,7 @@
+<% Jifty->web->render_messages %>
+
+<% $m->call_next %>
+
+<pre>
+<% Jifty::YAML::Dump(Jifty->web->request->arguments) %>
+</pre>

Added: jifty/branches/schema-plugins/t/Continuations/share/web/templates/black-knight-color.html
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/Continuations/share/web/templates/black-knight-color.html	Mon Jan 29 21:05:02 2007
@@ -0,0 +1 @@
+What is your favorite color?

Added: jifty/branches/schema-plugins/t/Continuations/share/web/templates/black-knight-name.html
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/Continuations/share/web/templates/black-knight-name.html	Mon Jan 29 21:05:02 2007
@@ -0,0 +1 @@
+What is your name?

Added: jifty/branches/schema-plugins/t/Continuations/share/web/templates/black-knight-quest.html
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/Continuations/share/web/templates/black-knight-quest.html	Mon Jan 29 21:05:02 2007
@@ -0,0 +1 @@
+What is your quest?

Added: jifty/branches/schema-plugins/t/Continuations/share/web/templates/black-knight.html
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/Continuations/share/web/templates/black-knight.html	Mon Jan 29 21:05:02 2007
@@ -0,0 +1 @@
+You try to cross the bridge

Added: jifty/branches/schema-plugins/t/Continuations/share/web/templates/help-help.html
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/Continuations/share/web/templates/help-help.html	Mon Jan 29 21:05:02 2007
@@ -0,0 +1 @@
+Help about help. <% Jifty->web->return(label => "Done") %>
\ No newline at end of file

Added: jifty/branches/schema-plugins/t/Continuations/share/web/templates/index-help.html
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/Continuations/share/web/templates/index-help.html	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,2 @@
+Help about the index.  <% Jifty->web->return(label => "Done") %>
+<% Jifty->web->tangent(label => "Get help", url => "/help-help.html") %>

Added: jifty/branches/schema-plugins/t/Continuations/share/web/templates/index.html
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/Continuations/share/web/templates/index.html	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,18 @@
+<%args>
+$been_helped => 0
+</%args>
+
+<% Jifty->web->form->start %>
+
+% my $action = Jifty->web->form->add_action( class => 'GetGrail', moniker => 'grail');
+
+<% Jifty->web->form->submit( label => 'Get the holy grail') %>
+
+<% Jifty->web->tangent(label => "Get help", url => "/index-help.html") %>
+<% Jifty->web->tangent(label => "Help as button", url => "/index-help.html", submit => $action) %>
+
+<% Jifty->web->form->end %>
+
+% if($been_helped) {
+<p>Congratulations on being helped: <% $been_helped %>.</p>
+% }

Added: jifty/branches/schema-plugins/t/Continuations/t/00-prototype.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/Continuations/t/00-prototype.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,19 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+=head1 DESCRIPTION
+
+This is a template for your own tests. Copy it and modify it.
+
+=cut
+
+use lib 't/lib';
+use Jifty::SubTest;
+
+use Jifty::Test tests => 1;
+
+ok(1, "Loaded the test script");
+1;
+

Added: jifty/branches/schema-plugins/t/Continuations/t/01-raw-api.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/Continuations/t/01-raw-api.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,147 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+=head1 DESCRIPTION
+
+Continuations tests
+
+=cut
+
+use lib 't/lib';
+use Jifty::SubTest;
+use lib '../lib';
+use Jifty::Test tests => 47;
+
+use_ok('Jifty::Test::WWW::Mechanize');
+
+# Create a continuation by hand
+
+
+# Set up server
+my $server = Jifty::Test->make_server;
+my $URL = $server->started_ok;
+my $mech = Jifty::Test::WWW::Mechanize->new;
+
+# Check that the first page is as we expect it
+$mech->get("$URL/");
+$mech->content_like(qr/Get the holy grail/, "Start page has expected content");
+$mech->content_unlike(qr/got the grail/, "Start page doesn't have output of run action");
+
+# ..and that the action does what we think it does
+$mech->get("$URL/index.html?J:A-grail=Continuations::Action::GetGrail");
+$mech->content_like(qr/got the grail/, "Running the action produces the expected result");
+
+
+#### Create
+# Create continuation with no return values
+$mech->get("$URL/index.html?J:CREATE=1;J:PATH=/index-help.html");
+like($mech->uri, qr/index-help.html/, "Got to new page");
+$mech->content_like(qr/help about the index/i, "Correct content on new page");
+ok($mech->continuation, "With a continuation set");
+
+# Create a continuation using with return values
+$mech->get("$URL/index.html?J:CREATE=1;J:M-foo=A`bar;J:PATH=/index-help.html");
+like($mech->uri, qr/index-help.html/, "Got to new page");
+$mech->content_like(qr/help about the index/i, "Correct content on new page");
+ok($mech->continuation, "With a continuation set");
+my $first = $mech->continuation->id;
+
+# Hit same URL again
+$mech->get("$URL/index.html?J:CREATE=1;J:M-foo=A`bar;J:PATH=/index-help.html");
+ok($mech->continuation, "Also sets a continuation");
+isnt($first, $mech->continuation->id, "Different continuation this time");
+
+# Create continuation from submit with an action
+$mech->get("$URL/index.html?J:CREATE=1;J:M-foo=A`bar;J:PATH=/index-help.html;J:A-grail=GetGrail");
+$mech->content_unlike(qr/got the grail/i, "Action didn't run");
+ok($mech->continuation->request->action("grail"), "Continuation has the action stored");
+my $pending = $mech->continuation->id;
+
+# Create continuation from submit with action that doesn't validate
+$mech->get("$URL/index.html?J:CREATE=1;J:M-foo=A`bar;J:PATH=/index-help.html;J:A-cross=CrossBridge");
+$mech->content_unlike(qr/crossed the bridge/i, "action didn't run");
+ok($mech->continuation->response->result("cross")->failure, "Action's result was failure");
+
+
+#### Call
+# Call the continuation using J:CALL=id
+$mech->get("$URL/index-help.html?J:CALL=$first");
+like($mech->uri, qr/index.html/, "Back at original page");
+unlike($mech->uri, qr/J:CALL=$first/, "With new continuation parameter");
+my $next = $mech->continuation->id;
+
+# Now with return value
+$mech->get("$URL/index-help.html?J:CALL=$first;bar=baz");
+like($mech->uri, qr/index.html/, "Back at original page");
+unlike($mech->uri, qr/J:CALL=$first/, "With different continuation parameter");
+isnt($next, $mech->continuation->id, "Different continuations are different");
+$mech->content_like(qr/foo: baz/i, "Return value got to right place");
+
+# Call continuation *to* page with actions
+$mech->get("$URL/index-help.html?J:CALL=$pending");
+like($mech->uri, qr/index.html/, "Back at original page");
+unlike($mech->uri, qr/J:CALL=$pending/, "With new continuation parameter");
+$mech->content_like(qr/got the grail/i, "Action ran");
+
+# Call continuation *from* page with actions
+# Check that redirect doesn't happen if validation fails
+$mech->get("$URL/index-help.html?J:CALL=$first;J:A-cross=CrossBridge");
+like($mech->uri, qr/index-help.html/, "Still at same page");
+like($mech->uri, qr/J:CALL=$first/, "With same continuation parameter");
+$mech->content_unlike(qr/crossed the bridge/i, "action didn't run");
+# Check that actions do run before redirect happens if validation succeeds
+$mech->get("$URL/index-help.html?J:CALL=$first;J:A-grail=GetGrail");
+like($mech->uri, qr/index.html/, "Back at first page");
+unlike($mech->uri, qr/J:CALL=$first/, "With new continuation parameter");
+$mech->content_like(qr/got the grail/i, "Action ran");
+
+
+#### Nesting
+# Inside one of the existing conts, create a new cont
+$mech->get("$URL/index-help.html?J:C=$first;J:CREATE=1;J:M-troz=A`zort;J:PATH=/help-help.html");
+like($mech->uri, qr/help-help.html/, "Got to new page");
+$mech->content_like(qr/help about help/i, "Correct content on new page");
+ok($mech->continuation, "With a continuation set");
+isnt($first, $mech->continuation->id, "Is a different continuation from before");
+is($first, $mech->continuation->parent, "Has previous continuation as parent");
+# Calling it should push back to second page
+$mech->get("$URL/help-help.html?J:CALL=".$mech->continuation->id);
+like($mech->uri, qr/index-help.html/, "Back at previous page");
+$mech->content_like(qr/J:C: (.*)/, "Has continuation set");
+$mech->content =~ /J:C: (.*)/;
+is($1, $first, "Back at same continuation as before");
+# Calling again should push back to original page
+$mech->get("$URL/index-help.html?J:CALL=$1");
+like($mech->uri, qr/index.html/, "Back at first page");
+
+
+#### Nested returns
+# Inside one of the existing conts, create a new cont with a CALL at the same time
+$mech->get("$URL/index-help.html?J:CALL=$first;J:CREATE=1;J:M`troz=A-zort;J:PATH=/help-help.html");
+like($mech->uri, qr/help-help.html/, "Got to new page");
+$mech->content_like(qr/help about help/i, "Correct content on new page");
+ok($mech->continuation, "With a continuation set");
+isnt($first, $mech->continuation->id, "Is a different continuation from before");
+is($first, $mech->continuation->parent, "Has previous continuation as parent");
+# One call should push all the way back to original page
+$mech->get("$URL/help-help.html?J:CALL=".$mech->continuation->id);
+like($mech->uri, qr/index.html/, "Back at first page");
+
+
+#### Clone
+# Make a new continuation by hand under some existing cont
+# Call J:CLONE=it;J:PATH=/somewhere
+# Should end up at /somewhere?J:C=new
+# With parent the same in both
+
+# Calling clone with an action that doesn't validate should update J:C
+# but not push to J:PATH
+# But pulls action values into continuation anyways
+# XXX: More goes here
+
+
+
+1;
+

Added: jifty/branches/schema-plugins/t/Continuations/t/02-api.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/Continuations/t/02-api.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,95 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+=head1 DESCRIPTION
+
+Continuations tests
+
+=cut
+
+use lib 't/lib';
+use Jifty::SubTest;
+use lib '../lib';
+use Jifty::Test tests => 31;
+
+use_ok('Jifty::Test::WWW::Mechanize');
+
+# Set up server
+my $server = Jifty::Test->make_server;
+my $URL = $server->started_ok;
+my $mech = Jifty::Test::WWW::Mechanize->new;
+
+# Check that the first page is as we expect it
+$mech->get("$URL/");
+$mech->content_like(qr/Get the holy grail/, "Start page has expected content");
+$mech->content_unlike(qr/got the grail/, "Start page doesn't have output of run action");
+
+# ..and that the action does what we think it does
+$mech->get("$URL/index.html?J:A-grail=Continuations::Action::GetGrail");
+$mech->content_like(qr/got the grail/, "Running the action produces the expected result");
+
+
+#### Create and call
+# Create a continuation
+ok($mech->find_link( text => "Get help" ), "'Get Help' link exists");
+$mech->follow_link_ok( text => "Get help" );
+
+# Redirects to /someplace?J:C=something
+like($mech->uri, qr/index-help.html/, "Got to new page");
+$mech->content_like(qr/help about the index/i, "Correct content on new page");
+ok($mech->continuation, "With a continuation set");
+my $first = $mech->continuation->id;
+$mech->back;
+
+# Hit same URL again
+$mech->follow_link_ok( text => "Get help" );
+ok($mech->continuation, "Also sets a continuation");
+isnt($first, $mech->continuation->id, "Different continuation this time");
+
+# Call the continuation using J:CALL=id
+ok($mech->find_link( text => "Done" ), "Done link exists");
+$mech->follow_link_ok( text => "Done" );
+like($mech->uri, qr/index.html/, "Back at original page");
+
+# Create continuation from submit with an action
+ok($mech->click_button(value => "Help as button"));
+$mech->content_unlike(qr/got the grail/i, "Action didn't run");
+ok($mech->continuation->request->action("grail"), "Continuation has the action stored");
+
+# Call continuation *to* page with actions
+$mech->follow_link_ok( text => "Done" );
+like($mech->uri, qr/index.html/, "Back at original page");
+$mech->content_like(qr/got the grail/i, "Action ran");
+
+
+#### Nesting
+# Inside one of the existing conts, create a new cont
+ok($mech->click_button(value => "Help as button"));
+$mech->follow_link_ok( text => "Get help" );
+like($mech->uri, qr/help-help.html/, "Got to new page");
+$mech->content_like(qr/help about help/i, "Correct content on new page");
+ok($mech->continuation, "With a continuation set");
+# Calling it should push back to second page
+$mech->follow_link_ok( text => "Done" );
+like($mech->uri, qr/index-help.html/, "Back at previous page");
+# Calling again should push back to original page
+$mech->follow_link_ok( text => "Done" );
+like($mech->uri, qr/index.html/, "Back at first page");
+
+#### Clone
+# Make a new continuation by hand under some existing cont
+# Call J:CLONE=it;J:PATH=/somewhere
+# Should end up at /somewhere?J:C=new
+# With parent the same in both
+
+# Calling clone with an action that doesn't validate should update J:C
+# but not push to J:PATH
+# But pulls action values into continuation anyways
+# XXX: More goes here
+
+
+
+1;
+

Added: jifty/branches/schema-plugins/t/Continuations/t/03-gc.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/Continuations/t/03-gc.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,14 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+# {{{ Setup
+use lib 't/lib';
+use Jifty::SubTest;
+
+use Jifty::Test skip_all => "test file not done yet";
+
+#### garbage collection
+#  for now, an "on request, sweep all continuations older than the last 50"?
+# continuations need a timestamp. so we can tell what's out of date.

Added: jifty/branches/schema-plugins/t/Continuations/t/04-before-blocks.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/Continuations/t/04-before-blocks.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,36 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+=head1 DESCRIPTION
+
+Test the interactions between continuations and dispatcher BEFORE
+blocks
+
+=cut
+
+use lib 't/lib';
+use Jifty::SubTest;
+
+use Jifty::Test tests => 9;
+
+use_ok('Jifty::Test::WWW::Mechanize');
+
+my $server = Jifty::Test->make_server;
+my $URL = $server->started_ok;
+my $mech = Jifty::Test::WWW::Mechanize->new;
+
+$mech->get($URL . '/tutorial');
+like($mech->uri, qr'index-help\.html', '/tutorial redirected to /index-help.html');
+$mech->follow_link_ok(text => 'Done');
+like($mech->uri, qr'/tutorial', 'Continuation call worked properly');
+$mech->content_contains('Congratulations', 'before blocks got run properly on continuation call');
+$mech->content_contains('being helped: 1', 'before blocks got run only once');
+
+$mech->get($URL . '/tutorial');
+$mech->content_contains('Congratulations', 'before blocks got run properly');
+$mech->content_contains('being helped: 2', 'before blocks got run only once again');
+
+1;
+

Added: jifty/branches/schema-plugins/t/DateTime.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/DateTime.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,9 @@
+#!/usr/bin/perl -w
+
+use Jifty::Test tests => 2;
+
+use_ok 'Jifty::DateTime';
+
+my $date = Jifty::DateTime->new_from_string("2006-05-03 01:23:45");
+my $date_clone = $date->clone();
+is $date, $date_clone;

Added: jifty/branches/schema-plugins/t/Jifty.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/Jifty.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,20 @@
+package t::Jifty;
+use Test::Base -Base;
+BEGIN { $ENV{'JIFTY_VENDOR_CONFIG'} = 't/test_config.yml' }
+use Jifty::Everything;
+
+
+filters {
+    form        => [qw< yaml request_from_webform >],
+    request     => [qw< yaml >],
+};
+
+package t::Jifty::Filter;
+use Test::Base::Filter -Base;
+
+
+sub request_from_webform {
+    my $form = shift;
+    Jifty::Request->new->from_webform(%$form);
+}
+

Added: jifty/branches/schema-plugins/t/Mapper/bin/jifty
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/Mapper/bin/jifty	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,15 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+use File::Basename qw(dirname); 
+use UNIVERSAL::require;
+
+BEGIN {
+    Jifty::Util->require or die $UNIVERSAL::require::ERROR;
+    my $root = Jifty::Util->app_root;
+    unshift @INC, "$root/lib" if ($root);
+}
+
+use Jifty::Script;
+$SIG{INT} = $SIG{TERM} = sub { warn "Stopped\n"; exit; };
+Jifty::Script->dispatch();

Added: jifty/branches/schema-plugins/t/Mapper/lib/Mapper/Action/CrossBridge.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/Mapper/lib/Mapper/Action/CrossBridge.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,36 @@
+package Mapper::Action::CrossBridge;
+
+use Jifty::Param::Schema;
+use Jifty::Action schema {
+
+param name      => default is 'something';
+param 'quest';
+param colour    => valid are ("Blue, I mean greeeeeen!", "Green");
+
+};
+
+sub validate_quest {
+    my $self = shift;
+    my $value = shift || '';
+    if ($value !~ /grail|Aaaaaargh/i) {
+        return $self->validation_error( quest => "Something about the grail or castle aaargh" );
+    }
+    return $self->validation_ok( 'quest' );
+}
+
+sub validate_colour {
+    my $self = shift;
+    my $value = shift || '';
+    if ($value =~ /blue/i) {
+        return $self->validation_error( colour => "That'll get you thrown off the bridge");
+    }
+    return $self->validation_ok('colour');
+}
+
+sub take_action {
+    my $self = shift;
+
+    $self->result->message("You crossed the bridge!");
+}
+
+1;

Added: jifty/branches/schema-plugins/t/Mapper/lib/Mapper/Action/GetGrail.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/Mapper/lib/Mapper/Action/GetGrail.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,12 @@
+package Mapper::Action::GetGrail;
+
+use base qw/Jifty::Action/;
+
+sub take_action {
+    my $self = shift;
+
+    $self->result->message("You got the grail!");
+    $self->result->content( castle => "Aaaaaargh" );
+}
+
+1;

Added: jifty/branches/schema-plugins/t/Mapper/share/web/templates/autohandler
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/Mapper/share/web/templates/autohandler	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,7 @@
+<% Jifty->web->render_messages %>
+
+<% $m->call_next %>
+
+<pre>
+<% Jifty::YAML::Dump(Jifty->web->request->arguments) %>
+</pre>

Added: jifty/branches/schema-plugins/t/Mapper/share/web/templates/index.html
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/Mapper/share/web/templates/index.html	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,27 @@
+<% Jifty->web->form->start %>
+
+% my $action = Jifty->web->form->add_action( class => 'GetGrail', moniker => 'grail');
+
+<% Jifty->web->form->submit( label => 'Get the holy grail') %>
+
+<% Jifty->web->form->end %>
+
+
+<% Jifty->web->form->start %>
+% my $grail_1  = Jifty->web->form->add_action( class => 'GetGrail', order => 1 );
+% my $bridge_1 = Jifty->web->new_action( class => 'CrossBridge',    order => 2 );
+<% $bridge_1->form_field( 'quest',  default_value => { result_of => $grail_1, name => 'castle' } ) %>
+<% $bridge_1->form_field( 'colour', default_value => 'Green' ) %>
+<% Jifty->web->form->submit( label => 'Do both') %>
+<% Jifty->web->form->end %>
+
+
+<% Jifty->web->form->start %>
+% my $grail_2  = Jifty->web->form->add_action( class => 'GetGrail', order => 1 );
+% my $bridge_2 = Jifty->web->new_action( class => 'CrossBridge', 
+%                                        order => 2,
+%                                        arguments => { quest => { result_of => $grail_2, name => 'castle' } } );
+<% $bridge_2->form_field( 'quest' ) %>
+<% $bridge_2->form_field( 'colour', default_value => 'Green' ) %>
+<% Jifty->web->form->submit( label => 'Do both') %>
+<% Jifty->web->form->end %>

Added: jifty/branches/schema-plugins/t/Mapper/t/00-prototype.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/Mapper/t/00-prototype.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,19 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+=head1 DESCRIPTION
+
+This is a template for your own tests. Copy it and modify it.
+
+=cut
+
+use lib 't/lib';
+use Jifty::SubTest;
+
+use Jifty::Test tests => 1;
+
+ok(1, "Loaded the test script");
+1;
+

Added: jifty/branches/schema-plugins/t/Mapper/t/01-raw-api.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/Mapper/t/01-raw-api.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,93 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+=head1 DESCRIPTION
+
+Tests for request mapper
+
+=cut
+
+use lib 't/lib';
+use Jifty::SubTest;
+
+use Jifty::Test tests => 32;
+use_ok('Jifty::Test::WWW::Mechanize');
+
+# Set up server
+my $server = Jifty::Test->make_server;
+my $URL = $server->started_ok;
+my $mech = Jifty::Test::WWW::Mechanize->new;
+
+# Check that the first page is as we expect it
+$mech->get("$URL/");
+$mech->content_like(qr/Get the holy grail/, "Start page has expected content");
+$mech->content_unlike(qr/got the grail/, "Start page doesn't have output of run action");
+
+# ..and that the action does what we think it does
+$mech->get("$URL/index.html?J:A-grail=GetGrail");
+$mech->content_like(qr/got the grail/, "Running the action produces the expected result");
+
+#### Degenerate cases
+$mech->get("$URL/index.html?J:M-foo=");
+$mech->content_like(qr/foo: &#39;&#39;/, "Nothing shows up as the empty string");
+$mech->content_lacks(qr/J:M-foo/, "Doesn't have mapping parameter");
+
+$mech->get("$URL/index.html?J:M-foo=bar");
+$mech->content_like(qr/foo: bar/, "String sets to value");
+$mech->content_lacks(qr/J:M-foo/, "Doesn't have mapping parameter");
+
+
+#### Flat arguments
+$mech->get("$URL/index.html?J:M-foo=A`bar");
+$mech->content_like(qr/foo: ~/, "Passing no parameter sets to undef");
+$mech->content_lacks(qr/J:M-foo/, "Doesn't have mapping parameter");
+
+$mech->get("$URL/index.html?J:M-foo=A`bar;bar=baz");
+$mech->content_like(qr/foo: baz/, "Passing parameter sets to value");
+$mech->content_lacks(qr/J:M-foo/, "Doesn't have mapping parameter");
+
+$mech->get("$URL/index.html?J:M-foo=A`bar;bar=baz;bar=troz");
+$mech->content_like(qr/bar: &#38;1\s*\n\s+- baz\n\s+- troz/, "Multiple parameters are list");
+$mech->content_like(qr/foo: \*1/, "Multiple parameters are to same reference");
+$mech->content_lacks(qr/J:M-foo/, "Doesn't have mapping parameter");
+
+
+#### Action results
+$mech->get("$URL/index.html?J:M-foo=R`grail`bar");
+$mech->content_like(qr/foo: ~/, "Action doesn't exist, sets to undef");
+$mech->content_lacks(qr/J:M-foo/, "Doesn't have mapping parameter");
+
+$mech->get("$URL/index.html?J:M-foo=R`grail`bar;J:A-grail=GetGrail");
+$mech->content_like(qr/foo: ~/, "Content name doesn't exist, sets to undef");
+$mech->content_lacks(qr/J:M-foo/, "Doesn't have mapping parameter");
+
+$mech->get("$URL/index.html?J:M-foo=R`grail`castle;J:A-grail=GetGrail");
+$mech->content_like(qr/foo: Aaaaaargh/, "Content name exists, sets to value");
+$mech->content_lacks(qr/J:M-foo/, "Doesn't have mapping parameter");
+
+
+#### Action arguments
+$mech->get("$URL/index.html?J:M-foo=A`bridge`bar");
+$mech->content_like(qr/foo: ~/, "Action doesn't exist, sets to undef");
+$mech->content_lacks(qr/J:M-foo/, "Doesn't have mapping parameter");
+
+$mech->get("$URL/index.html?J:M-foo=A`bridge`bar;J:A-bridge=CrossBridge");
+$mech->content_like(qr/foo: ~/, "Argument name doesn't exist, sets to undef");
+$mech->content_lacks(qr/J:M-foo/, "Doesn't have mapping parameter");
+
+$mech->get("$URL/index.html?J:M-foo=A`bridge`quest;J:A-bridge=CrossBridge");
+$mech->content_like(qr/foo: ~/, "Argument is valid but missing, sets to undef");
+$mech->content_lacks(qr/J:M-foo/, "Doesn't have mapping parameter");
+
+$mech->get("$URL/index.html?J:M-foo=A`bridge`name;J:A-bridge=CrossBridge");
+$mech->content_like(qr/foo: ~/, "Argument is valid with default_value but missing, sets to undef");
+$mech->content_lacks(qr/J:M-foo/, "Doesn't have mapping parameter");
+
+$mech->get("$URL/index.html?J:M-foo=A`bridge`quest;J:A-bridge=CrossBridge;J:A:F-quest-bridge=grail");
+$mech->content_like(qr/foo: grail/, "Argument is valid, sets to submitted value");
+$mech->content_lacks(qr/J:M-foo/, "Doesn't have mapping parameter");
+
+1;
+

Added: jifty/branches/schema-plugins/t/Mapper/t/02-api.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/Mapper/t/02-api.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,45 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+=head1 DESCRIPTION
+
+Continuations tests
+
+=cut
+
+use lib 't/lib';
+use Jifty::SubTest;
+
+use Jifty::Test tests => 11;
+
+use_ok('Jifty::Test::WWW::Mechanize');
+
+# Set up server
+my $server = Jifty::Test->make_server;
+my $URL = $server->started_ok;
+my $mech = Jifty::Test::WWW::Mechanize->new;
+
+# Check that the first page is as we expect it
+$mech->get("$URL/");
+$mech->content_like(qr/Get the holy grail/, "Start page has expected content");
+$mech->content_unlike(qr/got the grail/, "Start page doesn't have output of run action");
+
+# ..and that the action does what we think it does
+$mech->get("$URL/index.html?J:A-grail=GetGrail");
+$mech->content_like(qr/got the grail/, "Running the action produces the expected result");
+
+# Feeding the first action into the second should cause both to run;
+# first, test via setting arguments during action creation (which sets
+# sticky values)
+$mech->form(2);
+ok($mech->click_button(value => "Do both"));
+$mech->content_like(qr/got the grail/i, "Got the grail");
+$mech->content_like(qr/crossed the bridge/i, "And crossed the bridge");
+
+# And then, the same, but via default_values on the form field
+$mech->form(3);
+ok($mech->click_button(value => "Do both"));
+$mech->content_like(qr/got the grail/i, "Got the grail");
+$mech->content_like(qr/crossed the bridge/i, "And crossed the bridge");

Added: jifty/branches/schema-plugins/t/TestApp-Plugin-REST/bin/jifty
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp-Plugin-REST/bin/jifty	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,15 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+use File::Basename qw(dirname); 
+use UNIVERSAL::require;
+
+BEGIN {
+    Jifty::Util->require or die $UNIVERSAL::require::ERROR;
+    my $root = Jifty::Util->app_root;
+    unshift @INC, "$root/lib" if ($root);
+}
+
+use Jifty::Script;
+$SIG{INT} = $SIG{TERM} = sub { warn "Stopped\n"; exit; };
+Jifty::Script->dispatch();

Added: jifty/branches/schema-plugins/t/TestApp-Plugin-REST/etc/config.yml
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp-Plugin-REST/etc/config.yml	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,3 @@
+framework:
+    Plugins:
+        - REST: {}

Added: jifty/branches/schema-plugins/t/TestApp-Plugin-REST/lib/TestApp/Plugin/REST/Action/DoSomething.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp-Plugin-REST/lib/TestApp/Plugin/REST/Action/DoSomething.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,40 @@
+package TestApp::Plugin::REST::Action::DoSomething;
+
+use Jifty::Param::Schema;
+use base qw/TestApp::Plugin::REST::Action/;
+use Jifty::Action schema {
+
+param email =>
+    label is 'Email',
+    default is 'example at email.com',
+    ajax canonicalizes,
+    ajax validates;
+
+};
+
+sub canonicalize_email {
+    my $self = shift;
+    my $address = shift;
+    
+    return lc($address);
+}
+
+sub validate_email {
+    my $self = shift;
+    my $address = shift;
+
+    if($address =~ /bad\@email\.com/) {
+        return $self->validation_error('email', "Bad looking email");
+    } elsif ($address =~ /warn\@email\.com/) {
+        return $self->validation_warning('email', "Warning for email");
+    }
+    return $self->validation_ok('email');
+}
+
+sub take_action {
+    my $self = shift;
+
+    $self->result->message("Something happened!");
+}
+
+1;

Added: jifty/branches/schema-plugins/t/TestApp-Plugin-REST/lib/TestApp/Plugin/REST/Dispatcher.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp-Plugin-REST/lib/TestApp/Plugin/REST/Dispatcher.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,4 @@
+package TestApp::Plugin::REST::Dispatcher;
+use Jifty::Dispatcher -base;
+
+1;

Added: jifty/branches/schema-plugins/t/TestApp-Plugin-REST/lib/TestApp/Plugin/REST/Model/Group.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp-Plugin-REST/lib/TestApp/Plugin/REST/Model/Group.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,14 @@
+use strict;
+use warnings;
+
+package TestApp::Plugin::REST::Model::Group;
+use Jifty::DBI::Schema;
+
+use TestApp::Plugin::REST::Record schema {
+
+};
+
+# Your model-specific methods go here.
+
+1;
+

Added: jifty/branches/schema-plugins/t/TestApp-Plugin-REST/lib/TestApp/Plugin/REST/Model/User.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp-Plugin-REST/lib/TestApp/Plugin/REST/Model/User.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,25 @@
+package TestApp::Plugin::REST::Model::User;
+use base qw/TestApp::Plugin::REST::Record/;
+
+use Jifty::DBI::Schema;
+
+# Your column definitions go here.  See L<Jifty::DBI::Schema> for
+# documentation about how to write column definitions.
+
+use Jifty::Record schema {
+column 'name' =>
+  type is 'text',
+  is mandatory;
+column 'email' =>
+  type is 'text',
+  is mandatory;
+column 'tasty' =>
+  type is 'boolean',
+  is immutable;
+};
+
+
+# Your model-specific methods go here.
+
+1;
+

Added: jifty/branches/schema-plugins/t/TestApp-Plugin-REST/t/00-model-User.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp-Plugin-REST/t/00-model-User.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,53 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+
+=head1 DESCRIPTION
+
+A basic test harness for the User model.
+
+=cut
+
+
+use lib 't/lib';
+use Jifty::SubTest;
+
+use Jifty::Test tests => 12;
+# Make sure we can load the model
+use_ok('TestApp::Plugin::REST::Model::User');
+
+# Grab a system use
+my $system_user = TestApp::Plugin::REST::CurrentUser->superuser;
+ok($system_user, "Found a system user");
+
+# Try testing a create
+my $o = TestApp::Plugin::REST::Model::User->new(current_user => $system_user);
+my ($id) = $o->create( name => $$, email => $$ );
+ok($id, "User create returned success");
+ok($o->id, "New User has valid id set");
+is($o->id, $id, "Create returned the right id");
+is($o->name, $$, "Created object has the right name");
+
+# And another
+$o->create( name => $$, email => $$ );
+ok($o->id, "User create returned another value");
+isnt($o->id, $id, "And it is different from the previous one");
+
+# Searches in general
+my $collection =  TestApp::Plugin::REST::Model::UserCollection->new(current_user => $system_user);
+$collection->unlimit;
+is($collection->count, 2, "Finds two records");
+
+# Searches in specific
+$collection->limit(column => 'id', value => $o->id);
+is($collection->count, 1, "Finds one record with specific id");
+
+# Delete one of them
+$o->delete;
+$collection->redo_search;
+is($collection->count, 0, "Deleted row is gone");
+
+# And the other one is still there
+$collection->unlimit;
+is($collection->count, 1, "Still one left");
+

Added: jifty/branches/schema-plugins/t/TestApp-Plugin-REST/t/00-prototype.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp-Plugin-REST/t/00-prototype.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,19 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+=head1 DESCRIPTION
+
+This is a template for your own tests. Copy it and modify it.
+
+=cut
+
+use lib 't/lib';
+use Jifty::SubTest;
+
+use Jifty::Test tests => 1;
+
+ok(1, "Loaded the test script");
+1;
+

Added: jifty/branches/schema-plugins/t/TestApp-Plugin-REST/t/01-config.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp-Plugin-REST/t/01-config.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,18 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+
+
+#use Jifty::Test tests => 3;
+use Jifty::Test tests => 1;
+
+# todo: kevinr: these tests aren't right
+#is(Jifty->config->framework('ApplicationClass'), 'jifty');
+# is(Jifty->config->framework('LogConfig'), 't/btdttest.log4perl.conf');
+# Port is overridden by testconfig
+ok(Jifty->config->framework('Web')->{'Port'} >= 10000, "test nested config");
+
+
+1;
+
+

Added: jifty/branches/schema-plugins/t/TestApp-Plugin-REST/t/02-basic-use.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp-Plugin-REST/t/02-basic-use.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,184 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+=head1 DESCRIPTION
+
+This is a template for your own tests. Copy it and modify it.
+
+=cut
+
+
+use lib 't/lib';
+use Jifty::SubTest;
+
+use Jifty::Test tests => 70;
+use Jifty::Test::WWW::Mechanize;
+
+my $server  = Jifty::Test->make_server;
+
+isa_ok($server, 'Jifty::Server');
+
+my $URL     = $server->started_ok;
+my $mech    = Jifty::Test::WWW::Mechanize->new();
+
+ok(1, "Loaded the test script");
+
+my $u1 = TestApp::Plugin::REST::Model::User->new(
+    current_user => TestApp::Plugin::REST::CurrentUser->superuser );
+$u1->create( name => 'test', email => 'test at example.com' );
+ok( $u1->id );
+
+# on GET    '/=/model'       => \&list_models;
+
+$mech->get_ok("$URL/=/model.yml", "Got model list");
+my $list = Jifty::YAML::Load($mech->content);
+is(scalar @$list, 2, "Got one model");
+is($list->[0],'TestApp.Plugin.REST.Model.Group');
+is($list->[1],'TestApp.Plugin.REST.Model.User');
+
+# on GET    '/=/model/*'     => \&list_model_keys;
+$mech->get_ok('/=/model/User');
+is($mech->status,'200');
+$mech->get_ok('/=/model/user');
+is($mech->status,'200');
+$mech->get_ok('/=/model/TestApp::Plugin::REST::Model::User');
+is($mech->status,'200');
+$mech->get_ok('/=/model/TestApp.Plugin.REST.Model.User');
+is($mech->status,'200');
+$mech->get_ok('/=/model/testapp.plugin.rest.model.user');
+is($mech->status,'200');
+
+{
+    $mech->get('/=/model/Usery');
+    is($mech->status,'404');
+}
+
+
+$mech->get_ok('/=/model/User.yml');
+my %keys =  %{get_content()};
+
+is((0+keys(%keys)), 4, "The model has 4 keys");
+is_deeply([sort keys %keys], [sort qw/id name email tasty/]);
+
+
+# on GET    '/=/model/*/*'   => \&list_model_items;
+$mech->get_ok('/=/model/user/id.yml');
+my @rows = @{get_content()};
+is($#rows,0);
+
+
+# on GET    '/=/model/*/*/*' => \&show_item;
+$mech->get_ok('/=/model/user/id/1.yml');
+my %content = %{get_content()};
+is_deeply(\%content, { name => 'test', email => 'test at example.com', id => 1, tasty => undef });
+
+# on GET    '/=/model/*/*/*/*' => \&show_item_Field;
+$mech->get_ok('/=/model/user/id/1/email.yml');
+is(get_content(), 'test at example.com');
+
+# on PUT    '/=/model/*/*/*' => \&replace_item;
+# on DELETE '/=/model/*/*/*' => \&delete_item;
+
+
+# on GET    '/=/action'      => \&list_actions;
+
+my @actions = qw(
+                 TestApp.Plugin.REST.Action.CreateGroup
+                 TestApp.Plugin.REST.Action.UpdateGroup
+                 TestApp.Plugin.REST.Action.DeleteGroup
+                 TestApp.Plugin.REST.Action.SearchGroup
+                 TestApp.Plugin.REST.Action.CreateUser
+                 TestApp.Plugin.REST.Action.UpdateUser
+                 TestApp.Plugin.REST.Action.DeleteUser
+                 TestApp.Plugin.REST.Action.SearchUser
+                 TestApp.Plugin.REST.Action.DoSomething
+                 TestApp.Plugin.REST.Action.Record.Create
+                 TestApp.Plugin.REST.Action.Record.Delete
+                 TestApp.Plugin.REST.Action.Record.Search
+                 TestApp.Plugin.REST.Action.Record.Update
+                 Jifty.Action.Autocomplete
+                 Jifty.Action.Redirect);
+
+$mech->get_ok('/=/action/');
+is($mech->status, 200);
+for (@actions) {
+    $mech->content_contains($_);
+}
+$mech->get_ok('/=/action.yml');
+my @got = @{get_content()};
+
+is(
+    join(",", sort @got ),
+    join(",",sort @actions), 
+, "Got all the actions as YAML");
+
+
+# on GET    '/=/action/*'    => \&list_action_params;
+
+$mech->get_ok('/=/action/DoSomething');
+is($mech->status, 200);
+$mech->get_ok('/=/action/TestApp::Plugin::REST::Action::DoSomething');
+is($mech->status, 200);
+$mech->get_ok('/=/action/TestApp.Plugin.REST.Action.DoSomething');
+is($mech->status, 200);
+
+# Parameter name
+$mech->content_contains('email');
+# Parameter label
+$mech->content_contains('Email');
+# Default value
+$mech->content_contains('example at email.com');
+
+$mech->get_ok('/=/action/DoSomething.yml');
+is($mech->status, 200);
+
+my %args = %{get_content()};
+ok($args{email}, "Action has an email parameter");
+is($args{email}{label}, 'Email', 'email has the correct label');
+is($args{email}{default_value}, 'example at email.com', 'email has the correct default');
+
+
+# on POST   '/=/action/*'    => \&run_action;
+# 
+
+$mech->post( $URL . '/=/action/DoSomething', { email => 'good at email.com' } );
+
+$mech->content_contains('Something happened!');
+
+$mech->post( $URL . '/=/action/DoSomething', { email => 'bad at email.com' } );
+
+$mech->content_contains('Bad looking email');
+$mech->content_lacks('Something happened!');
+
+$mech->post( $URL . '/=/action/DoSomething', { email => 'warn at email.com' } );
+    
+$mech->content_contains('Warning for email');
+$mech->content_contains('Something happened!');
+
+# Test YAML posts
+$mech->post ( $URL . '/=/action/DoSomething.yml', { email => 'good at email.com' } );
+
+eval {
+    %content = %{get_content()};
+};
+
+ok($content{success});
+is($content{message}, 'Something happened!');
+
+    
+$mech->post ( $URL . '/=/action/DoSomething.yaml', { email => 'bad at email.com' } );
+
+eval {
+    %content = %{get_content()};
+};
+
+ok(!$content{success}, "Action that doesn't validate fails");
+is($content{field_errors}{email}, 'Bad looking email');
+
+
+sub get_content { return Jifty::YAML::Load($mech->content)}
+
+1;
+

Added: jifty/branches/schema-plugins/t/TestApp/bin/jifty
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/bin/jifty	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,15 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+use File::Basename qw(dirname); 
+use UNIVERSAL::require;
+
+BEGIN {
+    Jifty::Util->require or die $UNIVERSAL::require::ERROR;
+    my $root = Jifty::Util->app_root;
+    unshift @INC, "$root/lib" if ($root);
+}
+
+use Jifty::Script;
+$SIG{INT} = $SIG{TERM} = sub { warn "Stopped\n"; exit; };
+Jifty::Script->dispatch();

Added: jifty/branches/schema-plugins/t/TestApp/lib/TestApp/Action/DoSomething.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/lib/TestApp/Action/DoSomething.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,39 @@
+package TestApp::Action::DoSomething;
+
+use Jifty::Param::Schema;
+use Jifty::Action schema {
+
+param email =>
+    label is 'Email',
+    ajax canonicalizes,
+    ajax validates;
+
+};
+
+sub canonicalize_email {
+    my $self = shift;
+    my $address = shift;
+    
+    $self->canonicalization_note(email => "Lowercased your email");
+    return lc($address);
+}
+
+sub validate_email {
+    my $self = shift;
+    my $address = shift;
+
+    if($address =~ /bad\@email\.com/) {
+        return $self->validation_error('email', "Bad looking email");
+    } elsif ($address =~ /warn\@email\.com/) {
+        return $self->validation_warning('email', "Warning for email");
+    }
+    return $self->validation_ok('email');
+}
+
+sub take_action {
+    my $self = shift;
+
+    $self->result->message("Something happened!");
+}
+
+1;

Added: jifty/branches/schema-plugins/t/TestApp/lib/TestApp/Action/DoSomethingElse.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/lib/TestApp/Action/DoSomethingElse.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,22 @@
+package TestApp::Action::DoSomethingElse;
+
+use Jifty::Param::Schema;
+use Jifty::Action schema {
+
+param foo =>
+    label is 'Foo',
+    ajax validates,
+    is mandatory;
+
+param bar =>
+    label is 'Bar',
+    ajax validates,
+    is mandatory;
+};
+
+sub take_action {
+    my $self = shift;
+    $self->result->message("Something happened!");
+}
+
+1;

Added: jifty/branches/schema-plugins/t/TestApp/lib/TestApp/CurrentUser.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/lib/TestApp/CurrentUser.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,32 @@
+package TestApp::CurrentUser;
+
+use warnings;
+use strict;
+
+use base qw/Jifty::CurrentUser/;
+
+use TestApp::Model::User;
+
+sub _init {
+    my $self = shift;
+    my %args = (@_);
+
+    if ( delete $args{'_bootstrap'} ) {
+        $self->is_bootstrap_user(1);
+    } elsif (keys %args) {
+        $self->user_object(TestApp::Model::User->new(current_user => $self));
+        $self->user_object->load_by_cols(%args);
+
+        # tasty users are superusers
+        if ( $self->user_object->tasty == 1 ) {
+            $self->is_superuser(1);
+        }    
+    }
+    $self->SUPER::_init(%args);
+}
+
+sub current_user_can {
+    return 1;
+}
+
+1;

Added: jifty/branches/schema-plugins/t/TestApp/lib/TestApp/Dispatcher.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/lib/TestApp/Dispatcher.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,66 @@
+package TestApp::Dispatcher;
+use Jifty::Dispatcher -base;
+
+before '/redirect' => run {
+    Jifty->web->request->add_action(
+        moniker => 'thing',
+        class   => 'DoSomething',
+    );
+    redirect '/index.html';
+};
+
+
+
+on '/dispatch/' => run {
+    dispatch "/dispatch/basic";
+};
+
+on '/dispatch/show/' => run {
+    dispatch "/dispatch/basic-show";
+};
+
+
+my $count = 0;
+my $before = 0;
+my $after = 0;
+my $after_once = 0;
+
+on '/dispatch/basic' => run {
+    set count => $count++;
+};
+
+on '/dispatch/basic-show' => run {
+    set count => $count++;
+    show '/dispatch/basic-show';
+};
+
+before '/dispatch/*' => run {
+    set before     => $before++;
+    set after      => $after;
+    set after_once => $after_once;
+};
+
+after '/dispatch/*' => run {
+    return if already_run;
+    $after_once++;
+};
+
+after '/dispatch/*' => run {
+    $after++;
+};
+
+on qr{/setuser/(.*)} => run {
+    my $name = $1;
+
+    my $current_user = TestApp::CurrentUser->new( name => $name );
+    Jifty->web->current_user( $current_user );
+    show '/index.html';
+};
+
+
+
+before '/before_stage_show' => run { show '/index.html'; };
+on '/on_stage_show' => run { show '/index.html'; };
+after '/after_stage_show' => run { show '/index.html'; };
+
+1;

Added: jifty/branches/schema-plugins/t/TestApp/lib/TestApp/Model/User.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/lib/TestApp/Model/User.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,39 @@
+package TestApp::Model::User;
+use warnings;
+use strict;
+use base qw/TestApp::Record/;
+use Jifty::DBI::Schema;
+use Scalar::Defer;
+
+# Your column definitions go here.  See L<Jifty::DBI::Schema> for
+# documentation about how to write column definitions.
+use Jifty::Record schema  {
+column 'name' =>
+  type is 'text',
+  is mandatory;
+column 'email' =>
+  type is 'text',
+  is mandatory;
+column 'tasty' =>
+  type is 'boolean',
+  is immutable;
+column 'password' =>
+  type is 'text',
+  render_as 'Password',
+  is mandatory,
+  default is '';
+column 'created_on' =>
+  type is 'datetime',
+  is immutable,
+  default is defer { DateTime->now },
+  filters are 'Jifty::DBI::Filter::DateTime';
+};
+
+
+# Your model-specific methods go here.
+sub current_user_can {
+    return 1;
+}
+
+1;
+

Added: jifty/branches/schema-plugins/t/TestApp/share/web/static/images/pony.jpg
==============================================================================
Binary file. No diff available.

Added: jifty/branches/schema-plugins/t/TestApp/share/web/templates/concrete.html
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/share/web/templates/concrete.html	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,2 @@
+<% _('I have %1 concrete mixers', 2) %>
+

Added: jifty/branches/schema-plugins/t/TestApp/share/web/templates/currentuser
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/share/web/templates/currentuser	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,10 @@
+% if ( Jifty->web->current_user->id ) {
+Current user is <% Jifty->web->current_user->user_object->name %>
+%     if ( Jifty->web->current_user->is_superuser ) {
+The current user is a superuser.
+%     } else {
+The current user is not a superuser.
+%     }
+% } else {
+No current user set.
+% }

Added: jifty/branches/schema-plugins/t/TestApp/share/web/templates/dispatch/basic
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/share/web/templates/dispatch/basic	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,4 @@
+<&| /_elements/wrapper, title => "Test of dispatch" &>
+  Basic test.
+<% Jifty::YAML::Dump(\%ARGS) %>
+</&>

Added: jifty/branches/schema-plugins/t/TestApp/share/web/templates/dispatch/basic-show
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/share/web/templates/dispatch/basic-show	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,4 @@
+<&| /_elements/wrapper, title => "Test of dispatch" &>
+  Basic test with forced show.
+<% Jifty::YAML::Dump(\%ARGS) %>
+</&>

Added: jifty/branches/schema-plugins/t/TestApp/share/web/templates/dosomethingelse
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/share/web/templates/dosomethingelse	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,14 @@
+<%init>
+my $action = Jifty->web->new_action(
+    class => 'DoSomethingElse',
+    moniker => 'dosomething',
+);
+</%init>
+<&| /_elements/wrapper, title => "Test of simple form for validation" &>
+  Basic test of a simple form for validation.
+<% Jifty->web->form->start() %>
+<% $action->form_field('foo') %>
+<% $action->form_field('bar') %>
+<% Jifty->web->form->submit %>
+<% Jifty->web->form->end %>
+</&>

Added: jifty/branches/schema-plugins/t/TestApp/share/web/templates/editform
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/share/web/templates/editform	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,19 @@
+<%init>
+my $superuser = TestApp::CurrentUser->superuser;
+my $user = TestApp::Model::User->new( current_user => $superuser );
+$user->load('1');
+my $action = Jifty->web->new_action(
+    class => 'UpdateUser',
+    moniker => 'updateuser',
+    record => $user,
+);
+</%init>
+<&| /_elements/wrapper, title => "Test of simple form for editing" &>
+  Basic test of a simple form for editing.
+<% Jifty->web->form->start() %>
+<% $action->form_field('name') %>
+<% $action->form_field('email') %>
+<% $action->form_field('tasty') %>
+<% Jifty->web->form->submit %>
+<% Jifty->web->form->end %>
+</&>

Added: jifty/branches/schema-plugins/t/TestApp/share/web/templates/index.html
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/share/web/templates/index.html	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,4 @@
+<&|/_elements/wrapper, title => 'Jifty Test Application' &>
+<img src="/images/pony.jpg" alt="A Pony"/>
+This is content
+</&>

Added: jifty/branches/schema-plugins/t/TestApp/share/web/templates/manual_redirect
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/share/web/templates/manual_redirect	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,13 @@
+<%init>
+my $action = Jifty->web->new_action(
+    class => 'Jifty::Action::Redirect',
+    moniker => 'go',
+);
+</%init>
+<&| /_elements/wrapper, title => "Test of simple form for editing" &>
+  Basic test of a simple form for editing.
+<% Jifty->web->form->start() %>
+<% $action->form_field('url') %>
+<% Jifty->web->form->submit %>
+<% Jifty->web->form->end %>
+</&>

Added: jifty/branches/schema-plugins/t/TestApp/share/web/templates/regions/list
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/share/web/templates/regions/list	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,7 @@
+% for my $i (1 .. 5) {
+<% Jifty->web->region(
+        name => 'item-' . $i,
+        path => '/regions/short',
+        defaults => {id => $i})
+   %>
+% }

Added: jifty/branches/schema-plugins/t/TestApp/share/web/templates/regions/long
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/share/web/templates/regions/long	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,4 @@
+<%args>
+$id
+</%args>
+<% Jifty->web->link(label => "Long $id", onclick => {replace_with => "/regions/short"}) %>

Added: jifty/branches/schema-plugins/t/TestApp/share/web/templates/regions/short
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/share/web/templates/regions/short	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,4 @@
+<%args>
+$id
+</%args>
+<% Jifty->web->link(label => "Short $id", onclick => {replace_with => "/regions/long"}) %>

Added: jifty/branches/schema-plugins/t/TestApp/share/web/templates/somedir/dhandler
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/share/web/templates/somedir/dhandler	Mon Jan 29 21:05:02 2007
@@ -0,0 +1 @@
+dhandler arg is <% $m->dhandler_arg %>

Added: jifty/branches/schema-plugins/t/TestApp/t/00-model-User.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/t/00-model-User.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,52 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+
+=head1 DESCRIPTION
+
+A basic test harness for the User model.
+
+=cut
+
+use lib 't/lib';
+use Jifty::SubTest;
+
+use Jifty::Test tests => 12;
+# Make sure we can load the model
+use_ok('TestApp::Model::User');
+
+# Grab a system use
+my $system_user = TestApp::CurrentUser->superuser;
+ok($system_user, "Found a system user");
+
+# Try testing a create
+my $o = TestApp::Model::User->new(current_user => $system_user);
+my ($id) = $o->create( name => $$, email => $$, password => $$ );
+ok($id, "User create returned success");
+ok($o->id, "New User has valid id set");
+is($o->id, $id, "Create returned the right id");
+is($o->name, $$, "Created object has the right name");
+
+# And another
+$o->create( name => $$, email => $$, password => $$ );
+ok($o->id, "User create returned another value");
+isnt($o->id, $id, "And it is different from the previous one");
+
+# Searches in general
+my $collection =  TestApp::Model::UserCollection->new(current_user => $system_user);
+$collection->unlimit;
+is($collection->count, 2, "Finds two records");
+
+# Searches in specific
+$collection->limit(column => 'id', value => $o->id);
+is($collection->count, 1, "Finds one record with specific id");
+
+# Delete one of them
+$o->delete;
+$collection->redo_search;
+is($collection->count, 0, "Deleted row is gone");
+
+# And the other one is still there
+$collection->unlimit;
+is($collection->count, 1, "Still one left");
+

Added: jifty/branches/schema-plugins/t/TestApp/t/00-prototype.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/t/00-prototype.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,19 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+=head1 DESCRIPTION
+
+This is a template for your own tests. Copy it and modify it.
+
+=cut
+
+use lib 't/lib';
+use Jifty::SubTest;
+
+use Jifty::Test tests => 1;
+
+ok(1, "Loaded the test script");
+1;
+

Added: jifty/branches/schema-plugins/t/TestApp/t/01-config.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/t/01-config.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,17 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+
+#use Jifty::Test tests => 3;
+use Jifty::Test tests => 1;
+
+# todo: kevinr: these tests aren't right
+#is(Jifty->config->framework('ApplicationClass'), 'jifty');
+# is(Jifty->config->framework('LogConfig'), 't/btdttest.log4perl.conf');
+# Port is overridden by testconfig
+ok(Jifty->config->framework('Web')->{'Port'} >= 10000, "test nested config");
+
+
+1;
+
+

Added: jifty/branches/schema-plugins/t/TestApp/t/02-dispatch-show-rule-in-wrong-ruleset.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/t/02-dispatch-show-rule-in-wrong-ruleset.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,28 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+
+use lib 't/lib';
+use Jifty::SubTest;
+use Jifty::Test tests => 8;
+use Jifty::Test::WWW::Mechanize;
+
+my $server  = Jifty::Test->make_server;
+
+isa_ok($server, 'Jifty::Server');
+
+my $URL     = $server->started_ok;
+my $mech    = Jifty::Test::WWW::Mechanize->new();
+
+$mech->get("$URL/before_stage_show", "Got /before_stage_show");
+$mech->content_lacks("This is content");
+is( $mech->status , '404');
+
+$mech->get_ok("$URL/on_stage_show", "Got /on_stage_show");
+$mech->content_contains("his is content");
+
+$mech->get("$URL/after_stage_show", "Got /after_stage_show");
+$mech->content_lacks("This is content");
+is( $mech->status , '404');
+
+1;

Added: jifty/branches/schema-plugins/t/TestApp/t/02-dispatch.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/t/02-dispatch.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,48 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+
+use lib 't/lib';
+use Jifty::SubTest;
+use Jifty::Test tests => 28;
+use Jifty::Test::WWW::Mechanize;
+
+my $server  = Jifty::Test->make_server;
+
+isa_ok($server, 'Jifty::Server');
+
+my $URL     = $server->started_ok;
+my $mech    = Jifty::Test::WWW::Mechanize->new();
+
+$mech->get_ok("$URL/dispatch/basic", "Got /dispatch/basic");
+$mech->content_contains("Basic test.");
+$mech->content_contains("count: 0");
+$mech->content_contains("before: 0");
+$mech->content_contains("after: 0");
+$mech->content_contains("after_once: 0");
+
+$mech->get_ok("$URL/dispatch/basic-show", "Got /dispatch/basic-show");
+$mech->content_contains("Basic test with forced show.");
+$mech->content_contains("count: 1");
+$mech->content_contains("before: 1");
+$mech->content_contains("after: 1");
+$mech->content_contains("after_once: 1");
+
+$mech->get_ok("$URL/dispatch/show/", "Got /dispatch/show");
+$mech->content_contains("Basic test with forced show.");
+$mech->content_contains("count: 2");
+$mech->content_lacks("count: 3");
+$mech->content_contains("before: 3");
+$mech->content_contains("after: 2");
+$mech->content_contains("after_once: 2");
+
+$mech->get_ok("$URL/dispatch/", "Got /dispatch/");
+$mech->content_contains("Basic test.");
+$mech->content_contains("count: 3");
+$mech->content_lacks("count: 4");
+$mech->content_contains("before: 4");
+$mech->content_contains("after: 4");
+$mech->content_contains("after_once: 3");
+
+
+

Added: jifty/branches/schema-plugins/t/TestApp/t/03-static.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/t/03-static.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,26 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+
+use lib 't/lib';
+use Jifty::SubTest;
+
+use Jifty::Test tests => 6;
+use Jifty::Test::WWW::Mechanize;
+
+my $server  = Jifty::Test->make_server;
+
+isa_ok($server, 'Jifty::Server');
+
+my $URL     = $server->started_ok;
+my $mech    = Jifty::Test::WWW::Mechanize->new();
+
+for my $image (qw(pony.jpg)) {
+    $mech->get_ok("$URL/images/$image");
+    my $res = $mech->response;
+    
+    is($res->header('Content-Type'), 'image/jpeg', 'Content-Type is image/jpeg');
+    like($res->status_line, qr/^200/, 'Serving out the request');
+    is(length $res->content, 39186, 'Content is right length');
+}
+

Added: jifty/branches/schema-plugins/t/TestApp/t/04-sessions.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/t/04-sessions.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,38 @@
+#!/usr/bin/env perl 
+
+use warnings;
+use strict;
+
+use lib 't/lib';
+use Jifty::SubTest;
+
+use Jifty::Test tests => 11;
+
+use_ok('Jifty');
+Jifty->new();
+
+use_ok('Jifty::Web::Session');
+
+my $s = Jifty::Web::Session->new();
+
+isa_ok($s,'Jifty::Web::Session');
+
+$s->load();
+ok($s->loaded);
+is($s->get('foo'), undef);
+$s->set( foo => 'bar');
+is($s->get('foo'), 'bar');
+
+my $id = $s->id;
+$s->unload;
+ok(!$s->loaded);
+
+$s->load();
+ok($s->loaded);
+isnt($s->id, $id);
+is($s->get('foo'), undef);
+
+$s->load($id);
+is($s->get('foo'), 'bar');
+
+1;

Added: jifty/branches/schema-plugins/t/TestApp/t/05-actions-before-redirect.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/t/05-actions-before-redirect.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,31 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+=head1 DESCRIPTION
+
+If we do a redirect in a 'before' in the dispatcher, actions should
+still get run.
+
+=cut
+
+BEGIN {chdir "t/TestApp"}
+use lib '../../lib';
+use Jifty::Test tests => 6;
+use Jifty::Test::WWW::Mechanize;
+
+my $server  = Jifty::Test->make_server;
+
+isa_ok($server, 'Jifty::Server');
+
+my $URL     = $server->started_ok;
+my $mech    = Jifty::Test::WWW::Mechanize->new();
+
+$mech->get_ok("$URL/redirect", "Got redirect");
+like($mech->uri, qr|/index.html|, "At index");
+ok($mech->continuation,"We have a continuation");
+$mech->content_like(qr/Something happened/, "Action ran");
+
+1;
+

Added: jifty/branches/schema-plugins/t/TestApp/t/05-editactions-Cachable.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/t/05-editactions-Cachable.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,44 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+use lib 't/lib';
+use Jifty::SubTest;
+BEGIN { $ENV{'JIFTY_CONFIG'} = 't/config-Cachable' }
+
+use Jifty::Test tests => 8;
+use Jifty::Test::WWW::Mechanize;
+
+# Make sure we can load the model
+use_ok('TestApp::Model::User');
+
+# Grab a system use
+my $system_user = TestApp::CurrentUser->superuser;
+ok($system_user, "Found a system user");
+
+# Create a user
+my $o = TestApp::Model::User->new(current_user => $system_user);
+my ($id) = $o->create( name => 'edituser', email => 'someone at example.com',
+                       password => 'secret');
+ok($id, "User create returned success");
+
+my $server  = Jifty::Test->make_server;
+
+isa_ok($server, 'Jifty::Server');
+
+my $URL     = $server->started_ok;
+my $mech    = Jifty::Test::WWW::Mechanize->new();
+
+# Test action to update
+$mech->get_ok($URL.'/editform?J:A-updateuser=TestApp::Action::UpdateUser&J:A:F:F-id-updateuser=1&J:A:F-name-updateuser=edituser&J:A:F-email-updateuser=newemail at example.com', "Form submitted");
+undef $o;
+$o = TestApp::Model::User->new(current_user => $system_user);
+$o->flush_cache;
+$o->load($id);
+ok($id, "Load returned success");
+
+is($o->email, 'newemail at example.com', "Email was updated by form");
+
+1;
+

Added: jifty/branches/schema-plugins/t/TestApp/t/05-editactions-Record.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/t/05-editactions-Record.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,45 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+use lib 't/lib';
+use Jifty::SubTest;
+BEGIN { $ENV{'JIFTY_CONFIG'} = 't/config-Record' }
+
+use Jifty::Test tests => 10;
+use Jifty::Test::WWW::Mechanize;
+
+# Make sure we can load the model
+use_ok('TestApp::Model::User');
+
+# Grab a system use
+my $system_user = TestApp::CurrentUser->superuser;
+ok($system_user, "Found a system user");
+
+# Create a user
+my $o = TestApp::Model::User->new(current_user => $system_user);
+my ($id) = $o->create( name => 'edituser', email => 'someone at example.com',
+                       password => 'secret', tasty => 1 );
+ok($id, "User create returned success");
+is($o->tasty, 1, "User is tasty");
+
+my $server  = Jifty::Test->make_server;
+
+isa_ok($server, 'Jifty::Server');
+
+my $URL     = $server->started_ok;
+my $mech    = Jifty::Test::WWW::Mechanize->new();
+
+# Test action to update
+$mech->get_ok($URL.'/editform?J:A-updateuser=TestApp::Action::UpdateUser&J:A:F:F-id-updateuser=1&J:A:F-name-updateuser=edituser&J:A:F-email-updateuser=newemail at example.com&J:A:F-tasty-updateuser=0', "Form submitted");
+undef $o;
+$o = TestApp::Model::User->new(current_user => $system_user);
+$o->load($id);
+ok($id, "Load returned success");
+
+is($o->email, 'newemail at example.com', "Email was updated by form");
+is($o->tasty, 1, "User is still tasty (was not updated since immutable)");
+
+1;
+

Added: jifty/branches/schema-plugins/t/TestApp/t/06-validation.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/t/06-validation.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,62 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+
+use lib 't/lib';
+use Jifty::SubTest;
+
+use Jifty::Test tests => 22;
+use Jifty::Test::WWW::Mechanize;
+
+my $server  = Jifty::Test->make_server;
+
+isa_ok($server, 'Jifty::Server');
+
+my $URL     = $server->started_ok;
+my $mech    = Jifty::Test::WWW::Mechanize->new();
+
+$mech->get_ok("$URL/__jifty/validator.xml?J:A-dosomething=TestApp::Action::DoSomething&J:A:F-email-dosomething=good\@address.com&J:VALIDATE=1&_=",
+    "Getting validator.xml output for valid form entry");
+$mech->content_contains('<ok id="errors-J:A:F-email-dosomething" />',
+    " ... validator returned ok for errors");
+$mech->content_contains('<ok id="warnings-J:A:F-email-dosomething" />',
+    " ... validator returned ok for warnings");
+
+$mech->get_ok("$URL/__jifty/validator.xml?J:A-dosomething=TestApp::Action::DoSomething&J:A:F-email-dosomething=warn\@email.com&J:VALIDATE=1&_=",
+    "Getting validator.xml output for a warning form entry");
+$mech->content_contains('<ok id="errors-J:A:F-email-dosomething" />',
+    " ... validator returned ok for errors");
+$mech->content_contains('<warning id="warnings-J:A:F-email-dosomething">Warning for email</warning>',
+    " ... validator returned warning");
+
+$mech->get_ok("$URL/__jifty/validator.xml?J:A-dosomething=TestApp::Action::DoSomething&J:A:F-email-dosomething=bad\@email.com&J:VALIDATE=1&_=",
+    "Getting validator.xml output for a warning form entry");
+$mech->content_contains('<error id="errors-J:A:F-email-dosomething">Bad looking email</error>',
+    " ... validator returned error");
+$mech->content_contains('<ok id="warnings-J:A:F-email-dosomething" />',
+    " ... validator returned ok for warnings");
+
+$mech->get_ok("$URL/__jifty/validator.xml?J:A-dosomething=TestApp::Action::DoSomething&J:A:F-email-dosomething=UPPER\@EMAIL.com&J:VALIDATE=1&_=",
+    "Getting validator.xml output for a canonicalization");
+$mech->content_contains('<update name="J:A:F-email-dosomething">upper at email.com</update>',
+    " ... canonicalizer returned all lower case (good)");
+$mech->content_contains('<canonicalization_note id="canonicalization_note-J:A:F-email-dosomething">Lowercased your email</canonicalization_note>',
+    " ... canonicalizer warned user");
+
+$mech->get_ok("$URL/dosomethingelse");
+$mech->fill_in_action_ok('dosomething',
+    'foo' => 'something',
+    'bar' => '',
+);
+$mech->submit_html_ok();
+$mech->content_contains('<span class="error text  argument-bar" id="errors-J:A:F-bar-dosomething">You need to fill in this field</span>', 'got error for bar');
+$mech->content_contains('<span class="error text  argument-foo" id="errors-J:A:F-foo-dosomething"></span>', 'got no error for foo');
+
+$mech->get_ok("$URL/__jifty/validator.xml?J:A-dosomething=TestApp::Action::DoSomethingElse&J:A:F-foo-dosomething=&J:A:F-bar-dosomething=blam&J:VALIDATE=1&_=",
+    "Getting validator.xml output for a form entry");
+$mech->content_lacks('<error id="errors-J:A:F-bar-dosomething">', " ... validator didn't return error for bar");
+
+TODO: {
+local $TODO = "Not implemented in Jifty yet";
+$mech->content_contains('<error id="errors-J:A:F-foo-dosomething">You need to fill in this field</error>', " ... validator returned error for foo");
+};

Added: jifty/branches/schema-plugins/t/TestApp/t/07-sandboxing.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/t/07-sandboxing.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,108 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+
+use lib 't/lib';
+use Jifty::SubTest;
+
+use Jifty::Test tests => 79;
+use Jifty::Test::WWW::Mechanize;
+use Net::HTTP;
+use URI;
+
+my $server  = Jifty::Test->make_server;
+
+isa_ok($server, 'Jifty::Server');
+
+my $uri = URI->new($server->started_ok);
+
+my ($status, $body);
+($status, $body) = bogus_request("../../../../../../../../../etc/passwd");
+isnt($status, 200, "Didn't get a 200" );
+unlike( $body, qr/root/, "Doesn't have a root user in it");
+
+($status, $body) = bogus_request("/../../../../../../../../../etc/passwd");
+isnt($status, 200, "Didn't get a 200" );
+unlike( $body, qr/root/, "Doesn't have a root user in it");
+
+($status, $body) = bogus_request("/__jifty/../../../../../../../../../../etc/passwd");
+isnt($status, 200, "Didn't get a 200" );
+unlike( $body, qr/root/, "Doesn't have a root user in it");
+
+($status, $body) = bogus_request("/static/../../../../../../../../../../etc/passwd");
+isnt($status, 200, "Didn't get a 200" );
+unlike( $body, qr/root/, "Doesn't have a root user in it");
+
+($status, $body) = bogus_request("../templates/index.html");
+isnt( $status, 200, "Didn't get a 200" );
+unlike( $body, qr{\Q<&|/_elements/\E}, "Doesn't have the source code" );
+
+($status, $body) = bogus_request("../templates/_elements/nav");
+isnt( $status, 200, "Didn't get a 200" );
+unlike( $body, qr/Jifty->web->navigation/, "Doesn't have the source" );
+
+($status, $body) = bogus_request("/static/../templates/_elements/nav");
+isnt( $status, 200, "Didn't get a 200" );
+unlike( $body, qr/Jifty->web->navigation/, "Doesn't have the source" );
+
+($status, $body) = bogus_request("/static/css/../../templates/index.html");
+isnt( $status, 200, "Didn't get a 200" );
+unlike( $body, qr/Jifty->web->navigation/, "Doesn't have the source" );
+
+($status, $body) = bogus_request("/static/css/../../templates/_elements/nav");
+isnt( $status, 200, "Didn't get a 200" );
+unlike( $body, qr/Jifty->web->navigation/, "Doesn't have the source" );
+
+($status, $body) = bogus_request("/static/css/base.css");
+is( $status, 200, "Got a 200" );
+like( $body, qr/body/, "Has content" );
+
+($status, $body) = bogus_request("/static/css/../css/base.css");
+is( $status, 200, "Got a 200" );
+like( $body, qr/body/, "Has content" );
+
+($status, $body) = bogus_request("/static/css//../css/base.css");
+is( $status, 200, "Got a 200" );
+like( $body, qr/body/, "Has content" );
+
+($status, $body) = bogus_request("/somedir/stuff");
+is( $status, 200, "Got a 200" );
+like( $body, qr/dhandler arg is stuff/, "Has the content" );
+
+($status, $body) = bogus_request("/somedir/stuff/../things");
+is( $status, 200, "Got a 200" );
+like( $body, qr/dhandler arg is things/, "Has the right content" );
+
+($status, $body) = bogus_request("__jifty/webservices/yaml");
+is( $status, 200, "Got a 200" );
+like( $body, qr/--- {}/, "Got correct YAML response" );
+
+($status, $body) = bogus_request("/__jifty//../__jifty/webservices/yaml");
+is( $status, 200, "Got a 200" );
+like( $body, qr/--- {}/, "Got correct YAML response" );
+
+($status, $body) = bogus_request("/__jifty/webservices/../webservices/yaml");
+is( $status, 200, "Got a 200" );
+like( $body, qr/--- {}/, "Got correct YAML response" );
+
+($status, $body) = bogus_request("///__jifty/webservices/yaml");
+is( $status, 200, "Got a 200" );
+like( $body, qr/--- {}/, "Got correct YAML response" );
+
+($status, $body) = bogus_request("/__jifty/../index.html");
+is( $status, 200, "Got a 200" );
+unlike( $body, qr{\Q<&|/_elements/\E}, "Doesn't have the source code" );
+like( $body, qr/pony/, "Has the output" );
+
+sub bogus_request {
+    my $url = shift;
+    my($body, $buffer);
+
+    my $s = Net::HTTP->new( PeerHost => $uri->host, PeerPort => $uri->port || 80 );
+    ok($s, "Connected to host");
+    ok($s->write_request( GET => $url ), "Sent request $url");
+    my $status = $s->read_response_headers;
+    $body .= $buffer while $s->read_entity_body($buffer, 1000);
+
+    return ($status, $body);
+}

Added: jifty/branches/schema-plugins/t/TestApp/t/08-notifications.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/t/08-notifications.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,13 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+
+use lib 't/lib';
+use Jifty::SubTest;
+
+use Jifty::Test tests => 2;
+use_ok('Jifty::Notification');
+
+TODO: {local $TODO = "Actually write tests"; ok(0, "Test notifications")};
+
+1;

Added: jifty/branches/schema-plugins/t/TestApp/t/09-redirect.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/t/09-redirect.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,32 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+=head1 DESCRIPTION
+
+If we do a redirect in a 'before' in the dispatcher, actions should
+still get run.
+
+=cut
+
+BEGIN {chdir "t/TestApp"}
+use lib '../../lib';
+use Jifty::Test tests => 6;
+use Jifty::Test::WWW::Mechanize;
+
+my $server  = Jifty::Test->make_server;
+
+isa_ok($server, 'Jifty::Server');
+
+my $URL     = $server->started_ok;
+my $mech    = Jifty::Test::WWW::Mechanize->new();
+
+$mech->get_ok("$URL/manual_redirect", "Got redirect");
+
+$mech->fill_in_action_ok('go', url => $URL."/index.html");
+$mech->submit_html_ok();
+like($mech->uri, qr|/index.html|, "At index");
+
+1;
+

Added: jifty/branches/schema-plugins/t/TestApp/t/10-compress.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/t/10-compress.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,34 @@
+use warnings;
+use strict;
+use Compress::Zlib;
+
+=head1 DESCRIPTION
+
+If we do a redirect in a 'before' in the dispatcher, actions should
+still get run.
+
+=cut
+
+use lib 't/lib';
+use Jifty::SubTest;
+BEGIN { $ENV{'JIFTY_CONFIG'} = 't/config-Cachable' }
+
+use Jifty::Test tests => 5;
+use Jifty::Test::WWW::Mechanize;
+
+my $server  = Jifty::Test->make_server;
+my $URL     = $server->started_ok;
+
+my $mech    = Jifty::Test::WWW::Mechanize->new();
+$mech->get_ok($URL);
+my $expected = $mech->response->content;
+like($expected, qr/Jifty Test Application/);
+
+SKIP: {
+skip "blah", 2;
+my $request = HTTP::Request->new( GET => "$URL/", ['Accept-Encoding' => 'gzip'] );
+my $response = $mech->request( $request );
+is($response->header('Content-Encoding'), 'gzip');
+# blah, can't check if this is same as expected because there are continuation serials.
+like(Compress::Zlib::memGunzip($response->content), qr/Jifty Test Application/);
+}

Added: jifty/branches/schema-plugins/t/TestApp/t/11-current_user.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/t/11-current_user.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,53 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+
+=head1 DESCRIPTION
+
+Basic tests for CurrentUser.
+
+=cut
+
+use lib 't/lib';
+use Jifty::SubTest;
+
+use Jifty::Test tests => 19;
+use Jifty::Test::WWW::Mechanize;
+
+use_ok('TestApp::Model::User');
+use_ok('TestApp::CurrentUser');
+
+# Get a system user
+my $system_user = TestApp::CurrentUser->superuser;
+ok($system_user, "Found a system user");
+
+# Create two users
+my $o = TestApp::Model::User->new(current_user => $system_user);
+$o->create( name => 'A User', email => 'auser at example.com', 
+            password => 'secret', tasty => 0 );
+ok($o->id, "New user has valid id set");
+ok(!$o->tasty, "User is not tasty");
+$o->create( name => 'Bob', email => 'bob at example.com', 
+            password => 'secret2', tasty => 1 );
+ok($o->id, "New user has valid id set");
+ok($o->tasty, "User is tasty");
+
+# Create a CurrentUser
+my $bob = TestApp::CurrentUser->new( name => 'Bob' );
+ok($bob->id, "CurrentUser has a valid id set");
+is($bob->id, $o->id, "The ids match");
+ok($bob->user_object->tasty, "The CurrentUser is tasty");
+ok($bob->is_superuser, "CurrentUser is a superuser");
+
+my $server = Jifty::Test->make_server;
+isa_ok($server, 'Jifty::Server');
+
+my $URL = $server->started_ok;
+my $mech = Jifty::Test::WWW::Mechanize->new();
+
+$mech->get_ok("$URL/currentuser", "Got currentuser page");
+$mech->content_contains("No current user set.", "Good, no current user yet");
+$mech->get_ok("$URL/setuser/Bob", "Setting currentuser to Bob");
+$mech->get_ok("$URL/currentuser", "Refetched currentuser page");
+$mech->content_contains("Current user is Bob", "Now the current_user is set");
+$mech->content_contains("The current user is a superuser", "... and the current_user is a superuser");

Added: jifty/branches/schema-plugins/t/TestApp/t/12-search.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/t/12-search.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,228 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+
+=head1 DESCRIPTION
+
+Test Jifty::Action::Record::Search
+
+=cut
+
+use lib 't/lib';
+use lib 't/lib';
+use Jifty::SubTest;
+
+use Jifty::Test tests => 53;
+
+
+my $user = TestApp::Model::User->new(current_user => TestApp::CurrentUser->superuser);
+
+ok($user->create(
+        name       => 'test1',
+        email      => 'test1 at localhost',
+        password    => 'secret',
+        tasty      => 0,
+        created_on => '2006-07-08 19:30'
+    ),
+   "Created one user"
+);
+
+$user = TestApp::Model::User->new(current_user => TestApp::CurrentUser->superuser);
+
+ok($user->create(
+        name       => 'test2',
+        email      => 'test2 at example.com',
+        password    => 'password',
+        tasty      => 1,
+        created_on => '2005-09-02 15:16'
+    ),
+   "Created another user"
+);
+
+
+$user = TestApp::Model::User->new(current_user => TestApp::CurrentUser->superuser);
+
+ok($user->create(
+        name       => 'third_user',
+        email      => 'test3 at test2.com',
+        password    => 'hahaha',
+        created_on => '1999-12-31 23:59'
+    ),
+   "Created a third user"
+);
+
+
+my $search = Jifty::Test->web->new_action(
+    class        => 'SearchUser',
+    moniker      => 'search',
+    current_user => TestApp::CurrentUser->superuser,
+    arguments    => {}
+);
+
+isa_ok($search, 'Jifty::Action::Record::Search');
+
+my %args = %{$search->arguments};
+
+ok($args{name}, "Can search on name");
+ok(!$args{name}{mandatory}, "Fields aren't mandatory");
+ok($args{email}, "Can search on email");
+ok($args{tasty}, "Can search on tastiness");
+ok($args{created_on}, "Can search on created_on");
+ok(!$args{password}, "Can't search on password");
+
+
+# Search on name
+$search->argument_values({name => 'test1'});
+$search->run;
+
+my $result = $search->result->content('search');
+
+isa_ok($result, 'Jifty::Collection');
+is($result->count, 1);
+is($result->first->name, 'test1');
+
+# Search on email 
+$search->argument_values({email => 'test2 at example.com'});
+$search->run;
+
+$result = $search->result->content('search');
+
+isa_ok($result, 'Jifty::Collection');
+is($result->count, 1);
+is($result->first->name, 'test2');
+
+# Search on tastiness
+$search->argument_values({tasty => 1});
+$search->run;
+
+$result = $search->result->content('search');
+
+isa_ok($result, 'Jifty::Collection');
+is($result->count, 1);
+is($result->first->name, 'test2');
+
+# Search for a NULL tastiness
+$search->argument_values({tasty => undef});
+$search->run;
+
+$result = $search->result->content('search');
+
+isa_ok($result, 'Jifty::Collection');
+is($result->count, 1);
+is($result->first->name, 'third_user');
+
+# An empty search should return everything
+$search->argument_values({});
+$search->run;
+
+$result = $search->result->content('search');
+
+isa_ok($result, 'Jifty::Collection');
+is($result->count, 3);
+
+# We ignore empty but defined fields
+$search->argument_values({email => "", name => 'third_user'});
+$search->run;
+
+$result = $search->result->content('search');
+
+isa_ok($result, 'Jifty::Collection');
+is($result->count, 1);
+is($result->first->name, 'third_user');
+
+# Substring searching
+$search->argument_values({name_contains => 'test'});
+$search->run;
+
+$result = $search->result->content('search');
+
+isa_ok($result, 'Jifty::Collection');
+is($result->count, 2);
+is($result->items_array_ref->[0]->name, 'test1');
+is($result->items_array_ref->[1]->name, 'test2');
+
+# Negative substring
+$search->argument_values({name_lacks => 'test'});
+$search->run;
+
+$result = $search->result->content('search');
+
+isa_ok($result, 'Jifty::Collection');
+is($result->count, 1);
+is($result->first->name, 'third_user');
+
+
+
+# exact negative searching
+$search->argument_values({ name_not => 'third_user'});
+$search->run;
+
+$result = $search->result->content('search');
+
+isa_ok($result, 'Jifty::Collection');
+is($result->count, 2);
+ok($result->first->name =~ /test/, "it's a test user" );
+
+
+
+
+
+
+
+# This is case insensitive substring
+$search->argument_values({name_contains => 'TEST'});
+$search->run;
+
+$result = $search->result->content('search');
+
+isa_ok($result, 'Jifty::Collection');
+is($result->count, 2);
+
+# It makes no sense to contain NULL, so ignore that:
+$search->argument_values({name_contains => undef});
+$search->run;
+
+$result = $search->result->content('search');
+
+isa_ok($result, 'Jifty::Collection');
+is($result->count, 3);
+
+# Datetime searching
+$search->argument_values({created_on_after => '2006-01-01'});
+$search->run;
+
+$result = $search->result->content('search');
+
+isa_ok($result, 'Jifty::Collection');
+is($result->count, 1);
+is($result->first->name, 'test1');
+
+# More datetime
+$search->argument_values({created_on_before => '2000-05-32 4:37PM'});
+$search->run;
+
+$result = $search->result->content('search');
+
+isa_ok($result, 'Jifty::Collection');
+is($result->count, 1);
+is($result->first->name, 'third_user');
+
+# Searching on any field
+$search->argument_values({contains => 'test2'});
+$search->run;
+
+$result = $search->result->content('search');
+
+isa_ok($result, 'Jifty::Collection');
+is($result->count, 2);
+is($result->items_array_ref->[0]->name, 'test2');
+is($result->items_array_ref->[1]->name, 'third_user');
+
+
+$search->argument_values({contains => 'test2', email_contains => 'localhost'});
+$search->run;
+
+$result = $search->result->content('search');
+
+isa_ok($result, 'Jifty::Collection');
+is($result->count, 0, "found nothing");

Added: jifty/branches/schema-plugins/t/TestApp/t/13-page-regions.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/t/13-page-regions.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,40 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+
+use lib 't/lib';
+use Jifty::SubTest;
+
+use Jifty::Test tests => 39;
+use Jifty::Test::WWW::Mechanize;
+
+my $server  = Jifty::Test->make_server;
+
+isa_ok($server, 'Jifty::Server');
+
+my $URL = $server->started_ok;
+
+my $mech = Jifty::Test::WWW::Mechanize->new;
+
+$mech->get_ok("$URL/regions/list");
+
+$mech->content_contains("Short $_")
+  for (1 .. 5);
+
+for my $i (1 .. 5) {
+    ok($mech->find_link(text => "Short $i"), "Found link: Short $i");
+    $mech->follow_link_ok(text => "Short $i");
+    $mech->content_contains("Long $i");
+}
+
+$mech->content_contains("Long $_")
+  for (1 .. 5);
+
+ok($mech->find_link(text => "Long 1"), "Found link Long 1");
+$mech->follow_link_ok(text => "Long 1");
+
+$mech->content_contains("Short 1");
+for my $i (2 .. 5) {
+    $mech->content_contains("Long $i");
+    $mech->content_lacks("Short $i");
+}

Added: jifty/branches/schema-plugins/t/TestApp/t/config-Cachable
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/t/config-Cachable	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,4 @@
+---
+framework:
+  Database:
+    RecordBaseClass: Jifty::DBI::Record::Cachable

Added: jifty/branches/schema-plugins/t/TestApp/t/config-Record
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/t/config-Record	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,4 @@
+---
+framework:
+  Database:
+    RecordBaseClass: Jifty::DBI::Record

Added: jifty/branches/schema-plugins/t/TestApp/t/i18n-standalone.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/t/i18n-standalone.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,67 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+
+use lib 't/lib';
+use Jifty::SubTest;
+
+use Jifty::Test tests => 16;
+use Jifty::Test::WWW::Mechanize;
+use Net::HTTP;
+use URI;
+
+use Encode 'decode';
+use utf8;
+use LWP::UserAgent;
+my $server  = Jifty::Test->make_server;
+
+isa_ok($server, 'Jifty::Server');
+
+my $base = URI->new($server->started_ok);
+
+
+my $ua = LWP::UserAgent->new;
+my $res;
+
+$ua->default_header('Accept-Language' => "en");
+$res = $ua->get("$base/__jifty/admin/");
+ok $res->is_success, "can access admin console";
+like $res->content, qr/Models/, 'en works';
+
+$res = $ua->get("$base/concrete.html");
+ok $res->is_success, "can access concrete";
+like $res->content, qr/2 concrete mixers/, 'en works for an unknown string';
+
+$ua->default_header('Accept-Language' => "ja");
+$res = $ua->get("$base/__jifty/admin/");
+ok $res->is_success, "can access admin console";
+like adjust($res->content), qr/モデル/, 'ja works';
+
+$res = $ua->get("$base/concrete.html");
+ok $res->is_success, "can access concrete";
+like $res->content, qr/2 concrete mixers/, 'ja works for an unknown string';
+
+$ua->default_header('Accept-Language' => "fr");
+$res = $ua->get("$base/__jifty/admin/");
+ok $res->is_success, "can access admin console";
+like adjust($res->content), qr/Modèles/, 'fr locale works';
+
+$ua->default_header('Accept-Language' => "zh-cn");
+$res = $ua->get("$base/__jifty/admin/");
+ok $res->is_success, "can access admin console";
+like adjust($res->content), qr/数据库/, 'zh-cn works';
+
+$ua->default_header('Accept-Language' => "zh-tw");
+$res = $ua->get("$base/__jifty/admin/");
+ok $res->is_success, "can access admin console";
+like adjust($res->content), qr/資料庫/, 'zh-tw works';
+
+sub adjust {
+    my $s = shift;
+    $s = decode('UTF-8', $s);
+    $s =~ s/<.*?>//gs;
+    $s =~ s/\s+/ /gs;
+    #$s =~ s/\W+//g;
+    #$s =~ s/\d+//g;
+    $s;
+}

Added: jifty/branches/schema-plugins/t/TestApp/t/instance_id.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/t/instance_id.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,25 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+=head1 DESCRIPTION
+
+This is a template for your own tests. Copy it and modify it.
+
+=cut
+
+use lib 't/lib';
+use Jifty::SubTest;
+
+use Jifty::Test tests => 3;
+
+ok(1, "Loaded the test script");
+
+my $app_instance = Jifty->app_instance_id;
+ok(Jifty->app_instance_id, "We have an instance id ". Jifty->app_instance_id);
+is($app_instance, Jifty->app_instance_id, "We have an instance id ". Jifty->app_instance_id);
+
+
+1;
+

Added: jifty/branches/schema-plugins/t/TestApp/t/regex_meta_in_path_info.t
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/TestApp/t/regex_meta_in_path_info.t	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,21 @@
+#!/usr/bin/env perl
+
+# CGI.pm 3.17 (and maybe earlier) would puke if you had regex metacharacters
+# in the PATH_INFO.
+
+use warnings;
+use strict;
+
+use lib 't/lib';
+
+use Jifty::SubTest;
+use Jifty::Test tests => 2;
+use Jifty::Test::WWW::Mechanize;
+
+my $server  = Jifty::Test->make_server;
+my $URL     = $server->started_ok;
+my $mech    = Jifty::Test::WWW::Mechanize->new();
+
+$mech->get("$URL/*****");
+is( $mech->status, '404', 'regex metachars in URL does not cause error' );
+

Added: jifty/branches/schema-plugins/t/lib/Jifty/SubTest.pm
==============================================================================
--- (empty file)
+++ jifty/branches/schema-plugins/t/lib/Jifty/SubTest.pm	Mon Jan 29 21:05:02 2007
@@ -0,0 +1,13 @@
+package Jifty::SubTest;
+
+use FindBin;
+use File::Spec;
+BEGIN {
+    @INC = grep { defined } map { ref($_) ? $_ : File::Spec->rel2abs($_) } @INC;
+    chdir "$FindBin::Bin/..";
+}
+
+use lib 'lib';
+
+1;
+


More information about the Jifty-commit mailing list